Error Recovery - 错误恢复机制

2972 字
15 分钟
Error Recovery - 错误恢复机制

L11 要回答一个问题:如何在 L06 的基础上让 Agent 遇到错误能恢复,而不是崩溃?

L06 给了我们骨架:三层压缩 + 连续性保护 + 控制权归属。但真实 Agent 执行时会遇到各种错误——输出被截断、上下文太长、连接超时。全部崩溃会中断工作流,全部忽略会丢失信息。怎么做?

本章讲三个机制:

机制解决什么问题核心设计
max_tokens 恢复输出被截断注入续写消息 + 最多3次
prompt_too_long 恢复上下文太长压缩历史 + 重试
backoff 重试连接失败指数退避 + 随机抖动

这三个机制叠加在 L06 的 Context Compact 上,循环本身不变。理解 L11,就理解了 Agent 如何”错误恢复不崩溃”。

一、三种恢复路径:分类再处理#

“错误不是例外,是正常分支。先分类再恢复,最后才暴露失败。”

这句话概括了 Error Recovery 的本质。不是所有错误都崩溃——先分类,再选择恢复路径。

三种错误类型#

flowchart TD subgraph AgentLoop["Agent Loop"] A[调用 API] --> B{检查响应} B -->|正常 tool_use| C[执行工具] B -->|stop_reason=max_tokens| D[续写分支] B -->|正常 end_turn| E[循环结束] A -->|Exception| F{分类错误} F -->|prompt too long| G[压缩分支] F -->|timeout/rate limit| H[退避分支] F -->|unknown| I[失败分支] end subgraph Recovery["恢复分支"] D --> D1{续写预算?} D1 -->|<3| D2[追加 CONTINUE_MESSAGE] D2 --> A D1 -->|>=3| I G --> G1{压缩预算?} G1 -->|<3| G2[替换 messages 为摘要] G2 --> A G1 -->|>=3| I H --> H1{退避预算?} H1 -->|<3| H2[等待 backoff_delay] H2 --> A H1 -->|>=3| I end I --> J[告诉用户失败原因] C --> A

恢复路径对比#

错误类型触发条件恢复策略成本
输出截断stop_reason == "max_tokens"续写消息注入低(不改历史)
上下文过长prompt_too_long API 错误压缩历史高(丢失细节)
连接失败timeout/rate limit指数退避中(等待时间)

恢复优先级:max_tokens → prompt_too_long → connection error → fail gracefully。

为什么这个顺序?

  • max_tokens 最容易恢复(只需继续输出),成本最低
  • prompt_too_long 需要压缩(改变历史),成本较高
  • connection error 是外部问题,等待可能恢复
  • 优雅失败是最后兜底

验证你的理解#

Q1:为什么 max_tokens 恢复优先级最高?

:::details 答案 成本最低,最容易恢复。

max_tokens 只需注入续写消息,不改变历史,不丢失信息。

prompt_too_long 需要压缩历史,丢失细节,成本较高。

connection error 需要等待,时间成本中等。

恢复优先级原则:低成本优先恢复,高成本最后尝试。 :::

二、max_tokens 恢复:续写不重述#

max_tokens 触发时,输出被截断——需要继续输出,但不能让模型”重述”。

续写消息设计#

CONTINUATION_MESSAGE = (
"Output limit hit. Continue directly from where you stopped -- "
"no recap, no repetition. Pick up mid-sentence if needed."
)
def handle_max_tokens(messages: list) -> list:
max_output_recovery_count += 1
if max_output_recovery_count <= 3:
messages.append({"role": "user", "content": CONTINUATION_MESSAGE})
return messages # 继续循环
else:
print("Max output recovery attempts exhausted")
return None # 停止

设计要点

  • 不重述:“no recap, no repetition”——不让模型浪费时间复述已输出内容
  • 直接续写:“Continue directly from where you stopped”——从中断处接着写
  • 中断点续写:“Pick up mid-sentence if needed”——允许句子中间继续

为什么不重述?#

方案问题
让模型重述浪费 token,重复已输出内容,可能再次触发 max_tokens
直接续写高效,从中断点继续,不浪费 token

类比:笔写到一半没墨了,换笔继续写——不是从头重写。

验证你的理解#

Q2:CONTINUATION_MESSAGE 为什么强调”no recap”?

:::details 答案 防止模型浪费时间重述已输出内容。

如果让模型重述:

  1. 已输出内容再次输出 → token 浪费
  2. 可能再次触发 max_tokens → 进入恶性循环
  3. 用户看到重复内容 → 体验差

直接续写设计:从中断点继续,不浪费 token,高效恢复。 :::

三、prompt_too_long 恢复:压缩历史#

上下文太长时,API 返回 prompt_too_long 错误——需要压缩历史。

auto_compact 实现#

