关键词:Failover|多级重试|错误分类|幂等性|结果标准化
在上一篇中,我们探讨了 run.ts 如何通过模型调度、账号轮询与上下文压缩构建第一道防线。然而,在真实生产环境中,失败是常态——网络抖动、模型返回乱码、工具执行超时……系统必须具备智能的故障转移(Failover)能力。
本文将深入 run.ts 的下半部分,解析其如何:
这一切共同构成了 OpenClaw “永不放弃,但知道何时止损” 的韧性哲学。
run.ts 的核心思想是:不同错误需要不同应对策略。它通过 categorizeError() 函数对异常进行语义化分类:
enum ErrorCategory {
TRANSIENT, // 可重试(如网络超时)
RATE_LIMITED, // 需冷却(如 API 限流)
CONTEXT_OVERFLOW, // 需压缩上下文
MODEL_MISBEHAVIOR, // 模型返回无效格式(如缺失 tool_call)
PERMANENT // 不可恢复(如无效 API Key)
}
精准分类是智能 Failover 的前提。
OpenClaw 的 Failover 不是一次性跳转,而是分阶段尝试,优先保留原始意图:
RATE_LIMITED 或 TRANSIENTCONTEXT_OVERFLOW(目标模型不支持长上下文)或 MODEL_MISBEHAVIOR/think,自动移除相关指令转移不是逃避,而是策略性迂回。
为防止重试引发资源耗尽,run.ts 实施严格限制:
const MAX_TOTAL_RETRIES = 3; // 整个 run 最多重试 3 次重试是手段,不是目的——超过阈值即终止。
无论经历多少次 Failover,run.ts 最终返回一个结构化结果对象:
interface AgentRunResult {
status: 'success' | 'partial' | 'failed';
finalResponse: string; // 给用户的最终回复
usedModel: string; // 实际使用的模型 ID
tokenUsage: { prompt: number, completion: number };
errorSummary?: string; // 若失败,提供人类可读摘要
debugTrace: RunTrace[]; // 用于日志分析(脱敏后)
}status 字段:告知客户端是否完整完成任务 partial:例如“工具执行失败,但已给出建议”errorSummary:不暴露技术细节(如 429),而是说“当前服务繁忙,请稍后再试”debugTrace:记录每一步尝试(模型、错误、耗时),供 SRE 排查结果封装是“内部复杂,外部简单”的体现。
用户网络不稳定时可能重复发送请求。run.ts 通过 idempotencyKey 避免副作用:
if (request.idempotencyKey) {
const cached = await cache.get(request.idempotencyKey);
if (cached) return cached; // 直接返回历史结果
}exec 等工具调用,则拒绝重复提交安全第一:宁可让用户重发,也不让 AI 重复删库。
用户请求:
“分析昨天的日志,找出 5xx 错误最多的接口”
系统流程:
首次尝试:Claude + Team-A Key
→ 返回 429 Rate Limit
→ 分类:RATE_LIMITED
阶段 1:切换至 Team-B Key(同 Claude)
→ 网络超时(TimeoutError)
→ 分类:TRANSIENT
阶段 2:降级至 GPT-4o + Primary Key
→ 成功执行 bash_exec("grep '500' /logs/app.log")
→ 返回结构化分析
结果封装:
{
"status": "success",
"finalResponse": "共发现 127 次 5xx 错误,/api/v1/payment 最频繁...",
"usedModel": "gpt-4o",
"tokenUsage": { "prompt": 4200, "completion": 320 }
}用户全程无感知,仅看到一条正确回复。
run.ts 的故障处理逻辑,体现了 OpenClaw 的工程信条:
这种“有策略的韧性”,正是工业级 AI 系统的核心竞争力。
在下一篇中,我们将转向记忆系统,解析
memory-search.ts如何实现混合检索与配置合并。
下一篇预告:
第 7 篇:记忆系统基石 —— memory-search.ts 中的 RAG 配置解析与合并逻辑