# 本番LLMアプリの信頼性・回復性設計 2026｜多プロバイダ・フォールバック・リトライ・サーキットブレーカ・冪等性

> 生成AI/LLMアプリを本番で落とさないための信頼性・回復性の設計ガイド。タイムアウト、指数バックオフ+ジッター付きリトライ（一時障害のみ）、サーキットブレーカ、多プロバイダ・フォールバック（AI SDK / Vercel AI Gateway）、冪等性、レート制限まで、一次情報準拠のTypeScript実コードで解説します。

- 公開日: 2026-07-23
- 著者: 友田 陽大
- タグ: 生成AI, LLM, 信頼性, 回復性, 冪等性, AIエージェント, TypeScript, SRE, アーキテクチャ設計, コスト最適化
- URL: https://tomodahinata.com/blog/llm-application-reliability-resilience-fallback-retry-guide
- カテゴリ: 本番LLMアプリ実装（信頼できるAI）
- 総合ガイド: https://tomodahinata.com/blog/production-llm-application-engineering-guide

## 要点

- LLMアプリは非決定的な外部APIに依存する分散システム。障害・レート制限・レイテンシスパイク・コスト暴走で簡単に落ちる。回復性はアーキテクチャで作る。
- リトライは『一時障害（429・5xx・タイムアウト）だけ・冪等な呼び出しだけ』。指数バックオフにフルジッターを足して群集雪崩（thundering herd）を防ぐ。
- AI SDKは既定でmaxRetries=2で一時障害を自動リトライ。多プロバイダ・フォールバックはVercel AI Gatewayの providerOptions.gateway.models か自前で。多層でリトライを重ねない（retry amplification）。
- サーキットブレーカで落ちているプロバイダを一時遮断し、カスケード障害とコスト浪費を止める。時刻を注入して単体テスト可能に。
- 副作用のある操作は冪等キーで保護。リトライやエージェントの複数回呼び出しによる二重課金・二重発送を構造的に防ぐ。

---

「開発中は完璧だったのに、本番でトラフィックが乗った瞬間に落ちた」——LLMアプリで頻発する事故です。理由はシンプルで、**LLMアプリは、非決定的な外部APIに依存する分散システム**だからです。プロバイダ側の一時障害、レート制限（429）、レイテンシスパイク、そしてコスト暴走。これらは「起きるか」ではなく「いつ起きるか」の問題です。

結論から言えば、本番LLMアプリの**回復性（resilience）は、モデルではなくアーキテクチャで作ります**。柱は6つ。

1. **タイムアウト** — 返らない呼び出しで止まらない。
2. **リトライ（一時障害のみ・指数バックオフ＋ジッター）** — 一過性の失敗を吸収する。
3. **サーキットブレーカ** — 落ちているプロバイダへの攻撃を止め、カスケード障害を防ぐ。
4. **多プロバイダ・フォールバック** — 1社の障害でサービスを止めない。
5. **冪等性** — リトライや二重呼び出しで副作用を二重に起こさない。
6. **レート制限・コスト上限** — 濫用と暴走を封じる。

この記事は、決済基盤で**本番二重課金0件**を実現してきた[信頼性・冪等性の設計](/blog/dynamodb-single-table-design-reliability-idempotency-patterns)の知見を、生成AIに持ち込みます。**AI SDK / Vercel AI Gateway の現行APIと、AWS・Google SRE の一次情報に準拠**し、実コードで示します。これは、[セキュリティ](/blog/llm-application-security-owasp-top-10-prompt-injection-guide)・[MCP](/blog/mcp-model-context-protocol-production-server-guide)・[評価](/blog/llm-application-evaluation-testing-llm-as-judge-ci-guide)・[プライバシー](/blog/llm-application-data-privacy-compliance-pii-appi-gdpr-guide)に続く「**信頼できる本番AI**」の第5の柱です。

## LLM呼び出しは「失敗する前提」で設計する

まず、どんな失敗が起きるかを分類します。対応が変わるからです。

| 失敗 | 例 | 対応 |
|---|---|---|
| **一時障害** | 429（レート制限）、500/502/503/504、接続断、タイムアウト | **リトライ**（バックオフ＋ジッター）→ ダメなら**フォールバック** |
| **恒久エラー** | 400（不正）、401/403（認可）、422 | **リトライしない**（成功しない・コストの無駄） |
| **部分的劣化** | 応答は返るが遅い／品質が低い | **タイムアウト**＋**評価**で検知 |

HTTPの意味論は明確です。**429（Too Many Requests）は RFC 6585**、**Retry-After ヘッダと 503 は RFC 9110** が定義します。Retry-Afterが返ってきたら、独自のバックオフより**それを優先**します。

## タイムアウトとリトライ：一時障害だけを、ジッター付きで

**リトライの鉄則は2つ。「一時障害だけ」「冪等な呼び出しだけ」**。Google SREも「恒久エラーや不正なリクエストはリトライしない——決して成功しないから」と述べています。

