Claude Media
プロンプトキャッシュの事前ウォームアップをmax_tokens=0で行う

プロンプトキャッシュの事前ウォームアップを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 だけです。system promptやツール定義が大きいアプリほど、この初回の遅延は目立ちます。

max_tokens=1の時代と何が違うか

max_tokens: 0 が使えるようになる前は、max_tokens: 1 のウォームアップコールで同じ効果を狙うのが一般的でした。公式ドキュメントは新方式を推奨しています。

くらべる

ウォームアップ用リクエストの比較

移行元

max_tokens: 1(旧手法)

1トークン分の出力が生成され、出力トークンの課金が残ります。捨てるだけの応答が返り、リクエストの意図もコードから読み取りにくい方式です。

移行先

max_tokens: 0(現行)

出力は生成されず、出力トークンは0課金です。「キャッシュだけ書き込みたい」という意図がそのまま読み取れます。

ウォームアップしたのに後続がヒットしないときの原因

cache_control ブレークポイントは、後続リクエストと共有される最後のブロック(たいていはsystem promptかツール定義)に置きます。ツール定義側の置き方はツール定義のcache_controlはどこに置くかが詳しい領域です。プレースホルダーのユーザーメッセージに置いてはいけません。そこに置くと、キャッシュエントリがプレースホルダーの内容に紐づいてしまい、後続の実リクエストがヒットしなくなります。

自動キャッシュ(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"}]
  }'

usageの読み方 — 書き込めたかをレスポンスだけで確かめる

ウォームアップの成否は、レスポンスの usage で判定できます。フィールドの基本はClaudeのプロンプトキャッシュの仕組みにあり、ここではウォームアップ固有の読み方に絞ります。50,000トークンのプレフィックスを想定した、フィールド構成の抜粋を示します(数値は説明用の仮の値です)。

{
  "content": [],
  "stop_reason": "max_tokens",
  "usage": {
    "input_tokens": 12,
    "cache_creation_input_tokens": 50000,
    "cache_read_input_tokens": 0,
    "output_tokens": 0,
    "cache_creation": {
      "ephemeral_5m_input_tokens": 50000,
      "ephemeral_1h_input_tokens": 0
    }
  }
}

読み方の分岐は次のとおりです。

usage の状態意味
cache_creation_input_tokens が大きい意味プレフィックスを新規に書き込んだ。書き込み課金が発生
cache_read_input_tokens が大きい意味既存エントリに当たった。読み取り単価で課金され、エントリも更新される
どちらも0意味書き込みも読み取りも起きていない。最小キャッシュ長(Fable 5.1・Opus 5.5・Opus 5・Sonnet 5.5は512トークン、Opus 4.8は1,024、Haiku 4.5は4,096)に満たないと、エラーなしでキャッシュされずに処理される。長さを満たすなら cache_control の位置を見直す。後続がミスする原因の切り分けはCache diagnosticsベータを使う

cache_creation の内訳は、5分キャッシュと1時間キャッシュを混在させる構成で、狙ったTTLへ書けたかを検算するために使えます。2つの値の合計が cache_creation_input_tokens です。

事前ウォームアップのコストをモデル別に試算する

書き込みは通常のキャッシュ書き込み課金で、ベース入力単価に対し5分キャッシュが1.25倍、1時間キャッシュが2倍です。読み取り倍率はモデルで異なり、既定は0.1倍、Opus 5.5とSonnet 5.5は0.05倍、Fable 5.1とMythos 5.1は0.025倍です。「読み取りと更新」が同じ単価で表に載っているため、TTLの内側で再送するウォームアップは、2回目以降が読み取り単価になります。

次の4つは、Opus 5($5/MTok)で50,000トークンのプレフィックスを1回処理したときの単価です。

数字

Opus 5・50,000トークン1回あたり

  • キャッシュなし

    $0.25

    通常の入力単価

  • 5分キャッシュ書き込み

    $0.3125

    1.25倍

  • 1時間キャッシュ書き込み

    $0.50

    2倍

  • キャッシュ読み取り

    $0.025

    0.1倍

