AI設定(2/2)
API キー・コスト。
OpenAI API の安全な組み込み
最小安全構成
典型的な流れは client -> backend -> OpenAI API -> backend -> client。通常は 信頼できないフロントエンドから OpenAI API を直接呼ばない。APIキー露出の問題だけでなく、レート制限、監査ログ、prompt差し替え、レスポンス検証、リトライ/タイムアウト制御をサーバ側に集約できなくなるため。
# .env.example
OPENAI_API_KEY=your-openai-api-key
OPENAI_MODEL=your-openai-model
OPENAI_TIMEOUT_MS=15000
OPENAI_MAX_RETRIES=2
- 秘密情報:
OPENAI_API_KEYはサーバ側だけで読む。ローカルは.env、本番は Secret Manager / CI Secret を使い、.env.exampleにはダミー値だけ置く - 入力境界: client から受ける値は backend で認可・バリデーションし、必要なら個人情報や機密文字列をマスクしてから OpenAI に渡す
- 出力境界: 返却前に JSON schema / 型 / 禁止語 / 参照ID などを検証し、想定外の出力は fallback か再試行に倒す
運用と段階導入
- Prompt / version 管理: system prompt、few-shot、出力schema、使用モデルを識別可能にし、変更時はバージョンを上げて追跡できるようにする
- ログ / redaction: 入出力全文を無制限保存しない。
trace_id、model、token使用量、遅延、成功/失敗理由を残し、本文は必要最小限 + redaction 前提にする - 保護: レート制限、タイムアウト、指数バックオフ付きリトライを先に入れる。障害時の一時停止条件も決めておく
- 段階導入: まずは1機能・内部ユーザー限定で開始し、オフライン評価と実運用ログを見ながら公開範囲を広げる。
timeout、malformed output、rate limitの失敗系テストを先に足す - docs / ADR / testing との接続: 機能単位では
docs/<feature>/04_tech_decisions.mdにユースケース、モデル、同期/非同期、出力形式、コスト上限を書く。全体方針になるなら ADR 化し、05_test_plan.mdと06_logging.mdに評価観点と運用ルールを残す
NOTE: 既存プロジェクトでは、まず「LLMを使わない実装」で足りない理由を明文化してから小さく入れる。導入理由、失敗時のfallback、人手確認が必要な境界が曖昧なら先に設計を固める。
プロンプトキャッシュのプリウォーム(Claude API Pre-warming)
公式: Prompt caching — Pre-warming the cache。共有 system / ツール定義を事前キャッシュし、初回対話のキャッシュミス遅延を避ける。
仕組み(max_tokens: 0)
max_tokens: 0のリクエストではプレフィルまで実行され、cache_controlのブレークポイントでキャッシュが書き込まれたうえで出力は生成されずに即返る。レスポンスはcontent: []、stop_reason: "max_tokens"、usageは通常どおり。cache_controlは、本番リクエストと共有される末尾ブロック(多くの場合は system やツール定義)に置く。プレースホルダ用の user メッセージ(例:"warmup")にブレークポイントを置くと、キャッシュキーがズレて本番がヒットしない。プリウォームでは明示的な cache breakpoint を使う(自動キャッシュは最終ブロックにブレークポイントが付くため、プレースホルダが最後だと不適切)。プレースホルダの本文は空白以外なら任意で、prefill では読まれるが回答はされない。- プレフィックスが未キャッシュなら通常と同様のキャッシュ書き込み課金。確認は
usage.cache_creation_input_tokens等。出力トークンは 0 で課金されない。
運用パターンと TTL
- アプリ起動時や定期ジョブでプリウォームし、完了後に本番の
messages.createを送る。 - デフォルトの 5 分 TTL では、温め続けるなら少なくとも 5 分ごとにプリウォームを送り直す。ユーザー間隔が長い場合は公式の 1 時間キャッシュ(同一ドキュメント内の別節)を検討。
max_tokens: 0 が拒否される条件(公式どおり)
stream: true、Extended thinking(thinking.type: "enabled")、Structured outputs(output_config.format)、tool_choiceが{"type":"tool",...}または{"type":"any"}。- Message Batches 内のリクエストでは
max_tokens: 0は拒否される(バッチは TTFT 対象外で、書き込んだキャッシュも追随リクエストまでに失効しやすい、という旨が公式に記載)。
過去の max_tokens: 1 によるウォームアップは、出力が発生しない max_tokens: 0 が推奨とされている(意図が明確で出力トークン課金もない)。
// 共有 system を先にキャッシュ(本番リクエストと同一の system ブロック構成に合わせる)
const prewarm = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 0,
system: [
{ type: "text", text: SYSTEM_PROMPT_TEXT, cache_control: { type: "ephemeral" } },
],
messages: [{ role: "user", content: "warmup" }],
});
// prewarm.stop_reason === "max_tokens" / prewarm.content は []