0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

途中でツールを足すとClaudeのプロンプトキャッシュが全部飛ぶ問題と回避策

0
Posted at

エージェントを1時間走らせている最中に、「あ、このツールも渡しておけばよかった」と気づく。tools 配列に定義を1個足して次のリクエストを投げる。すると、それまで効いていたプロンプトキャッシュが丸ごと吹き飛び、数万トークンぶんの会話を全部再課金される。長時間動くエージェントを組んだことがある人なら、一度は踏んだことのある地雷だと思う。

Anthropic が7月から8月にかけて入れた「mid-conversation tool changes」と「mid-conversation system messages」は、まさにこの地雷を踏まないための機能だ。日本語ではまだほとんど紹介されていないが、エージェント設計の前提を1つ書き換える種類の変更なので、仕組みごと整理しておきたい。

なぜツールを1個足しただけで全部消えるのか

プロンプトキャッシュは「同じ前置き(プレフィックス)を再送したら、その部分は計算済みとして安く読み出す」仕組みだ。ポイントは、キャッシュがプレフィックスをバイト単位で完全一致で照合すること、そして照合する順序が toolssystemmessages と決まっていることにある。tools 配列は、ハッシュ化される前置きの一番手前に置かれる。

つまり tools に手を入れると、前置きの先頭が変わる。先頭が変われば、その後ろに続くシステムプロンプトも会話履歴も全部ハッシュが変わり、キャッシュはヒットしない。Anthropic のドキュメントは、何を変えると何が無効になるかを表で明示している。

変更した箇所 無効になるキャッシュ
ツール定義を書き換える tools・system・messages のすべて
tool_choice を変える messages のみ
画像の有無を切り替える messages のみ

Modifying tool definitions → Entire cache (tools, system, messages)

出典: Tool use with prompt caching

ここが厄介なのは、ツールの出し入れは「たまに起きる例外」ではなく、まともなエージェントなら普通にやりたいことだからだ。承認モードに入ったら破壊的な操作系のツールを解禁する、探索フェーズが終わったら検索ツールを引っ込める、といった制御は自然な設計に見える。ところが従来はそれをやるたびにキャッシュ全損というペナルティがついてきた。結果として「ツールは最初に固定して途中で変えない」という不自然な縛りを受け入れるしかなかった。

配列は触らず、あとから「出し入れ」を宣言する

新機能の発想はシンプルで、tools 配列そのものは絶対に書き換えない。使う可能性のあるツールは最初に全部宣言しておき、途中で変えるのは「いまモデルに見せているかどうか」だけにする。見せる/引っ込めるの指示は、会話の後ろのほうに tool_addition / tool_removal というブロックとして差し込む。前置きは1バイトも変わらないので、キャッシュは生き残る。

response = client.beta.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    betas=["mid-conversation-tool-changes-2026-07-01"],
    # 使う可能性のあるツールはここで全部宣言。以後この配列は変えない
    tools=[
        {
            "name": "get_weather",
            "description": "Get the current weather for a location.",
            "input_schema": {
                "type": "object",
                "properties": {"location": {"type": "string", "description": "City name"}},
                "required": ["location"],
            },
        },
    ],
    messages=[
        {"role": "user", "content": "Say OK."},
        # ここから get_weather を引っ込める。tools は触らず名前で参照するだけ
        {
            "role": "system",
            "content": [
                {"type": "tool_removal", "tool": {"type": "tool_reference", "name": "get_weather"}},
            ],
        },
    ],
)

tool_additiontool_removalrole: "system" メッセージの中に入れるコンテンツブロックで、text ブロックと混ぜてもいい。tool フィールドはツールを定義するのではなく参照するだけで、tools に宣言済みの名前を指す。宣言していない名前を書くと 400 が返る。最初から見せたくないツールは defer_loading: true を付けておけば、tool_addition が呼ばれるまで隠しておける。MCP コネクタ経由のツールも mcp_tool_reference(個別)や mcp_toolset_reference(まとめて)で同じように出し入れできる。

この機能はまだベータで、mid-conversation-tool-changes-2026-07-01 というベータヘッダが要る。対応モデルは Claude Fable 5、Mythos 5、Opus 4.8、Opus 5 で、Opus 5 と同時にリリースされた(リリースノート 2026-07-24)。

実はシステムプロンプトも同じ手が使える

同じ発想を system フィールドに適用したのが「mid-conversation system messages」で、こちらはベータヘッダ不要で正式提供されている(2026-07-15)。トップレベルの system を書き換える代わりに、{"role": "system"} のメッセージを会話の末尾に足す。「ここから先は SQL を必ずパラメータ化して書け」といった、セッション途中で初めて必要になる指示を、キャッシュを壊さずに操作者権限で差し込める。

ただの user メッセージに書くのとの違いは優先度だ。system はアプリ運営者からの指示として扱われ、エンドユーザーの発話と衝突したときは system が勝つ。だから運営者レベルで守らせたい制約は system ロールで送る意味がある。ドキュメントが挙げている使いどころで個人的に一番実用的だと思ったのは、エージェントがツールを実行している最中にユーザーが追加入力してきたケースだ。それを次のツール結果の直後に system メッセージとして流し込むと、いまの作業を中断して新しい話に飛びつく代わりに、進行中の仕事に折り込ませられる。

[
  { "role": "user", "content": "テストを流して失敗を直して。" },
  { "role": "assistant", "content": [{ "type": "tool_use", "id": "toolu_01", "name": "run_tests", "input": {} }] },
  { "role": "user", "content": [{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "12 passed, 0 failed" }] },
  { "role": "system", "content": "作業中にユーザーから追加依頼が来ました。終わる前にCHANGELOGも更新してください。" }
]

置ける場所には制約がある。system メッセージは user ターン(tool_result を運ぶ user ターンを含む)の直後、あるいはサーバーツール結果で終わる assistant ターンの直後にしか置けず、その後は assistant ターンが続くか配列の末尾でなければならない。tool_use とそれに対応する tool_result の間に挟むと 400 になる。先頭にも置けない。慣れるまではこの配置ルールで一度はつまずくはずだ。

もう一点、地の文で流されがちだが重要な注意がある。system ロールは運営者の指示として無条件に信用されるので、ツールの生出力や取得したWebコンテンツのような外部由来のテキストをここに置いてはいけない。それをやると、外から来た文字列に運営者権限を与えることになり、プロンプトインジェクションの入口になる。外部データはこれまで通り tool_result に入れておく。

どう嬉しいか

一言でいうと、「ツールは静的に固定するもの」という設計上の制約が外れた。承認状態やフェーズに応じてツールセットを動的に絞り込む設計を、キャッシュ全損を気にせず組めるようになる。似た目的の tool search + defer_loading がモデル自身にツールを発見させる方向なのに対し、こちらはアプリ側が明示的に出し入れを制御する方向で、権限管理やモード切り替えとは相性がいい。

対応が Opus 5 世代とその周辺(Fable 5 / Mythos 5 / Opus 4.8)に限られ、Sonnet 5 では mid-conversation system messages が使えない点は、採用前に確認しておきたい。それでも、長時間エージェントのコストと遅延をキャッシュで支えている実装ほど、この2つの効き目は大きい。仕様の一次情報は Mid-conversation system messages and tool changes にまとまっている。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?