《Effective AI Agent》条目 8:为错误进行分类建模,实现差异化重试
背景动机
在互联网架构里,“重试”从来不是一句 retry(3) 能解决的事。你敢对支付接口无脑重试,资损就会来;你敢对网络抖动无脑重试,体验就会烂。重试的前提不是“失败了”,而是“失败属于哪一类”。
把这个问题搬到 AI Agent 上,会更糟:Agent 不只是“发一次请求”,它会在 ReAct 循环里连续调用模型、读写文件、跑命令、改代码、跑测试——一次失败可能发生在任何层,还可能伴随副作用。你如果把所有失败都当成“临时波动”,Agent 会在错误方向上越跑越久,把 token、时间和信任一起烧掉。
现实里这些坑,Codex / Gemini CLI / OpenCode 都踩过:
- Codex CLI 曾被用户反馈:遇到 429 rate limit 直接退出,希望能自动 backoff 重试。
- Gemini CLI 的 issue 里出现过典型反例:明明是 token 超限(400 / INVALID_ARGUMENT,属于“不可重试”),却触发了
retryWithBackoff,导致无意义的连环重试。 - OpenCode 社区里则能看到统一封装的
AI_RetryError: Failed after 4 attempts,说明它至少有一个通用重试器,但“重试 4 次仍失败”本身并不告诉你该怎么修复(是配额?鉴权?模型方波动?)。
所以这条目要讲的不是“加重试”,而是:先把错误分门别类,再决定重试的策略、预算和补救动作。
原理分析
把 Agent 当作软件架构来看,你可以把失败粗暴拆成三层:传输层 / 协议层 / 语义层——对应到 Agent 工程,就是“能不能重试、怎么重试、重试前要不要先修复输入”。
传输层:网络抖动、超时、5xx、短期拥塞。 这类失败本质是“同样的请求换个时间大概率会成功”。典型策略就是指数退避 + 抖动(jitter),并且尊重服务端的
Retry-After。OpenAI 的 rate limit 指南与示例明确建议用随机指数退避来处理 429。协议层:429 配额、401/403 鉴权、被策略拒绝。它们含义完全不同:
- 429:有些是“每分钟限流”,等一等就好;有些是“账号配额不足”,你等一天也没用。
- 401/403:通常不是重试能解决,而是要换凭证 / 权限。 这层的关键是:把“可等待恢复”和“需要人工介入”的 429 分开。
- 语义层:输入 token 超限、参数非法、工具命令不存在、测试必然失败。 这类失败的特征是:不改变输入,重试 100 次也一样。Gemini CLI 的“token 超限却 backoff 重试”就是典型症状:把语义错误误判成了传输错误。 对语义层,正确姿势不是 retry,而是 repair:裁剪上下文、换更小的 diff、拆分任务、降级到只读分析等。
把这三层想清楚,你就能得到一个工程上很实用的结论:
重试策略不是“次数”,而是“分类 → 动作(retry / repair / fail-fast)→ 预算”。
预算非常关键:Agent 天生爱循环。你要像做 RPC 熔断一样,为每一类错误设上限、设冷却时间、设全局 token/time budget,否则它会在“我再试一次”里把系统拖死。
工程实践
下面给一套能直接落地的做法,按“架构接口”的方式组织,而不是堆规则清单。
第一步:在编排层定义统一错误模型(Error Model),不要让异常散落在工具与 SDK 里。 让 orchestrator 把底层异常统一映射成少数几类 ErrorKind。。
第二步:为每类错误绑定差异化策略:retry / repair / fallback / fail-fast。 这里最容易犯的错,是“所有错误都 backoff 重试”。Gemini CLI 那个 issue 已经展示了后果:token 超限也重试,只会把用户时间磨没。 更合理的策略是:
- Transient(超时/5xx):指数退避重试;
- Rate limit(429):优先读
Retry-After;短期限流等待,长期配额直接 fail-fast 并提示升级/换 key; - Token 超限 / 参数非法(400):不重试,先 repair(裁剪上下文、分片任务);
- 工具副作用风险:只有在 idempotent 或有事务边界时才允许重试;否则要让 Agent 输出“恢复建议”而不是自动再跑一次。
第三步:把“重试”做成可观测、可审计、可回放的系统行为。 你需要把这些变成 trace/log,记录每次失败记录元数据信息,关注每次回合的预算。
示例代码
下面这段 参考codex 错误定义机制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// https://www.agent-io.com/posts/Agent-Analysis_Codex
// Codex 的错误类型分为三大类:可恢复的瞬时错误(自动重试)、需要用户决策的错误(发送审批请求)、不可恢复的致命错误(终止任务)。
// codex-rs/core/src/error.rs (主要错误类型)
pub enum CodexErr {
// 可恢复的瞬时错误(run_turn 自动重试)
Stream(String, Option<Duration>), // 流断开,带可选的重试延迟
// 任务级错误(Task 层处理)
TurnAborted { dangling_artifacts: Vec<ProcessedResponseItem> }, // Turn 被用户中断
Interrupted, // Ctrl-C 中断
ContextWindowExceeded, // Token 超出上下文窗口
// 资源限制错误
UsageLimitReached(UsageLimitReachedError), // API 使用限额达到
// 沙箱相关错误(工具层处理)
Sandbox(SandboxErr), // 沙箱拒绝、超时、被信号杀死
// 系统级错误
InternalAgentDied, // Agent 循环异常退出
Fatal(String), // 致命错误,无法恢复
// 网络错误
UnexpectedStatus(UnexpectedResponseError),
ConnectionFailed(ConnectionFailedError),
RetryLimit(RetryLimitReachedError),
// 外部错误自动转换
Io(io::Error),
Json(serde_json::Error),
}
// Codex 在不同层次实施不同的错误处理策略,形成纵深防御。
// 1. Turn 层:自动重试瞬时错误
// codex-rs/core/src/codex.rs:1901-1964 (run_turn 内部)
loop {
match try_run_turn(...).await {
Ok(output) => return Ok(output),
// 网络流断开:自动重试
Err(e @ CodexErr::Stream(..)) => {
if retries < max_retries {
retries += 1;
let delay = backoff(retries); // 指数退避
sess.notify_stream_error(&turn_context, format!("Re-connecting... {}/{}", retries, max_retries)).await;
tokio::time::sleep(delay).await;
} else {
return Err(e); // 超出重试次数
}
}
// 不可重试错误:直接返回
Err(e @ CodexErr::Interrupted) => return Err(e),
Err(e @ CodexErr::TurnAborted { .. }) => return Err(e),
Err(e @ CodexErr::ContextWindowExceeded) => return Err(e),
}
}
// 2. Task 层:处理 Token 超限和任务中断
// 3. 工具层:沙箱失败降级
参考文献
- OpenAI — Introducing Codex
https://openai.com/index/introducing-codex/ - OpenAI — Custom instructions with AGENTS.md
https://developers.openai.com/codex/guides/agents-md/ - openai/codex — Add automatic retry with backoff for rate limit errors
https://github.com/openai/codex/issues/231 - Google Cloud — Gemini CLI
https://docs.cloud.google.com/gemini/docs/codeassist/gemini-cli - google-gemini/gemini-cli — Token limit exceeded triggers retryWithBackoff
https://github.com/google-gemini/gemini-cli/issues/5807 - sst/opencode — AI_RetryError: Failed after 4 attempts. Last error: Too Many Requests
https://github.com/sst/opencode/issues/2398 - OpenAI Cookbook — How to handle rate limits
https://cookbook.openai.com/examples/how_to_handle_rate_limits - Anthropic — Building effective agents
https://www.anthropic.com/research/building-effective-agents - AWS — Retry behavior - AWS SDKs and Tools
https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html