Claude 连续两天出故障以后,我重新看了一遍“多模型容灾”这件事

8 月 16 日,Claude 出现了一次服务中断。

Anthropic Status Page 记录的范围包括 claude.ai、platform.claude.com、Claude API、Claude Code 和 Claude Cowork。

8 月 17 日,又出现了 Claude Opus 5 和 Claude Sonnet 5 的 degraded performance。昨天这次事件从状态页第一次标记 investigating 到 resolved,大约经历了一个半小时;中间 Opus 先恢复,Sonnet 继续处于 degraded 状态。

今天状态页已经显示 All Systems Operational。

我把这段时间线重新看了一遍,最大的感受不是“Claude 不稳定”。任何大型云服务都会有故障。

真正值得检查的是:我们嘴里说的多模型容灾,到底是“换了一个模型名”,还是“真的换了故障域”。


很多代码长这样:

try {
    return claudeOpus.call(request);
}
catch (Exception e) {
    return claudeSonnet.call(request);
}

看起来有 Primary 和 Fallback 两个模型。

但如果两者共享同一家 Provider、同一鉴权系统、同一 API 入口、同一网络、同一额度和同一账户,那么它更接近模型级 Fallback,而不是 Provider 级容灾。

昨天的状态页恰好能说明这个区别。

我现在会把故障域拆成五层

Model
Provider
Account
Region / Endpoint
Dependency

举例:

routing:
  primary:
    provider: anthropic
    model: sonnet-5
    account: account-a

  fallback_1:
    provider: anthropic
    model: opus-5
    account: account-a

  fallback_2:
    provider: openai
    model: gpt-5.6-terra
    account: account-b

fallback_1 能解决单模型容量、特定模型错误和模型版本问题,但不太能解决 Provider API 整体故障、认证故障和账户问题。

fallback_2 才开始跨 Provider。

但跨 Provider 也不是加一行配置

最大的麻烦是模型并不完全等价。

同一个请求切过去之后:

  • Tool Schema 支持可能不同;
  • Structured Output 行为不同;
  • System Prompt 权重不同;
  • Context Window 不同;
  • Reasoning 参数不同;
  • 图片、PDF 支持不同;
  • Tool Call ID 格式不同;
  • 流式事件不同。

所以真正的容灾单位不是 model_name,而是 capability profile。

public record ModelCapabilityProfile(
        String provider,
        String model,
        boolean toolCalling,
        boolean structuredOutput,
        boolean vision,
        boolean pdf,
        int contextWindow,
        Set supportedToolTypes,
        RiskTier riskTier) {
}

一个任务要求 Tool Calling、JSON Schema、PDF 和至少 200K Context,那么不是所有 Fallback 都能进入候选池。

第二个坑:故障时所有请求一起切,备用也会被打爆

典型事件:

Primary错误率上升
↓
100%请求立即切Fallback
↓
Fallback突然吃到2倍流量
↓
Fallback限流
↓
全挂

所以切换应该渐进。

例如:

Primary error rate > 5%
→10%切Fallback

>10%
→30%

>20%
→70%

明确不可用
→100%

同时观察 Fallback P95、429、5xx、Queue Depth 和 Budget。

第三个坑:把 Provider Status Page 当实时路由 API

状态页很有用,但它不应该是唯一信号。

原因很简单:你的请求已经失败了,状态页可能还没更新;反过来,状态页显示故障,你所在 Region 或模型也可能已经恢复。

生产路由应该以自身遥测为主。

public record ProviderHealth(
        double successRate,
        double timeoutRate,
        double rateLimitRate,
        Duration p95Latency,
        int sampleCount,
        Instant measuredAt) {
}

再把外部 Status 作为辅助信号。

我会算一个 Health Score

不需要复杂机器学习。

double score =
        successRate * 0.60
        + latencyScore * 0.20
        + rateLimitScore * 0.10
        + externalStatusScore * 0.10;

然后粗分:

score >= 0.90  HEALTHY
0.70—0.90      DEGRADED
< 0.70         UNHEALTHY

阈值按真实历史校准。

第四个坑:失败重试把故障扩大

Provider 开始 5xx 后,应用每个请求重试 3 次,原来每秒 100 个请求,瞬间可能变成 300+。

所以要有 Retry Budget。

retry:
  max_attempts: 2
  max_retry_ratio: 0.10
  backoff: exponential
  jitter: true

全局最多只有 10% 的请求能进入额外重试。

第五个坑:流式请求已经输出一半,还能不能切模型

这是最麻烦的一类。

用户已经看到“根据你的合同……”,突然 Provider 连接断了。

如果直接把另一个模型输出接在后面,可能出现语气突变、内容重复、前后事实冲突和 Tool 状态不一致。

我的倾向是:

纯文本

明确提示生成中断,重新生成完整答案。

有 Tool 副作用

不能直接重跑。

先检查:

哪些Tool已经成功?
有没有UNKNOWN?
幂等Key是什么?

再恢复。

这就是为什么“模型容灾”和“Agent恢复”是两回事

聊天模型失败,重新请求通常还比较简单。

Agent失败时,可能已经查数据库、建工单、发邮件、更新CRM或提交审批。

换 Provider 只是换了大脑,外部世界并没有回滚。

所以需要:

Checkpoint
Tool Ledger
Idempotency Key
UNKNOWN Reconcile

我现在会做三个级别的演练

Level 1:单模型故障

人为让 Claude Sonnet 5 返回 503,检查能否切到 Claude Opus 5。

Level 2:Provider故障

Anthropic 全部拒绝,检查能否切到另一个 Provider,而且 Prompt、Tool、JSON 和成本都仍可接受。

Level 3:执行中故障

Agent 已经成功执行 Tool A,正在调用模型准备 Tool B 时断掉。

恢复后必须保证 Tool A 不会重复。

这一级才真正接近生产。

最后说一个我现在不太喜欢的配置

models:
  - claude-sonnet
  - claude-opus
  - claude-fable

看起来很丰富,但它没告诉我是不是一个 Provider、一个账户、一个故障域、同一权限和同一 API 入口。

我更愿意写成:

routes:
  standard:
    primary:
      provider: anthropic
      model: sonnet-5
      failure_domain: anthropic-prod

    fallback:
      provider: openai
      model: gpt-5.6-terra
      failure_domain: openai-prod

容灾真正要设计的是 failure domain,而不是模型列表。

昨天 Claude 的事件已经结束了。但这类故障一定还会发生。

如果一个 AI 应用只有在供应商 100% 健康时才能工作,那它不是生产系统,只是一个连接了 API 的 Demo。


更多企业级 AI 应用、Agent、RAG 与模型工程化内容,我会继续整理在 智元界

https://www.zyentor.com/