Skip to content

10 模型回退与鲁棒性原理(失败时为什么还能继续)

这篇聚焦“失败处理系统”,不是讲模型能力。

核心源码入口

  • src/agents/model-fallback.ts
  • src/agents/failover-error.ts
  • src/agents/pi-embedded-helpers/errors.ts
  • src/agents/pi-embedded-runner/run.ts

先看核心目标

真实目标不是“永不失败”,而是:

  1. 快速识别可回退失败
  2. 对不可回退失败立即停止
  3. 把失败轨迹结构化记录下来

模块一 错误分类器(failover-error.ts)

FailoverError 是统一错误容器,核心字段:

  1. reasonbilling/rate_limit/auth/timeout/format/unknown
  2. provider/model/profileId
  3. status/code

分类逻辑 resolveFailoverReasonFromError(...)

  1. 先看 HTTP status(402/429/401/403/408/400)
  2. 再看错误码(ETIMEDOUT 等)
  3. 最后看 message 文本(通过 classifyFailoverReason(...)

辅助函数:

  1. coerceToFailoverError(...):把普通错误转成 FailoverError
  2. describeFailoverError(...):提取 message/reason/status/code

模块二 候选模型序列生成(model-fallback.ts)

resolveFallbackCandidates(...) 不是简单拼数组,做了这些事:

  1. 主模型入队(当前 provider/model)
  2. 解析 agents.defaults.model.fallbacks
  3. 别名解析(resolveModelRefFromString(...)
  4. allowlist 约束(buildConfiguredAllowlistKeys(...)
  5. 去重(seen + modelKey(...)
  6. 无 override 时补入 primary(防配置漂移)

还支持 fallbacksOverride

  1. 传入数组(包括空数组)会替代默认 fallbacks
  2. 常用于特定 run 的临时策略

模块三 回退执行主循环

runWithModelFallback(...) 关键流程:

  1. 遍历 candidates
  2. params.run(provider, model)
  3. 成功立即返回 {result, provider, model, attempts}
  4. 失败后:
  • shouldRethrowAbort(...) 命中则直接抛(用户中断优先)
  • coerceToFailoverError(...) 失败则视为非回退错误,直接抛
  • 记录 attempts.push({provider, model, reason, status, code})
  1. 全失败抛汇总错误:All models failed (...)

模块四 与认证轮换联动

run.ts 里在 attempt 失败后不仅能 fallback 模型,还会:

  1. 标记 auth profile 失败
  2. advanceAuthProfile(...) 切账号重试
  3. 上下文溢出时优先 compaction,再考虑 fallback

重点:OpenClaw 是“账号轮换 + 会话恢复 + 模型回退”三层联动,不是单一重试。

模块五 不应该回退的场景

以下场景应立即停止:

  1. 用户主动 Abort(AbortError 且非 timeout)
  2. 业务逻辑错误(无法归一化为 failover reason)
  3. 参数/数据结构严重错误(需要修代码,不是换模型)

最小复刻骨架

ts
async function runWithFallback(candidates: ModelRef[], run: RunFn) {
  const attempts: Attempt[] = [];
  let lastErr: unknown;
  for (let i = 0; i < candidates.length; i++) {
    const c = candidates[i];
    try {
      return await run(c.provider, c.model);
    } catch (err) {
      if (isUserAbort(err) && !isTimeout(err)) throw err;
      const fe = coerceToFailoverError(err, c);
      if (!fe) throw err;
      attempts.push({
        provider: c.provider,
        model: c.model,
        reason: fe.reason,
        status: fe.status,
        code: fe.code,
      });
      lastErr = fe;
    }
  }
  throw new Error(`All models failed: ${JSON.stringify(attempts)}`, {
    cause: lastErr instanceof Error ? lastErr : undefined,
  });
}

自检清单

  1. abort 是否不会误进 fallback。
  2. 非 failover 错误是否会直接抛出。
  3. attempts 是否记录了 reason/status/code。
  4. fallbacksOverride 传空数组时是否按预期仅尝试主模型。
  5. 全失败时是否返回可读的汇总错误。

用工程视角拆解 AI 智能体框架