AG-26:错误处理与自动恢复机制

系列:AI Agent 工程化 | 关键词:重试策略、降级、自动恢复、幂等性、可观测性

在分布式系统中,故障不是异常,而是常态。对于 AI Agent 而言,错误处理不仅是防御性编程,更是系统韧性的核心支柱。本文从源码视角剖析 Agent 系统中的错误分类、重试策略、降级机制与自动恢复模式,揭示如何构建一个"打不倒"的 Agent。


1. 前言

2018 年,Michael Nygard 在经典著作 Release It! 中写道:

“The biggest threat to your availability is not hardware failure. It’s your own software.”

这句话在 AI Agent 时代变得更加深刻。Agent 系统面对的错误来源远比传统微服务复杂:LLM API 的不确定性响应、工具调用的超时与失败、上下文窗口的溢出、网络的瞬时抖动……每一个环节都可能成为故障的引爆点。

Claude Code 作为 Anthropic 官方的终端 Agent,其错误处理机制值得深入研究。本文将从源码出发,系统性地拆解 Agent 系统中的错误处理与自动恢复模式。


2. Agent 错误的分类

2. Agent 错误的分类

要处理错误,首先要理解错误。Agent 系统中的错误可以按多个维度进行分类:

2.1 按可恢复性分类

错误类型特征示例恢复策略
瞬态错误(Transient)短暂存在,自动恢复网络抖动、API 429 限流重试 + 退避
持续错误(Persistent)需要人工干预API Key 失效、磁盘满告警 + 降级
逻辑错误(Logical)语义层面的错误模型幻觉、参数误判补偿 + 重规划
不可恢复错误(Fatal)系统性崩溃核心依赖不可用优雅终止

2.2 按错误来源分类

Agent 错误
├── LLM 层错误
│   ├── API 超时(timeout)
│   ├── 限流(429 Too Many Requests)
│   ├── 上下文溢出(context window exceeded)
│   ├── 内容过滤(content policy violation)
│   └── 模型幻觉(hallucination)
├── 工具层错误
│   ├── 工具执行超时
│   ├── 权限不足
│   ├── 资源不存在
│   └── 输出格式异常
├── 系统层错误
│   ├── 内存溢出
│   ├── 磁盘空间不足
│   └── 进程崩溃
└── 网络层错误
    ├── DNS 解析失败
    ├── 连接超时
    └── TLS 握手失败

在 Claude Code 的源码中,错误处理的设计遵循一个核心原则:every error must be caught, classified, and handled at the appropriate layer。这意味着错误不是简单地向上抛出,而是在最接近错误源的层级进行分类和初步处理。


3. 重试策略:指数退避与抖动

3. 重试策略:指数退避与抖动

重试是最基础也是最重要的错误恢复手段。但"盲目重试"往往是灾难的开始。Claude Code 的重试机制体现了分布式系统领域多年积累的最佳实践。

3.1 指数退避(Exponential Backoff)

指数退避的核心思想是:每次重试等待的时间指数增长,避免在系统恢复期间施加更大压力。

/**
 * 指数退避重试器
 * 
 * 核心设计:
 * 1. 基础延迟 × 指数增长因子
 * 2. 加入随机抖动防止惊群效应
 * 3. 设置最大重试次数和最大延迟上限
 */
class ExponentialBackoffRetrier {
  private readonly baseDelay: number = 1000;      // 基础延迟 1 秒
  private readonly maxDelay: number = 30000;       // 最大延迟 30 秒
  private readonly maxRetries: number = 5;         // 最大重试次数
  private readonly jitterFactor: number = 0.5;     // 抖动因子

  async retry<T>(
    fn: () => Promise<T>,
    shouldRetry: (error: Error) => boolean  // 判断是否值得重试
  ): Promise<T> {
    let lastError: Error | undefined;
    
    for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
      try {
        return await fn();
      } catch (error) {
        lastError = error as Error;
        
        // 关键:不是所有错误都应该重试
        if (!shouldRetry(lastError)) {
          throw lastError;
        }
        
        if (attempt < this.maxRetries) {
          const delay = this.calculateDelay(attempt);
          console.log(`Retry ${attempt + 1}/${this.maxRetries} after ${delay}ms`);
          await this.sleep(delay);
        }
      }
    }
    
