AI Gateway を活用したプロンプト管理とコスト最適化
Cloudflare AI GatewayによるLLM APIの統合管理・キャッシュ・コスト可視化の実践。複数AIプロバイダーを束ねてプロンプト管理とAPIコストを最適化する設計パターン。
はじめに:LLM を本番投入したときに直面するコスト・可観測性の問題
LLM API を本番環境に組み込んだとき、最初に直面するのは「動く」より先に「いくらかかるかわからない」問題だ。
LLM 本番運用でよくある問題:
コストの問題:
・同じプロンプトを1日100回 LLM に送り続けている
・どのエンドポイントが費用の大半を占めているかわからない
・トークン使用量の急増に気づくのが請求書が来てから
信頼性の問題:
・OpenAI がダウンしたら全機能が止まる
・プロバイダー切り替えのたびに全コードを修正する
・レートリミット超過でユーザーにエラーが返る
観測性の問題:
・どのリクエストが失敗しているかログがない
・プロンプトのどのバリエーションが良い応答を返すか計測できない
・ユーザーフィードバックを応答ログに紐付けられない
これらは「LLM 自体の問題」ではなく、LLM API と自前のアプリケーションの間に管理レイヤーがないことによる問題だ。Cloudflare AI Gateway はそのレイヤーを担う。
Part 1:AI Gateway の位置づけと設計上の役割
AI Gateway はリバースプロキシとして、アプリケーションと LLM プロバイダーの間に入る。
AI Gateway の位置:
Without AI Gateway:
┌──────────┐ ┌───────────┐
│ Workers │ ────────→ │ OpenAI │
│ (Hono) │ │ Gemini │
└──────────┘ │ Workers AI│
└───────────┘
問題: プロバイダー直結。可視化なし。フォールバックなし。
With AI Gateway:
┌──────────┐ ┌─────────────────┐ ┌───────────┐
│ Workers │ → │ AI Gateway │ → │ OpenAI │
│ (Hono) │ │ ・キャッシュ │ │ Gemini │
└──────────┘ │ ・レートリミット │ │ Workers AI│
│ ・フォールバック │ └───────────┘
│ ・ログ/分析 │
│ ・コスト可視化 │
└─────────────────┘
利点: 1行のエンドポイント変更で全機能が有効になる
AI Gateway が提供するのは4つの機能だ。
| 機能 | 効果 |
|---|---|
| キャッシュ | 同一プロンプトへのレスポンスを再利用。LLM への課金リクエストを削減 |
| レートリミット | 1ユーザーあたりのリクエスト数を制限。暴走コストを防止 |
| フォールバック | プロバイダー障害時に自動で別モデルへ切り替え |
| ログ・分析 | リクエストごとのトークン数・コスト・レイテンシをダッシュボードで可視化 |
コアの機能(ログ・キャッシュ・レートリミット)は無料で使える。課金は Workers のリクエスト数に乗る形(月1,000万リクまで $5/月)。
Part 2:セットアップと基本的な接続
接続方法は2パターン
AI Gateway への接続には「URL 置換」と「Worker Binding」の2つのアプローチがある。
パターン①:URL 置換(最速・プロバイダー非依存)
既存コードの baseURL を AI Gateway のエンドポイントに変えるだけで動く。
// Before: OpenAI に直接接続
import OpenAI from 'openai'
const client = new OpenAI({
apiKey: env.OPENAI_API_KEY,
})
// After: AI Gateway 経由に変更(1行変更)
import OpenAI from 'openai'
const client = new OpenAI({
apiKey: env.OPENAI_API_KEY,
baseURL: `https://gateway.ai.cloudflare.com/v1/${ACCOUNT_ID}/${GATEWAY_ID}/openai`,
// ↑ この1行を追加するだけで、キャッシュ・ログ・レートリミットが有効になる
})
// 使い方は変わらない
const response = await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
})
パターン②:Worker Binding(型安全・Cloudflare 統合)
wrangler.jsonc に AI バインディングを追加し、ネイティブ API を使う。
// wrangler.jsonc
{
"name": "my-ai-app",
"ai": {
"binding": "AI"
}
}
// Worker Binding を使った接続
const gateway = env.AI.gateway('my-gateway')
// gateway.getUrl() で動的に接続先 URL を取得(Vercel AI SDK 等と連携しやすい)
const baseURL = await gateway.getUrl('openai')
const client = new OpenAI({
apiKey: env.OPENAI_API_KEY,
baseURL,
})
Worker Binding は patchLog / getLog といったログ操作 API にアクセスできる点が URL 置換との決定的な違いだ(後述)。
Part 3:キャッシュ設計でコストを削減する
キャッシュの仕組み
AI Gateway のキャッシュは、リクエストのハッシュ(モデル + プロンプト + パラメータ)をキーに、レスポンスを Cloudflare の CDN にキャッシュする。同一リクエストへのキャッシュヒット時は LLM プロバイダーへの課金が発生しない。
// キャッシュを有効にするリクエスト設定
const response = await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
}, {
// AI Gateway のキャッシュ設定(リクエストオプションで渡す)
headers: {
'cf-aig-cache-ttl': '3600', // キャッシュ有効期限: 1時間(秒)
// 'cf-aig-skip-cache': 'true' // キャッシュをスキップしたいときはこちら
}
})
どんなユースケースでキャッシュが効くか
キャッシュ効果が高いユースケース:
✅ FAQ 応答(同じ質問が繰り返されやすい)
✅ コンテンツ分類・タグ付け(同じテキストを複数ユーザーが送る)
✅ テンプレートベースの文書生成(入力パターンが限定的)
✅ 開発・テスト中のプロンプト実行(同じテストケースを何度も実行)
キャッシュ効果が低いユースケース:
❌ ユーザー固有のコンテキストを含む会話(毎回ユニーク)
❌ temperature > 0 のランダム性が必要なケース
❌ リアルタイムデータを参照する応答
キャッシュヒット率はダッシュボードで確認できる。「キャッシュヒット率が低いにもかかわらず同じプロンプトが多い」場合は、プロンプトのノーマライズ(前後の空白除去・大文字小文字統一)でキャッシュを効かせる余地がある。
Part 4:フォールバックで可用性を設計する
Universal Endpoint によるフォールバック
AI Gateway の Universal Endpoint は、プロバイダーのリストを配列で渡し、上から順に試みるフォールバック設計を提供する。
// Hono ルートでのフォールバック実装
app.post('/api/chat', async (c) => {
const { message } = await c.req.json()
// Universal Endpoint に複数プロバイダーの順序を渡す
const response = await fetch(
`https://gateway.ai.cloudflare.com/v1/${c.env.CF_ACCOUNT_ID}/${c.env.AI_GATEWAY_ID}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify([
{
// 第1候補: Workers AI(コスト安・Cloudflare ネイティブ)
provider: 'workers-ai',
endpoint: '@cf/meta/llama-3.3-70b-instruct-fp8-fast',
headers: {
Authorization: `Bearer ${c.env.CF_API_TOKEN}`,
},
query: {
messages: [{ role: 'user', content: message }],
},
},
{
// 第2候補: OpenAI(Workers AI が失敗したときの保険)
provider: 'openai',
endpoint: 'chat/completions',
headers: {
Authorization: `Bearer ${c.env.OPENAI_API_KEY}`,
},
query: {
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: message }],
},
},
]),
}
)
// cf-aig-step ヘッダーで「どのプロバイダーが応答したか」を確認できる
const usedStep = response.headers.get('cf-aig-step')
// "0" = Workers AI, "1" = OpenAI にフォールバック
const data = await response.json()
return c.json({ ...data, _provider_step: usedStep })
})
フォールバック戦略の設計
プロバイダー選択の優先順位設計:
第1候補: Cloudflare Workers AI
理由: Cloudflare アカウントで統合管理、Workers Paid の枠内で動く
制限: 利用可能モデルが限定的(Llama 3.3、Mistral 等)
第2候補: 専門モデル(ユースケース別)
コード生成 → Claude 3.5 Sonnet(Anthropic)
文書分析 → Gemini 1.5 Pro(Google)
汎用会話 → gpt-4o-mini(OpenAI)
第3候補: コスト重視のフォールバック
gpt-4o → gpt-4o-mini への自動降格
(品質より可用性を優先するとき)
Part 5:ログ・観測性の活用
patchLog でフィードバックを記録する
Worker Binding を使うと、ユーザーのフィードバックをリクエストログに後から付与できる。プロンプトの品質改善に直接使えるデータになる。
// Hono での実装例:応答 + ログ ID をフロントに返す
app.post('/api/chat', async (c) => {
const { message } = await c.req.json()
const gateway = c.env.AI.gateway('my-gateway')
// AI リクエストを実行
const response = await gateway.run({
provider: 'openai',
endpoint: 'chat/completions',
headers: { Authorization: `Bearer ${c.env.OPENAI_API_KEY}` },
query: {
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: message }],
},
})
// ログ ID を取得(後でフィードバックを紐付けるため)
const logId = c.env.AI.aiGatewayLogId
const data = await response.json()
return c.json({ ...data, _log_id: logId })
})
// フィードバックエンドポイント(👍/👎 ボタン押下時に呼ぶ)
app.post('/api/feedback', async (c) => {
const { logId, rating } = await c.req.json()
const gateway = c.env.AI.gateway('my-gateway')
// ログに評価とメタデータを付与
await gateway.patchLog(logId, {
feedback: rating === 'good' ? 1 : -1, // +1 = 良い、-1 = 悪い
score: rating === 'good' ? 100 : 0,
metadata: {
rated_at: new Date().toISOString(),
// ユーザー ID等を追加できる(PII に注意)
},
})
return c.json({ ok: true })
})
ダッシュボードで何を見るか
AI Gateway のダッシュボードでは以下の指標が確認できる。
観測すべき主要指標:
コスト管理:
・推定コスト(トークン数から計算)の時系列グラフ
・エンドポイント別・モデル別のコスト内訳
・キャッシュヒット率(ここが低いとコスト最適化の余地あり)
パフォーマンス:
・P50/P95/P99 レイテンシ(モデル別)
・フォールバック発生率(Workers AI → OpenAI の切り替え頻度)
・エラー率(4xx/5xx の割合)
品質:
・フィードバックスコアの分布(patchLog で記録したもの)
・feedback = -1 のリクエストのプロンプトパターン
Part 6:Hono + AI Gateway の実装パターン
レートリミットと認証を組み合わせた本番設計
// src/routes/ai-chat.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
type Bindings = {
AI: Ai
KV: KVNamespace
OPENAI_API_KEY: string
CF_ACCOUNT_ID: string
AI_GATEWAY_ID: string
}
export const aiChatRouter = new Hono<{ Bindings: Bindings }>()
const chatSchema = z.object({
message: z.string().min(1).max(4000),
session_id: z.string().optional(),
})
aiChatRouter.post(
'/',
zValidator('json', chatSchema),
async (c) => {
const { message, session_id } = c.req.valid('json')
// ① ユーザーごとのレートリミット(KV ベース)
const userId = c.req.header('X-User-ID') ?? 'anonymous'
const rateLimitKey = `ratelimit:ai:${userId}`
const currentCount = parseInt(await c.env.KV.get(rateLimitKey) ?? '0')
if (currentCount >= 20) { // 1分間に20リクエストまで
return c.json({ error: 'Rate limit exceeded. Try again in a minute.' }, 429)
}
await c.env.KV.put(rateLimitKey, String(currentCount + 1), { expirationTtl: 60 })
// ② セッション履歴の取得(KV から)
const historyKey = `chat:history:${session_id ?? userId}`
const history: { role: string; content: string }[] =
JSON.parse(await c.env.KV.get(historyKey) ?? '[]')
const messages = [
{ role: 'system', content: 'あなたは親切なアシスタントです。' },
...history.slice(-10), // 直近10ターンのコンテキスト
{ role: 'user', content: message },
]
// ③ AI Gateway 経由でリクエスト(フォールバック付き)
const gateway = c.env.AI.gateway(c.env.AI_GATEWAY_ID)
let response: Response
try {
response = await gateway.run({
provider: 'openai',
endpoint: 'chat/completions',
headers: {
Authorization: `Bearer ${c.env.OPENAI_API_KEY}`,
'cf-aig-cache-ttl': '0', // 会話はキャッシュ不要
},
query: {
model: 'gpt-4o-mini',
messages,
max_tokens: 1000,
temperature: 0.7,
},
})
} catch (err) {
return c.json({ error: 'AI service unavailable' }, 503)
}
const data = await response.json() as any
const reply = data.choices?.[0]?.message?.content ?? ''
const logId = c.env.AI.aiGatewayLogId
// ④ 履歴を更新して保存(TTL: 1時間)
const updatedHistory = [
...history,
{ role: 'user', content: message },
{ role: 'assistant', content: reply },
]
await c.env.KV.put(historyKey, JSON.stringify(updatedHistory), {
expirationTtl: 3600,
})
return c.json({
reply,
log_id: logId, // フロントがフィードバック送信に使う
session_id: session_id ?? userId,
})
}
)
Part 7:コスト最適化の実践チェックリスト
AI Gateway のログとダッシュボードを参照しながら実施できる最適化をまとめる。
コスト削減のアクションリスト:
① キャッシュヒット率を上げる
→ 同じプロンプトが繰り返されるエンドポイントに TTL を設定
→ ユーザー入力をノーマライズ(trim・lowercase)してからリクエスト
② モデルの使い分けを見直す
→ 分類・要約タスクは gpt-4o → gpt-4o-mini に降格
→ コード生成以外では Claude Opus → Claude Haiku に降格
→ Workers AI(Llama)で代替できるタスクは移行
③ トークン数を削減する
→ システムプロンプトは簡潔に(1,000 トークン以下を目標)
→ 会話履歴は直近 N ターンのみ保持(古いコンテキストはカット)
→ max_tokens を用途に応じて制限
④ フォールバック先のコストを考慮する
→ Workers AI → gpt-4o-mini の順番(高品質だが高コストを後回しに)
→ フォールバック発生率をモニタリングして根本原因を修正
⑤ レートリミットで暴走を防ぐ
→ IP ベース・ユーザー ID ベースの二重リミット
→ AI Gateway のレートリミット設定 + KV での独自制限
Conclusion:AI Gateway は「インフラとしての LLM 管理」の出発点
LLM を「機能として使う」フェーズから「インフラとして管理する」フェーズへの移行で、AI Gateway は最小限の変更で最大の可観測性を得られる手段だ。
| AI Gateway なし | AI Gateway あり | |
|---|---|---|
| コスト把握 | 月の請求書で把握 | リアルタイムでトークン単価×量を可視化 |
| プロバイダー障害 | 全機能停止 | 自動フォールバックで継続稼働 |
| 同一プロンプトの再計算 | 毎回 LLM へ課金 | キャッシュヒットでコストゼロ |
| デバッグ | ログなし・再現困難 | ログ ID でリクエスト単位の追跡 |
| 品質改善 | ユーザー反応を計測できない | patchLog でフィードバックを記録 |
重要なのは、AI Gateway はコードを大きく書き換えずに導入できる点だ。baseURL の1行変更から始めて、フォールバックや patchLog は必要になった時点で追加すれば良い。漸進的に導入できるレイヤー設計こそが、このアーキテクチャの実用的な価値だ。
この記事をシェア