AIチャットの可用性を高める複数モデルフォールバック設計|429エラー回避と二段切替
AIチャットのサービスダウンを防ぐには 「メインモデル失敗時の自動フォールバック回路」 を構築します。429レート制限や障害時に即座にサブモデルへ切替通信を行います。
単一のLLM APIプロバイダに依存したAIチャット運用では、API側の急なレート制限(HTTP 429 Too Many Requests)や障害時(HTTP 503)、特定時間帯のレスポンス遅延によってサービス全体が停止するリスクがあります。特に無料枠や従量課金の制限枠内で運用する場合、二段構えの切替設計が不可欠です。
1次モデルと2次モデルの構成パターン
メイン(Primary)とサブ(Secondary)の役割分担と、切替条件の設計マトリクスです。
| 構成レベル | プライマリモデル (Primary) | セカンダリモデル (Secondary) | 切替トリガー |
|---|---|---|---|
| 同系モデル切替 | 高精度モデル (例: Gemini Flash) | 同系軽量モデル (例: Gemini Flash-Lite) | レート制限 (429)、タイムアウト |
| 異種プロバイダ切替 | 外部 API (例: OpenAI / Gemini) | クラウド Workers (例: Cloudflare Workers AI) | API障害 (5xx)、日次枠枯渇 |
| 制限調整 | フルコンテキスト (8000トークン) | 最小コンテキスト (1000トークン) | クールダウン期間中の呼び出し |
フォールバック回路の導入手順
複数モデルのフォールバック回路を導入する実装ステップです。
- プライマリ API 呼び出しの try-catch 内で HTTP 429 などのエラーを捕捉します。
- 429 発生時にクールダウン期間を設定し、一時的にプライマリ呼び出しを迂回します。
- プロンプトや履歴をサブモデル用に調整した上でセカンダリ API へリクエストを試行します。
1. 二段切替(Primary -> Secondary)ラッパー関数の作成
Primary API がエラーを返した場合に catch ブロックで捕獲し、同一インターフェースのまま Secondary API へ切り替えます。
interface LlmResponse {
text: string;
usedModel: "primary" | "secondary";
}
export async function callLlmWithFallback(
messages: Array<{ role: string; content: string }>
): Promise<LlmResponse> {
// 1. Primary API の呼び出し試行
try {
const primaryResult = await callPrimaryLlmApi(messages);
return { text: primaryResult, usedModel: "primary" };
} catch (error: any) {
console.warn("Primary LLM API failed. Falling back to Secondary.", error?.message);
// 2. 429 (Rate Limit) や 5xx エラーの場合は Secondary へ
try {
// サブモデル向けにメッセージ・プロンプトを軽量化
const lightweightMessages = adaptMessagesForSecondary(messages);
const secondaryResult = await callSecondaryLlmApi(lightweightMessages);
return { text: secondaryResult, usedModel: "secondary" };
} catch (secondaryError) {
throw new Error("Both Primary and Secondary LLM APIs failed.");
}
}
}
2. クールダウン状態のキャッシュ管理
連続で429エラーが発生した際、毎回 Primary を呼んで無駄な遅延(タイムアウト待ち)を発生させないよう、一定時間(例: 5分間)Primary をスキップして直接 Secondary を呼ぶフラグ(クールダウン)を設けます。
let primaryCoolDownUntil = 0;
async function smartLlmCall(messages: any) {
const now = Date.now();
// クールダウン期間中の場合は直接 Secondary を呼び出す
if (now < primaryCoolDownUntil) {
return await callSecondaryLlmApi(adaptMessagesForSecondary(messages));
}
try {
return await callPrimaryLlmApi(messages);
} catch (err: any) {
if (err?.status === 429) {
// 5分間のクールダウンをセット
primaryCoolDownUntil = Date.now() + 5 * 60 * 1000;
}
return await callSecondaryLlmApi(adaptMessagesForSecondary(messages));
}
}
3. サブモデル用のプロンプト・履歴短縮調整
バックアップモデル(Secondary)は、Primary よりもトークン上限やコンテキストサイズが小さいケースが多いため、切り替え時にシステムプロンプトの長さを縮小し、会話履歴件数を絞り込みます。
function adaptMessagesForSecondary(messages: any[]) {
// システムプロンプトを短縮版へ差し替え、履歴を直近2件へ絞り込む
const userMessages = messages.filter((m) => m.role === "user").slice(-2);
return [
{ role: "system", content: "あなたは手短に回答するアシスタントです。" },
...userMessages,
];
}
現場で発生する失敗境界(Failure Boundary)
複数モデル切替時に直面する現実のトラブルパターンです。
[障害ログ例]: Error: 400 Context length exceeded on Secondary Model (Max: 2048 tokens, Received: 4500 tokens)
[原因]: Primary で使っていた長い会話履歴をそのまま Secondary へ渡したため、Secondary 側のトークン上限を超過して両方ダウンした。
バックアップ先でのコンテキスト溢れ(Double Failure)
Primary が落ちた後、Secondary API に切り替えたものの、Primary 用の長いシステムプロンプトと大容量の会話履歴をそのまま転送したため、Secondary 側でもトークン上限超過エラーが発生して全落ちする現象です。
- 対策: フォールバック処理に入る際、プロンプトの短縮と履歴配列の再スライスを行う
adaptMessagesForSecondaryのようなアダプター処理を必ず経由させます。
費用無制限の無制限リトライ
429エラーに対して単純な while ループで何度もリトライを繰り返すと、APIの待機時間が累積してクライアント側がタイムアウトするだけでなく、急激にクォータを消費します。
- 対策: 同一モデルへの即時リトライは最大1回(指数バックオフ付き)に留め、速やかにバックアップモデルへ迂回させる設計にします。