我的博客

给 Agent 工具调用加上生产级容错:重试、退避与熔断

Jul 31, 2026
Agent重试熔断可靠性
7分钟
1321字

给 Agent 工具调用加上生产级容错:重试、退避与熔断

前言

在上一篇文章写了 Agent Loop:工具调用失败时,把错误信息追加回对话,让 LLM 自己修正参数重试——这是语义级重试。但生产环境里还有另一类错误:网络超时、服务重启、瞬时不可用。这类错误让 LLM 介入是浪费——你不需要模型”思考怎么修正”,你只需要等半秒再试一次。

在这一期补上了这一层:RetryController,一个处理瞬时故障的工程级重试器。


零、三层架构图

先给全貌——工具调用失败后,错误怎么在两个重试层之间流转:

flowchart TD
    A[工具调用] --> B{RetryController 执行}
    B -->|成功| C[结果追加到消息]
    B -->|瞬时错误| D{重试次数 < 3?}
    D -->|是| E[指数退避 base*2^N]
    E --> B
    D -->|否| F[错误传给 LLM]

    C --> G[LLM 基于结果回复]
    F --> H{错误类型?}
    H -->|参数错误| I[format_error_for_llm
LLM 修正参数] I --> A H -->|服务不可用| J[告知用户稍后重试] style A fill:#F1EFE8,stroke:#888780,color:#444441 style B fill:#EEEDFE,stroke:#534AB7,color:#3C3489 style E fill:#E1F5EE,stroke:#0F6E56,color:#085041 style I fill:#FCEBEB,stroke:#A32D2D,color:#791F1F style J fill:#FCEBEB,stroke:#A32D2D,color:#791F1F style C fill:#EAF3DE,stroke:#3B6D11,color:#27500A

熔断器三态状态机——连续失败触发熔断,冷却后探测恢复:

stateDiagram-v2
    [*] --> CLOSED: 初始
    CLOSED --> OPEN: 连续失败 ≥ 阈值(5)
    OPEN --> HALF_OPEN: 冷却期过(30s)
    HALF_OPEN --> CLOSED: 探测成功
    HALF_OPEN --> OPEN: 探测失败
    CLOSED --> CLOSED: 成功重置计数

    note right of CLOSED: 正常调用,失败计数归零
    note right of OPEN: 直接拒绝所有调用
防止雪崩 note right of HALF_OPEN: 放一个探测请求
验证服务是否恢复

重试时序——前两次瞬时失败自动退避,第三次成功(LLM 全程无感知):

sequenceDiagram
    autonumber
    participant A as Agent
    participant R as RetryController
    participant T as Tool.fn

    A->>R: try_with_retry(fn, args)
    R->>T: 第 1 次尝试
    T-->>R: TimeoutError(瞬时)
    R->>R: 退避 0.5s
    R->>T: 第 2 次尝试
    T-->>R: ConnectionError(瞬时)
    R->>R: 退避 1s
    R->>T: 第 3 次尝试
    T-->>R: 成功返回数据
    R-->>A: {"success": True, result}

一、两类错误,两条处理路径

Agent 工具调用的失败分两类,处理方式完全不同:

错误类型例子处理谁介入
永久错误参数类型错、字段缺失不重试LLM(修正参数)
瞬时错误网络超时、连接被拒自动重试系统(无感知)

前端类比:瞬时错误 ≈ axios-retry 自动重试 5xx(用户无感知);永久错误 ≈ 表单校验 400(用户看到错误自己修)。

永久错误不重试是核心原则——TypeError 不会因为多跑一次变对。重试是给”环境波动”的第二次机会,不是给”代码 bug”的。


二、RetryController:错误分类

1
def categorize_error(self, exception):
2
if isinstance(exception, (asyncio.TimeoutError, ConnectionError, OSError)):
3
return ErrorCategory.TRANSIENT # 网络层错误 → 可重试
4
return ErrorCategory.PERMANENT # 代码/逻辑错误 → 不重试

分类的依据是错误本质,不是错误名:

  • TRANSIENT(瞬时):超时、连接被拒、DNS 失败——共同特征是环境问题。不是代码 bug,是外部波动,等一会儿大概率恢复。
  • PERMANENT(永久):类型错误、参数缺失——共同特征是逻辑问题。代码不变,结果永远一样,重试纯浪费。

三、指数退避:为什么不是固定间隔

1
delay = self.backoff_base * (2 ** attempt) # 0.5s → 1s → 2s → 4s

前端类比:HTTP 429 Too Many Requests 的标准处理——Retry-After 头 + 指数退避,是 Web 生态验证过的模式。

为什么指数而不是固定间隔?

  • 固定间隔太急:服务要重启 30 秒,你每 1 秒试一次——前 29 次全是浪费
  • 固定间隔太慢:网络抖动 200ms 就恢复了,你干等 5 秒才重试
  • 指数退避平衡:前几次快(应对抖动),后几次慢(给重启留时间)

四、熔断器:防止雪崩

1
CLOSED(闭合)──连续失败≥阈值──> OPEN(断开)──冷却期过──> HALF_OPEN(半开)
2
↑ │
3
└─────────── 探测成功,回到 CLOSED ────────────┘

如果下游服务已经挂了,重试只是反复打一个注定失败的接口——浪费资源、拖慢响应、放大压力。熔断器在连续失败达到阈值后直接拒绝,等冷却期过了再放一次探测流量。

前端类比:CDN 节点健康检查。连续探活失败就切备用节点,定期探活,恢复后自动切回。

1
class CircuitState(Enum):
2
CLOSED = "closed" # 正常调用
3
OPEN = "open" # 直接拒绝
4
HALF_OPEN = "half_open" # 允许一次探测

五、接入 Agent Loop

1
exec_fn = functools.partial(self.executor.execute, action["tool"])
2
result = await self.retry.try_with_retry(exec_fn, arguments=action["arguments"])

两层重试各司其职,互不干扰:

1
工具调用失败
2
├─ 瞬时错误(超时/断连)→ RetryController 退避重试 ≤3 次 → LLM 无感知
3
└─ 永久错误(参数错)→ format_error_for_llm → LLM 修正参数 → 再调

结论

错误重试的层次 = 可靠性设计的分层。先分类再重试——不是所有失败都值得重试;先熔断再等待——不是所有重试都该继续。

Agent 从”能跑”到”可靠”的差距,不在功能多不多,在失败时怎么表现。给工具调用加上重试、退避、熔断——这是玩具 Agent 和生产级 Agent 的分水岭。

本文标题:给 Agent 工具调用加上生产级容错:重试、退避与熔断
文章作者:vu-ji
发布时间:Jul 31, 2026
Copyright 2026
站点地图