Claude Media
Claude APIの明示的キャッシュブレークポイントの置き方

Claude APIの明示的キャッシュブレークポイントの置き方

cache_controlをどのブロックに置くとキャッシュが当たり、どこに置くと毎回外れるか。ルックバックの仕組みと、外れたときのusageとdiagnosticsによる切り分けを扱います。

cache_controlはブロック単位の位置指定

Claude APIのプロンプトキャッシュは自動キャッシュでもある程度動きます。変更頻度が異なる複数のセクションを個別にキャッシュしたいなら、個々のコンテンツブロックに cache_control を置く「明示的キャッシュブレークポイント」の出番です。たとえば、ほぼ変わらないツール定義と、日次で更新されるコンテキストの組み合わせです。

プロンプトの先頭には静的な内容(ツール定義・システム指示・コンテキスト・例示)を置き、再利用したい範囲の終端に cache_control を付けます。キャッシュのプレフィックスは tools → system → messages の順に積み上がり、上位が変われば下位もすべて作り直しになります。

自動プレフィックスチェックが何をしているか

ブレークポイントは静的コンテンツの末尾に1つ置くだけで足ります。あとはシステムが「過去のリクエストがどこまで書き込み済みか」を探します。この探索には3つの原則があります。

しくみ

ブレークポイントの探索を決める3つの原則

  1. 1

    書き込みはブレークポイントの位置でしか起きない

    cache_control を付けたブロックで、そのブロックまでのプレフィックス全体のハッシュが1件だけ書き込まれます。それより前の位置には何も書かれません。ハッシュは累積なので、ブレークポイント以前のどこか1ブロックが変わるだけで、次のリクエストのハッシュは別物になります。

  2. 2

    読み取りは、過去の書き込みを後方へ探す

    各リクエストはブレークポイント位置のハッシュを計算し、一致するエントリを探します。無ければ1ブロックずつ手前へ遡り、各位置のハッシュが既存のエントリと一致するかを見ます。探しているのは「安定して変わらない内容」ではありません。「過去に誰かが書き込んだ実績のある位置」です。

  3. 3

    探索範囲は20ブロック

    ブレークポイント自身を1つ目として、最大20位置しか遡りません。範囲内に一致が無ければ探索は止まります。次の明示的ブレークポイントがあれば、そこから再開します。

Claude APIでは、連続する tool_use ブロックはまとめて1位置、連続する tool_result ブロックもまとめて1位置と数えます。並列ツール呼び出しの多いターンが1つ入っても、前回のエントリがウィンドウの外へ押し出されにくい理由です。

成長する会話でルックバックがどう動くか

会話が伸びるたびにブロックを追加し、毎リクエストの最終ブロックに cache_control を設定する場合を考えます。

ターンごと

最終ブロックに置き続けた3ターン

  1. ターン110ブロック、ブレークポイントはブロック10

    既存のエントリはありません。ブロック10に新規書き込みです。

  2. ターン215ブロック、ブレークポイントはブロック15

    ブロック15は未登録ですが、ブロック10まで遡ると一致してヒットします。11〜15だけを新規に処理し、ブロック15に書き込みます。

  3. ターン335ブロック、ブレークポイントはブロック35

    35〜16の20位置を探しても一致がありません。ターン2のブロック15は探索範囲の1つ外で、ヒットしません。

ターン3のように会話が一気に伸びると、エントリが探索範囲の外へ押し出されます。ブロック15付近にも別のブレークポイントを置いておけば、そこに独立したルックバックウィンドウが生まれ、ターン2のエントリを拾えます。ただし、このブレークポイントは外れる前から置いておく必要があります。ルックバックは過去の書き込みしか拾えないので、外れてから追加しても、その位置に書き込みが無いからです。

よくある失敗: 毎回変わるブロックにブレークポイントを置く

静的なシステムコンテキスト(ブロック1〜5)のあとに、タイムスタンプとユーザーメッセージを含む可変ブロック(ブロック6)が続くプロンプトを例にします。

くらべる

ブレークポイントをどこに置くか

毎回外れる

ブロック6に置く

リクエスト1はブロック6に書き込みます。ハッシュにタイムスタンプが含まれるため、リクエスト2ではブロック6のハッシュが変わります。ルックバックはブロック5から1まで遡りますが、どの位置にも過去の書き込みはありません。毎回書き込み費用を払い、読み取りは一度も得られません。

2回目から当たる

ブロック5に置く

