第 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 次调用。当相同指纹与相同结果反复出现,就说明没有信息增益。
框架可以采取三级动作:
- 第一次重复:提醒模型说明新理由;
- 第二次重复:要求改变假设或工具;
- 第三次重复:停止并报告 blocked。
对于读取工具,偶尔重复是合理的,因为文件可能变化;因此指纹还应考虑文件版本或结果哈希。
5. 错误预算与用户体验
重试次数、最大 turn、总时间和费用是不同预算。一次复杂任务可以允许多轮逻辑修复,却不应无限等待同一个网络请求。界面要显示正在重试什么、已经等待多久,以及用户打断后会保留哪些状态。
最终报告应区分:任务目标失败、工具暂时不可用、权限不足、预算耗尽。只有第一种可能说明方案不正确,其他是运行环境结论。
6. 本章实验
实验 03 允许调整最大轮数、故障模式和退避策略。先选择“重复同一错误”,观察为什么重复检测比 max_turns 更早停止;再选择“持续失败”,理解预算耗尽与任务完成是两个不同终态。
设计检查
在给任何工具增加自动重试前,写下:这个动作重复执行是否安全?第一次执行结果未知时怎样查询?谁提供幂等键?回答不了,就不应自动重放。