Agent工具调用报错之后:90%的团队只做了重试,然后越重试越糟
工具报错是Agent每天都会遇到的事,但重试不是万能的。本文拆解七类工具错误、每类该用什么恢复策略,以及为什么「原样重试三次」是事故放大器。
Agent 上线第一周最常收到的反馈不是“答案不准”,而是“它卡住了,反复调同一个接口,最后给你一句抱歉”。
看日志会发现一模一样的 pattern:工具返回 500,Agent 原样再调一次,再 500,再调一次,第三次后放弃并道歉。三次调用把预算烧完,用户什么也没得到。问题不在重试本身,在于把“重试”当成唯一的恢复手段。
先说清楚:错误分七类,恢复方式完全不同
把错误当成一坨处理,是恢复设计的第一大坑。至少要在每类错误上做区分:
| 错误类型 | 典型信号 | 正确恢复策略 |
|---|---|---|
| 瞬时故障 | 503、超时、连接重置 | 指数退避重试(1–3 次) |
| 参数错误 | 400、schema 校验失败 | 修正参数后重试,禁止原样重试 |
| 鉴权/权限 | 401、403 | 不重试,报给用户或转人工 |
| 资源不存在 | 404 | 换检索路径,不要重放同一 ID |
| 限流 | 429 | 读 Retry-After,排队等待 |
| 业务拒绝 | 200 但 success:false | 按业务码分支,通常不重试 |
| 部分成功 | 批量接口部分条目失败 | 只重试失败子集,不要全量重放 |
最容易出事的是最后一行。批量创建 20 条记录,18 条成功,2 条失败,Agent 一口气把 20 条全部重放——产生 18 条重复数据。这种事故在真实环境里非常常见,而且通常没人写测试覆盖。
为什么“原样重试三次”是事故放大器
三个原因叠在一起:
一是把瞬时故障变成雪崩:上游已过载,最不需要的就是三倍流量,没有退避和抖动的重试本质是自制的 DDoS。二是很多错误重试多少次都不会成功:参数缺字段、token 过期、ID 写错,这类错误的不动点就是失败,Agent 却把它们当网络抖动处理。三是污染上下文:三次失败输出堆进历史,模型接收到“这个工具不可靠”的信号,接下来可能编造一个看似合理的答案绕过工具——这是最危险的一步。
恢复该怎么设计:把错误当作可读的输入
核心思路是把错误结构化地交回给模型,而不是让它猜。返回给 Agent 的错误对象应至少包含四项:
{
"error_type": "invalid_param",
"retryable": false,
"message": "缺少必填字段 order_id",
"suggestion": "若只有手机号,请先调用 search_customer 获取订单"
}
suggestion 字段的价值被严重低估。它不是给用户看的兜底文案,而是给模型看的下一步指令。加上这一个字段,参数类错误的二次成功率通常能明显上升——因为模型不再盲改,而是沿着你指的路走。
三条配套规则:
- 不可重试的错误,在返回里显式标注
retryable: false,并在系统提示中写明“标注为不可重试时不要再次调用”。 - 每个工具设调用上限(建议 2 次),超限后强制进入降级分支:换工具、换路径,或直接告知用户。
- 失败信息去噪,只把 message 和 suggestion 放进上下文,原始堆栈留在日志里。
降级必须显式,不能静默
如果所有恢复路径都失败,正确的做法不是“生成一个看起来合理的答案”,而是明确告知:“这一步我没能拿到订单数据,你可以提供订单号,或者我转人工。”
静默降级给出过时或编造的信息,用户是察觉不到的。这类错误的成本远比一次坦诚的失败高。
可落地的检查清单
上线前过一遍:每类错误有明确分支、批量操作只重试失败子集、幂等键覆盖所有写操作、工具调用有硬上限、降级路径有文案、所有失败都带 suggestion。六条不需要新技术栈,只改错误处理层,半天能补完,却能消掉大部分“Agent 卡住了”的投诉。