一个多步骤 Agent 任务,比如"查询订单 → 计算退款金额 → 调用支付接口 → 发送通知",任何一步失败,后续步骤都会落空。更麻烦的是,每一步不是固定代码路径,而是模型根据上下文动态决定的。失败原因可能藏在某次工具调用的输入参数里,也可能藏在模型对上下文的错误理解中。定位问题的第一步,是把"哪一步失败"和"为什么失败"分开。

三类典型失败:特征与判定方法

从工程角度看,多步骤 Agent 任务的失败可以归为三类。

第一类:工具调用超时或不可用。 外部工具变慢、服务不可达、被限流。特征是在链路日志中该步骤耗时异常,且没有返回体或返回错误码。排查时不要只看最终错误码,要区分"调用没有返回"和"返回了错误结果",这两者的恢复策略完全不同。

第二类:参数生成错误。 模型生成的工具参数类型不对、字段缺失、枚举值非法,或引用了前面步骤中不存在的 ID。这是 Agent 特有的失败模式:参数不是程序员写的,是模型生成的。定位时必须同时记录模型的原始输出(未解析的文本或 JSON)和工具实际收到的参数,否则你只能看到工具报错,无法判断是模型生成错了,还是参数在传递过程中被改坏了。

第三类:上下文丢失或状态错乱。 Agent 在执行中依赖前面步骤的结果摘要,如果上下文被截断、摘要被覆盖,或步骤间的状态没有被正确传递,模型可能在第 5 步使用了第 2 步的错误值。这类失败最隐蔽:每一步单独看都成功,但最终结果错误,或某一步引用的中间变量在日志中根本找不到。

日志与追踪:把多步骤串成一条可审计的链路

多步骤 Agent 的日志不能按单次调用来记。一个可行的做法是建立统一的结构化日志,至少包含以下字段:

  • trace_id:一次完整任务的唯一 ID,所有步骤共用
  • step_id / parent_step_id:当前步骤与父步骤,形成树状调用关系
  • tool_name:调用的工具
  • input_snapshot:工具实际收到的参数原文
  • output_snapshot:工具返回的原文
  • model_raw_output:模型生成的原始内容,包含工具调用指令
  • started_at / finished_at / duration_ms
  • error_type:失败分类标签
{
  "trace_id": "task_8f3a1c",
  "step_id": "step_4",
  "parent_step_id": "step_3",
  "tool_name": "payment.refund",
  "input_snapshot": {"order_id": "A-1024", "amount": 99.5},
  "output_snapshot": {"status": "error", "code": "INVALID_AMOUNT"},
  "model_raw_output": "{"tool":"payment.refund","args":{"order_id":"A-1024","amount":99.5}}",
  "error_type": "tool_rejected",
  "started_at": "2025-06-10T08:21:03Z",
  "duration_ms": 2310
}

排查时最有效的路径是:先按 trace_id 拉出整条链路,然后看失败步骤的前一步和后一步。很多"看起来是某一步失败"的问题,根因在前一步的输出没有被正确传递。比如 payment.refund 报 INVALID_AMOUNT,应该去查前一步计算金额的工具输出,而不是只盯着报错那一步。

这里真正值得关注的是 input_snapshot 与 model_raw_output 的对应关系。工具调用参数由模型生成,如果只记录工具收到的参数、不记录模型原始生成内容,就无法区分"模型生成错了"和"参数在解析传递中被改坏了"。而这个区分直接决定了重试有没有意义:前者需要让模型看到错误反馈后重新生成,后者只需要修复解析逻辑。

状态快照:让任务具备恢复的起点

多步骤任务执行到第 4 步时,前面几步的结果已经成为状态。恢复的前提是状态可还原。按 checkpoint 思路实施时,注意三点:

  1. 每一步完成后,把该步骤的输出、依赖关系和执行顺序写入持久化存储,而不是只记最终结果。
  2. 快照中保存模型原始输出,而不只是解析后的结构化数据,因为解析过程本身可能丢失信息。
  3. 记录该步骤执行时的上下文摘要。上下文摘要与步骤结果同等重要。