そして**ジッター（ゆらぎ）**。全クライアントが同じ間隔で再試行すると、再試行の山が同期して**群集雪崩（thundering herd）**を起こし、復旧しかけたプロバイダを再び潰します。AWSの推奨は、指数バックオフに**フルジッター**（0〜backoffの一様乱数）を加えて山を平準化することです。

まず良い知らせ：**Vercel AI SDK は一時障害を既定でリトライします**（`maxRetries`、既定=2）。多くのケースはこれで足ります。

```ts
import { generateText } from "ai";

// AI SDK は一時障害を既定で2回リトライ（maxRetries=2）。timeout は AbortSignal で。
// Vercel AI Gateway なら「モデル・フォールバック」を宣言的に書ける：
// primary が失敗したら順にバックアップを試し、最初に成功した応答を返す。
const { text } = await generateText({
  model: "anthropic/claude-opus-4-8",
  prompt,
  maxRetries: 2,                              // 既定値。多層でリトライを重ねない
  abortSignal: AbortSignal.timeout(15_000),   // 15秒で打ち切り
  providerOptions: {
    gateway: {
      models: ["anthropic/claude-opus-4-8", "openai/gpt-5"], // 順にフォールバック
    },
  },
});
```

自前でリトライを制御したい場合（ゲートウェイを使わない、独自のバックオフが要る）は、**一時障害だけ**を、指数バックオフ＋フルジッターで再試行します。

```ts
// 自前リトライ：一時障害(429/5xx/timeout)だけを、指数バックオフ+フルジッターで。
const RETRYABLE = new Set([429, 500, 502, 503, 504]);

async function withRetry<T>(fn: () => Promise<T>, retries = 2, baseMs = 500): Promise<T> {
  for (let attempt = 0; ; attempt++) {
    try {
      return await fn();
    } catch (err) {
      const status = (err as { statusCode?: number }).statusCode;
      // 恒久エラー(4xx)はリトライしない。上限に達したら諦める（無限リトライ=コスト暴走）。
      if (attempt >= retries || status === undefined || !RETRYABLE.has(status)) throw err;
      const backoff = Math.min(baseMs * 2 ** attempt, 20_000);
      const wait = retryAfterMs(err) ?? Math.random() * backoff; // Retry-After優先、無ければfull jitter
      await new Promise((resolve) => setTimeout(resolve, wait));
    }
  }
}
```

## サーキットブレーカ：落ちた相手を叩き続けない

一時障害が**続く**とき、リトライは逆効果になります。落ちているプロバイダを叩き続ければ、**カスケード障害**とコスト浪費を招くだけ。そこで**サーキットブレーカ**——連続失敗が閾値を超えたら回路を **open**（即失敗）にし、cooldown後に **half-open** で1回だけ試して復旧を確認します（Martin Fowler）。

テスト容易性のため、**時刻を引数で注入**します（`Date.now()`に依存させない＝決定的に単体テストできる）。

```ts
// サーキットブレーカ：連続失敗が閾値超で一定時間 open（即失敗）。
// now を注入してテスト可能に（closed → open → half-open）。
class CircuitBreaker {
  private failures = 0;
  private openedAt: number | null = null;
  constructor(
    private readonly threshold = 5,
    private readonly cooldownMs = 30_000,
  ) {}

  allow(now: number): boolean {
    if (this.openedAt === null) return true;                 // closed：通常通り
    if (now - this.openedAt >= this.cooldownMs) return true; // half-open：試行を1回許可
    return false;                                            // open：即失敗（叩かない）
  }
  onSuccess(): void {
    this.failures = 0;
    this.openedAt = null;
  }
  onFailure(now: number): void {
    if (++this.failures >= this.threshold) this.openedAt = now;
  }
}
```

## 多プロバイダ・フォールバック：1社の障害で止めない

回復性の本丸です。**primaryプロバイダが落ちたら、secondaryに切り替える**。Vercel AI Gateway なら前述の `providerOptions.gateway.models`（モデル順）や `order`（プロバイダ順）で宣言的に書けます。自前で管理するなら、サーキットブレーカとリトライを組み合わせたフォールバック連鎖にします。

```ts
// 多プロバイダ・フォールバック：primary が落ちたら次へ。open 中のプロバイダはスキップ。
const CHAIN = ["anthropic/claude-opus-4-8", "openai/gpt-5"] as const;
const breakers = new Map(CHAIN.map((model) => [model, new CircuitBreaker()]));

async function generateResilient(prompt: string, now: () => number): Promise<string> {
  let lastErr: unknown;
  for (const model of CHAIN) {
    const breaker = breakers.get(model)!;
    if (!breaker.allow(now())) continue; // サーキットが open ならこのプロバイダは飛ばす
    try {
      // withRetry が再試行を担うので、SDK側は maxRetries:0（多層リトライを避ける）
      const { text } = await withRetry(() =>
        generateText({ model, prompt, maxRetries: 0, abortSignal: AbortSignal.timeout(15_000) }),
      );
      breaker.onSuccess();
      return text;
    } catch (err) {
      lastErr = err;
      breaker.onFailure(now());
    }
  }
  throw new Error(`all providers failed: ${String(lastErr)}`);
}
```

