アーキテクチャ

SchneeAI Gateway の中身

すべてのチャット補完は同じ8ホップを通ります — 認証、ルーティング、スキャン、プロバイダー呼び出し、暗号化、記録、監査、返却。各ホップが何をするか、なぜそう設計したか。

2026-07-0915 分読了

世の「AI Gateway」ページの多くは、アイデアを1段落で説明し、アーキテクチャを箱と矢印の図1枚で済ませます。この記事はその逆です — アイデアは1段落、以降はリクエストパスの全ホップをプレーンテキストで、背後にある設計判断とともに。監査グレードの制御を必要とするワークロードで SchneeAI を評価しているなら、ここに書かれているものがあなたが買うものです。

概要と図は セキュリティページ に。本稿はその詳細版です。

8つのホップ

お客様のバックエンドが POST /v1/chat/completions を呼ぶと、リクエストは次の順序で Gateway を通ります:

  1. TLS 終端 — リクエストはクラスタイングレスで TLS 1.3 (レガシークライアント向けに forward secrecy 付き TLS 1.2) で受信。
  2. Bearer JWT 検証 — Ory Hydra の JWKS を取得、署名を検証、クレームを抽出。
  3. ルーティングポリシー適用 — モデルエイリアスを解決、予算を確認、PII ポリシーを読み込み。
  4. PII / シークレットスキャン — 17カテゴリ、リクエストが境界を出る前に。
  5. LiteLLM 経由でプロバイダー呼び出し — モデルエイリアスをプロバイダーへ、リトライ予算を適用。
  6. raw やり取りを Vault へ書き込み — S3 / R2 のサーバー側暗号化 (SSE-S3, AES-256)、アプリ層エンベロープ暗号化はロードマップ。
  7. 構造化利用と監査レコードを書き込み — Postgres、追記型監査テーブル。
  8. レスポンス返却 — 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_idtenant_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_blobsEncryptionKeyID カラムは既にスキーマに存在し、これをサポートします。

非自明な設計判断: 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.contentusage.prompt_tokensusage.completion_tokens。SchneeAI 拡張は併記 (schneeai.budget_remainingschneeai.vault_idschneeai.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 のコピー実行可能コード付きで解説します。