第二阶段 · 可靠的本地编码 Agent第 8 / 30 章
本章目录

第 08 章 · 错误、重试与幂等

本章目标:区分“再试一次可能恢复”的故障与“再试只会重复伤害”的故障,并把重试写成有预算、有证据的状态机。

1. 错误不是一个类别

Agent 会遇到模型 API 限流、网络超时、参数校验失败、文件不存在、测试失败、权限拒绝和逻辑无解。把它们全部 catch 后重试,会让系统变慢、变贵,甚至重复副作用。

可以先分成四类:

类型 例子 默认策略
瞬时故障 429、连接重置、服务暂时不可用 有上限地退避重试
输入错误 参数缺失、路径非法 返回模型修正参数
任务证据 测试失败、编译错误 作为 observation 继续推理
权限与安全 沙箱拒绝、用户拒绝 停止或请求明确授权

“测试失败”不是工具异常。pytest 正常运行并以退出码 1 告诉你行为不满足断言,这恰恰是有价值的观察。

2. 指数退避解决什么

瞬时故障若立即高频重试,会加重服务压力。指数退避让等待时间逐步增长,并加入随机抖动避免大量客户端同时重试。重试仍需最大次数和总时间预算。

delay = min(max_delay, base_delay * 2 ** attempt)
delay += random_jitter()

但退避不应包住整个 Agent turn。模型 API 请求失败可以重发;真实工具是否能重放,需要单独判断。

3. 幂等是副作用工具的生命线

读取文件天然近似幂等,重复读取只浪费资源。创建 issue、发送消息、扣款和数据库写入不是。Runtime 在执行前生成 operation_id,外部系统使用它识别重复请求;或者先查询结果是否已经存在。

如果客户端在发送后超时,你不知道服务端是否执行成功。这叫不确定提交。正确策略是用幂等键查询,而不是再次创建。Agent 框架要记录“调用已发出但结果未知”,不能简单回到未执行状态。

4. 重复行为检测

即使每次工具都正常返回,模型也可能不断做同一件事。为工具名和规范化参数生成指纹,记录最近 N 次调用。当相同指纹与相同结果反复出现,就说明没有信息增益。

框架可以采取三级动作:

  1. 第一次重复:提醒模型说明新理由;
  2. 第二次重复:要求改变假设或工具;
  3. 第三次重复:停止并报告 blocked。

对于读取工具,偶尔重复是合理的,因为文件可能变化;因此指纹还应考虑文件版本或结果哈希。

5. 错误预算与用户体验

重试次数、最大 turn、总时间和费用是不同预算。一次复杂任务可以允许多轮逻辑修复,却不应无限等待同一个网络请求。界面要显示正在重试什么、已经等待多久,以及用户打断后会保留哪些状态。

最终报告应区分:任务目标失败、工具暂时不可用、权限不足、预算耗尽。只有第一种可能说明方案不正确,其他是运行环境结论。

6. 本章实验

实验 03 允许调整最大轮数、故障模式和退避策略。先选择“重复同一错误”,观察为什么重复检测比 max_turns 更早停止;再选择“持续失败”,理解预算耗尽与任务完成是两个不同终态。

设计检查

在给任何工具增加自动重试前,写下:这个动作重复执行是否安全?第一次执行结果未知时怎样查询?谁提供幂等键?回答不了,就不应自动重放。