失败后恢复时,可以从最后一个成功的 checkpoint 继续,不必从头执行。但这里有一个 Agent 特有的约束:模型每一步的行为受对话历史影响,重放时给模型的上下文必须与原始执行时一致,否则模型可能在重放过程中选择不同的工具或参数。如果上下文已经被截断或覆盖,正确的做法是显式标记该步骤为"基于不完整上下文执行",而不是让模型在残缺状态下继续往下跑。

重试:不是所有失败都值得重试

三类失败的重试价值差异很大。

超时类失败值得重试,但要带退避。一个简单的做法是重试 3 次,间隔指数递增(如 1 秒、2 秒、4 秒),并设置总超时上限,超过上限后标记为失败而不是无限等待。

参数错误不值得盲目重试。同样的上下文大概率生成同样的错误参数。有效做法是让模型看到工具返回的错误信息,把错误反馈写回上下文,允许 Agent 重新生成参数——但这本质上是二次推理,不是重试,要单独计数并限制次数,防止模型在错误参数上反复打转。

上下文丢失或状态错乱时,重试没有意义,应直接走状态恢复或任务重规划。

幂等性是多步骤重试的大前提。工具调用可能已经执行成功,只是响应在返回途中丢失。如果重试时对同一个幂等键执行了两次,比如重复退款、重复发通知,就会产生脏数据。工程做法是为每个步骤生成幂等键(可以直接用 step_id),工具侧对相同幂等键的请求返回第一次执行结果。对于不支持幂等的工具,重试前必须人工确认该步骤是否已实际执行——这是自动重试的硬边界。

降级与补偿:重试失败之后怎么办

重试仍失败时,任务不应直接崩溃,至少要有三层降级路径:

  1. 工具降级:主工具失败时切换到备用通道。但降级选择不能由模型自由决定,应在工具定义层预设 fallback 顺序,否则模型可能选到业务上不允许的降级路径。
  2. 步骤降级:非关键步骤(如发送通知)失败时,允许标记为"已跳过,人工补发",让主流程继续完成。这要求任务编排阶段就声明哪些步骤是关键路径,哪些可以异步补做。
  3. 人工接管:连续多次失败后,把任务标记为 requires_review,并附上完整 trace 链路和状态快照。人工介入的价值在于掌握模型没有的信息——比如某个工具正在发版、某个参数在业务上不被允许。

补偿逻辑在多步骤 Agent 中容易被忽略。第 3 步调用了支付接口,第 4 步失败,整个任务失败。如果第 4 步永远不会成功,第 3 步的支付操作是否需要撤销?这需要在任务设计阶段预先声明每个步骤的补偿动作,而不是在失败时让模型自己决定。模型没有权限做这类判断,也不应该有。

排查检查清单

综合来看,构建多步骤 Agent 的故障排查与恢复能力,至少需要确认:

  1. 日志是否按 trace_id 串联?每一步是否同时记录了模型原始输出和工具实际收到的参数?
  2. 状态快照是否包含步骤依赖关系和上下文摘要?能否从任意一步恢复?
  3. 重试策略是否区分了超时和参数错误?对不支持幂等的工具是否关闭了自动重试?
  4. 任务编排是否声明了关键步骤、可降级步骤和补偿动作?
  5. 人工接管入口能否拿到完整 trace 链路和状态快照,而不是只有一句"任务失败"?

这套框架不依赖任何具体的 Agent 框架或模型实现,因为核心问题——"模型生成的步骤如何被审计、如何被可靠恢复"——是所有多步骤 Agent 系统都绕不开的。真正困难的不是重试机制本身,而是让每次失败的现场信息足够完整,让恢复动作有据可依。