在AI技术社区里泡久了你会发现,凡是自己动手做过Agent项目的开发者,多少都能给你讲几个“经典事故”。我自己的经历从最早的提示词包装开始,到后来一步步接入工具调用、多轮规划、甚至多Agent协作,踩过的坑加起来能写满一张表。今天这篇不聊框架选型,也不吹哪个模型更强,就实打实地把Agent开发中最常遇到的经典问题总结一遍,讲清楚问题长什么样、根子出在哪里、我在实际项目里是怎么解决的。
这篇内容适合两类人。一类是正准备从普通大模型接口调用转向Agent开发的开发者,想提前绕开别人已经踩烂的坑;另一类是已经写了几天Agent代码、总觉得结果“时好时坏不太可控”的工程师,需要一份系统性的排查清单。全文以我自己的实操经历为主线,尽量少讲虚的,多讲能落地的东西。
1. 先把Agent的核心问题归归类
Agent和普通模型接口调用最大的区别,在于它把模型从“单次问答工具”变成了“能自主执行任务的工作流引擎”。这个转变带来一个本质变化:不确定性从单点扩散到了整条链路。普通调用的错误顶多是输出内容不对,Agent的错误却往往是一连串错误叠加后的连锁反应,而且每个环节的错误还会被后面的步骤放大。
我在经历了好几个项目的毒打之后,习惯把Agent的问题归成六类:指令跟随、上下文与记忆、工具调用、多步规划、幻觉、调试可观测性。这六类问题几乎覆盖了我线上遇到的90%以上的故障场景。它们之间不是完全孤立的,比如工具调用出错往往源于指令约束不到位,多步规划跑偏又常常是因为上下文管理失效。但分开看更容易对症下药,排查的时候也更有条理。
需要说明的是,这六类问题不会因为模型升级而自动消失。模型能力在提升,新的问题也在不断冒出来,但底层规律是相对稳定的。你理解了这些规律之后,无论以后换什么模型、用什么开发框架,都会知道问题大概出在哪个环节,不至于面对一个异常结果完全无从下手。
2. 六大经典问题拆解与根治思路
2.1 指令不听话:Prompt链路上的第一个坑
所谓指令不听话,就是模型不按你的要求执行。典型表现包括:要求输出JSON却给你一段散文,要求只调用查询工具却自作主张调用了写入接口,要求分三步处理结果却一步全做完。这种问题在Agent场景里格外致命,因为Agent的下一步行动往往依赖上一步的解析结果,输出格式一乱,整个链路就直接断掉了。
我遇到过一个非常典型的案例。当时做一个信息收集Agent,要求模型最后输出一个结构化表格,我自认为Prompt写得够清楚了,还专门给了JSON示例。结果模型在真实运行时,偶尔会往JSON里塞一段解释性文字,导致解析器直接抛异常。最诡异的是这类问题不是必现的,同样的输入,上一轮正常,下一轮就翻车。这种不确定性是最让人头疼的地方。
根因要从两个层面看。第一是Prompt本身的设计问题,比如指令表述模糊、正反例不足、系统提示和用户输入之间有潜在冲突。第二是模型本身的概率特性,同样的Prompt每次推理在词元分布上都有细微差别,温度参数设得越高,输出漂移的概率就越大。
我的解决思路是三层保险。第一层在Prompt层面把约束写死,用“必须”“禁止”“只能”这类强约束词,并且给一到两个完整的输入输出示例,少用抽象描述。比如你要JSON,就直接给出JSON结构定义和一段合法的示例输出,不要写“请以合适的格式返回”这种模棱两可的话。第二层在解析层面做容错,不依赖模型的“自觉”,解析器要能处理常见变体,比如允许JSON前后有额外文本时自动剥离、容忍部分字段缺失时给默认值。第三层在运行层面做矫正,解析失败就自动让模型基于报错信息重新生成一次。这个“犯错后纠正”的重试机制,能救回很大一部分偶发故障。
2.2 记忆和上下文:永远在丢与塞之间摇摆
上下文窗口再大也装不下无限的历史,这是Agent开发里最现实的一条约束。经典问题有两个方向:一是token量不够用,多轮对话加上工具返回结果,几轮下来窗口就满了;二是信息被粗暴丢掉之后,Agent开始“失忆”,对用户前面说过的话毫无反应。
我做一个客服类Agent的时候,这个问题体现得特别明显。用户和Agent来回聊了十几轮,前面已经确认过收货地址,后面Agent又一本正经地问“请问您的收货地址是”。用户当场就炸了,体验极差。问题不在模型,而在于我把早期轮次的细节压缩掉了,压缩逻辑又没有做到关键信息无损。
这里要分清两种记忆需求。一种是短期工作记忆,就是当前任务执行过程中必须保留的上下文,比如用户刚提供的订单号、正在处理的问题类型。另一种是长期记忆,比如用户的历史偏好、之前处理过的事件记录。两者的管理策略完全不同。短期记忆适合用滑动窗口加关键信息抽取,长期记忆适合用外部存储加检索召回。
我目前验证过比较稳的方案是“三层记忆”。第一层是原始对话窗口,只保留最近N轮原文,保证短期内的完整对话语境。第二层是结构化摘要,每隔几轮用模型把关键上下文压缩成一个简短摘要,固定塞进系统提示里。第三层是外部记忆存储,把用户身份、订单状态这类强结构化信息落到独立的存储里,需要时精准读取,而不是靠模型自己回忆。这套组合下来,既控制住了token消耗,又保住了关键信息。实现的时候要注意摘要把关,摘要生成这一步本身也可能丢细节,所以关键字段要单独抽取出来存结构化副本。
2.3 工具调用:模型自作主张就是不听安排
工具调用是Agent区别于普通对话的核心能力,也是最容易翻车的环节。经典症状包括:该调工具的时候不调,不该调的时候乱调;参数填错,把字符串塞进数字字段;同一个意图连续调用好几个不必要的工具,白白浪费时间和成本。
我印象很深的一次事故是,一个数据分析Agent,用户问“上个月销售额是多少”,模型没有调用查询工具,而是直接基于自己编造的数字给出了答案。那个数字和真实数据差了整整一个数量级。这类“跳过工具直接编造”的问题比参数填错更可怕,因为结果看起来特别自然,不校验根本发现不了。
工具调用不稳定的原因有几层。一是模型对工具的理解依赖工具描述的自然语言质量,描述写得含糊,模型就容易误判适用场景。二是参数定义不严谨,类型、取值范围、枚举值没有约束清楚,给了模型自由发挥的空间。三是工具数量一多,模型的选择难度呈指数级上升,十几个工具摆在面前,选错的概率会明显增加。
针对这些问题,我的做法是给每个工具写“使用说明书式”的描述:什么场景用、什么场景坚决不能用、每个参数的含义和取值范围全部写清楚。写完之后还要做一轮“模拟对抗测试”,故意拿边缘case去试,看模型会不会误调用、会不会漏调用。另一个关键点是启用工具选择约束机制,把关键步骤的调用锁定为必须执行,不给模型跳过或自由发挥的余地。还有一点容易被忽略:工具返回的结果要强制格式化,让模型把原始返回先做摘要再交给下一步决策,能显著降低无关信息对后续判断的干扰。
2.4 多步规划:计划完不成,还容易原地打转
多步Agent最怕的不是任务本身难,而是Agent自己规划出来的路线走到一半发现走不通,然后陷入无限重试或循环。我见过一个Agent在某个步骤解析失败后,一直重复同一个调用,每一轮都报同样的错,接着又发起同样的调用,白白烧掉了几十万token,最后是我手动杀掉进程才停下来的。
这种死循环的根因,是Agent缺少全局视角和退出机制。每一步模型只能看到当前窗口里的局部信息,一旦当前动作失败,模型没有能力对比“我已经尝试了几次”“我是否应该换一种方案”,于是只能在原地重复。
我后来给所有多步Agent都加了三样东西。第一是最大重试次数限制,任何单一步骤失败重试超过两次就直接进入降级流程,比如放弃该步骤并报告失败原因,而不是继续硬试。第二是思路切换提示,当检测到连续两次相同动作时,在下一轮提示词里显式告诉模型“你已经尝试过这个方法两次,结果相同,请选择完全不同的方案”。第三是任务分解前置,不在运行时让模型自由发挥规划,而是先用一次独立的规划步骤把大任务拆成子任务清单,执行阶段就围绕这个清单逐项推进。加了这三样之后,循环类故障率下降非常明显。
说到规划,还要提一个反直觉的经验:任务越复杂,越不应该让Agent自由探索。自由探索适合探索性任务,但生产环境里的Agent,绝大多数任务都适合“确定性优先、灵活性补充”的模式。先固定主干流程,再允许模型在分支环节里发挥,可控性会好很多。特别是涉及真实数据操作的任务,比如改配置、发消息、记状态,主干流程的每一步都应该有明确的校验点。
2.5 幻觉:说得越流畅,错得越离谱
幻觉在Agent里不只是“信息不准确”,更危险的是“过程性幻觉”——模型编造出根本不存在的工具返回值、编造中间计算步骤、甚至编造一次看似合理的执行结果。因为Agent的决策链条建立在模型自己生成的中间结果上,一步幻觉就可能污染后续所有决策。
有个财务分析Agent的案例教训特别深刻。模型在回答里引用了一组数据,看起来有理有据,还带表格、带小数点后两位,实际上那组数据在原始数据源里根本不存在。我后来逐条核对了它的推理链才发现,它在一个中间步骤里把某个数“推算”了出来,后面所有计算都基于这个编造的数字,最后得出来的结论自然错得离谱。
对付幻觉,我有一套“防编造护栏”。第一,凡是涉及真实数据的回答,必须让模型引用数据源标识,没有引用标识的回答直接判定不合格。第二,关键数字在返回前用代码层面的规则校验,比如和原始数据源做数值比对,不一致就触发重新生成。第三,在Prompt里显式声明“禁止编造工具调用结果,如果未获得返回数据请如实说明”。别小看这句话,模型在没有明确禁令的时候默认倾向就是补全信息,给它一句硬约束能挡掉相当一部分编造行为。第四,有条件的话引入外部检索,让模型先检索后回答,而不是凭参数知识硬答。检索加引用这套组合,虽然不能做到百分百消除幻觉,但能把风险压到可以接受的范围。
2.6 调试黑洞:出了问题根本不知道发生在哪一步
Agent是天然的黑盒系统。一次任务可能经历模型推理、工具调用、结果解析、再次推理等多个环节,任何一个环节出错,都可能导致最终结果不对,但日志里往往只有一个“最终回答异常”的模糊记录,你根本不知道问题出在哪里。
我调试一个Agent的时候经常陷入这种困境:用户报障说回答不对,我把完整对话翻一遍,看不出是哪一步出了问题。因为没有分步日志,没有中间状态记录,模型每一步到底看到了什么语境、输出了什么内容、工具实际返回了什么,全部没有留存。这种情况下排查全凭猜,效率极低。
后来我把可观测性当成了和功能开发同等重要的事情来对待。核心做三件事:第一,完整记录每一步的事件日志,包括模型输入输出、工具调用参数、工具返回结果、token消耗、耗时,全部落库。第二,给每一次会话生成唯一的追踪标识,从用户提问到最终回答全链路串起来,出问题的时候可以随时回放每一步当时发生了什么。第三,记录中间状态快照,尤其是每轮生成的摘要、临时决策这类影响后续流程的关键状态,单独存一份。做完这些之后,线上再出问题,我基本可以在几分钟内定位到具体环节,排查效率提升了一个量级。
3. 实操方案:可以直接抄的配置思路
3.1 用结构化输出把不确定性按住
结构化输出是控制Agent输出质量最有效的手段之一。我的做法是所有关键输出都绑定固定的输出结构定义,要求模型返回严格符合定义的JSON,并且在解析层做双重校验。这里有个容易踩坑的细节:光在Prompt里贴结构定义不够,因为模型偶尔还是会给无效JSON。要启用模型接口自带的强制结构化能力,这类能力通常能把无效输出的概率压到极低。
即使如此,我还是会在解析层保留兜底逻辑。推荐的做法是解析失败时,把解析错误信息拼进Prompt,让模型“基于这个错误修正你的输出”并重试一次。实测下来,这种“报错后纠正”的重试策略比单纯重试的成功率高很多,因为模型明确知道了自己错在哪里。
3.2 记忆方案选型参考
记忆方案没有银弹,我在实际项目中根据业务特点做选择。如果是单轮问答为主的场景,滑动窗口就够用,不需要过度设计。如果是多轮对话且需要长期用户画像的场景,必须引入外部存储加检索。如果是复杂任务执行型场景,建议用分层记忆,就是前面说的原始窗口、结构化摘要、外部存储三层组合。
选型时还要考虑一个隐性成本:记忆检索本身的延迟和token消耗。我见过有人给每个用户请求都做向量检索,结果延迟增加了几百毫秒,成本也翻了一倍,收益却微乎其微。合理做法是给记忆检索加触发条件,比如检测到当前对话涉及历史偏好或用户身份信息时再检索,而不是每次无脑召回。另外,摘要更新的频率也要控制好,频繁压缩反而会在摘要里累积误差。
3.3 工具定义的写法要点
工具定义写得好不好,直接决定工具调用成功率。我的核心心得是:描述要写“模型视角”,而不是“开发者视角”。开发者知道这个工具是干什么的,模型不知道,所以描述里必须包含适用场景、禁用场景、参数的实际语义。
举个例子。一个查询订单状态的工具,开发者的第一反应是写“获取订单状态”,但靠这条描述,模型很容易在用户问物流进度时误用。更好的写法是:“仅当用户询问已下单订单的状态、进度、物流信息时使用。不要用于查询商品信息或售后服务。参数order_id为用户下单后得到的唯一订单编号,格式为18位数字。”这种写法看起来啰嗦,但实测能把误调用的概率降低一大半。工具数量超过十个后,建议按领域分组成命名空间,进一步降低模型的选择难度。
4. 高频故障排查速查表
把这一年多遇到的高频故障整理成表格,排查的时候可以直接对照。
| 故障现象 | 可能的根因 | 排查思路 | 解决方案 |
|---|---|---|---|
| 输出JSON经常解析失败 | Prompt约束不足或温度过高 | 查看原始输出,判断是格式问题还是内容问题 | 启用强制结构化输出,解析层做容错和重试 |
| Agent忘记用户早期说过的话 | 上下文压缩丢失关键信息 | 检查摘要生成逻辑,确认关键字段是否保留 | 关键信息抽取到结构化存储,需要时精准读取 |
| 该调工具时不调 | 工具描述不明确或工具选择未锁定 | 查看模型决策日志,确认模型对工具的理解 | 重写工具描述,增加适用和禁用场景说明 |
| 死循环重复调用同一个工具 | 缺少失败退出机制 | 查看追踪日志里的连续动作序列 | 加重试上限、思路切换提示、降级流程 |
| 回答里的数据是编造的 | 模型幻觉且无引用校验 | 核对推理链和引用标识 | 强制引用溯源、关键数据代码校验、检索增强 |
| 同样输入结果时好时坏 | 模型概率特性或上下文差异 | 对比正常轮和异常轮的上下文差异 | 降低温度、增加输出校验、重试纠错 |
| 多轮后token消耗暴涨 | 上下文无限累积 | 分析每轮token构成 | 滑动窗口加摘要压缩,控制上下文规模 |
| 调用了多余的不必要工具 | 工具选择逻辑过于宽松 | 检查是否启用了工具选择约束 | 锁定关键步骤必须调用的工具,放宽非关键步骤 |
| 计划走到一半推不动 | 子任务依赖关系未理清 | 查看规划阶段的输出完整性 | 任务分解前置,明确子任务顺序和依赖 |
| 线上故障无法定位 | 缺少分步日志和追踪标识 | 检查事件日志是否完整落库 | 完善可观测性,记录中间状态快照 |
这张表是我日常排查的起点,但不是终点。每个项目的Agent链路都有自己的特殊故障模式,建议你维护一份属于自己项目的排查文档,每踩一个新坑就补一条,慢慢就会变成团队里最有价值的资料。
5. 项目落地后的几点个人体会
文章最后说几句实在话。Agent开发最大的错觉,是以为把Prompt写漂亮了就能得到稳定的Agent。实际上Prompt只是整个系统里的一环,结构化输出、记忆管理、工具约束、失败重试、可观测性,每一环都是决定Agent能不能真正上线的重要因素。我在做第一个Agent项目时也是天真的“Prompt迷信者”,被现实教育过几次之后才开始老老实实搭全套方案。
另一个体会是,别指望一次上线就完美,Agent产品天生是迭代型的。上线前用边界case做一轮覆盖测试,上线后保持对错误日志的持续关注,把每一次线上故障都当成优化Prompt和流程的机会。随着项目越跑越久,Agent会从“时好时坏”慢慢变成“稳得一批”,这个过程靠的就是一轮轮真实故障的打磨,而不是一次性的完美设计。
最后分享一个小技巧:给Agent加一个“认错”机制。在系统提示里明确告诉模型,如果发现自己拿不到需要的数据、或者某个步骤执行失败,直接向用户说明“这个问题暂时无法完成”,而不是强行生成一个看似合理的答案。这个机制对我来说是所有护栏里最简单、也最管用的一个,它不能提升Agent的上限,但能守住体验的下限,值得每个项目都加上。