🛡️ 重试与容错策略
🎯 为什么 Agent 需要容错
Agent 的生产链路涉及LLM API、工具服务、数据库、外部 API 等多个依赖方,任何一个环节的失败都可能导致整个任务中断。与传统的微服务不同,Agent 的容错需要考虑 LLM 特有的失败模式(如 Rate Limit、Context Length Exceeded、Content Filter 等)。
🔑 幂等性设计
在构建 Agent 容错策略前,幂等性设计是前提。Agent 的工具调用可能因重试而被重复执行——如果操作不幂等(如重复扣款、重复发送邮件),容错机制反而会引入数据不一致风险。
- 天然幂等操作:查询、读取、检查状态(GET 类操作)
- 需要设计幂等:写入、删除、发送(使用幂等键 Idempotency Key)
- 难以幂等:物理世界操作(打印、机器人动作),需要确认机制
- 最佳实践:每个写操作的 API 接受幂等键,服务端通过幂等键去重
🔄 LLM 调用重试
LLM API 是最容易出错的环节之一。常见的失败模式包括:
| 失败类型 | 典型原因 | HTTP 状态码 | 重试策略 |
|---|---|---|---|
| Rate Limit | 超出 API 速率限制 | 429 | 指数退避 + 读取 Retry-After 头 |
| Server Error | API 服务端临时故障 | 500 / 502 / 503 | 指数退避重试 |
| Timeout | 请求超时(网络/模型推理慢) | 超时异常 | 退避重试 / 模型降级 |
| Context Length | 上下文超出模型限制 | 400 | 不重试 · 截断/压缩上下文 |
| Content Filter | 内容被安全策略拦截 | 400 | 不重试 · 返回安全提示 |
指数退避 + 抖动 (Exponential Backoff with Jitter)
指数退避是分布式系统中最经典的重试策略:每次重试的等待时间指数级增长,避免对服务端造成雪崩式压力。在此基础上加入随机抖动 (Jitter),防止多个客户端同时重试造成"惊群效应"。
def retry_with_backoff(max_retries=5, base_delay=1, max_delay=60):
for attempt in range(max_retries):
try:
return call_llm_api()
except RetryableError as e:
delay = min(base_delay * (2 ** attempt), max_delay)
jitter = random.uniform(0, delay * 0.1) # 10% 抖动
time.sleep(delay + jitter)
raise MaxRetriesExceeded()
- base_delay:初始等待时间(如 1 秒)
- 最大延迟上限:避免等待时间无限增长(如 60 秒)
- Jitter 范围:通常为延迟的 10%~25%
🔧 工具调用失败处理
Agent 的工具调用可能因参数错误、外部服务不可用、权限不足等原因失败。处理策略分为三个层次:
🔄 自我修复 (Self-Healing)
将错误信息返回给 LLM,让 Agent 根据错误信息调整策略——修正参数、选择替代工具、或调整执行计划。
- 错误信息需结构化——错误码 + 可读的描述
- 限制自我修复次数(防止无限循环)
- 示例:"API 返回 404,尝试搜索替代数据源"
⬇️ 降级 (Fallback)
当主要工具不可用时,切换到备用工具或简化处理。如搜索 API 故障时回退到本地缓存。
- 明确降级触发条件
- 降级路径需事先定义(编码而非 LLM 决策)
- 降级事件需告警——降级是"异常"状态
🛑 快速失败 (Fail-Fast)
对于不可恢复的错误(权限不足、参数非法),立即返回明确的错误信息,不浪费重试资源。
- 区分可重试和不可重试的错误类型
- 快速失败 + 精确的错误码 + 解决建议
- 避免用户/Agent 陷入无效的重试循环
📉 降级策略
降级是容错体系中最重要但最容易被忽视的环节。一个好的降级策略可以让 Agent 在部分依赖不可用的情况下,仍然提供有价值的服务(即使不是最优的)。
| 降级层级 | 策略 | 示例 | 用户体验影响 |
|---|---|---|---|
| L1 · 功能降级 | 关闭非核心功能,保留核心能力 | 关闭实时翻译,保留文本问答 | 低 |
| L2 · 模型降级 | 切换到更小/更便宜的模型 | GPT-4 → GPT-4o-mini | 中(质量下降) |
| L3 · 数据降级 | 使用缓存/静态数据替代实时数据 | 实时行情 → T-1 缓存数据 | 中(时效性下降) |
| L4 · 静态兜底 | 返回预定义的静态回复 | 返回"系统繁忙,请稍后再试" | 高 |
🔌 熔断机制 (Circuit Breaker)
熔断器是一种自动保护机制:当某个外部依赖的失败率超过阈值时,自动"断开"对该依赖的调用,直接返回降级响应,防止级联故障蔓延。
🟢 关闭 (Closed)
正常调用 · 监控失败率
正常调用 · 监控失败率
→
🟡 半开 (Half-Open)
试探性调用 · 评估恢复
试探性调用 · 评估恢复
→
🔴 断开 (Open)
拒绝调用 · 直接返回兜底
拒绝调用 · 直接返回兜底
熔断器核心参数
| 参数 | 说明 | 推荐值 |
|---|---|---|
| 失败阈值 | 在时间窗口内失败多少次触发熔断 | 5~10 次 |
| 时间窗口 | 统计失败率的滑动窗口大小 | 30~60 秒 |
| 熔断时长 | 断开状态持续多久后进入半开 | 30~120 秒 |
| 半开试探数 | 半开状态下放行多少请求用于探测 | 1~3 个 |
📊 容错策略对比
| 策略 | 保护对象 | 恢复方式 | 复杂度 | 适用场景 | 注意事项 |
|---|---|---|---|---|---|
| 重试 | 单次调用 | 透明重试,指数退避 | 低 | 偶发故障、网络抖动 | 需幂等;注意重试风暴 |
| 超时控制 | 调用链路 | 取消等待,触发降级 | 低 | 所有外部调用 | 超时时间需根据 P99 设定 |
| 熔断 | 下游服务 | 自动断开/恢复 | 中 | 高频调用、级联风险大 | 配置不当可能过于敏感 |
| 降级 | 业务功能 | 切换到备用路径 | 中 | 核心依赖不可用时 | 需提前设计降级路径 |
| 隔离 | 故障域 | 线程池/进程级隔离 | 高 | 多工具并发调用 | 增加资源开销 |
| 限流 | 系统整体 | 令牌桶/滑动窗口 | 中 | 保护 API 配额和预算 | 与业务优先级对齐 |
💡 容错设计原则
- 假设所有依赖都会失败:每个外部调用都应有超时、重试和降级
- 幂等性优先:重试的前提是操作可以安全重复
- 渐进式降级:尽可能保留核心功能,而非全有或全无
- 快速失败优于慢速失败:不可恢复的错误应立即返回,避免资源浪费
- 可观测驱动:容错事件必须可追踪、可告警、可复盘