ここから、夜間や休日など実リクエストが来ない24時間を維持する費用を試算します。5分キャッシュは4分おきに360回(書き込み1回+読み取り359回)、1時間キャッシュは55分おきに27回(書き込み1回+読み取り26回)再送する前提です。

モデル読み取り倍率5分・4分おき1時間・55分おき
Fable 5.1読み取り倍率0.025倍5分・4分おき$5.111時間・55分おき$1.33
Opus 5.5読み取り倍率0.05倍5分・4分おき$3.841時間・55分おき$0.66
Opus 5読み取り倍率0.1倍5分・4分おき$9.291時間・55分おき$1.15
Sonnet 5.5読み取り倍率0.05倍5分・4分おき$1.921時間・55分おき$0.33
Haiku 4.5読み取り倍率0.1倍5分・4分おき$1.861時間・55分おき$0.23

すべてのモデルで、1時間キャッシュ+間引いた再送のほうが安くなります。読み取り倍率が低いモデルほど再送1回が安いため、差は縮みます(Fable 5.1は約3.9倍、Opus 5は約8.1倍)。一方、実リクエストが5分以内の間隔で継続して届くなら、実トラフィック自体がキャッシュを更新するため、この維持費は発生しません。試算は公式の料金表の単価から計算した値で、Batch API割引やUS限定推論(1.1倍)などの倍率は含みません。金額は小数第3位を四捨五入しており、実際の請求は usage の値で確認してください。

同じ試算で、max_tokens: 1 時代に残っていた出力課金も消えます。ウォームアップ1回あたり出力1トークン分は$25/MTokなら$0.000025で、360回続けても1日約$0.009です。額は小さく、効くのは意図の明確さとレスポンス処理の単純さです。

運用フロー — 起動時に1回、稼働中はTTLの内側で再送

手順

ウォームアップの組み込み順

  1. 1

    起動時に1回送る

    サービスが起動したら、実リクエストを受け付ける前にウォームアップを1回送り、完了を待ちます。ここで cache_creation_input_tokens が埋まることを確認します。

  2. 2

    TTLより短い間隔で再送する

    起動時だけでは、稼働中にキャッシュが期限切れになり、次の初回リクエストがまたミスします。スケジュール実行でTTLの内側(5分キャッシュなら5分未満)に再送すると、稼働期間全体でTTFTの短縮を維持できます。

  3. 3

    利用間隔に合わせてTTLを選ぶ

    実リクエストの間隔が5分を超えるなら、cache_control に ttl を足して1時間キャッシュへ切り替えます。

"cache_control": {
  "type": "ephemeral",
  "ttl": "1h"
}

事前ウォームアップが効くのは、time-to-first-token(TTFT)を縮めたい用途です。バッチ処理や低頻度の社内ツールのようにTTFTが体験に影響しない用途では、定期送信の仕組みを作るコストが上回ります。また、キャッシュヒットはレート制限の消費にカウントされないため、キャッシュを維持しておくと実トラフィックのレート制限枠を温存できる副次効果もあります。

max_tokens=0が拒否される構成

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も同様で、逐次的にトークンを送り返す仕組みを前提にしているため、送り返すトークンが存在しない状態とは噛み合わないのです。事前ウォームアップを既存のリクエスト処理に組み込むときは、本番の設定から拒否対象(stream、tool_choice の tool / any、output_config.format)だけを外した専用リクエストを用意します。system prompt、ツール定義、cache_control の位置、thinking設定や output_config.effort のようにプロンプトの描画に効く値は本番と揃えます。拒否対象と揃えるべき設定が重なる構成(extended thinkingを本番で使う場合など)は、ウォームアップでヒットさせられない可能性があります。

まとめ

max_tokens: 0 は、旧方式の出力課金と捨てる応答をなくした正式なウォームアップ手段です。移行は max_tokens を 1 から 0 に変え、ブレークポイントが後続と共有されるブロックにあるかを確かめるだけで済みます。制約に当たる設定だけを外したウォームアップ専用リクエストを用意し、usage で書き込みを確認する、という流れで運用に載ります。

この記事を共有:XはてブLinkedIn