    throw lastError;
  }

  private calculateDelay(attempt: number): number {
    // 指数增长:1s, 2s, 4s, 8s, 16s...
    const exponentialDelay = this.baseDelay * Math.pow(2, attempt);
    
    // 加入随机抖动:delay ∈ [baseDelay * 2^attempt * 0.5, baseDelay * 2^attempt * 1.5]
    const jitter = exponentialDelay * this.jitterFactor * Math.random();
    
    return Math.min(exponentialDelay + jitter, this.maxDelay);
  }
}

3.2 抖动策略的三种变体

AWS 的 Architecture Blog 在 2015 年提出了三种抖动策略,Claude Code 的实现综合了其中的"全抖动"(Full Jitter)策略:

Full Jitter:       sleep = random(0, base * 2^attempt)
Equal Jitter:      sleep = base * 2^attempt / 2 + random(0, base * 2^attempt / 2)
Decorrelated Jitter: sleep = min(cap, random(base, prev_sleep * 3))

Full Jitter 在高竞争场景下表现最优——它最大化了重试时间的分散度,有效降低了"惊群效应"(Thundering Herd)的概率。

3.3 Claude Code 中的 LLM API 重试

Claude Code 对 LLM API 调用的重试策略尤为精细。它不仅考虑 HTTP 状态码,还解析响应体中的错误类型:

  • 429 Too Many Requests:指数退避重试,最大等待 60 秒
  • 500/502/503:服务端瞬态错误,指数退避重试
  • 408 Request Timeout:立即重试(可能是连接层面的超时)
  • 400 Bad Request:不重试(请求本身有问题)
  • 401/403:不重试(认证/授权问题)

这种分类决策是重试策略的核心——知道什么时候不重试,比知道什么时候重试更重要。


4. 降级策略:优雅降级 vs 硬降级

4. 降级策略:优雅降级 vs 硬降级

当重试无法解决问题时,降级(Fallback)成为维持系统可用性的关键手段。

4.1 优雅降级(Graceful Degradation)

优雅降级的核心思想是:在核心功能不可用时,提供一个"足够好"的替代方案。

在 Claude Code 中,典型的优雅降级场景包括:

  1. 模型降级:主模型不可用时,切换到备用模型(如 Claude Opus → Claude Sonnet)
  2. 工具降级:某个工具调用失败时,尝试替代工具或回退到纯文本推理
  3. 功能降级:复杂分析不可用时,提供简化版本的输出
/**
 * 优雅降级管理器
 * 
 * 设计模式:Chain of Responsibility
 * 每个降级层级定义了替代方案和降级条件
 */
class GracefulDegradationManager {
  // 降级链:按优先级排列的替代方案
  private readonly fallbackChain: FallbackLevel[];

  constructor(fallbackChain: FallbackLevel[]) {
    this.fallbackChain = fallbackChain;
  }

  async executeWithFallback<T>(
    primaryFn: () => Promise<T>,
    context: RequestContext
  ): Promise<DegradedResult<T>> {
    // 首先尝试主方案
    try {
      const result = await primaryFn();
      return { result, degraded: false, level: 'primary' };
    } catch (primaryError) {
      console.warn('Primary execution failed, entering fallback chain');
    }

    // 逐级尝试降级方案
    for (const level of this.fallbackChain) {
      try {
        console.log(`Attempting fallback level: ${level.name}`);
        const result = await level.execute(context);
        
        // 记录降级事件(用于可观测性)
        this.metrics.recordDegradation(level.name, context);
        
        return { result, degraded: true, level: level.name };
      } catch (fallbackError) {
        console.warn(`Fallback ${level.name} also failed, trying next`);
      }
    }

    // 所有降级方案都失败
    throw new AllFallbacksExhaustedError('No fallback succeeded');
  }
}

// 使用示例:Claude Code 的多模型降级链
const modelFallbackChain: FallbackLevel[] = [
  { name: 'claude-opus', execute: (ctx) => callModel('claude-opus-4', ctx) },
  { name: 'claude-sonnet', execute: (ctx) => callModel('claude-sonnet-4', ctx) },
  { name: 'claude-haiku', execute: (ctx) => callModel('claude-3-5-haiku', ctx) },
];

4.2 硬降级(Hard Degradation)

硬降级是一种更激进的策略:主动关闭非核心功能,集中资源保障核心路径。

在 Agent 系统中,硬降级的典型场景:

  • 禁用并行工具调用:在系统高负载时,改为串行执行
  • 缩短上下文:在接近上下文窗口限制时,截断历史消息
  • 降低采样温度:在需要稳定输出时,降低模型的创造性