def auto_compact(messages: list) -> list:
# 1. 提取对话历史(截断到80000字符)
conversation_text = json.dumps(messages)[:80000]
# 2. 调用LLM生成摘要
prompt = """Summarize this conversation for continuity. Include:
1) Task overview and success criteria
2) Current state: completed work, files touched
3) Key decisions and failed approaches
4) Remaining next steps"""
summary = call_llm(prompt + conversation_text)
# 3. 返回续接消息
return [{"role": "user", "content": f"Compacted.\n{summary}"}]

摘要四要素#

要素内容为什么重要
Task overview目标 + 成功标准方向不偏离
Current state已完成 + 文件修改知道当前状态
Key decisions决策 + 失败尝试决策追溯
Next steps下一步方向能立即恢复

复用 L06 设计:auto_compact 的摘要四要素与 L06 连续性5要素一致——复用已有机制。

优雅降级#

# 失败时优雅降级
except Exception:
return [{"role": "user", "content": "Previous context lost. Please restate your current task."}]

设计原则:即使摘要失败,也不崩溃——告诉用户上下文丢失,让用户重新陈述。

验证你的理解#

Q3:auto_compact 摘要为什么要包含”失败尝试”?

:::details 答案 防止模型重复错误方向。

如果摘要丢失”失败尝试”:

  1. 模型恢复后不知道哪些方向已失败
  2. 可能重复尝试错误方向
  3. 时间浪费,效率下降

摘要包含”失败尝试”:让模型知道哪些路不通,避免重复错误。 :::

四、backoff 重试:指数退避+抖动#

连接失败时,需要等待后重试——但不能立刻重试,避免短时间大量请求。

指数退避设计#

def backoff_delay(attempt: int) -> float:
delay = min(1.0 * (2 ** attempt), 30.0) # 指数增长,上限30秒
jitter = random.uniform(0, 1) # 加入随机抖动
return delay + jitter

退避序列#

attemptbase_delayjitter总延迟
01.0s0-1s1-2s
12.0s0-1s2-3s
24.0s0-1s4-5s
3+停止--

设计要点

  • 指数增长:1s → 2s → 4s,避免短时间大量重试
  • 上限30秒:防止等待过长
  • 随机抖动:防止多客户端同步重试

为什么需要 jitter?#

问题:多客户端同时遇到错误 → 同时重试 → 同时冲击服务器 → 惊群效应。

解决:jitter 让每个客户端等待时间不同 → 错开重试时间 → 避免同步冲击。

类比:缓存击穿防护——多个请求同时发现缓存失效 → 同时查询数据库 → 数据库压力爆炸。jitter 错开请求时间。

验证你的理解#

Q4:为什么 jitter 能防止惊群效应?完整链条是什么?

:::details 答案 jitter 错开多客户端重试时间,避免同步冲击服务器。

没有 jitter 的链条

  1. 多客户端同时遇到错误
  2. 同时计算 backoff_delay(attempt相同 → delay相同)
  3. 同时等待相同时间
  4. 同时重试 → 服务器压力爆炸

有 jitter 的链条

  1. 多客户端同时遇到错误
  2. 同时计算 backoff_delay
  3. 但 jitter 不同(随机0-1s)
  4. 等待时间错开(1-2s, 1.5-2.5s, 0.8-1.8s)
  5. 重试时间错开 → 服务器压力分散

类比:缓存击穿防护——随机过期时间错开请求。 :::

五、回顾:L11 在循环上叠加了什么#

回到开头的问题:如何在 L06 的基础上让 Agent 遇到错误能恢复,而不是崩溃?

答案:

叠加机制代码位置循环的变化
max_tokens 恢复stop_reason 检查后不变——只是注入续写消息
prompt_too_long 恢复APIError 处理不变——只是压缩历史
backoff 重试Exception 处理不变——只是等待后重试

循环本身仍是 L01 的 while True + stop_reason + messages[]。L11 只是在”错误处理层”叠加了恢复机制。

与后续课程的关系#

课程关系
L06 Context Compactauto_compact 复用 L06 压缩机制
L10 System Prompt恢复提示注入
L12 Task System保护长任务流

核心洞察:Error Recovery 是”错误可恢复”的机制。理解 L11,就理解了 Agent 如何”分类错误再恢复”。后续课程都在扩展恢复机制——保护长任务、注入提示。

六、心智模型升级:从 L0 到 L2#

读完这篇文章,你的理解经历了怎样的升级?

L11 核心概念的理解升级路径#

