# 三行速读
- 高完成率系统的关键差异在 “失败后是否能继续”。
- 恢复策略必须按失败类型分层,而不是统一重试。
- 退出条件同样属于恢复设计的一部分。
# 先修知识
- 已理解 s06 压缩策略(恢复会复用 compact)。
- 知道网络失败、超长输入、输出截断是不同问题。
# 读完后你应该能做到(可检验清单)
# 本篇要解决什么
一个能落地的 Agent,不能只会在顺风局工作。s11 解决的是恢复能力:遇到常见失败时,系统如何继续推进而不是直接退出。
# 用一个类比先理解
像导航软件:
- 路况临时拥堵,不是立刻放弃出行;
- 会换路、限速、重规划。
错误恢复也是同样思路:先判类型,再走对应恢复路径。
# 为什么这一步必须现在做
后面会引入后台任务、团队协作、外部能力调用。复杂度上升后,失败是常态,不是例外。没有恢复层,完成率会很低。
# 关键代码怎么读
# 1) 恢复阈值与上限
1 | MAX_RECOVERY_ATTEMPTS = 3 |
恢复策略必须有边界,避免无限重试。
# 2) 压缩恢复分支
1 | def auto_compact(messages: list) -> list: |
上下文过长时,不是失败退出,而是压缩后续跑。
# 3) 退避重试
1 | def backoff_delay(attempt: int) -> float: |
指数退避 + 抖动,是处理瞬时网络问题的标准工程做法。
# 4) max_tokens 续写
1 | if response.stop_reason == "max_tokens": |
这是最常见且最实用的一条恢复路径:让模型从中断处继续。
# 你可以怎么复现
- 人为降低
max_tokens,触发续写恢复。 - 构造超长上下文,触发 compact 恢复。
- 模拟网络失败,观察 backoff 行为。
# 常见误区
- 误区 1:所有错误都走同一路径处理。
- 误区 2:重试无上限,导致资源浪费。
- 误区 3:恢复后不保留连续性信息。
# 一句话总结
s11 的核心是把 “失败” 转化为 “可管理状态”,这直接决定系统真实完成率。
# 补充解读:恢复分支的优先级哲学
# A. 为什么先处理 max_tokens
因为这类问题通常不是任务失败,而是输出被截断。优先续写可以最快恢复主线连续性。
# B. APIError 分层处理
1 | if "overlong_prompt" in error_body or ("prompt" in error_body and "long" in error_body): |
这段把 “提示过长” 与 “网络抖动” 分开处理,避免错误恢复策略错配。
# C. 网络级异常专门分支
1 | except (ConnectionError, TimeoutError, OSError) as e: |
这说明恢复策略要按错误来源分组,而不是按异常类名粗暴统一。
# D. 恢复失败也要优雅退出
1 | print(f"[Error] API call failed after {MAX_RECOVERY_ATTEMPTS} retries: {e}") |
“退出” 本身也是系统行为的一部分。可解释退出比静默失败更重要。
# 进阶练习
- 在日志中记录每次恢复策略命中次数。
- 增加 circuit breaker:连续多轮恢复失败后自动降级为 plan 模式。
- 做一次 “人工故障注入” 演练(限流、断网、超长输入)。
# 再补一层:把恢复链路当作 “失败编排” 来看
很多初学者会把恢复逻辑写成 “哪里报错补哪里”。更稳定的做法是先定义失败类型,再定义恢复动作,再定义退出条件。
# 失败类型建议分三层
- 输出层失败:
max_tokens(信息不完整)。 - 上下文层失败:prompt 过长(输入超载)。
- 传输层失败:连接异常 / 限流(通道不稳定)。
每层对应不同恢复策略,千万别混在一起。
# 恢复动作建议与状态字段绑定
你可以额外记录:
recovery_attempt_countlast_recovery_strategylast_recovery_ts
这样在日志中可以快速回答:系统刚才为什么重试、重试了几次、还要不要继续。
# 退出条件同样重要
恢复逻辑不是 “永不放弃”,而是 “在可控成本内努力恢复”。超过上限后应当给出清晰失败原因,帮助人类接管。
# 统一术语口径(本章)
Recovery Strategy:针对不同失败类型的恢复动作。Backoff:失败后延时重试的退避机制。Continuation:中断后保持上下文连续的续跑过程。
# 章节衔接(从易到难)
- 本章解决 “失败可恢复”。
- 下一章
s12进入 “持久任务图如何建模和解锁”。