LLM API の失敗に対して、同じ経路を再試行できるのは「一時的な障害」「安全な再送」「復旧予算内」の三条件がそろうときです。代替モデルへ切り替えられるのは、そのモデルが同じ業務の出力スキーマ、ツール、安全性、データ、費用、遅延、品質テストを事前に通過しているときだけです。それ以外はキュー、限定機能への縮退、または安全側での停止を選びます。
まず30秒で確認する5項目
| 確認 | 合格条件 | 不合格時 |
|---|---|---|
| 障害の所有者 | ネットワーク/プロバイダーの短期障害 | リクエスト、認証、ポリシー、予算を修正 |
| コミット状態 | visible output も side effect もない | 状態照合または停止 |
| 復旧予算 | attempts、time、cost、quality に余裕 | queue または縮退 |
| 代替経路の等価性 | 同じ fixture に合格済み | 自動切替しない |
| 経路の健全性 | 限定的なヘルスプローブが有効で試行を追跡できる | サーキットを開く |
フォールバックは「別モデルでもう一度」という意味ではありません。model/provider を変えると、context、tool calling、structured output、安全フィルター、データ保持、価格、応答品質が変わり得ます。
429 を見ても、すぐ backoff しない
OpenAI の現行エラーコードガイドでは、429 は request rate と quota/spend exhaustion に分かれます。前者は送信速度を落とす対象ですが、後者は予算所有者が解決する問題です。同じ payload を待って送り直しても、残高は増えません。
実運用では次のように正規化します。
- 公式のエラー詳細が一時障害と示す 5xx/過負荷:上限付きバックオフとジッター。HTTP コードだけでは判断しない。
- レート上限の 429:
retry-afterや限流ヘッダーを読み、同時実行数を下げる。 - 429 quota/budget:同期 retry を止める。許可された job だけ queue。
- 400、schema、context overflow:request を変える。
- 401/403:credential、permission、route owner を修正。
- safety/policy:別の緩いモデルで回避しない。
- partial stream/tool uncertainty:再送前に実状態を照合する。
SDK の内部動作も数えます。Anthropic の現行API errorsでは、公式 SDK は transient connection、rate limit、5xx をデフォルトで2回再試行し、retry-after を尊重します。Gemini のトラブルシューティングも自動 retry を記載しています。アプリ側の回数だけを見てはいけません。
「タイムアウトした」は未実行の証明ではない
再送可否は、失敗回数よりコミット境界で決まります。
- provider が受理する前:replay しやすい。
- 受理後、結果未取得:結果は unknown。
- 最初の token を表示後:別モデルの出力を透明に足せない。
- tool、DB、webhook、メールなどを開始後:side effect が完了した可能性がある。
外部動作にはアプリ側で発行する operation ID または idempotency key を付け、requested / started / committed / acknowledged を保存します。状態が不明なら実状態を問い合わせて照合します。別モデルを呼ぶことは照合ではありません。
ストリーミングの製品仕様も先に決めます。最初の可視イベント前なら状態を初期化して承認済み経路を使えます。表示後は中断を明示し、利用者が再実行するか、正式な再開プロトコルを使います。
復旧予算を一枚にする
text総試行数 = 初回 + SDK 内部 + gateway + application retry + fallback + queue redelivery
回数だけでなく、総経過時間、追加 token/費用、許容品質低下を上限化します。対話 UI と夜間 batch で同じ値を使う理由はありません。
AWS の timeouts, retries, backoff with jitter は、多層 retry が負荷を乗算し、overload の回復を遅らせると説明します。判断点は一か所に集約し、SDK/gateway は実際の試行回数を上位へ返す必要があります。
yamlworkflow: faq-extraction max_total_attempts: 3 max_elapsed_ms: 7000 max_extra_cost_usd: 0.015 safe_replay_until: output_persisted fallback: approved-json-backup queue_allowed: true fail_closed_on: [auth, policy, unknown_commit]
値はサンプルです。SLO と fault injection の結果から決めます。
代替モデルの受け入れ試験
同じ fixture で次の8項目を合格させます。
- input:context、画像/ファイル、system instruction、言語。
- output:JSON Schema、required field、refusal、truncation。
- tools:argument schema、parallel call、tool result、idempotency。
- safety:禁止内容、high-impact stop、人手への escalation。
- data:region、retention、tenant、扱えるデータ分類。
- operations:p95/p99 latency、stream、request ID、status。
- economics:価格単位、cache、quota owner、最大費用。
- quality:workflow 固有の eval と合格閾値。
classification 用の fallback が、顧客向け説明にも使えるとは限りません。生成モデルが合格しない場合は、cache、決定論的 rule、または明確な「後で再試行」を使います。
分岐をコードに閉じ込める
tstype Action = "retry" | "fallback" | "queue" | "degrade" | "fail_closed"; function decide(p: { owner: "transient" | "rate" | "quota" | "request" | "auth" | "policy" | "unknown"; commitState: "none" | "committed" | "unknown"; attemptsLeft: number; elapsedMsLeft: number; costLeft: number; retryAfterMs: number; retryExpectedMs: number; retryExpectedCost: number; fallbackExpectedMs: number; fallbackExpectedCost: number; primaryRoute: "healthy" | "degraded" | "open"; fallbackRoute: "healthy" | "unhealthy"; fallbackApproved: boolean; queueAllowed: boolean; degradeApproved: boolean; }): Action { if (["request", "auth", "policy"].includes(p.owner)) return "fail_closed"; if (p.commitState !== "none") return "fail_closed"; if (p.owner === "unknown") return p.queueAllowed ? "queue" : "fail_closed"; const canRetry = p.attemptsLeft > 0 && p.elapsedMsLeft >= p.retryAfterMs + p.retryExpectedMs && p.costLeft >= p.retryExpectedCost; const canFallback = p.attemptsLeft > 0 && p.elapsedMsLeft >= p.fallbackExpectedMs && p.costLeft >= p.fallbackExpectedCost; if (canRetry && ["transient", "rate"].includes(p.owner) && p.primaryRoute !== "open") { return "retry"; } if ( canFallback && ["transient", "rate", "quota"].includes(p.owner) && p.fallbackApproved && p.fallbackRoute === "healthy" ) return "fallback"; if (p.queueAllowed) return "queue"; return p.degradeApproved ? "degrade" : "fail_closed"; }
retryAfterMs は主経路の再試行だけに使います。代替経路は固有の予測時間・費用で判定し、fallbackApproved には独立した quota 確認も含めます。そのため主経路の長い retry-after が、残り SLO 内で完了できる健全な代替経路を妨げません。committed または unknown は、自動分岐の外で状態照合するか、検証済みの再開・冪等性プロトコルを使います。次の送信前に試行レコードを保存し、要求経路、選択経路、プロバイダーの request ID、エラー分類、待機時間、トークン、費用、コミット状態、切替理由を残します。500 でも入力コンテキストが長すぎる場合など、リクエスト側の原因があり得るため、エラー詳細を見ずに一時障害へ分類しません。
障害注入で合格を確認する
staging で rate 429、quota 429、stream 前 503、token 表示後の切断、tool commit 後 timeout、fallback schema 欠落、open circuit を再現します。さらに、主経路の retry-after が残り SLO より長くても、承認済みの健全な代替経路が固有の時間・費用予算内に収まるケースでは、期待 action を queue ではなく fallback にします。各ケースは期待する action が一つでなければなりません。
メトリクスは primary success、retry recovery、fallback recovery、degraded、fail closed を分離します。最終 HTTP 200 だけでは、壊れた primary route を隠します。
今あるのが具体的な provider の 429 なら、先に OpenAI API rate limit、Claude API rate limit、Gemini API rate limit で owner を特定してください。
承認済みモデルを一つの互換 endpoint で試す場合は、staging で最新の LaoZhang AI API ドキュメントと照合できます。ただし統一 endpoint は等価性、復旧予算、ログ、stop rule を自動で保証しません。
完了条件は、すべての障害が説明可能な一つの action に進み、全レイヤーが一つの予算を共有し、代替経路が同じ業務契約を通過し、最終成功が途中の失敗を消さないことです。



