プロンプトキャッシュの事前ウォームアップをmax_tokens=0で行う
Anthropic APIのキャッシュ事前ウォームアップが正式機能になりました。max_tokens=0を使う仕組みと、旧max_tokens=1手法との違いを解説します。
プロンプトキャッシュの事前ウォームアップとは
事前ウォームアップ(cache pre-warming)とは、ユーザーからの実リクエストが届く前に、system promptやツール定義をあらかじめプロンプトキャッシュへ書き込んでおく仕組みです。狙いは初回リクエストのキャッシュミスによる遅延をなくすことにあります。
やり方は単純です。max_tokens: 0 を指定したリクエストを送ると、APIはプロンプトをモデルに読み込ませてキャッシュを書き込んだうえで、出力を一切生成せずに即座に応答を返します。レスポンスの content は空配列、stop_reason は "max_tokens"、usage ブロックだけが埋まった状態で戻ってきます。
トークン課金の観点でも重要な変更です。出力トークンは0課金、つまりウォームアップのためだけに応答を捨てるコストが発生しません。
この仕組みが役立つのは、system promptやツール定義が大きいアプリケーションほど顕著です。プロンプト全体をモデルに読み込ませる処理はレイテンシーとして利用者に直接跳ね返るため、初回リクエストのタイミングで初めてキャッシュが書き込まれる設計のままだと、サービス起動直後やデプロイ直後にアクセスした利用者だけが遅い応答を受け取ることになります。事前ウォームアップは、この「最初にアクセスした人だけが損をする」構造を運用側の工夫であらかじめ解消しておく手段だと捉えると、どこに組み込むべきかが判断しやすくなります。
max_tokens=1ワークアラウンドから何が変わったか
max_tokens: 0 が使えるようになる前は、多くのアプリケーションが max_tokens: 1 のウォームアップコールで同じ効果を狙っていました。1トークンだけ生成させ、その出力を捨てる方法です。
この旧手法には3つの不利な点がありました。1トークン分の出力が実際に生成されるため出力トークン課金が発生する、生成された1トークンをどこにも使わず捨てるだけの無駄な処理が挟まる、そして max_tokens: 1 というリクエストの意図がコードを読む側に伝わりにくい、という点です。
max_tokens: 0 は3つとも解消します。出力が一切生成されないので捨てるべき1トークンの応答も存在せず、課金も発生しません。リクエストの意図も「出力ゼロで構わない、キャッシュだけ書き込みたい」と明確です。公式ドキュメントもこの新方式を推奨しています。
キャッシュブレークポイントの置き方を間違えると効かない
cache_control ブレークポイントは、後続リクエストと共有される最後のブロック(たいていはsystem promptかツール定義)に置きます。プレースホルダーのユーザーメッセージに置いてはいけません。そこに置くと、キャッシュエントリがプレースホルダーの内容に紐づいてしまい、後続の実リクエストがヒットしなくなります。
自動キャッシュ(automatic caching)はブレークポイントを最後のブロックへ自動配置する仕様なので、事前ウォームアップの文脈では最後のブロックがプレースホルダーのユーザーメッセージになってしまいます。したがって明示的なキャッシュブレークポイント(explicit cache breakpoints)を使う必要があります。
thinking設定や output_config.effort も後続リクエストと揃えてください。これらの値はプロンプトに描画される要素なので、ウォームアップ時と実リクエスト時で設定が違うと、実際のトラフィックが決してヒットしないキャッシュエントリを書いてしまいます。
プレースホルダーのユーザーメッセージ自体の中身は問いません。空白以外の文字を含む任意の文字列でよく、公式の例では "warmup" という文字列を使っています。この内容はモデルに読み込まれますが、応答されることはありません。
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-5",
"max_tokens": 0,
"system": [
{
"type": "text",
"text": "You are an expert software engineer...",
"cache_control": {"type": "ephemeral"}
}
],
"messages": [{"role": "user", "content": "warmup"}]
}'課金はどう発生するか
事前ウォームアップのリクエストは、プレフィックスがまだキャッシュされていない場合にキャッシュ書き込み(cache write)課金が発生します。これは通常のリクエストと同じ扱いです。応答の usage.cache_creation_input_tokens を見れば、実際に書き込みが発生したかを確認できます。
書き込み単価はモデルとTTLで決まります。Claude Opus 5・Claude Opus 4.8の場合、ベース入力単価が $5/MTokに対し、5分キャッシュの書き込みは1.25倍の$6.25/MTok、1時間キャッシュの書き込みは2倍の$10/MTokです。読み取り(キャッシュヒット)は標準入力単価の10%相当($0.50/MTok)なので、5分キャッシュは1回の読み取りで、1時間キャッシュは2回の読み取りで元が取れる計算になります。
max_tokens: 0 では出力トークンが0課金です。旧max_tokens: 1方式では1トークン分の出力課金が残っていた点が、課金面での違いになります。
書き込みが実際にどのTTLで発生したかを細かく確認したい場合は、usage.cache_creationオブジェクトを見ます。5分キャッシュと1時間キャッシュを混在させている構成では、ephemeral_5m_input_tokensとephemeral_1h_input_tokensが別々に記録されるため、事前ウォームアップが狙い通りのTTLへ書き込めているかをレスポンスだけで検算できます。cache_creation_input_tokensはこの2つの値の合計と一致します。
いつ事前ウォームアップを使うべきか
事前ウォームアップが効くのは、時間に敏感でtime-to-first-token(TTFT)を縮めたい用途に限られます。ユーザーがリクエストを送る前提でシステムを起動するタイミング、あるいは定期的な間隔でウォームアップを撃つ運用が典型例です。
キャッシュのTTLは事前ウォームアップでも変わりません。デフォルトの5分キャッシュなら、少なくとも5分に1回はウォームアップリクエストを送り続ける必要があります。ユーザーリクエストの間隔がそれより空くアプリケーションでは、1時間キャッシュ(1-hour cache duration)への切り替えを検討したほうが運用がシンプルになります。切り替え自体はcache_controlにttlフィールドを足すだけです。
逆に、バッチ処理や社内向けの低頻度ツールのようにTTFTが体験にほとんど影響しない用途では、事前ウォームアップを導入する必然性は薄くなります。導入には定期的にリクエストを送り続ける仕組みを別途用意するコストが伴うため、まず自分のアプリケーションがTTFTに敏感かどうかを切り分けてから採用を検討するのが実務的な順序です。
"cache_control": {
"type": "ephemeral",
"ttl": "1h"
}1時間キャッシュを選ぶ基準は、事前ウォームアップの文脈でも通常のキャッシュ戦略と同じです。5分より頻度が低いが1時間よりは頻繁に使われるプロンプト、レイテンシーが重要で後続リクエストが5分を超えて送られる可能性がある場合、そしてレート制限の消費を抑えたい場合に向いています。キャッシュヒットはレート制限の消費にカウントされないため、事前ウォームアップでキャッシュを維持しておくこと自体が、実トラフィックのレート制限枠を温存する副次効果を持ちます。
起動時ウォームアップとスケジュール実行の典型パターン
公式ドキュメントが示す典型的な使い方は、アプリケーション起動時に1回ウォームアップリクエストを送り、その完了を待ってから実際のユーザーリクエストを受け付け始める、というパターンです。長時間稼働するサービスでは、これに加えてTTLの間隔(5分キャッシュなら5分未満)でウォームアップリクエストを再送するスケジュール実行を組み合わせます。
起動時ウォームアップだけでは、デプロイ直後からサービスが稼働し続ける間にキャッシュが期限切れになり、結局ユーザーの初回リクエストでキャッシュミスが起きる期間ができてしまいます。スケジュール実行と組み合わせて初めて、稼働期間全体でTTFTの短縮効果を維持できます。
使えない条件を先に確認する
max_tokens: 0 のリクエストは、以下のいずれかが設定されていると invalid_request_error で拒否されます。いずれも「ゼロトークンの出力予算では実現できない出力」を暗に要求する設定だからです。
stream: true- Extended thinking(
thinking.type: "enabled") - Structured outputs(
output_config.format) tool_choiceが{"type": "tool", ...}または{"type": "any"}
Message Batchesリクエストの内側でも max_tokens: 0 は拒否されます。事前ウォームアップが狙うのはTTFTの短縮であり、バッチ処理にはそもそもTTFTという概念が当てはまりません。加えて、バッチ処理中に書き込まれたキャッシュエントリは、後続リクエストが実行される頃には期限切れになっている可能性が高いという実務上の理由もあります。
たとえばtool_choiceを{"type": "any"}に固定したリクエストは、モデルが必ずいずれかのツールを呼び出す出力を返すことを前提にしています。出力トークンを1つも生成しないmax_tokens: 0とは、要求そのものが両立しません。stream: trueも同様で、逐次的にトークンを送り返す仕組みを前提にしているため、送り返すトークンが存在しない状態とは噛み合わないのです。事前ウォームアップを既存のリクエスト処理に組み込む際は、本番用のリクエスト設定をそのまま複製するのではなく、system promptとツール定義とcache_controlだけを残したウォームアップ専用の最小構成を別途用意しておくと、こうした制約に引っかかりにくくなります。
事前ウォームアップと通常のプロンプトキャッシュはどう役割分担するか
通常のプロンプトキャッシュは「同じ内容が繰り返し送られること」を前提に、実トラフィックの2回目以降のリクエストでコストと遅延を下げる仕組みです。これに対し事前ウォームアップは、1回目のリクエストが実際に届く前にキャッシュを温めておくことで、初回応答からTTFTを短縮する狙いに特化しています。
したがって両者は排他的ではなく組み合わせて使うものです。system promptやツール定義が大きく、初回応答の遅延がユーザー体験に直結するアプリケーションほど、事前ウォームアップを起動時やスケジュール実行に組み込む価値が上がります。逆に、system promptが小さくキャッシュ書き込みのコストメリットが薄いアプリケーションでは、事前ウォームアップの運用コスト(定期的なリクエスト送信の仕組みを作る手間)のほうが上回ることもあります。
まとめ
max_tokens: 0 による事前ウォームアップは、旧max_tokens: 1方式が抱えていた「出力課金が発生する」「捨てるだけの1トークンを生成する」「意図が伝わりにくい」という3つの弱点をまとめて解消した正式な仕組みです。移行はシンプルで、既存のウォームアップコードの max_tokens を 1 から 0 に変え、キャッシュブレークポイントが実リクエストと共有されるブロックに置かれているかを確認するだけです。streamingやextended thinking、tool_choiceの制約に該当する構成では使えないため、事前ウォームアップを組み込む前に自分のリクエスト構成を照らし合わせておくと安全です。
プロンプトキャッシュの基本的な仕組みと課金体系はClaudeのプロンプトキャッシュの仕組みで扱っています。ツール定義へのcache_control配置はツール定義のcache_controlはどこに置くか、キャッシュミスの原因特定にはCache diagnosticsベータでキャッシュミスの原因を1発で特定するが役立ちます。