8.4 故障转移错误处理

生成模型:Claude Opus 4.6 (anthropic/claude-opus-4-6) Token 消耗:输入 ~400,000 tokens,输出 ~32,000 tokens(本章合计)


故障转移的核心问题是:什么样的错误应该触发回退?什么样的错误应该直接报告给用户?OpenClaw 通过 FailoverError 类型和一套错误分类系统来精确控制故障转移的触发条件。

8.4.1 错误分类(src/agents/failover-error.ts

FailoverError 类

FailoverError 是一个自定义错误类,携带了故障转移所需的全部上下文信息:

// src/agents/failover-error.ts
export class FailoverError extends Error {
  readonly reason: FailoverReason;    // 失败原因分类
  readonly provider?: string;         // 提供者标识
  readonly model?: string;            // 模型标识
  readonly profileId?: string;        // Auth Profile ID
  readonly status?: number;           // HTTP 状态码
  readonly code?: string;             // 错误代码

  constructor(message: string, params: {
    reason: FailoverReason;
    provider?: string;
    model?: string;
    profileId?: string;
    status?: number;
    code?: string;
    cause?: unknown;
  }) {
    super(message, { cause: params.cause });
    this.name = "FailoverError";
    // ...赋值
  }
}

FailoverReason 枚举了所有可能的故障原因:

reason
含义
典型 HTTP 状态码

billing

计费错误(额度不足)

402

rate_limit

速率限制

429

auth

认证失败

401 / 403

timeout

请求超时

408

format

请求格式错误

400

unknown

未知错误

错误到故障原因的映射

resolveFailoverReasonFromError 函数负责将任意错误对象映射到 FailoverReason

分类逻辑的优先级是:已有标记 > HTTP 状态码 > 网络错误代码 > 错误消息文本。这确保了即使来自不同 SDK 的错误格式不统一,也能被正确分类。

超时检测

超时错误的检测尤其复杂,因为不同环境和 SDK 的超时错误表现形式各异:

超时与 AbortError 的区分至关重要——用户主动 /stop 产生的 AbortError 不应触发故障转移,而超时导致的 AbortError(如某些 HTTP 库将超时表现为 abort)应该触发。

错误强制转换

普通错误可以被"提升"为 FailoverError

如果一个普通错误可以被分类为某种故障原因,它就会被包装为 FailoverError,进入故障转移流程。如果无法分类(返回 null),说明这是一个不可恢复的错误(如配置错误),应该直接上抛。

8.4.2 认证错误、计费错误、上下文溢出错误的检测与处理

除了通用的错误分类外,OpenClaw 还针对几种特定错误类型实现了专门的检测和恢复策略。

认证错误

认证错误通常发生在 API Key 失效、Token 过期或权限不足时。在 runEmbeddedPiAgent 中,认证错误触发 Auth Profile 轮换:

计费错误

计费错误(HTTP 402)意味着用户的额度已用完。OpenClaw 对计费错误有特殊处理:

  1. 标记 Profile 进入长冷却期(计费错误的退避时间更长)

  2. 尝试切换到其他 Profile(可能有不同的计费账号)

  3. 如果所有 Profile 都是计费错误,尝试回退到其他模型

  4. 最终向用户展示专用的计费错误消息

上下文溢出错误

上下文溢出(Context Overflow)发生在对话历史超过模型的上下文窗口时。这种错误的恢复策略是自动压缩会话历史:

自动压缩通过总结对话历史的早期部分来释放 Token 空间。如果连续 3 次压缩都无法解决溢出问题(可能是单条消息本身就超过了上下文窗口),则向用户报告错误。

消息角色顺序错误

某些 LLM API 要求消息角色严格交替(user → assistant → user → ...)。如果历史中出现了连续的同角色消息,会导致角色顺序错误:

角色顺序错误不触发故障转移(因为换个模型也一样会失败),而是直接提示用户重试或开启新会话。

图片大小错误

当用户发送的图片超过模型的限制时:

图片大小错误也不触发故障转移——用户需要压缩图片后重新发送。

8.4.3 故障转移日志与用户可见的错误消息

故障转移日志

每次故障转移尝试都会被记录,包括:

  • 尝试的提供者和模型

  • 错误消息和分类原因

  • HTTP 状态码和错误代码

错误描述

describeFailoverError 函数将任意错误对象转换为结构化的错误描述:

全部失败时的错误汇总

当所有候选模型都失败时,系统汇总所有尝试的错误信息:

例如,一个典型的全部失败错误消息可能是:

这个汇总消息清楚地展示了每个候选模型的失败原因,帮助用户或管理员快速定位问题。

用户可见的错误消息

面向用户的错误消息经过精心设计,避免暴露内部细节:

错误类型
用户看到的消息

上下文溢出

"Context overflow: prompt too large for the model. Try again with less input or a larger-context model."

角色顺序冲突

"Message ordering conflict - please try again. If this persists, use /new to start a fresh session."

图片过大

"Image too large for the model (max NMB). Please compress or resize the image and try again."

计费错误

专用的 BILLING_ERROR_USER_MESSAGE 常量

通用超时

"LLM request timed out."

通用认证失败

"LLM request unauthorized."

通用速率限制

"LLM request rate limited."

所有用户可见的错误消息都附带了可行的操作建议——告诉用户接下来可以做什么,而不是仅仅报告问题。


本节小结

  1. FailoverError 是故障转移系统的核心类型,携带了故障原因(reason)、提供者、模型、HTTP 状态码等完整上下文。

  2. 错误分类按四级优先级进行:已有标记 → HTTP 状态码 → 网络错误代码 → 错误消息文本,确保不同 SDK 的错误都能被正确分类。

  3. 超时与 AbortError 的区分是关键——用户主动取消不触发回退,超时导致的中止触发回退。

  4. 特定错误类型(认证、计费、上下文溢出、角色顺序、图片大小)各有专门的检测和恢复策略,不能一概而论。

  5. 全部失败时的错误汇总展示了完整的失败链,用户可见的错误消息附带可行的操作建议。

Last updated