4.3 降级策略对比

维度优雅降级硬降级
触发条件主方案失败系统资源紧张
用户体验功能略有降质部分功能不可用
实现复杂度中(需要替代方案)低(关闭功能开关)
适用场景单一功能故障系统级压力
恢复方式主方案恢复后自动切回资源释放后手动/自动恢复

5. 自动恢复:回滚与补偿

当错误已经产生副作用时(如部分写入的文件、已执行的命令),简单的重试不够——需要回滚(Rollback)或补偿(Compensation)。

5.1 Saga 模式

Agent 的多步骤任务执行天然适配 Saga 模式。每个步骤都有对应的补偿操作:

正常流程:Step1 → Step2 → Step3 → Done

失败恢复:Step1 → Step2 → Step3(失败)
                         ← Compensate2
               ← Compensate1
               → 报告错误

Claude Code 在执行多步骤任务时,维护一个"操作日志"(Operation Log),记录每个可逆操作的执行状态和补偿方法。当后续步骤失败时,按逆序执行补偿操作。

5.2 检查点恢复(Checkpoint Recovery)

对于长时间运行的任务,Claude Code 会定期保存检查点(Checkpoint)。当任务中断时,可以从最近的检查点恢复,而不是从头开始:

/**
 * 检查点恢复管理器
 * 
 * 每个任务步骤完成后保存状态快照
 * 任务中断后,从最近的有效检查点恢复
 */
class CheckpointManager {
  private checkpoints: Map<string, Checkpoint> = new Map();

  // 保存检查点
  async saveCheckpoint(taskId: string, stepIndex: number, state: TaskState): Promise<void> {
    const checkpoint: Checkpoint = {
      taskId,
      stepIndex,
      state: JSON.parse(JSON.stringify(state)),  // 深拷贝,防止引用污染
      timestamp: Date.now(),
      // 保存校验和,用于验证检查点完整性
      checksum: this.computeChecksum(state),
    };
    
    this.checkpoints.set(taskId, checkpoint);
    // 持久化到磁盘
    await this.persistToDisk(checkpoint);
  }

  // 从检查点恢复
  async recover(taskId: string): Promise<RecoveryResult> {
    const checkpoint = this.checkpoints.get(taskId);
    
    if (!checkpoint) {
      return { recovered: false, reason: 'no_checkpoint_found' };
    }

    // 验证检查点完整性
    const currentChecksum = this.computeChecksum(checkpoint.state);
    if (currentChecksum !== checkpoint.checksum) {
      return { recovered: false, reason: 'checkpoint_corrupted' };
    }

    return {
      recovered: true,
      stepIndex: checkpoint.stepIndex,
      state: checkpoint.state,
      // 告知调用方从哪一步继续
      resumeFromStep: checkpoint.stepIndex + 1,
    };
  }
}

5.3 幂等性保障

自动恢复的前提是幂等性(Idempotency)——同一个操作执行多次,结果与执行一次相同。Claude Code 通过以下机制保障幂等性:

  1. 操作 ID 去重:每个操作分配唯一 ID,重复执行时检测并跳过
  2. 状态快照比对:执行前检查当前状态,避免不必要的重复操作
  3. 乐观锁:使用版本号机制,防止并发冲突

6. 错误处理中间件:完整代码示例

以下是一个完整的错误处理中间件实现,综合了本文讨论的所有模式:

/**
 * Agent 错误处理中间件
 * 
 * 职责链模式:
 * 1. 错误捕获与分类
 * 2. 瞬态错误自动重试
 * 3. 持续错误触发降级
 * 4. 不可恢复错误优雅终止
 * 5. 所有错误记录到可观测性系统
 * 
 * 参考:Claude Code 的错误处理管道设计
 */
interface ErrorContext {
  operation: string;          // 操作名称
  attempt: number;            // 当前尝试次数
  metadata: Record<string, unknown>;  // 上下文元数据
}

type ErrorHandler<T> = (
  fn: () => Promise<T>,
  ctx: ErrorContext
) => Promise<T>;

