为什么 Agent 失败不能笼统归因,必须按节点拆开
把 Agent 失败拆成输入 / 工具 / 推理 / 输出四类节点,先定位再改进。
立靶:不做失败分类,你只会得到一句「它不太行」
先看一个真实场景。某团队上线一个语音对话 Agent 后,运营反馈「经常答非所问」。PM 把这句话原样转给工程,工程查了三天,结论是「模型能力有限,等下个版本」。两周后问题照旧。
问题不在模型,在归因。「答非所问」是一个结果,不是一个节点。同样一句「答非所问」,背后可能是:
- 用户那句话压根没被听清(输入层崩了)
- 听清了,但要查的工具超时返回了空(工具层崩了)
- 工具返回正常,但模型把两个字段对调了(推理层崩了)
- 推理对了,但输出的 JSON 少了个括号,下游解析失败(输出层崩了)
这四种「答非所问」的修法完全不同:第一种要做降噪和复述确认,第二种要加重试和降级,第三种要改 prompt 或加校验,第四种要做格式约束。如果 PM 只会说「它不太行」,团队就只能在这四个方向里乱撞。
不做失败分类的三个典型后果:
- 笼统归因——所有问题都归到「模型不行」「数据不够」,这两个筐什么都装得下,等于什么都没说。
- 无法定位——一次失败发生在哪个环节说不清,工程师只能从头复现,排查成本极高。
- 无法改进——没有节点维度的统计,你永远不知道改 prompt 和加重试哪个 ROI 更高,迭代变成拍脑袋。
PM 在失败这件事上的核心价值,不是修 bug,而是把「一团失败」切成可定位、可统计、可分配的节点。
框架:失败节点的 MECE 四分类
一次 Agent 任务,本质是一条流水线:理解输入 → 调用工具 → 推理决策 → 产出输出。失败只可能发生在这四个节点之一(或回滚这个正交维度上)。按节点切分,天然满足 MECE——不重不漏。
| 类别 | 节点 | 典型表现 | 根因 | 对应处理 |
|---|---|---|---|---|
| A 类·输入理解失败 | 任务入口 | 没听清、意图识别错、参数抽取缺失、表述歧义 | 物理信道差、表述模糊、上下文丢失 | 复述确认、选项式提问、降噪打分、入口拦截 |
| B 类·工具调用失败 | 外部依赖 | 超时、限流、返回空、调错工具 | 网络瞬态、依赖故障、工具/参数选错 | 指数退避重试、降级、双供应商热备、幂等保护 |
| C 类·推理失败 | 模型内部 | 字段对调、漏步骤、幻觉、逻辑跳步 | prompt 歧义、上下文超窗、能力边界外 | 改 prompt、加中间校验、拆步、置信度门控 |
| D 类·输出格式失败 | 任务出口 | JSON 不合法、字段缺失、结构不符 schema | 缺约束、长输出截断、格式漂移 | 结构化输出约束、schema 校验、解析失败兜底重生成 |
四类之外还有一个正交维度·回滚:失败发生后是否产生了副作用、要不要清理。它不属于「在哪个节点失败」,而是「失败后留下了什么烂摊子」,所以单独拎出来,不能混进上面四类的占比统计里。这一点对应错误恢复策略里「4+1」的处理——主路径回答如何完成,回滚回答如何清理副作用。
几个划分上的关键判断:
- A 类和 C 类容易混。 区分点:输入信号本身是否完整。信号没进来(没听清)是 A 类;信号进来了但模型用错了是 C 类。
- B 类要再分瞬态和永久。 网络超时、限流是瞬态,重试有用;工具返回「无此数据」是永久,重试只是浪费时间。
- D 类常被误当成 C 类。 推理内容是对的、只是包装坏了,属于 D 类,靠格式约束解决,不要去动 prompt 的推理逻辑。
case:一个语音 Agent 的 A 类失败拆解
把上面框架落到一个脱敏的真实案例——一个面向电话场景的语音对话 Agent。电话场景的物理信道不可控(噪音、网络抖动、口音、中老年用户语速慢),A 类输入理解失败是这里最高频的失败节点。
团队没有把「听不清」当成一个笼统问题,而是先在 A 类内部再做根因二分:
| 根因 | 特征 | 处理方向 |
|---|---|---|
| 环境噪音 | 空转写、语气词占比高、识别字数极少 | 渐进劝离 / 转人工 |
| 用户语速慢 | 思考停顿长、反复确认 | 参数自适应 / 不打断 |
关键认知是「人特征稳定、环境信号动态」,所以先识别人、再判断环境。落地上做了三件事:
- 量化捕获——不靠单次判断,而是对每条用户语音按四个信号累计打分(空转写、语气词比例、字密度、短答+停顿),把「听不清」从主观感受变成一个可观测的分数。
- 分级响应——分数低时正常;中等时自身调整话术(避开开放题、改二选一);高时协商用户(「方便换个地方吗」);超阈值止损转人工并标记线索回呼。
- 边界封顶——字错率超阈值直接触发人工介入,AI 不再硬扛;同时双供应商热备,错误率持续超标自动切换。
注意这里同时调用了 B 类的处理手段(供应商热备 = 工具层降级容错)。这正说明分类的价值:一旦把失败定位到具体节点,每个节点都有自己成熟的处理套路可以套,而不是面对「它不太行」束手无策。
为了不误伤,还加了三层防误报兜底——确认词白名单、合法拖长音豁免、严格计分阈值。这对应一条通用规律:失败检测本身也会失败(误报),埋点时必须把误报率一起监控。
可操作做法:怎么埋点捕获、怎么定位
埋点捕获清单:
- 每个节点显式打 tag——一次任务流经四个节点,每个节点的进入/退出/异常都打点,禁止 silent fallthrough(错误被静默吞掉是头号反模式)。
- 失败必带节点字段——日志里每条失败记录都带
failure_node ∈ {A,B,C,D},这样才能出「四类占比」分布表。 - A 类记输入快照——存下原始输入信号(识别文本、置信度),便于复盘到底是没进来还是用错了。
- B 类记瞬态/永久标记——区分该不该重试,并记录重试次数和最终结果。
- C 类记中间推理——把模型的中间步骤/工具选择记下来,才能定位是哪一步跳了。
- D 类记 schema 校验结果——解析失败要单独成类,别混进「模型答错」。
- 误报率一并监控——失败检测器自身的误触发要可观测,否则会过度干预。
定位的纪律——下判断前先答四问(借用错误恢复的诊断 SOP):
Q1 错误类型? 瞬态(网络/超时/限流) vs 永久(输入质量/边界外)
Q2 副作用? 无 vs 已写未交付 vs 已交付给用户
Q3 可恢复? 可续接 vs 必须重做 vs 不可恢复
Q4 层别? 预防层(设计漏洞) vs 恢复层(运行时)
没答这四问之前不下任何判断。这能挡掉最常见的两个错误:把永久错误当瞬态去无脑重试,以及把设计漏洞丢到运行时去兜底。
统计与排期:
- 出一张节点失败分布表(A/B/C/D 各占多少),按占比决定迭代优先级——别凭感觉,让数据说话。
- 回滚触发率单独看(正交维度),异常升高说明副作用清理频繁,是系统稳定性的红灯。
- 每类失败的修法 ROI 不同:D 类加 schema 约束往往最便宜、见效最快;C 类改 prompt 见效慢但天花板高。先摘低垂果实,再啃硬骨头。
收口
失败不是一团,是一条流水线上的四个节点。PM 的功夫不在修 bug,而在能不能一眼说出「它死在了第几个节点」——输入听错了、工具没回、模型想歪了、还是输出包坏了。分不清节点,所有改进都是赌博。