thinking設定を変えるとプロンプトキャッシュのヒット率が下がる理由
thinkingモード・effort・budget_tokensはキャッシュされるプロンプトの一部。変更すると何が無効になり、per-message effortでどこまで避けられるかを、usageの実測値つきで解説。
thinking設定を変えるとキャッシュが効かなくなる理由
cache_read_input_tokens が急に0になった。thinkingのモードやeffort、budget_tokensのどれか一つを変えただけなら、原因はほぼ確定です。thinkingの設定はキャッシュされるプロンプトのプレフィックスの一部として扱われるため、設定を変えるとそこから新しいプレフィックスが始まり、それより後ろのキャッシュは無効になります。
この現象はエラーにはなりません。リクエストは成功し、レスポンスも返ります。変わるのは usage.cache_read_input_tokens が0に落ち、cache_creation_input_tokens が跳ね上がることだけです。ログを字面で追っていると気づきにくく、コスト増を先に見つけて原因を遡ることになりがちです。
thinkingモード・effort・budget_tokensはキャッシュ鍵の一部
thinkingの設定と、実際に使われるeffortの値(省略時は既定値)は、プロンプトの中にレンダリングされます。次のどれを変えても新しいプレフィックスになります。
- thinkingモードの切り替え(
adaptiveとenabledとdisabledの間) - effortの値の変更(
low/medium/high/xhigh/max) budget_tokensの変更(手動のextended thinkingを使うモデル)
budget_tokens を使う手動モードは、Claude 4.6系で非推奨、4.7以降では400エラーで拒否されます。Claude 4.5以前でthinkingを使うなら、この手動モードが唯一の選択肢です。
影響の出方は階層で決まります。キャッシュのプレフィックスは tools、system、messages の順に作られ、ある階層の変更はその階層と後ろのすべてを無効にします。thinking設定の変更では、messagesのブレークポイントは必ず外れます。toolsとsystemのブレークポイントが外れるかどうかは、モデルが設定をどこにレンダリングするかで決まります。
usageの数字で見る無効化
手動モードの例が公式にあります。Claude Sonnet 4.6でメッセージ単位のキャッシュを使い、3回目のリクエストだけ budget_tokens を4,000から8,000に変えた実行結果です。
budget_tokensを変えた3回目のusage
1回目 cache_creation
1,370
キャッシュを新規に書き込み。cache_readは0
2回目 cache_read
1,370
同じ設定なのでヒット。creationは0
3回目 cache_creation
1,370
budget_tokens変更で再作成。cache_readは0に戻る
同じ実験をadaptiveモードで行うと、効くのはeffortです。公式のサンプルは同じ会話の2回目まで output_config.effort を揃えてヒットさせ、3回目だけ low に変えてミスさせています(Claude Opus 5.5)。
20ブロックの遡り窓切れとの見分け方
症状(cache_read_input_tokens が0になる)が同じでも、原因がthinking設定とは限りません。もう一つの定番が遡り窓です。
プロンプトキャッシュは、ブレークポイントの位置でプレフィックスのハッシュを計算し、一致するエントリが無ければ1ブロックずつ手前へ遡ります。確認するのは、ブレークポイント自身を1つ目として最大20か所です。窓の中に前のリクエストが書いたエントリが無ければ、そこで探索は止まります。
公式の例では、35ブロックまで育った会話の末尾にブレークポイントを置くと、20か所の探索で16番目までしか見ません。前のターンが書いた15番目のエントリは窓の1つ外で、ヒットしません。15番目にも2つ目のブレークポイントを置いておけば、そこから別の遡り窓が始まり、前のエントリに届きます。並列のツール呼び出しが多くても、連続する tool_use ブロックの並びと連続する tool_result ブロックの並びは、それぞれ1か所として数えられます。
キャッシュが0に落ちたときの切り分け
- 1
直前のリクエストで設定を変えたか
thinkingのモード、effortの値、
budget_tokensのどれかが前のリクエストと違えば、これが原因です。ログにeffortを残していれば突き合わせられます。 - 2
1回で戻ったか、続いているか
設定変更が原因なら、変更した回だけ
cache_creation_input_tokensが増え、設定を固定した次の回からヒットが戻ります。数回続くなら別の要因です。 - 3
会話が20ブロック以上伸びていないか
ターンごとに20ブロック以上増えると、前の書き込み位置が窓から外れます。会話の途中にもう1つブレークポイントを置き、書き込みを積んでおく構成が必要です。
- 4
ブレークポイントの手前に毎回変わる内容がないか
タイムスタンプのように毎回変わるブロックの後ろにブレークポイントを置くと、ハッシュが一致せず、書き込みだけが続きます。固定部分の末尾に置き直します。
thinkingブロックの扱いで起きる別の無効化
設定を触っていなくても、thinkingブロックの扱いでキャッシュが切れることがあります。公式の無効化表には、次の2つがあります。
- ツール結果以外のユーザー入力を挟む場合: Opus 4.5以降とSonnet 4.6以降はthinkingブロックが既定で残るためキャッシュは有効です。それより前のOpus・Sonnetと、すべてのHaikuでは、以前のthinkingブロックが外れ、それ以降のメッセージもキャッシュから落ちます。
- APIがthinkingブロックを落とした場合: 対象はFable 5.1・Mythos 5.1・Opus 5.5・Sonnet 5.5のthinkingブロックです。読めないモデルへ再送するとブロックが落ちます。その位置から先のプレフィックスが変わります。読めるモデルへ変更せずに戻したブロックなら、キャッシュは保たれます。
キャッシュを保ったままthinkingを運用する
最も確実なのは、同じ会話の中でthinkingのモードとeffortの値を固定することです。ターンごとに深さを変えたいときの選択肢は2つあります。
ターンごとに深さを変えたいときの2つの手段
プロンプトで調整
最新のユーザーメッセージに指示を足します。以前のブレークポイントは外れません。thinkingの設定自体は固定のままです。
per-message effort(ベータ)
role: "system" のメッセージでeffortを変えます。トップレベルの output_config.effort の変更と違い、キャッシュ済みのプレフィックスは一致したままです。
per-message effortは、ベータヘッダー mid-conversation-output-config-2026-07-01 が必要です。対応しているのはFable 5.1・Mythos 5.1・Opus 5.5・Opus 5・Sonnet 5.5です。それ以外のモデルは、トップレベルに新しい値を指定する形になり、キャッシュも作り直しになります。Fable 5は非対応で、output_config.effort requires a model that supports per-turn effort; this model does not という400エラーを返します。
書き方は、content を空にした role: "system" のメッセージに新しいeffortを載せ、次のユーザーターンの手前に置きます。新しい値は次のユーザーターンから効き、別のメッセージで変えるまで続きます。
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: mid-conversation-output-config-2026-07-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 4096,
"output_config": {"effort": "high"},
"messages": [
{"role": "user", "content": "Plan a migration from SQLite to PostgreSQL in three short steps."},
{"role": "assistant", "content": "1. Export the SQLite data. 2. Create the PostgreSQL schema. 3. Import the data and verify row counts."},
{"role": "system", "content": [], "output_config": {"effort": "low"}},
{"role": "user", "content": "Summarize the plan in one sentence."}
]
}'上の呼び出しは、最初のeffortを high に固定し、2ターン目だけ low に落としています。トップレベルの値を low に書き換える方法と違い、system と tools、それ以前の messages のキャッシュは無効になりません。
Sonnet 5.5には落とし穴があります。thinking: {"type": "between_tools"} では、per-message effortで現在と違う値を指定すると400エラーになります。ターンごとにeffortを変えたいなら、adaptive thinkingを使います。
プロンプトの事前ウォームアップをする構成にも同じ注意が当てはまります。ウォームアップのリクエストと本番のリクエストでthinking設定やeffortが違うと、本番が読めないエントリを書いてしまいます。
よくあるつまずき方
- ルーティング層でeffortをターンごとに自動決定している: たとえば入力の複雑さで切り替える実装では、閾値のぶれで似た入力に別のeffortが選ばれ、キャッシュを作り直すことがあります
- SDKの既定値変更に気づかず巻き込まれる: たとえばSDKの更新でthinkingやeffortの既定値が変わると、実質的な値が変わってキャッシュが切れることがあります
- A/Bテストでthinkingモードを出し分けている: たとえば同一ユーザーの会話内でテスト群によって設定が違うと、ターンをまたぐたびにキャッシュが作り直される場合があります
- マルチテナントでモデルやeffortをテナントごとに変えている: たとえば同じsystem promptでも設定が違えば別のエントリになり、書き込みコストが重複することがあります
thinkingとeffortの設計はキャッシュ設計でもある
thinkingやeffortの調整は出力品質のチューニングとして語られがちですが、キャッシュを多用する構成ではキャッシュ設計の一部です。長いsystem promptやツール定義をキャッシュして単価を下げている実装ほど、設定の頻繁な変更で失うものが大きくなります。
effortの基本的な使い分けはClaude effortとは、拡張思考のトークン予算を固定する運用はMAX_THINKING_TOKENSとはで扱っています。プロンプトキャッシュがワークフロー全体でどう効くかはClaude Codeワークフローのプロンプトキャッシュの効き方を参照してください。チャット用システムプロンプトをeffort前提の書き方へ見直す具体例はOpus 5.5のチャットで「think carefully」を外す理由で扱っています。
なおeffortを変えてもthinkingの深さが変わらないという逆方向の症状は、原因がキャッシュではなくthinkingモードの取り違えであることが多く、切り分けの観点が異なります。
まとめ
ヒット率が落ちたら、まず直前のリクエストでthinkingのモード・effort・budget_tokens が変わっていないかを見て、変わっていなければ遡り窓とthinkingブロックの扱いを疑います。ターンごとに深さを変えたい構成には、トップレベルの書き換えより、プロンプトでの調整かper-message effortが向いています。