function createErrorMiddleware<T>(config: MiddlewareConfig): ErrorHandler<T> {
  const retrier = new ExponentialBackoffRetrier(config.retry);
  const degradationManager = new GracefulDegradationManager(config.fallbacks);
  const metrics = new ErrorMetricsCollector();

  return async (fn: () => Promise<T>, ctx: ErrorContext): Promise<T> => {
    const startTime = Date.now();

    try {
      // 第一层:重试(仅对瞬态错误)
      const result = await retrier.retry(fn, (error) => {
        const category = classifyError(error);
        // 只有瞬态错误才值得重试
        return category === 'transient';
      });

      // 记录成功指标
      metrics.recordSuccess(ctx.operation, Date.now() - startTime);
      return result;

    } catch (error) {
      const classified = classifyError(error as Error);
      const duration = Date.now() - startTime;

      // 记录错误指标
      metrics.recordError(ctx.operation, classified, duration);

      switch (classified) {
        case 'transient':
          // 重试耗尽 → 触发降级
          console.error(`[${ctx.operation}] Retries exhausted, entering degradation`);
          return degradationManager.executeWithFallback(fn, ctx);

        case 'persistent':
          // 持续错误 → 告警 + 降级
          await alerting.send({
            severity: 'high',
            operation: ctx.operation,
            error: error,
            context: ctx.metadata,
          });
          return degradationManager.executeWithFallback(fn, ctx);

        case 'logical':
          // 逻辑错误 → 通知 Agent 进行重规划
          throw new Rep lanRequiredError(
            `Logical error in ${ctx.operation}: ${(error as Error).message}`,
            { originalError: error, context: ctx }
          );

        case 'fatal':
          // 不可恢复 → 优雅终止
          console.error(`[${ctx.operation}] Fatal error, graceful shutdown`);
          await gracefulShutdown(ctx.operation, error as Error);
          throw error;
      }
    }
  };
}

7. 错误处理的可观测性

错误处理不仅要"处理"错误,还要让人能"看见"错误。Claude Code 的可观测性体系包含三个支柱:

7.1 结构化日志

每条错误日志包含完整的上下文信息:

{
  "timestamp": "2025-07-14T10:30:00Z",
  "level": "error",
  "operation": "tool.execution",
  "error": {
    "type": "timeout",
    "message": "Tool execution exceeded 30s timeout",
    "retriable": true,
    "attempt": 2,
    "maxAttempts": 5
  },
  "context": {
    "toolName": "bash",
    "command": "npm install",
    "sessionId": "abc-123"
  },
  "duration_ms": 30042
}

7.2 错误率监控

Claude Code 维护一个滑动窗口计数器,实时计算各类错误的发生率。当错误率超过阈值时,自动触发更激进的降级策略。

7.3 错误链追踪

在 Agent 的多步骤执行中,一个错误可能引发连锁反应。Claude Code 使用因果链(Causal Chain)追踪错误的传播路径,帮助开发者定位根因。


8. 总结

AI Agent 的错误处理是一个系统工程,涉及多个层次的协同:

  1. 分类是前提:不同类型的错误需要不同的处理策略,盲目重试是最大的反模式
  2. 重试要聪明:指数退避 + 抖动是基础,但更重要的是知道何时不重试
  3. 降级要分层:优雅降级保持功能可用,硬降级保障系统存活
  4. 恢复要自动化:检查点恢复和补偿操作让 Agent 能够"自愈"
  5. 可观测性是眼睛:没有可观测性的错误处理是"盲人摸象"

正如 Nygard 所言,系统的可用性取决于其最脆弱的环节。对于 AI Agent 来说,错误处理不是附加功能,而是核心竞争力。


参考资料

  1. Nygard, M. (2018). Release It! Design and Deploy Production-Ready Software (2nd ed.). Pragmatic Bookshelf.
  2. Brooker, M. (2015). “Exponential Backoff and Jitter.” AWS Architecture Blog. https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
  3. Anthropic. (2025). Claude Code Source Code Analysis — Error handling modules. https://github.com/anthropics/claude-code
  4. Richardson, C. (2018). Microservices Patterns. Manning Publications. (Chapter 4: Managing transactions with the Saga pattern)
  5. Fowler, M. (2017). “Circuit Breaker.” https://martinfowler.com/bliki/CircuitBreaker.html

本系列覆盖 AI 大模型基础、Agent 开发、MCP 协议、Skill 开发、RAG、模型微调、部署推理 七大方向,从入门到实战的全栈内容持续更新中。

所有文章的 Markdown 源文件、可运行代码、高清配图已整理成完整资料包。

👍 点赞 + ⭐ 关注,评论区扣「1」,挨个发你领取方式 👇

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