世の「AI Gateway」ページの多くは、アイデアを1段落で説明し、アーキテクチャを箱と矢印の図1枚で済ませます。この記事はその逆です — アイデアは1段落、以降はリクエストパスの全ホップをプレーンテキストで、背後にある設計判断とともに。監査グレードの制御を必要とするワークロードで SchneeAI を評価しているなら、ここに書かれているものがあなたが買うものです。
概要と図は セキュリティページ に。本稿はその詳細版です。
8つのホップ
お客様のバックエンドが POST /v1/chat/completions を呼ぶと、リクエストは次の順序で Gateway を通ります:
- TLS 終端 — リクエストはクラスタイングレスで TLS 1.3 (レガシークライアント向けに forward secrecy 付き TLS 1.2) で受信。
- Bearer JWT 検証 — Ory Hydra の JWKS を取得、署名を検証、クレームを抽出。
- ルーティングポリシー適用 — モデルエイリアスを解決、予算を確認、PII ポリシーを読み込み。
- PII / シークレットスキャン — 17カテゴリ、リクエストが境界を出る前に。
- LiteLLM 経由でプロバイダー呼び出し — モデルエイリアスをプロバイダーへ、リトライ予算を適用。
- raw やり取りを Vault へ書き込み — S3 / R2 のサーバー側暗号化 (SSE-S3, AES-256)、アプリ層エンベロープ暗号化はロードマップ。
- 構造化利用と監査レコードを書き込み — Postgres、追記型監査テーブル。
- レスポンス返却 — OpenAI と同じ形状、関連する場合は SchneeAI 拡張を併記。
各ホップは故障面であり設計判断です。順に見ていきましょう。
1. TLS 終端
TLS は最初の制御であり、チームが最も考えない制御でもあります。クラスタエッジで HSTS を強制し、TLS をそこで終端し、クラスタ内の内部ホップは再暗号化します (Gateway ↔ LiteLLM、Gateway ↔ Vault writer は mTLS)。
重要な選択: CDN で TLS を終端してオリジンへ平文で転送することはしません。コストにはあまり効きません (CDN 終端は安い) が、脅威モデルに効きます — CDN の設定ミスでオリジントラフィックが露出するのは、「転送中暗号化」が実は「あなたと CDN の間だけ暗号化、以降は平文」だったと発覚する典型的な経路です。エンドツーエンドの TLS + 内部 mTLS により、エッジの設定ミスがあってもプロバイダープロンプトは読めません。
2. JWT 検証
Gateway は全リクエストを Ory Hydra の JWKS で検証します。JWT は sub (プラットフォームユーザーID、UUIDv7)、active_tenant_id、tenant_role、そして呼び出しを登録済みサービスにスコープする service_id クレームを携えます。service_id が無いレガシートークンは aud の先頭要素へフォールバックします。
注目すべき設計判断: service_id はヘッダではなくトークンのクレームです。クライアント側で発行しやすいので長く X-Service-ID を検討しました。問題は、有効なユーザー JWT を発行できる者なら誰でもヘッダを容易に偽装できること — つまりサービス境界が強制ではなく助言になります。署名されたクレームに置くことで、サービス境界を本当の境界にします。
JWKS は短い TTL でキャッシュされ、固定スケジュールでローテーションされます。鍵のローテーションのために Gateway を再デプロイする必要はありません。
3. ルーティングポリシー
アイデンティティが確立したら、Gateway はルーティングポリシーを読み込みます — モデルエイリアス → プロバイダーモデル、(service, tenant, feature) の予算状態、適用中の PII ポリシー。
"model": "auto" はここで解決されます。ワイルドカードではありません。お客様のテナントに設定されたポリシーに対するルーティング決定で、次を考慮できます:
- フィーチャータグ (
feature=reply_suggestion→ バリューティアへルーティング) - 予算残 (Sonnet 予算を使い切った → Flash へフォールバック)
- レイテンシターゲット (インタラクティブフィーチャー → Gemini Flash / Haiku を優先)
- PII ポリシー (critical な PII を検出 → プロバイダー呼び出しをブロック、早期リターン)
これがお客様のサービスコードではなく Gateway にある理由: ポリシーは drift します。バックエンドに「返信には Sonnet を使う」と硬 coding したチームは、Sonnet が間違った選択になってからもずっとそれを使い続けます。ポリシーを中央化することで、変更は1回の config 編集、監査ログ付き、canary サポート付きになります。
4. PII / シークレットスキャン
リクエストが SchneeAI の境界を出る前に、システム/ユーザーメッセージ両方に構造化スキャンを走らせます。本日時点で17カテゴリ: メール、クレジットカード (Luhn 付き)、日本のマイナンバー (チェックデジット付き)、米国 SSN、AWS / Google / OpenAI / Anthropic / Stripe / Slack の API キー、JWT、PEM 秘密鍵、電話番号 (JP / E.164)、IPv4 / IPv6、接続文字列。
各パターンには重大度ティアがあります。Critical (API キー、接続文字列) は呼び出し全体をブロックできます。Warning (メール、電話) はマスクまたはフラグ。アクションはテナント毎のポリシーで決まります。
非自明な設計判断: スキャンはプロバイダー呼び出しの後ではなく前に走る。リクエストが一度お客様の境界を出ると、以降はプロバイダーのデータ常駐と保持ポリシー次第 — 取り消せません。呼び出し前のスキャンにより、プロンプトに混入した漏洩 API キーはプロバイダーに届きません。
設計詳解は 本番での PII スキャン を参照。
5. LiteLLM 経由のプロバイダー呼び出し
Gateway は LiteLLM を通してプロバイダーを呼びます。これが扱うもの:
- プロバイダー固有の認証 (各プロバイダーのキーは SchneeAI が保持、スコープ限定、ローテーション)。
- プロバイダー固有のリクエスト/レスポンス形状 (OpenAI / Anthropic / Google / Mistral / Cohere / DeepSeek)。
- 429 / 500 / 502 / 503 / 504 に対する exponential backoff + jitter 付きリトライ。
- ストリーミング SSE パススルー。
LiteLLM はブラックボックスではなくライブラリです。バージョンを pin し、設定を vendor 化し、Clean Architecture の port adapter として扱います — Gateway のドメイン層は LiteLLM の型を見ません。いつか別のプロキシ (または直接統合) に載せ替えても、ドメインコードは変わらない。
なぜそもそも LiteLLM なのか? プロバイダーアダプタの構築と保守はトレッドミルだからです。新モデルは週次で出ます。価格は月次で変わります。機能フラグ (ツール呼び出し、ビジョン、推論コンテンツ) は現れたり消えたりします。自社でアダプタを保守するチームは、エンジニアリング時間の無視できない割合を、ビジネス価値を生まない層に費やします。その層を特化ライブラリへ外部委託することで、チームは価値を生む層に集中できます。
6. Vault 書き込み
raw のリクエストとレスポンス — システムプロンプト、ユーザーメッセージ、モデル出力、あれば推論コンテンツ — は Vault へ書かれます。Vault は S3 / R2 互換のオブジェクトストレージで、サーバー側暗号化 (SSE-S3, AES-256) を有効化済み。バケットはプレーンテキストを見ることはありません — S3 が書き込み時に暗号化し、読み取り時に復号し、鍵はストレージプロバイダーが管理します。
アプリケーション層のエンベロープ暗号化 (AES-256-GCM + KMS ラップ済みエンベロープ鍵、90日ローテーション) はロードマップ上。実装されると、各 Blob は KMS マスターキーでラップされた独自のエンベロープ鍵を持ちます — 鍵のローテーションで既存 Blob を再暗号化する必要がなくなり、ストレージプロバイダーの鍵は唯一の層ではなく第二の層になります。vault_blobs の EncryptionKeyID カラムは既にスキーマに存在し、これをサポートします。
非自明な設計判断: Vault は Postgres と分離。raw プロンプトを列レベル暗号化付きの Postgres カラムに保存することを検討しました。アーキテクチャはシンプルです。問題は保持 — Postgres はオンラインクエリに最適化されており、ほとんど読まれない大きな Blob を何年も保持するようには作られていません。raw コンテンツをオブジェクトストレージに置くことで、運用データベースは小さく速く保ち、監査グレードの raw 記録は安価な長期保持向けのストアに生きます。完全な詳解は Inside the Vault を。
7. 利用と監査レコード
プロバイダーが応答した後、2つの書き込みが走ります:
- 利用レコード (Postgres): service、tenant、user、model、input トークン、output トークン、micro-USD のコスト、レイテンシ、ステータスコード。インデックス可能、
/v1/usageで照会可能。これがコストダッシュボードと予算強制を支えます。 - 監査レコード (Postgres、追記型): actor、target、action、timestamp。リクエスト、プロンプト変更、ポリシー更新、運用アクションを網羅。インシデント時にレビューアーが求めるもの。
なぜ分けるのか? アクセスパターンが違うから。利用は絶えずクエリされ (ダッシュボード、予算確認、顧客向け)、監査は稀にしかクエリされない (インシデント、コンプライアンスレビュー) が、黙って変更できない必要があります。別テーブルに分け、アクセス制御も分けることで、利用ダッシュボードのバグが監査トレイルを誤って壊すことはありません。
8. レスポンス
レスポンスは OpenAI 形状: choices[0].message.content、usage.prompt_tokens、usage.completion_tokens。SchneeAI 拡張は併記 (schneeai.budget_remaining、schneeai.vault_id、schneeai.trace_id) されるので、既存 OpenAI SDK は変更なしで動き、可観測性フックに必要なものが揃います。
ストリーミングの場合、Gateway はプロバイダーの SSE を到着順にクライアントへ流します。Vault 書き込みと監査レコードはストリームが閉じた後に走ります。
問題が起きたとき
8ホップの説明はハッピーパスです。面白い設計仕事は故障モードにあります:
- JWKS が一時的に到達不能 → Gateway は 503 を
schneeai_error: jwks_unavailable付きで返します。TTL を超えたキャッシュ鍵へはフォールバックしません。 - ルーティングポリシーが予算超過と判定 → 402 を
schneeai_error: budget_exceeded付きで。プロバイダー呼び出しなし、Vault 書き込みなし、監査レコードは拒否を記録。 - PII スキャンが critical なシークレットを検出 → 422 を
schneeai_error: pii_detected付きで、カテゴリ一覧を記載。検出自体はレスポンスボディにエコーされません (シークレットがエラー本文に現れるべきではありません)。 - プロバイダーがタイムアウト → リトライ予算を使い切った後、504 を
schneeai_error: provider_timeout付きで。タイムアウト前にストリームされた分については部分 Vault 書き込みが走ります。 - Vault 書き込み失敗 → Gateway は呼び出し元へ 200 を返します (彼らは答えを得ました) が、監査ログにインタラクションを
vault_write_failedとフラグします。オンコール worker が backoff 付きで書き込みをリトライし、継続的に失敗する場合は raw やり取りを最後の切り札として暗号化ディスクにログ記録し、ページングします。
原則: ユーザーのデータを守る方向に失敗する。当社のエラー率を守る方向ではない。vault_write_failed フラグ付きの 200 は、ダッシュボード上では黙って 200 を返して記録をドロップするより悪く見えます — しかしインシデント対応者が求めるのはそちらです。
これが買えるもの
8ホップは多いです。AI トラフィックをこれらに通す — バックエンドから直接 OpenAI を呼ぶのではなく — 理由は、各ホップがさもなければ自社で構築する制御だからです:
- エンドツーエンド暗号化付き TLS 終端: 1-2 エンジニア週
- クレームベースのサービススコープ付き JWT / JWKS 検証: 2-3 エンジニア週
- テナント毎設定付き中央化ルーティングポリシー: 3-4 エンジニア週
- 17カテゴリとポリシーアクション付き PII スキャン: 4-8 エンジニア週
- リトライとストリーミング付きプロバイダー抽象: 1-2 エンジニア週 (自社保守なら継続)
- SSE-S3 付き暗号化 raw 保持 (実装済み) + アプリ層エンベロープ暗号化 (ロードマップ): 3-6 エンジニア週 + KMS セットアップ + 法務レビュー
- 構造化利用と追記型監査: 2-4 エンジニア週
- OpenAI 互換レスポンス形状と拡張: 1-2 エンジニア週
合計: 同等品を構築するのに約 17-31 エンジニア週、加えて継続的な運用。デザインパートナー段階の SchneeAI はプロバイダー原価を 1× でパススルー (markup なし)、ai_model_pricing.markup_bps カラムは既に接続済みで、マイグレーション不要でモデル毎のマージンを導入できます。ほとんどのチームにとって、自社構築より安い。
実際に見たい方は、チュートリアル が同じ8ホップを呼び出し元の視点から、curl / Python / TypeScript のコピー実行可能コード付きで解説します。