# 大模型 API 该重试还是切备用模型？先过这五道门

> 故障短暂、调用可安全重放且预算还够，才重试同一路由；备用模型只有通过同一业务合同后，才可以自动切换。

- URL: https://blog.laozhang.ai/zh/posts/llm-api-retry-vs-fallback-model
- Published: 2026-07-27
- Updated: 2026-07-27
- Author: LaoZhang AI Team (https://blog.laozhang.ai/zh/about)
- Topic: API 接入指南
- Tags: 大模型 API, 重试策略, 备用模型, API 稳定性

---
大模型 API 失败后，不要先数“这是第几次失败”。先回答五个问题：谁拥有故障、请求是否已经产生不可重复的结果、恢复预算还剩多少、备用模型是否真的等价、当前路由是否健康。只有故障可能短暂、调用可安全重放、预算仍足够时，才重试同一路由；只有备用路由已经通过同一业务验收，才允许切换。其他情况应排队、返回可控降级结果，或直接停止。

| 关卡 | 必须确认的问题 | 不满足时的动作 |
|---|---|---|
| 故障归属 | 是临时网络/上游故障，而不是请求、权限、策略或预算问题吗？ | 修正归属方或停止 |
| 提交状态 | 再发一次不会重复 token、写入或外部动作吗？ | 先对账或显式恢复 |
| 恢复预算 | 总次数、总耗时、token、费用和用户耐心还够吗？ | 排队、降级或停止 |
| 备用等价性 | 备用路由通过了当前业务的 schema、工具、安全、数据和质量测试吗？ | 禁止静默切换 |
| 路由健康度 | 主路由值得有限探测，且每次尝试都可观测吗？ | 熔断并进入已批准模式 |

![大模型 API 重试还是切备用模型的五道门](https://blog.laozhang.ai/posts/zh/llm-api-retry-vs-fallback-model/img/cover.webp)

“换一个模型再试”不是普通重试。它可能同时改变上下文窗口、结构化输出、工具调用、安全过滤、数据位置、价格和回答质量。把 fallback 当成另一次 retry，恰恰是生产事故容易被隐藏的地方。

## 同一个 429，可能属于两个完全不同的分支

状态码只能帮助定位，不能直接决定动作。OpenAI 当前的[错误码说明](https://developers.openai.com/api/docs/guides/error-codes)把 429 分成两类：发送过快要减速；额度或月度支出耗尽要处理 credits/limits。对第二类做指数退避，并不会凭空产生预算。

生产日志至少要把下面几类拆开：

| 现象 | 默认动作 | 必须保留的证据 |
|---|---|---|
| 上游接收前的连接失败 | 可重放时做一次受限重试 | 连接阶段、客户端 trace、耗时 |
| 有短 `retry-after` 的限速 | 按返回窗口等待并降低并发 | 错误子类、限流 header |
| 额度、余额或 spend limit 耗尽 | 停止或等 owner 调整 | project、account、quota 类型 |
| 供应商证据明确归为瞬时故障的 5xx/过载 | 有上限的抖动退避，再考虑已批准备用 | 错误子类/消息、请求形状、request ID、路由健康度 |
| [400 请求校验错误](https://blog.laozhang.ai/zh/posts/aws-claude-400-error)：格式错误或参数不支持 | 修改请求，不原样重发 | 具体错误代码、错误信息与请求结构 |
| 401/403 | 修复 credential 或 permission | key owner、project、endpoint |
| 安全/策略阻断 | 按 policy 停止或人工复核 | 安全类别，不记录敏感原文 |
| 流式输出半截或工具结果不确定 | 先对账，不透明重放 | 最后事件、operation ID |

还要检查 SDK 是否已经替你重试。Anthropic 当前的[错误参考](https://platform.claude.com/docs/en/api/errors)说明，官方 SDK 默认会对连接错误、rate limit 和 5xx 做两次指数退避，并在存在时遵守 `retry-after`。Gemini 的[排障指南](https://ai.google.dev/gemini-api/docs/troubleshooting)也记录了官方 SDK 的自动重试。如果业务层再写“三次重试”，真实调用数往往不是三次。

## 把所有层的尝试装进一个恢复预算

恢复预算不是 `maxRetries=3`。它至少包含四个维度：

```text
总尝试数 =
  初始调用
  + SDK 内部尝试
  + 网关尝试
  + 业务层重试
  + 备用路由尝试
  + 队列重新投递
```

同时设置最大总耗时、额外 token/费用和可接受质量变化。在线客服可能只允许一次短重试；夜间批处理可以等得更久，但不能无限消耗预算。

AWS 的[超时、重试、退避与抖动](https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/)解释了多层重试为什么危险：如果调用链每一层都各自重试，负载会成倍放大；上游本来过载时，重试还会拖慢恢复。应在一层统一决策，并使用有上限的指数退避和 jitter。

可以给每个业务工作流建立一张策略卡：

```yaml
workflow: customer-support-answer
max_total_attempts: 3
max_elapsed_ms: 9000
max_extra_cost_usd: 0.02
safe_replay_until: first_visible_token
fallback_routes:
  - approved-support-backup
queue_allowed: false
stop_on: [auth, policy, unknown_side_effect, unapproved_route]
```

数字只是示例。正确数值来自用户可接受延迟、成本上限和故障演练，不来自博客里的固定答案。

## 超时不等于“上游什么都没做”

决定重放前，先找提交边界。

- 上游还没有接收请求：通常最容易安全重放。
- 上游已经接收，但客户端没有拿到结果：结果状态未知。
- 用户已经看到第一个 token：透明切模型可能重复或矛盾。
- 工具、数据库、Webhook、邮件或外部动作已经启动：超时不证明动作失败。

涉及写操作时，把“模型生成”与“业务执行”分开。每个外部动作使用应用自己的 operation ID 或 idempotency key，记录 requested、started、committed、acknowledged 四个状态。状态不确定时先查询或对账，不能让第二个模型再执行一次。

流式输出也要有明确产品规则：首个可见事件前可以清空状态，改用已批准路由；首个事件后应显示“响应中断”、让用户显式重新开始，或使用真正的 resume 协议。把另一个模型的新回答拼在半截输出后，不是无缝容灾。

## 备用模型要签“等价合同”

“都兼容 Chat Completions”远远不够。每个备用路由必须针对具体工作流通过八项验收：

1. 输入：上下文长度、图片/文件、系统指令、语言和请求上限。
2. 输出：JSON Schema、拒绝格式、finish reason、引用和截断。
3. 工具：工具名、参数 schema、并行调用、tool result 回传和幂等。
4. 安全：内容过滤、高风险任务、人工升级和停止规则。
5. 数据：保留策略、区域、租户隔离、允许的数据类别。
6. 运维：延迟分布、流式行为、request ID、状态可见性。
7. 经济：计价单位、缓存、重试费用、quota owner、最大支出。
8. 质量：用同一批 fixture 跑评测，并达到该工作流阈值。

一个轻量模型可以胜任标签分类，但未必适合给客户解释退款政策；另一个供应商可能更可用，却不支持当前工具 schema 或数据边界。没有生成式备用通过时，缓存、确定性规则或“稍后重试”往往比未经验证的答案更安全。

## 把决策写成状态机，而不是散落的 `catch`

下面的 TypeScript 展示核心边界。provider adapter 负责把错误归一化；策略层只决定动作。

```ts
type Action = "retry" | "fallback" | "queue" | "degrade" | "fail_closed";

type Failure = {
  owner: "transient" | "rate" | "quota" | "request" | "auth" | "policy" | "unknown";
  commitState: "none" | "committed" | "unknown";
  retryAfterMs?: number;
};

function decide(
  f: Failure,
  budget: { attemptsLeft: number; elapsedMsLeft: number; costLeft: number },
  estimate: { retryMs: number; retryCost: number; fallbackMs: number; fallbackCost: number },
  routes: { primary: "healthy" | "degraded" | "open"; fallback: "healthy" | "unhealthy" },
  fallbackApproved: boolean,
  queueAllowed: boolean,
  degradeApproved: boolean
): Action {
  if (["request", "auth", "policy"].includes(f.owner)) return "fail_closed";
  if (f.commitState !== "none") return "fail_closed";
  if (f.owner === "unknown") return queueAllowed ? "queue" : "fail_closed";

  const canRetry =
    budget.attemptsLeft > 0 &&
    budget.elapsedMsLeft >= (f.retryAfterMs ?? 0) + estimate.retryMs &&
    budget.costLeft >= estimate.retryCost;
  const canFallback =
    budget.attemptsLeft > 0 &&
    budget.elapsedMsLeft >= estimate.fallbackMs &&
    budget.costLeft >= estimate.fallbackCost;

  if (canRetry && ["transient", "rate"].includes(f.owner) && routes.primary !== "open") {
    return "retry";
  }
  if (
    canFallback &&
    ["transient", "rate", "quota"].includes(f.owner) &&
    fallbackApproved &&
    routes.fallback === "healthy"
  ) return "fallback";

  if (queueAllowed) return "queue";
  return degradeApproved ? "degrade" : "fail_closed";
}
```

`retryAfterMs` 只约束主路由重试；备用分支使用自己的预计耗时与费用，且 `fallbackApproved` 已包含备用路由独立的额度检查。主路由要求长时间等待时，只要健康备用仍能落在 workflow SLO 内，就不会被错误拦截。`committed` 或 `unknown` 必须在自动分发器之外先对账，或使用已经验证的 resume/idempotency 协议。每次新网络调用前都先持久化 attempt；只有供应商错误子类、消息与请求阶段共同证明它是瞬时故障时才退避。HTTP 500 也可能来自请求本身，例如 Gemini 当前排障文档把输入上下文过长列为一种原因，因此不能只凭状态码把 `owner` 设为 `transient`。

## 用故障注入验收，不用 200 骗自己

在 staging 至少注入这些情况：

- 短 `retry-after` 的 429：只等待一次，并发不升高。
- 主路由 `retry-after` 超过剩余 SLO，但已批准的健康备用能在自身时间与费用预算内完成：选择 `fallback`，不是排队。
- `insufficient_quota`：不原样重试，不暗中切换另一个预算。
- 首 token 前的 503：预算内重试，再进入已批准备用。
- 错误 tool schema：返回可修复错误，不切模型掩盖。
- 工具已提交后的 timeout：先对账，不重复执行。
- 已输出 token 后断流：进入明确中断状态，不拼接新回答。
- 备用结果缺少必填字段：output validator 拒绝。
- 熔断打开：普通流量绕开，只有有限 half-open 探测恢复。

每次尝试记录 workflow ID、attempt index、requested route、selected route、provider request ID、错误类、等待、token、费用、提交状态、fallback 原因和最终动作。指标也要拆成 primary success、retry recovery、fallback recovery、degraded response 与 fail closed。否则“最终成功率”会把已经坏掉的主路由藏起来。

## 从具体错误进入正确下一步

如果你现在拿到的是某一家供应商的 429，先使用对应 owner：[OpenAI API rate limit](https://blog.laozhang.ai/zh/posts/openai-api-rate-limit)、[Claude API 限流](https://blog.laozhang.ai/zh/posts/claude-rate-exceeded-error)或 [Gemini API rate limit](https://blog.laozhang.ai/zh/posts/gemini-api-rate-limits-guide)。先证明故障归属，再让通用策略接管。

如果需要用一个兼容入口测试多个已经批准的模型，可以在 staging 对照当前 [LaoZhang AI API 文档](https://docs.laozhang.ai/)验证。统一 endpoint 能减少 adapter 工作，但不会自动证明模型等价，更不会替你定义预算、日志和停止规则。

*说明：[laozhang.ai 是我们自己的服务](https://blog.laozhang.ai/zh/about#laozhang)，其价格是平台报价，不是独立比较的结论。*

完成标准不是“最后返回 200”，而是每类故障只进入一个可解释动作、所有重试共享一份预算、每个备用都通过同一业务合同，并且最终成功不会抹掉此前的失败证据。

## 参考来源

本文引用的外部页面，按正文出现顺序排列。最后更新于 2026-07-27。

- [错误码说明](https://developers.openai.com/api/docs/guides/error-codes) (developers.openai.com)
- [错误参考](https://platform.claude.com/docs/en/api/errors) (platform.claude.com)
- [排障指南](https://ai.google.dev/gemini-api/docs/troubleshooting) (ai.google.dev)
- [超时、重试、退避与抖动](https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/) (aws.amazon.com)
