上个月有个做后端的朋友喊我帮忙查一个线上事故:他带团队做了三个月的智能客服Agent,本地测试一切正常,一上线就被真实流量击穿。进程反复重启、工具调用集体超时、上下文越堆越满,最后不得不回滚到老的规则引擎。他挺困惑,问我为什么AI Agent开发看着不难,一上生产就现原形。
这个问题我太有感触了。AI Agent开发这两年热度一直很高,从热搜词就能看出来——有人搜怎么搭、有人搜怎么扛并发、有人搜学习路线,还有人搜具体框架。但真正上手做过的人都知道,这玩意跟写传统CRUD接口完全是两个物种。这篇文章我想按自己的实操经验,把AI Agent开发从认知、选型、编码到上生产的完整链路拆开讲一遍。不吹概念,只讲落地时会遇到的真问题。
1. 先弄清一件事:AI Agent开发到底在开发什么
很多人在第一步就走了弯路,以为Agent开发就是在代码里调一下大模型的API,在对话框后面加个“你是一个智能助手”的System Prompt,就算完事了。这个理解不能说错,但离“能下地干活”差着十万八千里。
1.1 Agent与普通“大模型应用”的本质区别
普通的大模型应用,比如一个简单的聊天机器人或者一个“把问题丢给模型然后展示回答”的工具,本质是一条直线:用户输入 -> 构造Prompt -> 调用模型 -> 输出回答。模型不接触外部世界,不需要工具,也不需要根据新信息调整自己的行动。
Agent应用长什么样?它多了一个“循环”结构:
用户请求 -> 模型分析 -> 决定是否调用工具 -> 执行工具 -> 把结果返回给模型 -> 模型继续分析 -> 决定下一步……直到完成目标。
这个循环就是Agent的灵魂。模型在这里不是“答案提供者”,而是“决策者”。它要决定自己还需要什么信息、该调用哪个工具、参数填什么、何时结束。这个模式通常被称为ReAct(Reason + Act),说白了就是“想一步、做一步、看结果、再想下一步”。
一旦理解了这一层,你就会明白Agent开发真正难在哪里:你写的不是一段“请求-响应”代码,而是一个让模型在复杂状态空间里做决策的运行时系统。
1.2 三种典型的Agent工作模式
我做过和见过的Agent系统,按复杂程度大致可以分成三类:
- 单轮规划Agent:模型接到请求后,生成一个完整的“行动计划”,然后一次性执行所有步骤。适合任务路径比较明确、步骤之间没有太多依赖关系的场景,比如“帮我查三个平台的机票价格并汇总”。
- 动态决策Agent:模型在每执行完一步之后,根据结果决定下一步。这是真正的ReAct模式,适合开放性问题,比如“帮我制定一份旅行计划,先看天气再根据天气推荐行程”。因为执行到一半可能需要改变计划,所以必须走一步看一步。
- 多Agent协作:一个复杂任务拆给多个角色Agent分工处理,比如一个负责搜索、一个负责分析、一个负责写报告。这种模式最灵活,但也最难调试,因为Agent之间靠消息通信,一旦某个Agent“发疯”,整个链条就乱了。
初学者往往一上来就冲多Agent架构,觉得“多个Agent各干各的才够AI”。我的建议是反过来的:能用单Agent解决就不要上多Agent。多Agent的效率损失和调试成本是几何级上升的,你会在排查问题上花掉比写功能多十倍的时间。
1.3 为什么“会调API”不等于“会做Agent”
我在面试和带团队的时候发现一个普遍现象:很多人简历上写了“熟悉大模型应用开发”,实际问下来就是会写一个带System Prompt的API调用。而真正做过Agent的人,聊两句就会发现不同——他谈论的是工具返回格式的容错、上下文窗口的管理、模型出现幻觉调用时怎么兜底、多轮对话中状态怎么保存。
为什么要强调这个区别?因为Agent开发的复杂性不来自“调用模型”这个动作本身,而来自“让模型在一个不可靠的环境中持续做出正确决策”这件事。模型会调错工具、会解析错格式、会在不该结束的时候提前结束、会在上下文里迷失方向。你所有的开发工作,本质上都是在跟这些“模型的不确定性”较劲。
2. 开发栈全景:六个主流方向怎么选
打开搜索页面你会发现,AI Agent相关的框架和平台多得眼花缭乱。LangChain、LangGraph、扣子Coze、Spring AI、FastAPI自研、还有各种国内新出的智能体平台。到底选哪个?我的观点是:选型不是看谁最火,而是看你的场景、团队技术栈和对可控性的要求。
2.1 代码优先派:LangChain / LangGraph + FastAPI 的生产骨架
如果你是要做一个正经的产品,并且希望核心逻辑掌握在自己手里,那“代码优先”是最扎实的路线。目前这个路线里最主流的搭配是:
- LangGraph负责编排Agent的节点和状态机,它比早期的LangChain Agent更适合生产——因为它把Agent流程建模成一张图,每一步的状态转换都是可控的,这对调试和回滚来说太重要了。
- FastAPI负责把Agent包成HTTP服务,提供同步/异步接口,天然支持并发和流式输出。
- 向量数据库(如Milvus、pgvector、Chroma)负责知识库检索。
- 消息队列(如Redis Stream、RabbitMQ或Kafka)处理长耗时任务的异步化。
这套组合的逻辑很清楚:LangGraph管Agent的内部智能,FastAPI管外部通信,向量库管记忆和知识,消息队列管削峰填谷。每层职责单一,出了问题定位起来也快。
LangChain本身比较重,刚上手容易被它的一堆概念(Chain、Runnable、Tool、Memory)绕晕。我的建议是:你可以用LangChain,但心里要清楚它只是一个辅助工具箱,不要被它的抽象带着跑。真正核心的Agent循环逻辑,最好是你能徒手写出来的。
2.2 低代码平台派:扣子这类平台适合什么场景
热搜词里频繁出现“扣子开发AI Agent智能体应用”,说明低代码平台确实吸引了不少人。扣子这类平台的优势是快,拖拽几个节点就能做出一个能聊天的Bot,内置了插件、知识库、工作流、数据库等能力,特别适合产品原型验证、运营活动、个人小工具。
但它的天花板也很明显。当你需要深度定制——比如接入自己的私有协议、需要细粒度的权限控制、要处理极高并发、要把Agent嵌入复杂的业务系统时,平台往往会成为瓶颈。我见过不少团队从扣子起步,做大了以后又回到代码自研,因为业务逻辑复杂到平台的工作流画布根本画不动了。
选型建议很简单:验证想法用平台,做产品用代码。尤其是要上生产、要面对真实用户流量的时候,代码派的优势是完全可控。
2.3 Spring AI Agent与Java技术栈的务实选择
Java后端圈子的人对“Spring AI Agent”的关注度一直很高。Spring AI是Spring生态里对接大模型的尝试,最新版本里也加入了对Agent模式的支持。如果你所在团队全是Java,那强行引入Python技术栈来做Agent不一定明智——团队学习成本、运维体系、依赖管理都要推倒重来。
Spring AI的做法是用Spring惯用的方式(如类似RestTemplate的API风格)去调用大模型,同时支持结构化输出、工具调用等Agent基础能力。它虽然不是当前Agent框架里功能最丰富的,但对于大型Java企业系统来说,“能融入现有Spring Cloud体系”这个价值本身就很大。
我个人的结论是:如果你的公司已经有成熟的Java微服务体系,Agent功能只是其中的一个模块,那用Spring AI比单独起一套Python服务更划算。如果你是要从零做一个以AI为核心的创新项目,那Python生态的框架成熟度还是更占优势。
2.4 自研派:直接调大模型API,自己写循环
这条路是我目前最推荐大家在“学习阶段”走一遍的。不依赖LangGraph,不依赖任何Agent框架,直接用大模型原生API(比如OpenAI规范里的chat/completions接口配合tools参数),自己写那个“模型决策-工具执行-结果回填”的循环。
这样做有三大好处:
- 你会彻底理解Agent的本质,而不会被框架的抽象掩盖。
- 排查问题时不至于抓瞎。很多人用LangGraph出了问题根本不知道去哪看,自研之后你对自己代码的每个环节一清二楚。
- 性能可控。框架为了通用性会加很多层包装,自研可以针对自己的场景做深度优化。
等你自己写过一遍循环,再用LangGraph或者其它框架,你会发现理解完全不一样——那时候框架是帮你省时间的工具,而不是黑盒。
2.5 工具链的完整拼图:MCP、可观测性和向量检索
现在做Agent开发,光有框架已经不够了。2024年下半年开始,MCP协议成为一个绕不开的话题。MCP想解决的核心问题是:Agent需要调用大量外部工具(查数据库、调内部API、访问文件、读网页),每个工具一套接入方式,这谁受得了。MCP相当于给工具定义了一套统一协议,Agent通过一个标准接口就能发现和调用所有工具。
向量检索是Agent记忆和知识库的基石,这块选型只需记住一件事:小规模用Chroma或pgvector,数据量大了再上独立的向量数据库,不要一上来就搞重装备。
可观测性被很多人忽略,但Agent系统的可观测性比传统系统更重要。传统系统出错是确定的,Agent出错是不确定的。你必须能看到每一次“模型说了什么、决定调什么工具、工具返回了什么、下一轮模型怎么反应”的完整链路。LangSmith、Langfuse这类工具强烈建议项目一开始就接入,而不是等出了问题再补。
3. 从零搭一个“能干活”的Agent:最小骨架实战
理论说再多,不如跑起来一个Demo。这一节我带你写一个最简的Agent骨架。我选Python + FastAPI + OpenAI工具调用规范来写,这个写法在各大模型平台上基本通用(国内主流模型也都兼容OpenAI的工具调用格式)。
3.1 核心循环:模型决策-工具执行-结果回填
先看最核心的循环长什么样:
from openai import OpenAI import json client = OpenAI() # 1. 定义工具:给Agent使用的“手” TOOLS = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的实时天气", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如北京、上海" } }, "required": ["city"] } } }, { "type": "function", "function": { "name": "get_current_time", "description": "获取当前时间", "parameters": { "type": "object", "properties": {} } } } ] # 2. 工具的真实实现 def execute_tool(name: str, args: dict) -> str: if name == "get_weather": # 真实项目里换成对天气API的调用 return json.dumps({"city": args["city"], "weather": "晴", "temperature": "25℃"}) if name == "get_current_time": from datetime import datetime return datetime.now().isoformat() return json.dumps({"error": "tool not found"}) def run_agent(user_input: str, max_steps: int = 5): messages = [ {"role": "system", "content": "你是一个有用的助手。当需要实时信息时,必须调用工具获取。"}, {"role": "user", "content": user_input} ] for step in range(max_steps): # 3. 让模型思考,并给它提供工具选项 resp = client.chat.completions.create( model="gpt-4o-mini", # 生产环境换成自己的模型 messages=messages, tools=TOOLS, tool_choice="auto" ) msg = resp.choices[0].message messages.append(msg) # 4. 判断模型的决策:是调工具还是直接回复 if msg.tool_calls: for tc in msg.tool_calls: result = execute_tool(tc.function.name, json.loads(tc.function.arguments)) messages.append({ "role": "tool", "tool_call_id": tc.id, "content": result }) continue # 继续循环,让模型看结果 # 5. 没有工具调用,说明Agent认为任务完成了 return msg.content return "达到最大执行步数,任务可能未完成。" if __name__ == "__main__": print(run_agent("北京今天天气怎么样?适合出门跑步吗?"))这段代码虽然短,但它把Agent开发的本质完整呈现出来了。注意几个关键点:
tools参数:这是模型能看到的能力清单。你的工具定义越清楚,模型用得越准。tool_choice="auto":让模型自己决定要不要调用工具。这在开放场景里是正确的选择。如果你希望强制模型先调工具再回答,可以改成tool_choice={"type": "function", "function": {"name": "get_weather"}}。max_steps:这是安全阀门。没有它,模型可能陷入无限循环,一次对话烧掉你几十次API调用费用。continue让循环继续:模型拿到工具返回结果后,会结合结果生成最终回答,或者提出调用下一个工具。这个“工具结果回填”的动作,是整个Agent循环的发动机。
3.2 为什么工具的描述决定了Agent的智商
在这个最小例子里,最容易被忽略但实际最重要的是工具描述里的那个description字段。我见过很多新手把工具描述写得极简,比如"查询天气",结果模型经常在参数填写上犯错,甚至干脆不调用工具。
原因很简单:模型靠阅读描述来理解“这个工具是干什么的、什么情况下该用它、参数怎么填”。你写“查询指定城市的实时天气”,模型就知道它需要提取城市名;你加上“如北京、上海”,模型就知道这是中文城市名。一个工具描述写得好的真实标准是:描述里包含触发条件、参数语义、返回内容说明。比如:
"description": "查询指定城市实时天气。当用户询问某个地点的天气情况时调用。 city参数必须是中国城市中文名,如'北京'。返回包含温度、天气现象、风力。"别小看这几句话,它直接决定了模型工具调用的准确率。在实际项目中,我甚至会花半天时间专门打磨所有工具的描述——这比调模型参数带来的效果提升更显著。
3.3 加记忆:让Agent记住上下文
上面这个骨架还缺一块:记忆。目前的状态只在一次请求里有效,聊天机器人每轮对话之间是断开连接的。真实产品里,Agent需要记住用户之前在聊什么。
最简单的做法是把历史消息塞进messages数组:
messages = history_messages + [ {"role": "user", "content": user_input} ]但这里有个非常现实的问题:上下文越长,单次调用越慢、越贵,而且超出模型上下文窗口后前面的内容会丢失。生产级方案需要引入“记忆管理层”——把对话历史做摘要压缩、把关键事实提取到长期存储、检索相关历史片段回填上下文。
这块先不展开,记住“记忆是需要设计的”就够。你现在需要跑通的是骨架逻辑,记忆和知识库可以在这之后一步步加。
4. 并发问题:Agent为什么一上线就卡死
热搜词里“ai agent怎么扛并发”排名很靠前,这说明大家已经被这个问题毒打过。我那位朋友的线上事故,本质也是并发问题,但它跟传统并发不一样。传统Web服务的高并发是“短而快”的,Agent服务的并发是“长而重”的。
4.1 传统并发模型在Agent场景下为什么会失效
算一笔账就明白了。
一个普通API请求,后端处理时间通常是几十毫秒。一台机器配8个Gunicorn worker,QPS上百没问题。
一个Agent请求呢?它内部要进行多次模型调用,每次模型调用需要1到5秒(大模型生成速度很难快过每秒钟几十个token)。再加上工具调用的网络往返、上下文处理,一个完整Agent请求从几秒到几十秒都很常见。
也就是说,同样的单机并发能力,Agent场景下QPS会暴跌一到两个数量级。更麻烦的是,这个请求是“占着连接不放”的——用户要等Agent几轮思考、调用、生成,期间客户端和服务端的连接不能断。如果后端用的是同步阻塞模型,一个worker在等模型返回的时候,它所在线程就完全被占住了,什么别的请求都处理不了。
所以Agent服务扛并发,第一个原则就是:请求处理必须走异步。FastAPI在这方面有天然优势,async def端点配合异步HTTP客户端(比如httpx.AsyncClient)去调用模型API,才能在等待模型返回的同时继续处理其他请求。
4.2 从同步阻塞到异步非阻塞:并发能力的质变
给你看一个对比。同步版本的Agent服务:
@app.post("/agent") def agent_sync(request: AgentRequest): result = run_agent(request.query) # 阻塞10秒 return result这个写法,单worker并发能力基本是0——因为处理第一个请求的10秒内,这个worker接不了新请求。
异步版本:
@app.post("/agent") async def agent_async(request: AgentRequest): result = await asyncio.to_thread(run_agent, request.query) return result甚至更好的方案是整个Agent循环用异步实现,模型调用用await等待。这样在等待模型返回的时候,事件循环可以把控制权让给其他请求,并发能力从0变成几十上百。
但要注意,异步不是银弹。异步解决的是“IO等待时的资源释放”,如果你的Agent内部还有大量CPU密集型的本地计算(比如处理大文本、本地跑模型),那该用进程池还是得用进程池,否则事件循环照样被卡死。
4.3 一张完整的“扛并发”作战图
根据我的实战经验,一个Agent服务要能接住真实流量,通常需要下面几层配合:
| 层级 | 手段 | 解决的问题 |
|---|---|---|
| 入口 | 负载均衡 + 水平扩容 | 单机能力上限,多实例分担 |
| 流量控制 | 限流 + 熔断 + 降级 | 防止突发流量打爆下游模型API |
| 请求处理 | 全链路异步 + 流式输出 | 释放IO等待占用的资源 |
| 任务调度 | 消息队列/任务队列 | 长耗时任务异步化,用户先收到“处理中” |
| 缓存 | 结果缓存 + Prompt缓存 | 同类请求不重复调用模型 |
| 下游保护 | 连接池 + 超时控制 + 重试退避 | 模型API和工具API的稳定性 |
篇幅有限,我挑几个最关键的拆开讲。
流式输出是必须的。Agent在后台思考10秒,用户界面不能白屏10秒。用SSE(Server-Sent Events)把模型的token流实时推给前端,用户至少能看到“正在打字”的效果。这在体感上能把“10秒等待”变得可接受。
超时和重试必须做分层。给工具调用设置1-2秒的超时,给模型调用设置10-30秒超时,给整个Agent请求设置总超时。重试要带指数退避(比如失败后等1秒、2秒、4秒再试),否则下游一抖动,你的重试风暴会直接把对方打挂。
消息队列是Agent系统的安全气囊。当所有Worker都忙不过来时,大量请求会堆积在内存里,最终OOM。增加一层消息队列,把请求先落盘、再消费,流量再大也只是队列变长,而不是服务崩溃。代价是用户等待时间变长,但至少系统是活着的。
用缓存对付重复提问。很多用户问的问题高度相似。对相同或近似的问题做缓存(向量相似度检索命中后直接返回缓存结果),能把真实打到模型的流量降一个数量级。这块的成本收益比极高,值得优先做。
5. 学习路线:从普通后端到能独立开发的Agent工程师
“ai agent学习路线”是另一个被高频搜索的关键词。结合被搜到的前后端、嵌入式、ROS2机器人等热词,我能看出来,现在想进入Agent开发的,不只是纯后端工程师,还有前端、算法、嵌入式各个背景的人。这个趋势很好,Agent开发恰恰需要多背景的人一起参与。
5.1 按角色拆解能力清单
我给不同背景的读者分别列一下最关键的补课方向:
后端工程师(最接近主力):你的HTTP、数据库、并发基础直接适用。需要补的是——大模型API协议细节(messages角色、工具调用格式、流式输出)、异步编程(FastAPI的async、异步HTTP客户端、任务队列)、向量数据库和RAG基础、LangGraph或自研Agent编排。
前端/全栈开发者:你的优势是交互体验。Agent产品的人机界面(对话UI、流式渲染、任务状态可视化、工具调用过程展示)其实是一个被严重低估的竞争力点。需要补的是——知道Agent的API结构、SSE怎么对接、如何把Agent的中间过程展示给用户。
算法工程师:你的优势是对模型本身的理解。可以深入研究——Prompt工程和工具描述的优化、模型的工具调用成功率、如何用微调提升特定Agent任务的准确率、如何设计评测集。
嵌入式/机器人方向:热搜词里有ROS2机器人开发,这个方向跟Agent结合得越来越紧密。传统机器人的行为树和状态机是死的,大模型Agent作为“大脑”做高层决策,底层还是ROS2控制。你需要补的主要是——Agent与硬件低延时通道怎么打通、工具抽象层怎么把“移动”“抓取”也封装成Agent可调用的工具。
5.2 三个阶段的练手路径
不管背景如何,我建议你按下面三个台阶一步一个脚印地走。
第一阶段:把大模型API玩到滚瓜烂熟。不碰任何Agent框架,用原生API分别实现:普通多轮对话、带结构化输出的对话、带工具调用的对话、流式输出。每完成一个,写一篇笔记记录你踩的坑。等你发现“调用模型的那些参数我已经倒背如流”时,进入下一阶段。
第二阶段:徒手写一个Agent循环。按照本文第三节的骨架,自己从零实现一个Agent,让它能调用至少三个真实工具(比如天气、时间、计算器),并且能处理多轮对话。然后试着给它加记忆和知识库——用向量数据库做检索增强。
第三阶段:框架化 + 工程化。把你自己写的循环换成LangGraph或自己重构一版面向生产的版本。加上异步化、消息队列、可观测性、限流缓存。把服务部署到服务器上,用压测工具模拟并发流量,把QPS和延迟数据记录下来。
走到第三阶段,你就不再是“会调API的开发者”,而是能独立承担Agent产品后端的人。
5.3 动手项目建议:从个人助理到垂直领域Agent
很多人在练手项目上纠结,不知道该做什么。我给三个递进难度的选题,都是不依赖外部公司数据就能上手的:
- 个人资讯助理:Agent每天定时抓取某个垂直领域的信息(比如AI行业新闻),用大模型做摘要,推送到你的IM。难度集中在“爬取-清洗-入库-检索-生成”全链路打通。
- 数据库运维问答Agent:把数据库的表结构存到向量库,让Agent根据用户自然语言问题生成SQL并执行查询,把结果转成自然语言回答。这个项目能让你深刻理解“工具调用”和“结构化输出”的难点。
- 多Agent协作项目:搞一个“选题-写作-审校”三Agent流水线,一个负责调研选题,一个负责写初稿,一个负责审核修改。这个项目能让你体验多Agent的协作和调试问题。
至于热搜里那个“个人使用AI Agent可以做期货交易吗”,我的看法是技术上确实可以——Agent可以写脚本拉行情、做数据汇总、生成分析报告。但涉及实盘下单,问题就不只是技术了:还有数据源的稳定性和延时、风控策略、以及相关合规要求。用一个Agent做点复盘分析和提醒是可以的,真金白银的交易决策,慎重。
6. 生产环境里的真实挑战:评测、上下文与不确定性
前面讲的都是怎么把Agent做出来,这一节我讲真正决定Agent项目能不能活下去的三件事:评测、上下文管理、以及应对模型的不确定性。
6.1 Agent的“确定性”问题:同一个问题两次回答不一样
这是Agent上线后售后成本最高的一个坑。传统软件同一个输入必然产生同一个输出,但Agent不是,模型生成有随机性(temperature参数大于0时)。用户会发现,同样一个问题,昨天答得挺好,今天答得驴唇不对马嘴——但他不知道这是因为模型升级了还是因为上下文被污染了。
解决这个问题有几个手段,按优先级排序:
- 降temperature:大部分任务里,把temperature调到0或者0.2,能显著提高稳定性。只有在创意写作等任务里才需要开高。
- 强约束输出:模型返回的内容尽量用JSON Schema约束格式,不要让它自由发挥。
- 固定模型版本:不要用“最新模型”这类动态别名,上线时必须锁死具体模型版本号,否则厂商一升级,你的Agent行为就变了。
- 兜底话术:当模型输出不符合预期时,有一套“我暂时无法回答,已经记录反馈”的兜底策略,避免用户看到荒诞的回答。
6.2 上下文与记忆的成本控制
上下文管理是Agent项目里最容易被低估的成本黑洞。一个Agent请求动辄携带几千甚至上万token的历史消息,乘以每次请求的调用次数,再乘以流量,账算下来非常吓人。
我的经验做法是三层记忆结构:
- 短期记忆:最近的对话,直接全量携带。
- 中期记忆:更早的对话先做摘要压缩。比如每10轮对话,让模型生成一个200字的摘要塞进上下文。
- 长期记忆:关键事实(用户的偏好、项目的背景信息)存到向量库,每次请求时检索最相关的3-5条回填。
这样做的效果是:既保留了对用户有用的信息,又把token消耗控制在合理范围内。
6.3 评测体系:没有测试集的Agent就是裸奔
传统软件开发有单元测试、集成测试、回归测试,Agent开发呢?很多团队连一份测试用例都没有,全靠开发人员手动聊几轮“感觉还行”。
Agent评测确实比传统软件测试难,因为正确答案不是唯一的。但难不代表不能做。我建议搭建三层评测体系:
- 规则评测:检查输出是否包含必须的关键字段、格式是否正确、是否调用了正确的工具。这部分可以自动跑,适合做回归测试。
- 模型裁判(LLM-as-judge):让一个大模型给Agent的回答打分,根据预定义的标准(准确性、完整性、语气友好度)。这是目前最主流的方式,虽然不完美,但能覆盖大部分场景。
- 人工抽检:每个版本上线前,准备20-30个代表真实用户场景的问题,人工跑一遍,记录失败案例。数量不大,但这是最后一道保底。
我见过很多团队跳过评测直接上线,然后被用户的截图吐槽淹没。评测体系的建立,应该跟Agent功能开发同步进行,而不是上线前临时抱佛脚。
6.4 几条来自一线的排错经验
最后分享一下我排查Agent问题时的几个心法,都是踩坑踩出来的:
- Agent出错了,先看完整链路日志,而不是重跑一遍。重跑大概率得到的是另一个随机结果,对定位问题没有帮助。接入Langfuse这类工具,把每一轮“模型响应-工具调用参数-工具返回结果”完整记录下来,问题在哪一环一目了然。
- Agent反复调用同一个工具,通常是上下文里缺信息。模型每次拿到工具结果但没有足够信息判断“任务已完成”,就会再调一次。先检查工具返回的内容是否完整,再看系统提示。比如你让它查天气并决定“能否跑步”,工具返回“晴,25℃”,模型其实已经有信息了,但如果你的工具只返回了天气没返回温度,模型就会觉得信息不够,反复尝试。
- 上下文被污染时,Agent会“人格分裂”。如果用户历史消息里有一些恶意或混乱的指令,Agent的行为会变得不可预测。鉴权、输入过滤、和输出安全过滤都不能省。
- 模型吐JSON参数总是格式错误怎么办?不要重试硬等。在工具描述里明确说明“参数必须是JSON对象”,给模型一个例子;也可以把参数解析失败的情况做成一个工具返回错误信息,模型看到错误信息后会自行修正参数。
Agent开发这件事,说到底是“把不可控的模型,封装进可控的工程系统”里。你没法让模型百分之百稳定,但你可以通过工具描述、上下文管理、异步架构、评测回归这些工程手段,让系统的整体表现稳定在一个可接受的范围内。我自己每次上线新Agent之前,都会先跑一遍固定20个问题的回归集——更新了模型版本、改了工具逻辑、调了提示词,都先过一遍这个回归集再放流量。这不能保证线上不出任何问题,但至少能帮我挡掉八成肉眼可见的劣化。这套笨办法,建议你也试试。