概念L0(表面)L1(关联)L2(深层)
恢复本质”错误恢复""三种恢复路径""错误不是例外,是正常分支。有限重试+优雅失败。先分类再恢复,最后才暴露失败”
续写机制”继续输出""注入 CONTINUATION_MESSAGE""续写不重述——no recap, no repetition。类比笔没墨换笔继续写,不是从头重写”
压缩恢复”压缩历史""auto_compact 四要素""复用 L06 机制——摘要四要素与连续性5要素一致。失败尝试必须包含,防止重复错误方向”
退避设计”等待重试""指数退避 + jitter""jitter 防惊群——多客户端错开重试时间。类比缓存击穿防护,随机过期时间错开请求”
优雅失败”重试失败就停""告诉用户失败原因""有限重试上限3次 + 暴露失败原因。不是崩溃或无限循环,是设计决策”

如何检验你达到了 L2?#

用这个问题自测:

为什么恢复机制要有预算限制(最多3次),而不是无限重试?

:::details L2 层级回答 有限重试是设计决策——不是无限循环。

理由:

  1. 资源预算:无限重试会消耗 token 和时间,可能永远无法完成
  2. 用户反馈:重试3次后仍失败,说明问题可能需要用户介入
  3. 优雅失败:暴露失败原因,让用户决定下一步(修改配置、换模型、简化任务)

类比:TCP 重传上限——不是无限重传,超过上限就放弃连接。网络协议同样采用有限重试策略。 :::

本章目标:让每个核心概念都从 L0 升级到 L2。理解 L11,不只是记住三种恢复路径,而是掌握”如何分类错误再恢复”的设计原则。

七、复习可视化#

Error Recovery 主流程#

flowchart TD subgraph AgentLoop["Agent Loop"] A[调用 API] --> B{检查响应} B -->|正常 tool_use| C[执行工具] B -->|stop_reason=max_tokens| D[续写分支] B -->|正常 end_turn| E[循环结束] A -->|Exception| F{分类错误} F -->|prompt too long| G[压缩分支] F -->|timeout/rate limit| H[退避分支] F -->|unknown| I[失败分支] end subgraph Recovery["恢复分支"] D --> D1{续写预算?} D1 -->|<3| D2[追加 CONTINUE_MESSAGE] D2 --> A D1 -->|>=3| I G --> G1{压缩预算?} G1 -->|<3| G2[替换 messages 为摘要] G2 --> A G1 -->|>=3| I H --> H1{退避预算?} H1 -->|<3| H2[等待 backoff_delay] H2 --> A H1 -->|>=3| I end I --> J[告诉用户失败原因] C --> A

恢复状态机#

stateDiagram-v2 [*] --> NormalExecution NormalExecution --> Continuation: stop_reason=max_tokens NormalExecution --> CompactRecovery: prompt too long NormalExecution --> BackoffRetry: timeout/rate limit NormalExecution --> FinalFail: unknown error Continuation --> NormalExecution: 成功 Continuation --> FinalFail: 预算耗尽 CompactRecovery --> NormalExecution: 成功 CompactRecovery --> FinalFail: 预算耗尽 BackoffRetry --> NormalExecution: 成功 BackoffRetry --> FinalFail: 预算耗尽 FinalFail --> [*]: 暴露给用户

choose_recovery 决策树#

flowchart LR E[错误输入] --> C{choose_recovery} C -->|stop_reason=max_tokens| R1["kind: continue"] C -->|"prompt" + "long"| R2["kind: compact"] C -->|"timeout/rate/unavailable"| R3["kind: backoff"] C -->|其他| R4["kind: fail"] R1 --> A1[追加续写提示] R2 --> A2[压缩 messages] R3 --> A3[退避等待] R4 --> A4[暴露失败]

三种恢复策略对比#

flowchart TB subgraph Continue["max_tokens 恢复"] C1[stop_reason=max_tokens] --> C2[注入 CONTINUATION_MESSAGE] C2 --> C3[不重述,直接续写] C3 --> C4[最多3次] C4 --> C5[成本最低] end subgraph Compact["prompt_too_long 恢复"] P1[API返回 overlong_prompt] --> P2[调用 auto_compact] P2 --> P3[摘要四要素] P3 --> P4[替换 messages] P4 --> P5[成本较高] end subgraph Backoff["连接失败恢复"] B1[timeout/rate limit] --> B2[指数退避] B2 --> B3[jitter 错开重试] B3 --> B4[最多3次] B4 --> B5[时间成本] end C5 --> Result[恢复成功或暴露失败] P5 --> Result B5 --> Result style C5 fill:#e8f5e9,stroke:#333 style P5 fill:#fff3e0,stroke:#333 style B5 fill:#e1f5fe,stroke:#333

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Error Recovery - 错误恢复机制
https://firefly.cuteleaf.cn/posts/learn-claude-code/11-error-recovery/
作者
AltumSisy
发布于
2026-04-19
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
AltumSisy
Hello, I'm AltumSisy.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
32
分类
3
标签
25
总字数
68,347
运行时长
0
最后活动
0 天前

文章目录