Agent失败排查:从粗错误码到失败链,别急着换模型 1. 别急着换模型Agent 失败排查的认知纠偏做 Agent 开发的人大概都经历过这种场景任务跑着跑着突然挂了日志里只留下一行冷冰冰的agent execution terminated due to error或者一个422、一个model request failed。第一反应是什么换模型。GPT 不行换 ClaudeClaude 不行换国产换来换去发现问题还在。我踩过这个坑不止一次后来才想明白一件事绝大多数 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 的稳定性是靠一条条失败链喂出来的不是靠换个更强的模型换出来的。