Claude Media
thinking設定を変えるとプロンプトキャッシュのヒット率が下がる理由

thinking設定を変えるとプロンプトキャッシュのヒット率が下がる理由

thinkingモード・effort・budget_tokensはキャッシュされるプロンプトの一部。どれか一つを変えるだけでmessage/tool/system-promptの各ブレークポイントが無効化される仕組みを解説。

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はキャッシュ鍵の一部

Claude APIのプロンプトキャッシュは、リクエストの先頭から一致する部分を「プレフィックス」として扱い、そこまでを丸ごとキャッシュします。thinkingの設定はこのプレフィックスのどこかにレンダリングされているため、次の3つのうちどれを変えても新しいプレフィックスになります。

  • thinkingモードの切り替え(adaptiveenableddisabled)
  • effortの値の変更(low / medium / high / xhigh / max、または既定値からの変更)
  • budget_tokensの変更(extended thinkingを使う旧世代モデル・Opus 4.5系)

公式ドキュメントは「モデルがどこに設定をレンダリングするかによって、message・tool・system-promptの各キャッシュブレークポイントが無効化されうる」と説明しています。つまり影響範囲はmessageブロックだけに留まりません。長大なsystem promptやツール定義にcache_controlを打っていても、thinkingの設定変更がその手前に位置していれば連鎖的に巻き込まれます。

キャッシュのハッシュがどう壊れるかを仕組みから見る

プロンプトキャッシュは、ブレークポイントに指定したブロックまでの内容を累積的にハッシュ化して1つのキャッシュエントリを書き込みます。次のリクエストで同じ位置のハッシュが一致すればヒット、一致しなければ最大20ブロック分だけ手前を遡って一致するエントリを探し、それも無ければキャッシュミスとして扱われます。

このハッシュはブレークポイント以前のどこか1ブロックでも変われば別物になります。キャッシュのプレフィックスはtoolssystemmessagesという階層で作られ、ある階層での変更はその階層と、それより後ろの階層すべてを無効化します。thinkingのモード・effort・budget_tokensがこの階層のどこにレンダリングされるかはモデル実装依存であり、モデルによってはtools・systemより手前でレンダリングされるため、そこより後ろにあるtools・messagesすべてのハッシュが連鎖的に変わることがあります。これが「thinkingの設定を1つ変えただけなのに、system-promptのキャッシュまで巻き込まれる」ケースが起こる理由です。

20ブロックの遡り窓も関係します。会話が育つにつれてブレークポイントを毎ターン末尾に置き直す運用では、直前ターンの書き込み位置が窓の外に出てしまうとそもそもヒットしません。thinkingの設定変更による無効化と、この遡り窓切れによる無効化は別の原因ですが、症状(cache_read_input_tokens が0になる)は同じなので、まず「直前に設定を変えていないか」を確認してから遡り窓の設計を疑う順序が効率的です。

どのブレークポイントが壊れるか

キャッシュブレークポイントは通常、system prompt・tools定義・会話履歴(messages)の3か所に置かれます。thinkingの設定変更が壊すのは、この3か所のうち「設定がレンダリングされる位置より後ろ」のすべてです。

変更した設定影響を受けるブレークポイント
thinkingモード(adaptive/enabled/disabled)影響を受けるブレークポイントmessage分は必ず無効化。tool・system-prompt分もモデルによって無効化
effortの値影響を受けるブレークポイントmessage分は必ず無効化。tool・system-prompt分はモデルのレンダリング位置次第
budget_tokens影響を受けるブレークポイントmessage分を含め、設定より後ろのブレークポイントが無効化

実務上は「thinkingの設定が変わった回だけキャッシュ書き込みコストを払い、次のリクエストから再びヒットする」という挙動になります。1回のキャッシュ切れなら許容範囲でも、セッション内でeffortを頻繁に切り替える実装(例: 簡単なターンはeffort低め、複雑なターンだけxhighに上げる)は、切り替えるたびにキャッシュを作り直すことになり、狙った節約効果が相殺されます。

キャッシュを保ったままthinkingを運用する

同じ会話(同じキャッシュプレフィックスを共有したい一連のリクエスト)の中では、thinkingのモードとeffortの値を固定するのが最も確実な対処です。ターンごとにeffortを変える設計にしたい場合は、キャッシュ書き込みコストと再ヒットまでの往復を天秤にかけて、変更の頻度を絞り込みます。