ブロック5は、リクエスト間で変わらない最後のブロックです。リクエスト1が書き込んだエントリを、リクエスト2以降が読み取れます。

ルックバックは、ブレークポイントの手前にある安定した内容を勝手にキャッシュしてはくれません。直すべき場所は、可変なブロック6に付けた cache_control です。

// before(ブロック6=可変な末尾に付けてしまう。毎回ミス)
{ "type": "text", "text": "<timestamp + user message>", "cache_control": { "type": "ephemeral" } }
 
// after(ブロック5=直前の静的な最終ブロックに付ける)
{ "type": "text", "text": "<static block 5>", "cache_control": { "type": "ephemeral" } }

置き場所の基準は1つです。共有したいリクエスト群の間でプレフィックスが同一であり続ける、最後のブロックに置きます。成長する会話では、1ターンで増えるブロックが20未満なら最終ブロックで足ります。可変な末尾を持つプロンプトでは、静的プレフィックスの終端です。

ブレークポイントを置いたのに外れるとき

置き場所が正しくても、プロンプトの側が変わればミスします。切り分けは、レスポンスの usage から始めます。

  • cache_creation_input_tokens: 今回キャッシュに書き込んだトークン数
  • cache_read_input_tokens: 今回キャッシュから読んだトークン数
  • input_tokens: 最後のブレークポイントより後ろ、つまりキャッシュ対象外のトークン数

input_tokens は入力の総量ではありません。総量は3つの合計です。書き込みも読み取りも0なら、そもそもキャッシュされていません。多くの場合、プロンプトが最小長に届いていない状態で、エラーは返りません。最小長はモデルで違い、次の表はClaude API上の値です。Amazon Bedrockでは別の値になります。

最小のキャッシュ可能長対象モデル
512トークン対象モデルClaude Fable 5.1 / Mythos 5.1 / Opus 5.5 / Opus 5 / Sonnet 5.5 / Fable 5 / Mythos 5
1,024トークン対象モデルClaude Opus 4.8 / Sonnet 5 / Sonnet 4.6 / Sonnet 4.5など
2,048トークン対象モデルClaude Mythos Preview / Opus 4.7
4,096トークン対象モデルClaude Opus 4.6 / Opus 4.5 / Haiku 4.5

読み取りが0で書き込みだけが続くなら、ミスの原因はプロンプトの変化です。次の変更は、それぞれキャッシュを無効にします。

  • ツール定義の変更は、tools・system・messagesのすべてを無効にする
  • 画像の追加・削除や tool_choice の変更は、messagesを無効にする
  • thinkingの設定や output_config.effort の変更も、messagesを無効にする
  • 一部の言語(SwiftやGoなど)がJSONへの変換でキー順をランダムにすると、tool_use ブロックの中身が毎回変わり、キャッシュが壊れる

どの部分が変わったのかを推測で追う代わりに、Claude APIにはキャッシュ診断があります。連続する2つのリクエストを比べて、プロンプトの接頭辞がどこで分岐したかを返す仕組みです。

手順

キャッシュ診断の使い方

  1. 1

    毎ターンのリクエストに diagnostics を付ける

    最初のターンは "diagnostics": {"previous_message_id": null} を渡します。診断用の指紋が保存されるのは、このオブジェクトを付けたリクエストだけだからです。

  2. 2

    2ターン目から前回のレスポンスidを渡す

    previous_message_id に直前のレスポンスの id を入れます。APIが2つのリクエストを比べ、最初に分岐した箇所を diagnostics に載せて返します。分岐が見つからなければ null です。比較がまだ実行中のときは cache_miss_reason が null になるので、結果は次のターンで確認します。

  3. 3

    type と usage を突き合わせる

    system_changed ならタイムスタンプなどの可変値がシステムプロンプトに入っています。tools_changed はツールの増減・順序・スキーマのシリアライズを疑います。messages_changed は履歴が追記でなく書き換えられています。model_changed はルーターやフォールバックでモデルが替わった状態です。比較そのものができないときは previous_message_not_found(前回のリクエストが見つからない)や unavailable(診断情報が得られない)が返ります。

diagnostics が分かるのは「リクエストが変わったか」であり、「キャッシュが当たったか」は usage の領分です。診断が null で読み取りが多ければ正常です。診断が null なのに読み取りが0または少ないなら、プロンプトは同じなのにエントリが残っていません。ターンの間隔を縮めるか、1時間TTLを検討します。診断が *_changed で読み取りが少ないなら、原因はリクエスト側の変更です。

