1. 别急着换模型:Agent 失败排查的认知纠偏
做 Agent 开发的人,大概都经历过这种场景:任务跑着跑着突然挂了,日志里只留下一行冷冰冰的agent execution terminated due to error,或者一个422、一个model request failed。第一反应是什么?换模型。GPT 不行换 Claude,Claude 不行换国产,换来换去发现问题还在。我踩过这个坑不止一次,后来才想明白一件事:绝大多数 Agent 失败,根因根本不在模型本身,而在于我们把错误码当成了终点,而不是起点。
这篇文章想聊的就是这个事。标题里说的“从粗错误码到失败链”,核心意思是:一个 Agent 任务失败时,你看到的那个错误码只是冰山露出水面的一角,水面下藏着一整条因果链——可能是工具调用参数错了,可能是上下文超了,可能是某个中间步骤的返回值格式不对,也可能是并发写入冲突。你要做的不是盯着那个错误码猜,而是顺着它把整条链还原出来。
这篇内容适合谁看?如果你正在做 Agent 开发、智能体编排、多 Agent 协作,或者你负责线上 Agent 服务的稳定性,那这篇基本就是给你写的。如果你刚入门 Agent,还在学 Agent 框架和编排,那也没关系,我会把排查思路拆得足够细,你照着做就能上手。关键词里提到的terminalBlocker、失败链、错误码定位路径,这些我都会在实操部分一一展开。
先说一个我自己的判断:Agent 的可观测性建设,优先级应该高于模型选型。一个模型差一点但日志清晰的 Agent,比一个模型强但黑盒的 Agent 好维护十倍。原因很简单,Agent 是“多步决策 + 工具调用 + 状态传递”的复合体,任何一步出问题都会以“模型失败”的表象暴露出来,你不把链路打通,永远在治标。
2. 错误码为什么总是“骗人”:Agent 失败的三种伪装
2.1 表象错误码与真实根因的错位
先讲清楚一个概念:Agent 系统里的错误码,绝大多数是最外层捕获器抛出来的,而不是真正的故障点。什么意思?假设你的 Agent 执行流程是这样的:接收任务 → 规划 → 调用工具 A → 调用工具 B → 汇总 → 返回。如果工具 A 返回了一个格式不对的结果,规划模块没校验就往下传,工具 B 拿到脏数据崩了,最后外层捕获到的是工具 B 的异常,报出来的错误码是“工具 B 执行失败”。你去看工具 B 的代码,怎么看都没问题,因为问题根本不在它那儿。
这就是典型的错误码错位。我把它叫做“背锅侠现象”——最后一个倒下的模块,替第一个犯错的模块背了锅。要解决这个问题,你得在每一层都留下足够的上下文,让错误能“溯源”。
2.2 三类高频伪装:超时、格式、状态
根据我自己的排查记录,Agent 失败最常见的伪装有三类,我整理成了一张表,你可以对照自己的日志看看:
| 表象错误码 | 可能的真实根因 | 排查切入点 |
|---|---|---|
| 模型请求失败 / 超时 | 上下文超长、prompt 拼接错误、上游工具返回空 | 检查实际发送的 payload 大小和内容 |
| 422 通信故障 | 请求体字段缺失、类型不匹配、编码问题 | 抓取原始请求体逐字段核对 |
| agent execution terminated | 中间步骤抛异常未捕获、状态机卡死 | 按步骤回放,定位第一个异常步骤 |
| 工具调用失败 | 参数 schema 不匹配、鉴权过期、依赖服务抖动 | 打印工具入参和原始返回 |
这张表不是让你背的,是让你在遇到问题时有个“怀疑方向”。比如你看到 422,第一反应不应该是“网络问题”,而应该是“我的请求体是不是哪里不符合对方 schema”。422 在语义上就是“请求格式正确但语义有误”,它跟网络半毛钱关系都没有,很多人一看到通信类错误就往网络上想,方向就偏了。
2.3 为什么“换模型”是最差的解法
我见过太多团队,Agent 一挂就换模型,换完发现好了,就以为解决了。其实大概率是换了模型之后,prompt 的容错边界变了,原来会触发的问题被绕过去了,但根因还在。等哪天换个任务、换个输入,同样的坑又踩一遍。
更麻烦的是,频繁换模型会让你的排查基线彻底乱掉。你连“这个模型在这个任务上的正常表现”都没建立起来,怎么判断什么是异常?所以我一直建议:先把失败链打通,再谈模型优化。模型是最后一块拼图,不是第一块。
3. 失败链的构建方法:让每一步都“可回溯”
3.1 什么是失败链,为什么它比错误码有用
失败链(Failure Chain)这个词听起来玄,其实很朴素:把 Agent 从接收到失败之间的每一个关键节点,按时间顺序串起来,形成一个可回溯的链条。每个节点记录四样东西:输入、输出、耗时、状态。有了这条链,你就能一眼看出是哪一步开始“不对劲”。
举个例子,我之前排查一个terminalBlocker相关的问题,Agent 在执行到某个终端命令时卡死。光看错误码是“执行超时”,但失败链一拉出来就清楚了:前一步工具返回了一个带特殊字符的路径,终端命令拼接时没转义,导致命令一直在等待输入。问题不在终端,在路径处理。如果没有失败链,我可能要去查终端服务的负载、网络、权限,全是无用功。
3.2 埋点设计:四个必须记录的字段
构建失败链的核心是埋点。我总结了一个最小可用集,四个字段缺一不可:
- step_id:步骤唯一标识,最好带序号,方便排序
- input_snapshot:这一步的输入快照,注意是快照不是引用,避免后续被改
- output_snapshot:输出快照,同样要固化
- status + error_detail:状态和错误详情,错误详情要包含原始异常栈
这里有个坑:很多人埋点只记step_id和status,不记输入输出。结果排查时知道哪步错了,但不知道错的时候喂进去的是什么,等于白记。输入输出快照是失败链的灵魂,宁可多占点存储,也别省这个。
提示:快照要注意脱敏,尤其是涉及用户数据、密钥的字段,记录前先做一层过滤,别为了排查把敏感信息写进日志。
3.3 链路串联:从离散日志到因果图
光有单步埋点还不够,你得把它们串起来。串联的关键是传递一个全局 trace_id,让同一个任务的所有步骤共享这个 id。这样你在日志系统里一搜 trace_id,整条链就出来了。
更进一步,如果你想让排查更高效,可以在每个步骤记录它依赖的上一步 step_id,形成一个有向图。这样当某步失败时,你可以自动回溯它的上游,快速定位“第一个异常节点”。我自己的做法是:在 Agent 框架的调度层统一注入 trace_id 和 parent_step_id,业务代码不用关心,框架层自动完成串联。这样既保证了覆盖率,又不会给开发者增加负担。
4. 实操:一次完整的失败链排查过程
4.1 现场还原:一个 422 引发的血案
说个真实案例。某次线上 Agent 批量任务,成功率突然从 98% 掉到 70%,错误码清一色是 422。团队第一反应是“模型服务商出问题了”,准备切备用模型。我拦了一下,说先看失败链。
拉出 trace 一看,失败的请求有个共同点:都在调用某个外部工具时返回 422。再往下看 input_snapshot,发现这些请求的某个字段是空字符串。而正常情况下这个字段应该有值。继续回溯,发现是上游一个数据清洗步骤,在特定输入下会把该字段清空。问题定位:数据清洗逻辑有边界 bug,导致下游工具收到非法参数,返回 422。跟模型、跟网络,一点关系都没有。
4.2 排查路径:从错误码到根因的五步法
我把这次排查的方法固化成了五步,你可以直接套用:
- 锁定错误码的抛出位置:先搞清楚这个错误码是哪个模块抛的,别猜。
- 拉取该 trace 的完整失败链:按 step 顺序排列,找到第一个 status 异常的节点。
- 对比正常与异常 trace:把失败 trace 和成功 trace 并排看,差异点往往就是根因。
- 检查第一个异常节点的输入:输入不对,往上游找;输入对但输出错,问题在本节点。
- 验证假设:构造一个最小复现,确认根因后再改。
这五步里,第三步“对比正常与异常”是最容易被忽略但最有效的。人眼对差异很敏感,两条链一对比,异常点自己就跳出来了。
4.3 工具选型:日志、追踪、回放怎么配
工欲善其事。我自己的技术栈是这样的,供参考:
- 日志:结构化日志,JSON 格式,字段固定,方便检索
- 追踪:用 OpenTelemetry 这类标准方案,trace_id 贯穿全链路
- 回放:把失败链的输入输出存下来,支持一键重放,验证修复效果
这里要强调回放的价值。很多 bug 你改完不确定有没有真修好,回放一下,用原来的输入再跑一遍,成功即修复。这比写单元测试还直接,因为它是真实场景的复现。
注意:回放要注意幂等性,尤其是涉及写操作的工具,别回放一次写一次,先把写操作 mock 掉。
5. 常见问题速查与避坑心得
5.1 排查中的高频误区
做 Agent 排查这几年,我总结了几条“反直觉”的经验,都是踩坑换来的:
- 错误码越具体,越要警惕:像 422 这种具体错误码,往往指向的是表象,真正的根因可能很泛。
- 不要相信“偶发”:Agent 里的偶发失败,99% 是并发或状态污染,不是玄学。
- 日志不是越多越好:关键节点记全,非关键节点记摘要,否则日志淹没你。
- 先复现再修:不能复现的 bug,修了也不知道对不对。
5.2 terminalBlocker 类问题的专项排查
terminalBlocker是热词里提到的一个点,我理解它指的是终端执行被阻塞的情况。这类问题排查有个专门套路:
| 现象 | 可能原因 | 验证方法 |
|---|---|---|
| 命令长时间无输出 | 等待交互输入 | 检查命令是否含交互式参数 |
| 命令立即退出 | 环境变量缺失 | 打印执行环境 |
| 输出被截断 | 缓冲区限制 | 检查输出长度和读取方式 |
| 命令找不到 | PATH 配置问题 | 打印实际 PATH |
终端类问题的核心是把执行环境完整记录下来,包括工作目录、环境变量、用户权限。很多“命令失败”其实是环境不对,不是命令本身的问题。
5.3 建立你自己的排查手册
最后分享一个习惯:我会给每个 Agent 项目维护一份“排查手册”,记录这个项目特有的失败模式和对应解法。因为每个 Agent 的业务逻辑不同,通用方法只能解决 80% 的问题,剩下 20% 得靠项目专属经验。这份手册不用写得多正式,一个 Markdown 文件就行,遇到新问题就加一条。时间长了,它就是你这个项目最值钱的资产。
我在实际使用中发现,真正让排查效率翻倍的,不是多高级的工具,而是坚持记录。每次排查完,花五分钟把过程和结论写下来,下次遇到类似问题,直接翻手册,省下的时间远超这五分钟。踩过几次坑之后我越来越确信:Agent 的稳定性,是靠一条条失败链喂出来的,不是靠换个更强的模型换出来的。