注意点は**品質の一貫性**です。フォールバック先はモデルが変わるため、出力の質・形式がぶれます。切替時も品質を保つには、[structured output の信頼性設計](/blog/structured-output-reliability-constrained-decoding-semantic-validation)でスキーマ検証し、[評価（eval）](/blog/llm-application-evaluation-testing-llm-as-judge-ci-guide)で継続的に品質を測ります。

> **retry amplification に注意。** SDK・自前リトライ・ゲートウェイ・上流サービス——各層がリトライを持つと、再試行回数が**掛け算**になり、障害時に負荷を爆発させます。Google SREの原則は明快で、**「リクエストあたりのリトライを制限する」**。リトライは1つの層に集約し、他は0にします。

## 冪等性：リトライで副作用を二重に起こさない

リトライとフォールバックは、**副作用のある操作では諸刃の剣**です。1回目が実は成功していた（応答だけ失われた）場合、リトライは**二重実行**になります。LLMエージェントは同じ意図でツールを複数回呼ぶこともあり、リスクはさらに高い。

対策は**冪等キー**。同じキーの操作は1回だけ実行されることを、下流で保証します（[MCPのツール実行設計](/blog/mcp-model-context-protocol-production-server-guide)や決済基盤と同じ）。

```ts
// 副作用のある操作は冪等キーで保護。リトライ/二重呼び出しでも一度きり。
async function chargeIdempotent(orderId: string, amountJpy: number): Promise<Receipt> {
  const key = `charge:${orderId}:${amountJpy}`; // 意図が同じなら同じキー
  const existing = await receipts.get(key);
  if (existing) return existing;                // 既に処理済みなら再実行しない
  const receipt = await paymentApi.charge(orderId, amountJpy, { idempotencyKey: key });
  await receipts.put(key, receipt);
  return receipt;
}
```

## レート制限とコスト回復性

信頼性は**コスト**の問題でもあります。リトライやループが暴走すれば、可用性ではなく請求書が壊れます。対策はセキュリティ記事の[OWASP LLM10「Unbounded Consumption」](/blog/llm-application-security-owasp-top-10-prompt-injection-guide)と同じ——**出力トークン上限（`maxOutputTokens`）、per-userレート制限、タイムアウト、そしてリトライ回数の上限**。Google SREの「過負荷時は劣化した応答を返す（load shedding / graceful degradation）」も有効な選択肢です。

## 可観測性で回復性を運用する

これらの仕組みは、**測って初めて機能します**。失敗率・**フォールバック率**・サーキットのopen時間・p95レイテンシ・リトライ回数・コストを記録し、異常を検知します。「フォールバックが常時発火している＝primaryが慢性的に不調」のように、指標が設計の穴を教えてくれます。土台は [OpenTelemetryによる本番可観測性](/blog/opentelemetry-observability-production-tracing-metrics-logs) を参照してください。

## 導入チェックリスト（本番投入前）

- [ ] **タイムアウト** — すべてのLLM呼び出しに `abortSignal`（`AbortSignal.timeout`）を設定したか。
- [ ] **リトライ範囲** — 429/5xx/タイムアウトの一時障害だけ、かつ冪等な呼び出しだけをリトライしているか。
- [ ] **バックオフ＋ジッター** — 指数バックオフにフルジッター、Retry-After優先になっているか。
- [ ] **retry amplification** — リトライを1層に集約し、多層で重ねていないか（上限も設定）。
- [ ] **サーキットブレーカ** — 連続失敗で落ちたプロバイダを一時遮断しているか。
- [ ] **多プロバイダ・フォールバック** — 1社障害で止まらないか。フォールバック時の品質を評価で担保しているか。
- [ ] **冪等性** — 副作用のある操作を冪等キーで保護し、二重実行を防いでいるか。
- [ ] **コスト回復性** — トークン上限・レート制限・リトライ上限で暴走を封じているか。
- [ ] **可観測性** — 失敗率・フォールバック率・レイテンシ・コストを監視し、劣化を検知できるか。

## まとめ

本番LLMアプリの信頼性は、特別な魔法ではなく、**分散システムの回復性設計をLLMに適用する**ことです。**「LLMは失敗する外部依存」と割り切り、タイムアウト・（一時障害限定の）リトライ・サーキットブレーカ・多プロバイダ・フォールバック・冪等性で被害を封じ込める**。

- **リトライは一時障害だけ・冪等だけ・ジッター付き。** 多層で重ねない（retry amplification）。
- **サーキットブレーカで落ちた相手を叩かない。** 多プロバイダ・フォールバックで1社障害に耐える。
- **冪等キーで二重実行を防ぎ、レート制限とコスト上限で暴走を封じる。**

セキュリティ・MCP・評価・プライバシー・**信頼性**——この5本柱が揃って、生成AIは「速く作れて、安全で、壊れず、信頼でき、そして**止まらない**」本番システムになります。作る速さと、止めない堅さは、正しく設計すれば両立します。