診断には制約があります。Claude APIだけで使え、Amazon BedrockとGoogle Cloudには出ません。前回のリクエストは同じ組織・同じワークスペースで送ったものに限ります。指紋は短時間で失効するので、間隔の空いたリクエスト同士の比較には向きません。

複数ブレークポイントと自動キャッシュの使い分け

明示的ブレークポイントは最大4つまで定義でき、自動キャッシュと併用できます。公式の推奨順序は「まず自動キャッシュから」です。多くの多ターン会話は、自動キャッシュが末尾ブロックにブレークポイントを移し続けるだけで機能します。次のどれかに当てはまるときに、明示的ブレークポイントを足すか切り替えます。

  • ツール定義とコンテキストのように、変更頻度が違うセクションを個別にキャッシュしたい
  • 何がキャッシュされるかを細かく制御したい
  • 静的なプレフィックスの末尾が「キャッシュ可能な最後のブロック」と一致しない(前節の失敗例)
  • 会話が伸びて、既存のブレークポイントが直近の書き込みから20ブロック以上離れそうだ

併用の典型は、システムプロンプトに明示的な cache_control を1つ固定で置き、messages 側は自動キャッシュに追わせる形です。自動キャッシュは4つの枠のうち1つを使うので、明示的ブレークポイントを4つ使い切った状態ではトップレベルの cache_control を足せません。

Claude Codeの側から見たキャッシュの効き方は、Claude Codeの利用上限をプロンプトキャッシュがどう軽くするかで扱っています。

枠が尽きたときと、TTLがぶつかったとき

併用では、次の組み合わせが400エラーになります。

  • 明示的ブレークポイントが4つ埋まっている状態で、トップレベルの cache_control を追加した。自動キャッシュ用の枠が残っていません
  • 最後のブロックに明示的な cache_control があり、そのTTLが自動キャッシュのTTLと違う。TTLが同じなら、自動キャッシュは何もしません
  • レガシー版のAmazon Bedrock(Claude Opus 4.6以前の統合)で、トップレベルの cache_control を指定した。この統合は自動キャッシュに対応していないため、明示的ブレークポイントだけで組みます

TTLの扱いにも順序の制約があります。同じリクエストに1時間と5分を混在させるときは、1時間のエントリを5分のエントリより前に置きます。1時間を指定するには "ttl": "1h" を使い、書き込みは基本入力トークン価格の2倍です。5分は1.25倍です。

ブレークポイント自体に追加コストは無い

ブレークポイントを置くこと自体に料金はかかりません。課金されるのは次の3つです。

  • キャッシュ書き込み: 新しい内容を書き込むとき。5分TTLでベース入力トークンの1.25倍
  • キャッシュ読み取り: 読み取り時。ベース入力トークン価格の0.1倍で、Claude Opus 5.5とSonnet 5.5は0.05倍、Claude Fable 5.1とClaude Mythos 5.1は0.025倍
  • 通常の入力トークン: キャッシュされていない内容

ブレークポイントを増やしても、実際にキャッシュされて読み取られた分しか課金は変わりません。増やす判断は、コストでなく独立してキャッシュしたいセクションの数で決まります。

プリウォーミングでも同じ原則が適用される

ユーザーの実リクエストより先にキャッシュへ書き込んでおくプリウォーミングは、max_tokens: 0 で行います。ここでもブレークポイントの置き方が結果を左右します。プレースホルダーのメッセージに置くと、エントリはそのプレースホルダーに紐づき、実際のフォローアップでは二度とヒットしません。フォローアップと共有する最後の静的ブロックに置きます。

自動キャッシュは最後のブロックに置く仕組みで、そこがプレースホルダーになるため、プリウォーミングには使えません。thinkingの設定と output_config.effort も、フォローアップと同じにします。どちらもプロンプトに組み込まれるので、違う値で温めると、実トラフィックが読まないエントリを書くことになります。手順の詳細と max_tokens=1 からの移行はプロンプトキャッシュの事前ウォームアップにあります。

まとめ

ルックバックが探すのは「過去に書き込まれた位置」だけです。静的プレフィックスの終端に置けば読み取りが続き、毎回変わるブロックに置くと書き込みだけが積み上がります。外れたときは、usage の書き込みと読み取りを見て、診断の type でプロンプトのどこが動いたかを確かめます。

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