Agent Skill 原理与工程实践(十七):循环、重试、Fallback 怎么设计
Agent Skill 原理与工程实践(十七):循环、重试、Fallback 怎么设计
上一章讨论了条件判断与分支。分支解决“下一步走哪条路”,但复杂任务还会出现另一类控制流:同一类动作需要重复执行。
例如研究任务:
1 | |
又例如工具调用失败:
1 | |
如果设计得好,循环能逐步收集证据、修正结果并完成恢复;如果设计得差,Agent 会陷入无限搜索、重复写入、成本失控或静默降级。
本章的核心认知是:
每个循环都必须有进度指标和终止条件;每次重试都必须有错误分类和预算;每个 Fallback 都必须声明适用条件与质量损失。
1. 这一章要解决什么问题
本章集中回答:
1 | |
2. 三个概念先分清
2.1 Loop:重复执行一个阶段
循环的目标通常是逐步逼近完成条件:
- 搜索更多证据;
- 修复验证错误;
- 分页读取数据;
- 处理批量对象;
- 驱动 Tool Call Agent Loop;
- 迭代生成和审查。
2.2 Retry:重新尝试同一逻辑操作
Retry 通常针对暂时性失败:
- 网络中断;
- 限流;
- 服务过载;
- 短暂超时;
- 可解决的并发冲突。
重试的前提是:导致失败的条件可能随时间或参数修正而改变。
2.3 Fallback:切换到替代路径
Fallback 不是再次尝试原路径,而是使用另一个方案:
- Tool A 不可用,使用 Tool B;
- 实时数据不可用,使用标明时间的缓存;
- 专用解析器失败,使用通用解析器;
- 无法生成图表,交付表格;
- 高质量路径超出预算,切换到较低成本但合格的路径。
可以概括为:
1 | |
3. 整体控制流
1 | |
任何返回循环顶部的边都必须回答:
- 状态发生了什么变化?
- 为什么下一次可能成功?
- 还剩多少预算?
- 是否会重复副作用?
4. Loop Contract:循环必须有契约
一个可靠循环至少包含七个元素。
4.1 Initial State
循环开始前已经知道什么:
1 | |
4.2 Loop Body
每轮执行什么:
1 | |
4.3 Progress Measure
如何知道正在前进:
- 新增有效来源数量;
- 未解决问题数量下降;
- 验证错误数量下降;
- 分页 cursor 前进;
- 待处理对象数量减少;
- 置信度达到阈值。
4.4 Success Condition
什么时候认为完成:
1 | |
4.5 Termination Condition
即使没有成功,也必须退出:
- 最大轮数;
- 最大耗时;
- 最大成本;
- 无新增进展;
- 重复相同错误;
- 用户取消;
- 安全边界触发。
4.6 State Update
每轮结束后保存:
- 新结果;
- 去重集合;
- 下一 cursor;
- 剩余问题;
- 尝试次数;
- 最新错误。
4.7 Exit Behavior
退出后怎样处理:
- 成功进入下一阶段;
- 部分完成并披露限制;
- 尝试 Fallback;
- 请求用户信息;
- 停止。
5. 循环的三类终止条件
5.1 目标驱动终止
1 | |
目标必须可判定。until the answer is good enough 太主观,除非进一步定义评价维度。
5.2 预算驱动终止
1 | |
预算是安全阀,不是完成标准。达到预算时应报告未完成状态,而不是强行宣称成功。
5.3 无进展终止
1 | |
无进展检测可以避免 Agent 用不同措辞重复同一操作。
最稳健的写法通常同时包含三类:
1 | |
6. 搜索循环怎样设计
一个常见反模式:
1 | |
问题在于 enough 没有定义。
更好的搜索循环:
1 | |
循环不是“多搜几次”,而是每轮减少一个明确的知识缺口。
7. 分页循环怎样设计
分页看似简单,也容易出错:
1 | |
还要验证:
- cursor 是否真的变化;
- 页面是否重复;
- item ID 是否去重;
next_cursor缺失是结束还是异常;- 是否有总页数或上限;
- 中途失败后从哪个 cursor 恢复。
安全规则:
1 | |
8. Validate–Fix 循环怎样设计
1 | |
Anthropic Skill 最佳实践强调在质量关键任务中使用验证反馈循环:立即运行验证器、根据具体错误修复、重新验证,只有通过后才能继续。
8.1 修复必须针对错误
不要每轮完全重写全部产物。应使用错误列表定位受影响部分。
8.2 修复后必须从验证器重新开始
模型认为“已经修好”不等于验证通过。
8.3 限制轮数
1 | |
如果相同错误持续出现,说明自动修复路径失效,应停止并报告。
8.4 错误变化时重新分类
第一次是缺字段,修复后变成文件损坏,不应继续沿用原来的重试策略。
9. Agent Tool Loop 的退出条件
客户端工具调用通常形成:
1 | |
Anthropic 官方 Tool Use 文档指出,遇到其他停止原因时循环应退出并按其语义处理,例如:
end_turn:正常结束;max_tokens:输出被预算截断;stop_sequence:遇到指定停止序列;refusal:模型拒绝;pause_turn:服务器侧循环暂时暂停,需要按协议继续。
不能把所有非 end_turn 状态都当成失败后重新发送同一请求,否则可能重复工具副作用。
10. Retry 前先分类错误
| 错误类别 | 例子 | 原样重试是否通常有效 | 正确动作 |
|---|---|---|---|
| 输入错误 | 缺参数、Schema 错误 | 否 | 修正或询问 |
| 认证错误 | Token 失效 | 否 | 修复凭据 |
| 权限错误 | 无权访问资源 | 否 | 请求授权或停止 |
| 额度错误 | 余额、预算耗尽 | 否 | 等待外部处理 |
| 冲突错误 | 资源已被修改 | 有条件 | 读取最新状态后重建请求 |
| 临时错误 | 限流、超时、过载 | 是 | 有界退避重试 |
| 工具实现错误 | 脚本崩溃 | 通常否 | 修复实现或 Fallback |
| 语义失败 | 结果不满足 Contract | 原样重试通常否 | 修改策略或输入 |
原则:
如果不改变任何条件,同一操作必然再次失败,就不应自动重试。
11. Retryable 与 Non-Retryable
11.1 通常可重试
- 临时网络失败;
- 服务器过载;
- 有明确恢复时间的速率限制;
- 短暂超时;
- 服务端标记可重试的错误;
- 已重新读取状态并修正的并发冲突。
11.2 通常不可原样重试
- 参数非法;
- 文件不存在;
- 凭据错误;
- 没有权限;
- 固定大小限制;
- 账户额度耗尽;
- 不支持的格式或版本;
- 安全策略拒绝;
- 确定性脚本 Bug。
11.3 429 也要分类
有些 429 是短期限流,带 Retry-After;有些可能表示消费上限,等待几秒不会恢复。应依据结构化错误类型和响应头,而不是只看状态码。
12. Retry Budget:重试必须有预算
至少定义:
max_attempts:最大尝试次数;max_elapsed_time:最大总时长;retryable_codes:允许重试的错误;backoff:等待策略;stop_on_error_change:错误变化是否停止;fallback_after:何时切换替代方案;on_exhausted:预算耗尽后的行为。
示例:
1 | |
OpenAI 官方错误恢复文档要求重试遵守 Retry-After,并设置尝试次数或截止时间;达到上限或错误变化时应停止自动重试。
13. 指数退避与抖动
13.1 固定间隔的问题
大量客户端同时每秒重试,会形成惊群效应,让刚恢复的服务再次过载。
13.2 指数退避
1 | |
例如:
1 | |
13.3 Jitter
加入随机抖动:
1 | |
13.4 服务端提示优先
如果响应包含 Retry-After 或等价指示,应优先遵守。
13.5 不在 Skill 中重新实现 SDK 已有策略
Anthropic 官方 SDK 对部分临时错误已经默认进行有限指数退避重试。Skill 层若再次重试,可能出现次数相乘:
1 | |
必须明确重试责任属于:
- SDK 请求层;
- Tool 层;
- Skill 步骤层;
- 整体任务层。
14. 重试前必须检查副作用
这是本章最重要的安全原则之一。
请求超时只代表客户端没有收到确定结果,不代表服务端没有执行。
1 | |
如果直接重试,可能创建两个 Issue。
OpenAI 官方 Agent 恢复文档明确要求:失败的 Turn 可能已经修改文件或调用外部工具,重试前必须检查保存状态和已完成动作。
推荐模式:
1 | |
15. Idempotency:安全重试的基础
幂等表示同一个逻辑操作执行多次,最终效果与执行一次相同。
15.1 较容易幂等
- 读取资源;
- 将字段设置为确定值;
- 使用稳定键执行 upsert;
- 生成到新临时路径。
15.2 通常非幂等
- 发送邮件;
- 创建资源;
- 扣款;
- 对计数器加一;
- 追加日志或评论;
- 触发外部任务。
15.3 Idempotency Key
同一逻辑操作的重试应复用同一幂等键:
1 | |
每次重试都生成新键会失去幂等保护。
15.4 服务不支持幂等键时
1 | |
16. Fallback 与 Degradation 的区别
16.1 Fallback:替代实现
目标基本相同,实现路径改变:
1 | |
16.2 Degradation:降低交付等级
无法满足完整目标,交付较小但仍有用的成果:
1 | |
16.3 Retry:路径不变
1 | |
因此:
1 | |
17. Fallback Contract
每个 Fallback 至少说明:
17.1 Trigger
什么条件下启用:
1 | |
17.2 Preconditions
替代方案自身需要什么:
1 | |
17.3 Capability Difference
它与主路径有什么差别:
- 数据更新频率;
- 字段完整度;
- 精度;
- 权限;
- 成本;
- 可验证性。
17.4 Output Label
最终结果怎样标注使用了 Fallback。
17.5 Validation
Fallback 结果需要独立的 Postcondition,不能因为它是“备用”就降低到没有验证。
17.6 Exit
Fallback 也失败时是再换路径、询问用户,还是停止。
18. Fallback 不是随意换工具
错误写法:
1 | |
问题:
- 另一个工具可能语义不同;
- 数据来源可信度不同;
- 返回字段不兼容;
- 可能产生额外副作用;
- 用户可能没有授权。
更好的写法:
1 | |
Fallback 必须是经过设计和测试的备用路径。
19. Fallback 优先级
多个备用路径可按质量排序:
1 | |
排序依据:
- 正确性;
- 数据新鲜度;
- 安全性;
- 授权范围;
- 成本;
- 延迟;
- 用户目标。
不能只按“哪个还能调用”排序。
20. 透明 Fallback
最终结果应说明:
- 主路径为什么不可用;
- 启用了哪个替代方案;
- 替代结果的时间和来源;
- 覆盖、精度或能力损失;
- 哪些结论因此不能成立;
- 怎样获得完整结果。
示例:
1 | |
静默 Fallback 会让用户误认为结果来自主数据源。
21. Checkpoint 与 Resume
长循环应保存检查点,避免失败后从头重复。
21.1 检查点内容
1 | |
21.2 Resume 前验证
- 检查点版本兼容;
- 输入未改变;
- 中间产物完整;
- cursor 仍有效;
- 外部状态未使计划失效;
- 原授权仍适用。
21.3 只继续未完成工作
OpenAI 官方恢复指导建议在已有 Session 中请求继续未完成部分,而不是让 Agent 重复整个 Turn。
22. Partial Success:循环中的部分完成
批量任务可能是:
1 | |
不要用单个布尔值表示整体状态。
1 | |
恢复时只处理未完成集合,并对已经完成的副作用去重。
23. 修复、重试、Fallback、询问、停止的决策树
1 | |
24. 一份完整的 SKILL.md 模板
1 | |
25. 完整示例:Performance Analysis Skill
25.1 证据收集循环
1 | |
25.2 Tool Retry
1 | |
25.3 Parser Fallback
1 | |
25.4 Report Fix Loop
1 | |
26. 可观测性
每轮循环和重试应记录:
run_id;- 阶段;
- iteration / attempt;
- 输入摘要;
- 错误码;
- 进度指标;
- 等待时间;
- 剩余预算;
- Fallback 原因;
- 副作用状态;
- 退出原因。
示例:
1 | |
记录 request ID 便于定位持续性错误,但不要记录凭据和不必要的敏感数据。
27. 如何测试循环
27.1 成功退出
检查完成条件满足后是否立即退出,不再产生额外调用。
27.2 预算退出
目标始终不能满足时,是否在最大轮数、时间或成本处停止。
27.3 无进展退出
连续返回重复结果时,是否检测到没有新信息。
27.4 状态前进
分页 cursor、待处理集合和验证错误数量是否按预期变化。
27.5 恢复
在第 N 轮中断后,是否从检查点继续,而不是从头重复。
27.6 重复检测
是否正确去重来源、对象和副作用。
28. 如何测试 Retry 与 Fallback
28.1 错误分类
- 400 输入错误不应退避重试;
- 401/403 不应自动重试;
- 临时 429 应遵守等待提示;
- 额度型 429 不应持续重试;
- 5xx 和超时进入有限重试;
- 未知错误安全停止。
28.2 重试预算
- 最大次数精确生效;
- 总时长截止生效;
- 错误变化立即停止;
- SDK 与 Skill 重试没有意外相乘。
28.3 副作用
- 服务端成功但响应丢失;
- 回读显示已创建;
- 同一幂等键重复调用;
- 状态未知且操作非幂等。
28.4 Fallback
- 主路径临时失败但重试后恢复,不应过早 Fallback;
- 主路径确定性不支持,应直接走批准的 Fallback;
- Fallback 覆盖不足时应降级置信度;
- Fallback 失败时应停止;
- 最终输出必须披露替代路径。
28.5 恢复后验证
无论 Retry 或 Fallback 返回成功,都必须重新执行对应 Postcondition 和 Final Check。
29. 常见错误
29.1 无限循环
1 | |
没有预算和停止条件。
29.2 目标条件主观
1 | |
无法测试。
29.3 所有错误统一重试
权限、输入和额度问题不会因等待自动消失。
29.4 超时后直接重做写操作
可能制造重复副作用。
29.5 忽略 SDK 自带重试
导致实际调用次数远高于 Skill 声明。
29.6 Fallback 没有明确触发条件
模型可能过早放弃主路径或随意换工具。
29.7 静默 Fallback
用户无法判断数据新鲜度、精度和来源变化。
29.8 Fallback 不做验证
备用路径也可能产生错误或不兼容结果。
29.9 恢复后重新执行已完成工作
浪费资源,并可能重复外部动作。
29.10 把部分成功压成成功或失败
前者隐瞒缺口,后者丢弃已经验证的成果。
29.11 错误变化后仍沿用原重试循环
恢复策略已经失效,需要重新分类。
29.12 达到预算后强行给出确定结论
预算耗尽只说明必须停止,不代表证据已经充分。
30. 博客总结
Loop、Retry 和 Fallback 分别解决不同问题:
1 | |
OpenAI 官方错误恢复文档强调:重试临时失败前要检查保存状态和已完成副作用,遵守 Retry-After,并设置尝试次数或截止时间。Anthropic 官方文档则明确了工具 Agent Loop 的停止原因、结构化错误反馈、指数退避以及 SDK 已有重试行为。
请记住:
- 每个循环都有初始状态、进度指标、成功条件和终止条件;
- 预算耗尽不等于成功;
- 只有可能变化的错误才适合重试;
- 重试前先检查副作用;
- 非幂等写操作不能盲目重做;
- Fallback 是设计好的替代路径,不是随便换工具;
- Fallback 的质量损失必须透明;
- Resume 只继续未完成工作;
- Retry 或 Fallback 后必须重新验证。
31. 学完检查题
- Loop、Retry 和 Fallback 的区别是什么?
- Loop Contract 包含哪七个元素?
- 目标、预算和无进展终止条件分别解决什么问题?
- 为什么
until good enough不是可靠终止条件? - 哪些错误通常可以重试?
- 为什么 429 不能一律按短期限流处理?
- Retry Budget 至少包含哪些字段?
- 为什么需要指数退避和 Jitter?
- 超时后为什么不能直接重复写操作?
- Idempotency Key 应该在每次重试时变化吗?
- Fallback 与 Degradation 有何区别?
- Fallback Contract 应包含什么?
- Checkpoint 恢复前要重新检查什么?
- 为什么恢复成功后仍要重新执行 Final Check?
32. Skill 专项复盘
32.1 这个逻辑应该写在 SKILL.md 还是 reference?
循环主干、停止条件、重试预算、Fallback 触发和失败出口写入 SKILL.md;平台错误码表和大型兼容矩阵可放 reference。
32.2 这个步骤应该交给 LLM 还是 script?
错误分类中的固定规则、预算计数、退避和去重应由代码保障;LLM 可以评估语义证据是否充分、生成修复方案和解释降级影响。
32.3 这个能力应该是 Skill 还是 Tool?
Retry 和 Fallback 的业务策略属于 Skill;Tool 应返回结构化错误、幂等能力和操作状态。不要让 Tool 隐藏无限重试。
32.4 什么条件下应该触发这个 Skill?
Skill 仍由用户目标触发;Loop、Retry 和 Fallback 是 Skill 内部在执行状态、错误类别和证据缺口满足条件时启用的控制流。
32.5 如何证明这个 Skill 比没有 Skill 更好?
测量完成率、平均尝试次数、无进展循环率、重复副作用率、Fallback 正确率、透明披露率、恢复成功率和成本上限遵从率。
33. 本章实践
为一个证据研究 Skill 设计:
- 一个最多五轮的搜索循环;
- 一个“两轮无新增证据”终止条件;
- 一个按 URL 与 claim 去重的状态集合;
- 一个 3 次尝试、90 秒截止的 Retry Budget;
- 一组 retryable 与 non-retryable 错误;
- 一个遵守
Retry-After的指数退避策略; - 一个写操作超时后的状态确认路径;
- 一个主搜索服务到可信缓存的 Fallback Contract;
- 一个披露缓存时间和覆盖差异的输出模板;
- 一套包含死循环、错误变化、重复副作用和 Fallback 失败的测试。
下一章将严格按照大纲进入:
第 18 章:多个 Skill 如何组合与调用。