「OpenAI が障害を起こしたとき何が起きるか?」は、design partner が最初に聞く質問の一つです。正当な質問で、世の “AI Gateway” ランディングページの多くは1行の約束 — シームレスなプロバイダー横断フェイルオーバー — と、プロバイダーのロゴ間に矢印を引いた図で答えます。
本稿はその長い版です。プロバイダーのレジリエンスを3層に分け、どの層が今日出荷されていて、どの層が domain code にはあるが request path に未統合で、どの層がロードマップ上かを明示します。その上で、ほとんどのベンダーが day one で出荷すると主張する部分を何故あえて抑制したかを説明します。
短い版: same-provider retry on 5xx / 429 は本番で動いています。cross-provider failover は internal/core/routing/ に domain model として実装されていますが、chat completion path からはまだ呼ばれていません。理由は技術ではなく (統合は数日の作業です)、audit / billing / cost predictability に関わることであり、請求で誰も驚かない形で出したいからです。
1. レジリエンスの3層
「フェイルオーバー」がチームで意味するものを3つに分けると分かりやすいです:
- プロバイダー内 retry — 上流が 5xx / 429 を返した、または network が drop した。同じプロバイダーの同じ model に対して同じ呼び出しを、exponential backoff 付きで再送する。現実の transient 障害の大半はこれで拾えます。
- プロバイダー横断 failover — primary プロバイダーが劣化している (5xx 率上昇、地域障害、発表済みインシデント)。リクエストを別プロバイダーの同等 model へ再ルーティングする。呼び出し元は回答を得るが、model は異なりうる。
- カナリア / model migration failover — 新 model がリリースされ、挙動が変わり、code deploy 無しに一部を即前版へ戻したい。provider レジリエンスより release engineering に近いですが、同じ会話に登場します。
これらは積層します。本稿は前の2層に集中し、3層目は完全性のために言及します (さらに先の構想です)。
2. 今日出荷されているもの
全プロバイダー呼び出しは LiteLLM を通り、Go layer が coreerrors.Retryable() で呼び出しを wrap します。retry 規則は単純です:
- HTTP status
>= 500または429で retry。 - Exponential backoff + jitter (base 1秒、上限 60秒、jitter 約25%)。
- プロバイダーが
Retry-Afterを送れば従う。 - 同じプロバイダー、同じ model — retry は同じ endpoint への新規 POST。
- retry 予算はデフォルトで最大5回。
retry 予算を使い切ると、Gateway は呼び出し元へ構造化エラーを返します: 502 と code: provider_failed (全試行でプロバイダーがエラーを返した) または 504 と code: provider_timeout (timeout 予算を超えた)。両者とも、retry semantics を含めて Error Handling に文書化されています。
これで一般的なケース — プロバイダーの一時的な 503、短い rate-limit の flicker、network の half-open connection — は処理できます。処理できないのは、プロバイダーが1時間完全 down するようなケースで、そのためには第2層が必要です。
ここでの原則は、全故障モードに適用するものと同じ: 明示的であること。最終的に retry が静かに成功するのは問題ないですが、静かに諦めて部分 response を 200 で返すのは問題です。リクエストを serve できないなら、分岐できる code で伝えます。
3. 構築済みだが未統合の domain model
internal/core/routing/routing.go は、第2層のための完全な domain model です:
type RoutingPolicy struct
type ProviderHealth struct
ResolveModel は policy を歩きます: primary が healthy なら primary を、そうでなければ secondary へ fallback、いずれも不可なら strategy (priority 順、cost 順、latency 順) で healthy なものから最良を選ぶ。3つの strategy (StrategyPriority / StrategyCost / StrategyLatency) が domain に出荷されています。
今日起きていないこと: chat completion handler (internal/delivery/http/ginserver/handler/openai_compat.go:193) は h.catUC.ResolveModel(ctx, req.Model) を呼び、これは catalog.ResolveModel — model catalog への slug lookup です。単一の model を返します。RoutingUsecase は DI container で instantiate されます (internal/app/gateway/app.go:197) が、OpenAICompatHandler には渡されていません。domain code は存在し、request path はそれを呼んでいません。
何故 domain を先に作り、統合を後にしたか? chat handler が RoutingUsecase を呼ぶようになると、caller visible な全挙動 — resp.model、audit record、billing、PII scan 結果 — を routing 決定と一致させなければならないからです。先に domain を作ることで、caller へ露出する前に不変条件を確定できました。統合は簡単な部で、caller との契約が難しい部です。
4. 何故抑制したか
プロバイダー横断 failover は、多くの gateway ベンダーが過剰に主張し、不足して届ける部分です。我々が出荷していない正直な理由:
ヘルスチェック。 プロバイダーが unhealthy と知ること自体が設計問題です。能動 probe (定期 synthesize request) はプロバイダーへ負荷を加え、実際の customer traffic を反映しない可能性があります。受動観測 (実際の request の error rate と latency の EWMA) はより正確ですが遅れ — EWMA が閾値を越える頃には、実際の顧客は既に失敗を見ています。両方が必要で、能動 signal で circuit breaker を開き、受動 signal で閉じる形になります。
Audit の含意。 リクエストが claude-haiku-4-5 から gpt-4o-mini へ failover するとき、2つのプロバイダーの data policy が同じ論理リクエストに適用されます。data residency の理由で Anthropic を選んだ EU の顧客は、単一の failover リクエストであっても prompt が OpenAI のインフラを通ることを望まないかもしれません。failover は policy を考慮する必要があり、routing policy はどのテナントがどのプロバイダーへ failover できるかを知る必要があります。
PII scanning。 pre-call の PII scan は primary プロバイダーの policy に対して走ります。call が別プロバイダーへ failover した場合、fallback 側の policy で再 scan するのか? 今日 scanner は1セットの finding を生成しますが、failover は request と共に移動する cache 済み結果か、新しい境界での再 scan のいずれかを必要とします。
Cost predictability。 Gemini Flash から Claude Opus への黙示的 failover は、token 単価で概ね 50× の cost surprise です。design partner は部分的には予測可能な請求を求めて SchneeAI を評価しています。一時的な 5xx のたびに自動的に model tier を上げる failover は、1回の billing cycle でその信頼を焼き尽くします。
Idempotency。 クライアントが Idempotency-Key: abc123 を送ります。primary プロバイダーが stream 中に失敗。fallback へ切り替えて成功。これは課金対象 interaction が1つか2つか? 今日の答えは単純 — 1 interaction、1 provider。failover は答えを興味深くし、興味深い billing は悪い billing です。
至った結論: retry を先に出荷する (same provider、same model、same cost なので安全)。failover を次に、routing policy による明示的 opt-in、fallback が primary の N× を超えたら拒否する cost ceiling、caller に failover を観測させる response extension 付きで。
5. 出荷のために必要なもの
依存順に並べた具体チェックリスト:
- chat handler へ
RoutingUsecaseを統合。catalogUCをroutingUCで置き換えるか、chain する (catalog が alias を解決し、routing が policy と health を元に provider を決定)。 - provider health infrastructure。 プロバイダー毎の in-process circuit breaker。実際の request 成果による受動 EWMA で駆動し、設定可能な schedule での能動 probe を option で。
- policy を考慮した failover eligibility。 routing policy は、テナントがどのプロバイダーへ failover できるかを知る必要がある (data residency、PII policy、契約上の除外)。
- cost ceiling。 routing policy の field (
max_fallback_cost_multiplier: 5等) で、fallback の token 単価が primary の5倍を超えたら拒否。拒否は構造化エラーで、primary への黙示的 retry ではない。 - response extension。 failover 発火時、response は
schneeai.fallback_reason、schneeai.fallback_from_model、schneeai.fallback_to_modelを携え、caller が観測・audit 可能に。resp.modelは今日 resolved な model を示しており、failover は format を拡張します。 - audit record の拡張。 audit log は request された model だけでなく、実際に serve した provider を記録。
- idempotency 規則。 同じ
Idempotency-Keyは、どの provider が serve しても1つの課金 interaction。reconciliation worker は既に retry に由来する二重 reservation ケースを扱っており、failover はその仕組みを再利用します。
いずれも研究問題ではなく、engineering work です — 数週間、feature flag の後ろ、少数の design partner が canary として opt-in する形で。
6. 出荷時に何が見えるか
caller の視点では、failover は opt-in で観測可能です。default 挙動は変わらず: routing policy で fallback を設定しなければ、call は primary プロバイダーへ行き、そこに留まり、一時的な失敗に対しては今日と同じように retry します。
opt-in した場合、response shape が何が起きたかを伝えます:
model field は既存の semantics を保ちます — 実際に出力を生成した model を伝えます。extension object は failover を programmatic に検出可能にし、dashboard や alerting で追跡できます。
audit log は request された provider/model だけでなく実際のものを記録します。cost dashboard は service / feature 毎に fallback 分布を分離し、routing policy が想定より頻繁に発火していないかを確認できます。
7. ロードマップと準備
failover 向けの公開 phase spec はまだありません — domain model は存在しますが、統合作業は他の優先事項 ("model": "auto" router 本体、prompt-management UX、GA 準備) と競合しています。design partner の皆様から cost ceiling と policy eligibility の default について input を頂いた上で、将来の phase で出荷する予定です。
今日できる準備:
- 評価期間中は model を固定。 SchneeAI を direct provider 呼び出しと比較 benchmark するなら、両側で同じ model を pin して apples-to-apples で。router は今日まだ面白いことをしていません。
provider_failed/provider_timeoutrate を監視。 プロバイダーが今日どれだけ hard-fail しているかを示します。その数値が、failover が実際に catch する上限です。- 制約を教えてください。 data residency で failover 先が制限される、予算天井で fallback の费用上限を設けたい等、まさにその input が policy field の default を正しく設定するために必要です。[email protected] まで。
正直なサマリ
短い版を求める読者へ:
- 今日出荷済み: same-provider retry on HTTP 5xx / 429、exponential backoff +
Retry-Afterhonor。retry 予算を使い切ったときの明示的失敗モード (provider_timeout、provider_failed)。 - モデル化済み・未統合: primary/fallback の provider と model を持つ
RoutingPolicy、ProviderHealthtracking、3つの routing strategy (priority、cost、latency)。domain code は完全だが、chat completion path は未呼び出し。 - ロードマップ上: cross-provider failover の統合。明示的 opt-in、cost ceiling、policy を考慮した eligibility、response extension、audit 拡張付き。
- v1 では計画外: 自動 provider-quality scoring、ML-based routing。今日の router は policy 駆動で、opaque にする計画はありません。
ベンダーが「プロバイダー障害をシームレスに扱う」と言ったら、failover 発火時の response shape、audit log が実際の provider を記録するか、cost ceiling があるかを聞いてください。答えは marketing copy より大抵興味深いです。
retry と routing が request path のどこに位置するかの全体は、SchneeAI Gateway の中身 を。本稿が参照する error code は、エラーハンドリング を。build-vs-buy 判断との関係は、SchneeAI vs LiteLLM を。そして設計 — とくに cost ceiling の default — へ input いただける場合は、メール まで。