AI Agent 错误处理与自愈机制:构建生产级可靠智能体
"Reliable AI systems are designed for failure, not against it."
在 AI Agent 从 Demo 走向生产环境的道路上,错误处理是最容易被忽视却最具破坏力的环节。与传统软件不同,LLM API 具有独特的故障模式——Rate Limit、上下文溢出、内容策略拒绝、模型版本漂移——这些问题不是可以"修复"的 Bug,而是需要系统性应对的运营现实。
本文将深入解析 AI Agent 的错误处理与自愈机制,涵盖:错误分类体系、重试策略设计、熔断器模式、多层级降级方案、状态持久化与断点恢复,以及生产级可观测性设计。
一、错误分类:一切策略的起点
错误处理的第一步不是"重试",而是分类。不同类型的错误需要截然不同的处理策略——对瞬态错误重试是正确选择,对永久性错误重试则是浪费资源和时间。
1.1 LLM API 错误谱系
| 错误类型 | 描述 | 处理策略 |
|---|---|---|
| Rate Limit (429) | 按 Token/分钟或请求/分钟配额超限 | 指数退避重试 |
| Timeout | 请求超时(通常 30-60 秒) | 重试 |
| Server Error (5xx) | 提供商服务端问题 | 重试 |
| Context Overflow | 输入超过模型上下文窗口 | 降级或截断 |
| Content Filter | 安全策略拒绝 | 降级或拒绝 |
| Auth Error (401/403) | 认证失败 | 立即失败,告警 |
| Bad Request (400) | 请求格式错误 | 立即失败,修 Bug |
1.2 错误分类实现
from enum import Enum
from openai import (
APIError, APIConnectionError, RateLimitError,
APITimeoutError, AuthenticationError, BadRequestError,
ContentFilterFinishReasonError
)
class ErrorSeverity(Enum):
TRANSIENT = "transient" # 带退避重试
PERMANENT = "permanent" # 立即失败
DEGRADED = "degraded" # 切换到降级路径
def classify_llm_error(error: Exception) -> ErrorSeverity:
"""
LLM API 错误分类器
核心原则:宁可误分类为永久,也不应对认证错误盲目重试
"""
# 瞬态错误 - 网络抖动、配额耗尽、服务端临时故障
if isinstance(error, (RateLimitError, APITimeoutError, APIConnectionError)):
return ErrorSeverity.TRANSIENT
# 永久错误 - 认证失败
if isinstance(error, AuthenticationError):
return ErrorSeverity.PERMANENT
# 请求错误 - 需要细分
if isinstance(error, BadRequestError):
error_msg = str(error).lower()
if "context_length_exceeded" in error_msg:
return ErrorSeverity.DEGRADED # 上下文超限,需要降级处理
return ErrorSeverity.PERMANENT # 其他请求错误,立即失败
# 内容策略拒绝 - 降级处理
if isinstance(error, ContentFilterFinishReasonError):
return ErrorSeverity.DEGRADED
# 服务器错误 - 瞬态
if isinstance(error, APIError) and error.status_code in (500, 502, 503, 504):
return ErrorSeverity.TRANSIENT
# 兜底 - 无法分类的视为永久错误,避免无限重试
return ErrorSeverity.PERMANENT
关键设计原则:
- 宁可误判为永久,不盲目重试认证错误:重试 401 只会加速封禁
- 上下文超限单独处理:不是失败,是需要降级的信号
- 兜底为永久:未分类错误立即失败,避免资源浪费
二、重试策略:指数退避与抖动
2.1 为什么简单重试不够
# ❌ 错误示范:固定间隔重试
for i in range(3):
try:
return call_llm()
except RateLimitError:
time.sleep(1) # 多实例同时等待1秒,同时恢复,同时触发限流...
固定间隔重试会导致雷鸣般的群体效应(Thundering Herd)——大量实例在限流恢复后同时发起请求,再次触发限流。
2.2 指数退避 + 随机抖动
from tenacity import (
retry, retry_if_exception_type,
stop_after_attempt, wait_exponential_jitter,
before_sleep_log,
)
import openai
@retry(
retry=retry_if_exception_type((
openai.RateLimitError,
openai.APITimeoutError,
openai.APIConnectionError,
)),
wait=wait_exponential_jitter(
initial=1, # 第一次等待 1 秒
max=60, # 最大等待 60 秒
jitter=5, # 添加最多 ±5 秒随机抖动
),
stop=stop_after_attempt(5), # 最多尝试 5 次
before_sleep=before_sleep_log(logger, logging.WARNING),
reraise=True,
)
def call_llm_with_retry(client: openai.OpenAI, messages: list[dict], **kwargs):
"""
生产级 LLM 调用封装
重试延迟计算示例:
- 第1次重试: 1s + random(0~5)s = 1~6s
- 第2次重试: 2s + random(0~5)s = 2~7s
- 第3次重试: 4s + random(0~5)s = 4~9s
- ...
- 最多 60s 上限
"""
return client.chat.completions.create(
model="gpt-4",
messages=messages,
timeout=30, # 单次请求硬超时
**kwargs,
)
抖动退避的核心价值:
- 分散请求峰值:多实例不会同时重试
- 自适应:失败越多,等待越久
- 有上限:不会无限等待
2.3 异步重试实现
from tenacity import AsyncRetrying, retry_if_exception_type, stop_after_attempt, wait_exponential_jitter
async def call_llm_async(client: openai.AsyncOpenAI, messages: list[dict], **kwargs):
async for attempt in AsyncRetrying(
retry=retry_if_exception_type((
openai.RateLimitError,
openai.APITimeoutError,
openai.APIConnectionError,
)),
wait=wait_exponential_jitter(initial=1, max=60, jitter=5),
stop=stop_after_attempt(5),
reraise=True,
):
with attempt:
return await client.chat.completions.create(
model="gpt-4",
messages=messages,
timeout=30,
**kwargs,
)
三、熔断器模式:防止级联故障
3.1 什么是熔断器
熔断器(Circuit Breaker)借鉴自电路保护机制,其核心思想是:当一个组件持续失败时,"熔断"后续请求,快速失败而不是无谓等待。
┌─────────────────────────────────────────────────────────┐
│ Circuit Breaker │
│ │
│ CLOSED ──(连续失败达到阈值)──▶ OPEN │
│ ▲ │ │
│ │ │ (reset_timeout) │
│ │ (2次成功) ↓ │
│ ◀────────────────────────── HALF-OPEN │
│ │ │
│ │ (测试请求失败) │
│ ▼ │
│ (保持OPEN) │
└─────────────────────────────────────────────────────────┘
| 状态 | 行为 | 何时进入 |
|---|---|---|
| CLOSED | 请求正常通过 | 初始状态;HALF-OPEN 成功后 |
| OPEN | 立即拒绝,抛出 CircuitBreakerError | 连续失败达到 fail_max |
| HALF-OPEN | 允许一个测试请求 | reset_timeout 超时后 |
3.2 每个提供者独立熔断器
import pybreaker
# ❌ 错误:共享熔断器
shared_breaker = pybreaker.CircuitBreaker(fail_max=5)
# OpenAI 宕机 → Anthropic 请求也被拒绝
# ✅ 正确:每个提供者独立熔断器
openai_breaker = pybreaker.CircuitBreaker(
fail_max=5, # 5 次连续失败后开放
reset_timeout=60, # 60 秒后尝试恢复
success_threshold=2, # 需要 2 次成功才完全关闭
name="openai-gpt4",
)
anthropic_breaker = pybreaker.CircuitBreaker(
fail_max=5,
reset_timeout=60,
success_threshold=2,
name="anthropic-claude",
)
关键设计要点:
- 独立熔断:OpenAI 宕机不应阻塞 Anthropic 调用
- Success Threshold > 1:防止不稳定提供者过早恢复
- 独立命名:便于监控和问题定位
3.3 装饰器风格集成
@openai_breaker
def call_openai(client: openai.OpenAI, messages: list[dict], **kwargs):
return client.chat.completions.create(
model="gpt-4",
messages=messages,
timeout=30,
**kwargs,
)
@anthropic_breaker
def call_anthropic(client, messages: list[dict], **kwargs):
return client.messages.create(
model="claude-sonnet-4",
messages=messages,
max_tokens=4096,
timeout=30,
)
# 使用示例
def resilient_call(messages: list[dict], **kwargs):
try:
return call_openai(openai_client, messages, **kwargs)
except pybreaker.CircuitBreakerError:
logger.warning("OpenAI 熔断器开放,切换到 Anthropic")
return call_anthropic(anthropic_client, messages, **kwargs)
四、多层级降级策略
4.1 降级金字塔
┌─────────────────┐
│ 静态 Fallback │ ← 最后防线
├─────────────────┤
│ 缓存响应 │ ← 相似查询缓存
├─────────────────┤
│ 降级模型 │ ← 小模型/开源模型
├─────────────────┤
│ 备用提供者 │ ← 跨提供商 Fallback
├─────────────────┤
│ 重试 │ ← 瞬态错误恢复
└─────────────────┘
主请求路径
4.2 Fallback 链实现
from dataclasses import dataclass
@dataclass
class ModelConfig:
provider: str
model: str
max_tokens: int
class LLMFallbackChain:
"""
多提供者 Fallback 链
按优先级尝试每个提供者,失败后自动切换
"""
def __init__(self, models: list[ModelConfig]):
self.models = models
self._clients = {} # 延迟初始化
def _get_client(self, provider: str):
if provider not in self._clients:
if provider == "openai":
self._clients[provider] = openai.OpenAI()
elif provider == "anthropic":
self._clients[provider] = anthropic.Anthropic()
return self._clients[provider]
def call(self, messages: list[dict], **kwargs) -> dict:
errors = []
for config in self.models:
severity = None
try:
client = self._get_client(config.provider)
if config.provider == "openai":
resp = client.chat.completions.create(
model=config.model,
messages=messages,
timeout=30,
**kwargs,
)
return {"content": resp.choices[0].message.content, "provider": "openai"}
elif config.provider == "anthropic":
resp = client.messages.create(
model=config.model,
messages=messages,
max_tokens=config.max_tokens,
timeout=30,
)
return {"content": resp.content[0].text, "provider": "anthropic"}
except Exception as e:
severity = classify_llm_error(e)
errors.append({"model": config.model, "error": str(e), "severity": severity.value})
# 永久错误不重试
if severity == ErrorSeverity.PERMANENT:
raise RuntimeError(f"永久错误,停止 Fallback: {e}")
# 继续尝试下一个提供者
continue
raise RuntimeError(f"所有 {len(self.models)} 个提供者都失败了: {errors}")
# 使用示例
chain = LLMFallbackChain([
ModelConfig("openai", "gpt-4", 4096),
ModelConfig("anthropic", "claude-sonnet-4-20250514", 4096),
ModelConfig("openai", "gpt-4o-mini", 4096), # 降级模型
])
4.3 完整降级路径
class DegradationTier(Enum):
FULL = "full" # 主模型完整能力
REDUCED = "reduced" # 降级模型
CACHED = "cached" # 缓存响应
STATIC = "static" # 静态模板
class ResilientAgent:
"""
完整弹性 Agent 实现
自动按降级路径处理各类故障
"""
def __init__(self, chain: LLMFallbackChain, cache: dict):
self.chain = chain
self.cache = cache
def invoke(self, messages: list[dict]) -> tuple[dict, DegradationTier]:
# Tier 1: 完整质量 (通过 Fallback 链)
try:
result = self.chain.call(messages)
# 缓存成功响应
cache_key = messages[-1]["content"][:100]
self.cache[cache_key] = result
return result, DegradationTier.FULL
except RuntimeError:
pass
# Tier 2: 缓存响应
cache_key = messages[-1]["content"][:100]
if cache_key in self.cache:
logger.warning("主路径失败,提供缓存响应")
return self.cache[cache_key], DegradationTier.CACHED
# Tier 3: 静态模板
logger.error("所有降级路径耗尽,返回静态响应")
return {
"content": "抱歉,我遇到了暂时性技术问题,请在几分钟后重试。",
"provider": "static",
}, DegradationTier.STATIC
五、状态持久化与断点恢复
5.1 LangGraph 检查点机制
对于复杂的多步骤 Agent,状态持久化是断点恢复和会话保持的基础。
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, END
# 创建检查点管理器
checkpointer = MemorySaver() # 开发环境用内存存储
# 生产环境使用 PostgreSQL
# from langgraph.checkpoint.postgres import PostgresSaver
# checkpointer = PostgresSaver.from_conn_string("postgresql://user:pass@host/db")
# checkpointer.setup()
# 创建 Agent 图
graph = StateGraph(AgentState)
graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)
graph.add_edge("agent", "tools")
graph.add_edge("agent", END)
# 编译时绑定检查点
app = graph.compile(checkpointer=checkpointer)
# 调用时会自动保存状态
config = {"configurable": {"thread_id": "user-session-123"}}
result = app.invoke({"messages": ["帮我分析销售数据"]}, config)
5.2 状态恢复与续跑
# 获取历史状态
config = {"configurable": {"thread_id": "user-session-123"}}
history = list(app.get_state_history(config))
print(f"已保存 {len(history)} 个检查点")
# 从断点恢复并继续
# 场景:Agent 在执行过程中失败,重启后继续
config = {"configurable": {"thread_id": "user-session-123", "checkpoint_id": "xxx"}}
app.invoke(None, config) # None = 使用保存的状态,不传入新输入
# 更新状态后继续
app.update_state(config, {"messages": [...new_message...]})
app.invoke(None, config)
5.3 检查点存储对比
| 存储 | 适用场景 | 持久性 | 并发 |
|---|---|---|---|
| MemorySaver | 开发/测试 | 进程结束丢失 | 单进程 |
| SqliteSaver | 小规模生产 | 磁盘持久化 | 单进程 |
| PostgresSaver | 大规模生产 | 数据库持久化 | 多进程/多机器 |
六、工具调用容错
6.1 工具级重试装饰器
from functools import wraps
import logging
logger = logging.getLogger(__name__)
def resilient_tool(max_retries: int = 2, fallback_return=None):
"""
Agent 工具容错装饰器
自动重试 + fallback 返回值
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(max_retries + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_error = e
if attempt < max_retries:
logger.warning(
f"工具 '{func.__name__}' 第 {attempt + 1} 次失败: {e}"
)
continue
# 所有重试耗尽
if fallback_return is not None:
logger.error(f"工具 '{func.__name__}' 使用 fallback 返回值")
return fallback_return
raise last_error
return wrapper
return decorator
# 使用示例
@resilient_tool(max_retries=3, fallback_return=[])
def search_documents(query: str) -> list[dict]:
"""
搜索向量数据库
失败时返回空列表而不是崩溃
"""
results = vector_db.similarity_search(query, k=5)
return [{"content": r.page_content, "score": r.score} for r in results]
6.2 工具调用超时控制
import asyncio
from asyncio.timeout import timeout as async_timeout
async def tool_with_timeout(tool_fn, timeout_seconds: int = 10, **kwargs):
"""
带超时的工具调用
防止工具执行时间过长导致 Agent 卡死
"""
try:
async with async_timeout(timeout_seconds):
return await tool_fn(**kwargs)
except asyncio.TimeoutError:
logger.error(f"工具 '{tool_fn.__name__}' 执行超时 ({timeout_seconds}s)")
raise
七、生产级可观测性
7.1 关键监控指标
| 指标 | 计算方式 | 告警阈值 | 含义 |
|---|---|---|---|
| 重试率 | 重试次数 / 总请求数 | > 20% | 临时故障频率过高 |
| Fallback 激活率 | Fallback 调用 / 总请求数 | > 10% | 主提供者健康问题 |
| 熔断器状态变化 | 状态变更次数 | 每次 | 频繁切换 = 不稳定 |
| P95 延迟 | 延迟分布 95 百分位 | 基准 + 50% | 重试对延迟的影响 |
| 降级分布 | 各降级层级占比 | - | 系统整体健康度 |
7.2 结构化日志设计
import structlog
logger = structlog.get_logger()
async def call_with_logging(messages: list[dict], **kwargs):
request_id = str(uuid.uuid4())
logger.info(
"llm_request_start",
request_id=request_id,
model=kwargs.get("model"),
message_count=len(messages),
)
try:
start = time.monotonic()
result = await call_llm_async(client, messages, **kwargs)
duration = time.monotonic() - start
logger.info(
"llm_request_success",
request_id=request_id,
duration_ms=duration * 1000,
provider=result.get("provider"),
)
return result
except Exception as e:
logger.error(
"llm_request_failed",
request_id=request_id,
error_type=type(e).__name__,
error_message=str(e),
severity=classify_llm_error(e).value,
)
raise
八、架构总览:纵深防御
┌──────────────────────────────────────────────────────────────────┐
│ Agent / 业务逻辑 │
├──────────────────────────────────────────────────────────────────┤
│ 响应验证 (Pydantic Schema) │ ← 捕获格式错误
├──────────────────────────────────────────────────────────────────┤
│ 可观测性 (日志/指标) │ ← 问题发现
├──────────────────────────────────────────────────────────────────┤
│ 熔断器 (每个提供者独立) │ ← 故障隔离
├──────────────────────────────────────────────────────────────────┤
│ Fallback 链 (多提供者) │ ← 降级处理
├──────────────────────────────────────────────────────────────────┤
│ 指数退避 + 抖动重试 │ ← 瞬态恢复
├──────────────────────────────────────────────────────────────────┤
│ 工具级容错装饰器 │ ← 细粒度保护
├──────────────────────────────────────────────────────────────────┤
│ LLM Provider API │
└──────────────────────────────────────────────────────────────────┘
每层职责:
- 重试:处理瞬态故障(网络抖动、偶发限流)
- Fallback:处理提供者级别故障(宕机、内容拒绝)
- 熔断器:快速失败,防止资源浪费和级联故障
- 降级:保证核心功能可用,而不是完全崩溃
- 检查点:支持断点恢复,保证会话连续性
九、总结与最佳实践
核心原则
- 错误分类先行:不同错误类型需要不同策略,盲目重试是万恶之源
- 每个提供者独立保护:OpenAI 宕机不应影响 Claude 调用
- 降级优于崩溃:用户更能容忍降级能力,而不是"系统错误"
- 检查点保证连续性:长时任务必须有状态持久化
- 可观测性是基础:没有监控的错误处理是盲目的
检查清单
- 错误分类完整,无未分类异常泄露
- 每个提供者独立重试策略(含指数退避和抖动)
- 每个提供者独立熔断器
- Fallback 链端到端测试覆盖
- 优雅降级路径(缓存/静态响应)
- 所有外部工具调用有重试和 fallback
- 可观测性就位(日志、指标、告警)
- 每个外部调用设置明确超时
- 故障注入负载测试验证
参考框架
- LangChain/LangGraph:
RetryPolicy、checkpointer - Tenacity:Python 重试库
- PyBreaker/aiobreaker:熔断器实现
- LiteLLM:统一的多模型调用和 Fallback
- structlog:结构化日志
记住:在生产环境中,错误不是意外,而是常态。设计系统时要假设任何组件都可能失败,然后确保系统在面对这些失败时仍然能够优雅地降级和恢复。这才是真正的可靠性工程。