ただし「ルーティング層でeffortをターンごとに自動決定している」ようなケースでは、固定も頻度を絞ることも要件に合わないことがあります。この場合に使えるのが、対応モデルで提供されているper-message effort(ベータ)です。トップレベルのoutput_config.effortを書き換える代わりに、role: "system"のメッセージをmessagesの末尾に追加してそこにeffortの変更を載せると、そのターンだけeffortを変えつつ、systemフィールドやtools、それ以前のmessagesのキャッシュ済みプレフィックスは無効化されません。トップレベルのoutput_config.effortを直接変更した場合との違いはここで、通常の変更はmessage分のキャッシュを必ず巻き込みますが、この方式は変更自体をmessages内の新しいブロックとして追加するため、既存のプレフィックスを書き換えずに済みます。対応モデルや呼び出し方の詳細は一次ソースを確認してください。

curl https://api.anthropic.com/v1/messages \
    --header "x-api-key: $ANTHROPIC_API_KEY" \
    --header "anthropic-version: 2023-06-01" \
    --header "content-type: application/json" \
    --data '{
        "model": "claude-opus-5",
        "max_tokens": 4096,
        "system": [{"type": "text", "text": "...", "cache_control": {"type": "ephemeral"}}],
        "thinking": {"type": "adaptive"},
        "output_config": {"effort": "high"},
        "messages": [{"role": "user", "content": "..."}]
    }'

上の例のように thinkingoutput_config.effort を固定値で送り続ける限り、system のcache_controlは有効なままです。effortを動的に決める実装では、その値をログに残しておくと、キャッシュヒット率が落ちた回とeffort変更のタイミングを突き合わせて確認できます。

usage統計で発生タイミングを特定する

レスポンスの usage フィールドには cache_creation_input_tokenscache_read_input_tokens が含まれます。thinking設定の変更が原因であれば、変更した回だけ cache_creation_input_tokens が急増して cache_read_input_tokens がゼロに落ち、次のリクエストからは(設定を変えていなければ)再び通常のヒット率へ戻ります。この2つの値を時系列でログに残しておけば、「いつ・どのリクエストで」無効化が起きたかを、実際にthinkingの設定を変更したタイミングと突き合わせて確認できます。逆に無効化が数回にわたって続く場合は、thinking設定ではなく20ブロックの遡り窓切れなど別要因を疑う判断材料になります。

よくあるつまずき方

  • ルーティング層でeffortをターンごとに自動決定している: ユーザー入力の複雑さを判定してeffortを動的に切り替える実装で、判定ロジックの閾値が微妙にぶれて同じような入力でも別のeffort値が選ばれ、意図せずキャッシュを毎回作り直している
  • SDKの既定値変更に気づかず巻き込まれる: SDKやラッパーライブラリのバージョンアップでthinkingやeffortの既定値が変わり、明示的に指定していたつもりの値が実質的に変化してキャッシュが切れる
  • A/Bテストでthinkingモードを出し分けている: 同一ユーザーの会話内でテスト群によってthinking設定が異なると、ターンをまたぐたびにキャッシュが作り直される
  • マルチテナントでモデルやeffortをテナントごとに変えている: テナントAとテナントBで同じsystem promptを共有していても、thinking設定が違えば別々のキャッシュエントリになり、共有できるはずのキャッシュ書き込みコストが重複する

いずれも「thinkingの設定が変わった」こと自体は意図的でも、その変更がキャッシュに波及する範囲を見落としているのが共通点です。

thinkingとeffortの設計はキャッシュ設計でもある

thinkingやeffortの調整はモデルの出力品質を変えるチューニングとして語られがちですが、プロンプトキャッシュを多用する構成ではキャッシュ設計の一部でもあります。長いsystem promptやツール定義をキャッシュして単価を下げている実装ほど、thinking設定の頻繁な変更による無効化の影響は相対的に大きくなります。effortを動的に切り替える設計自体をやめる必要はありませんが、切り替えのたびにキャッシュが作り直される代償を織り込んだうえで頻度を決めるべきです。

effortの基本的な使い分けはClaude effortとは、拡張思考のトークン予算を固定する運用はMAX_THINKING_TOKENSとはで扱っています。プロンプトキャッシュがワークフロー全体でどう効くかはClaude Codeワークフローのプロンプトキャッシュの効き方を参照してください。

なおeffortを変えてもthinkingの深さが変わらないという逆方向の症状に遭遇した場合は、原因はキャッシュではなくthinkingモードの取り違えであることが多く、切り分けの観点が異なります。

まとめ

thinkingモード・effort・budget_tokensの変更はキャッシュされるプロンプトのプレフィックスを書き換える操作です。message分のブレークポイントは必ず無効化され、tool・system-prompt分もモデルのレンダリング位置次第で巻き込まれます。既定値と同じ値を明示するだけなら影響はありません。長いプロンプトをキャッシュに依存させている実装では、同一会話内でthinkingの設定を固定するか、変更の頻度とキャッシュ書き込みコストを見比べてから設計するのが実務的な対処です。

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