Computer Useでプロンプトキャッシュを保つスクリーンショット管理
Computer Useの長時間ループはスクリーンショットを急速に積み上げ、プロンプトキャッシュを壊しがちです。バッチ単位の間引きとcache_controlの置き方をまとめます。
Computer Useの長時間ループでスクリーンショットがキャッシュを壊す
Computer Useの1回のスクリーンショットは、おおよそ1,000〜1,800入力トークンを消費します。エージェントループが数十ターン続くと、会話履歴に積み上がった画像の総量は無視できない大きさになります。
加えて、APIのリクエスト上限も効いてきます。1回のリクエストに画像が20枚を超えて含まれると、そのリクエスト内の全画像に、より厳しい辺長の上限がかかります。数えられるのは、過去のターンから送り直す画像や、tool_resultの中に入れたスクリーンショットも含めたすべてのimageブロックです。上限を超えた画像は、メッセージに「many-image requests」を含むinvalid_request_errorで拒否されます。
スクリーンショット履歴をそのまま持ち続けるループは、数十ターンでこの20枚に届きます。回避策は2つあり、各スクリーンショットを縦横2000px以内にリサイズするか、古いスクリーンショットを間引いて1リクエストの画像枚数を20枚以下に保つかです。ただし間引き方を誤ると、今度はプロンプトキャッシュが壊れます。
2000pxは「20枚超えのリクエストでも拒否されにくい辺長」の目安で、モデルごとの正確な上限とは別物です。Claude Opus 4.7以降(computer_toolset_20260801に対応するモデルはすべて含む)は長辺2576px・視覚トークン総数4784まで、それより前のモデルは長辺1568px・約1.15メガピクセルまでと、世代で分かれています。
どの症状から手を付けるか
症状によって、効く対策が違います。
症状から対策を選ぶ
- 1
キャッシュヒットが急に落ちた
間引きのタイミングを疑います。毎ターン1枚ずつ消していると、プレフィックスが毎ターン変わります。間引きは一定間隔でまとめて行い、間隔の間はバイト単位で同一に保ちます。
- 2
画像拒否のエラーが出る
リクエスト内の画像が20枚を超えています。対処は上の20枚の節にある2通りです。
- 3
thinkingが途中から消える
Claude Fable 5.1・Opus 5.5・Sonnet 5.5を使っているなら、クライアント側の削除が原因です。下のモデル別の節に進みます。
- 4
トークン消費が重い
まず1枚あたりをリサイズで下げます。そのうえで、履歴に残す枚数を間引きで抑えます。
cache_controlブレークポイントの置き方
プロンプトキャッシュを効かせるには、system promptとツール定義の直後に1つ、直近ターンのtool_resultブロックの末尾に最大3つまでcache_controlのブレークポイントを置きます。直近3ターン分のブレークポイントは、ターンが進むごとに前へスライドさせます。
batch action(1ターンで複数のcomputer use操作をまとめて返す仕組み)では、同じターンの複数ブロックに置いたマーカーは1つのブレークポイントとして働きます。その効果はバッチの終端に現れます。ただし各マーカーは上限の4つに数えられるので、1ターンにつき1つに留めるのが安全です。
ツール定義側のブレークポイントは、computer_toolset_20260801のエントリそのものに指定します。ツールセットのメンバー(screenshotやzoomなど)ごとのconfigsには置けません。メンバーは1つの定義としてまとめて読み込まれるためです。
置く価値は定義の大きさから分かります。ツールセットを既定のメンバーで宣言すると、定義とツール利用のシステムプロンプトだけで入力が約4,500トークン増えます。zoomを無効にすると、そのうち約410トークンが減ります。実際の数値はレスポンスのusageに出ます。
{
"type": "computer_toolset_20260801",
"cache_control": { "type": "ephemeral" }
}古いスクリーンショットは「まとめて」間引く
最も陥りやすい失敗が、毎ターン1枚ずつスクリーンショットを間引く実装です。毎ターンの間引きはプレフィックスを毎ターン変え、キャッシュを毎回無効化します。せっかくcache_controlを置いても、間引きのタイミングが揃っていなければ意味がありません。
公式ドキュメントが示す妥当な既定値は、直近3枚を残し、25ターンごとにまとめて間引く方法です。25ターンの間はプレフィックスがバイト単位で同一に保たれるため、間引きの瞬間以外はキャッシュが効き続けます。
25ターンは固定の必須値ではありません。スクリーンショットが2000pxを超えるサイズなら、1リクエストの画像が20枚以下に収まるよう間隔を詰めます。
リサイズと間引き、どちらを優先するか
リサイズは1枚あたりのトークン消費を下げる対策で、ターンが伸びても画像の枚数は減りません。間引きは履歴に残る枚数を抑える対策で、1枚あたりのサイズには手を付けません。長時間ループでは両方が必要になります。
順序は、まずリサイズで1枚のコストを下げ、そのうえで間引き間隔を決めるのが素直です。リサイズだけで済ませると、枚数がいずれ20枚に達し、全画像が厳しい辺長上限の対象になります。間引きだけに頼ると、次の間引きまでの25ターンは大きな画像が残り続けます。
実装では、スクリーンショットの取得直後にリサイズを通し、間引きは別のターンカウンターで独立に動かします。2つの処理が互いの前提を壊さず、あとから片方だけを差し込む場合も楽です。
Fable 5.1・Opus 5.5・Sonnet 5.5ではクライアント側の間引きを避ける
Claude Fable 5.1、Claude Opus 5.5、Claude Sonnet 5.5を使う場合、この間引き戦略には例外があります。クライアント側で過去のスクリーンショットを削除すると、そのターンを含むすべてのリクエストで、それ以降のthinkingブロックが無効になるためです。
スクリーンショットの落とし方
クライアント側で間引く
過去のターンを書き換えるので、以降のthinkingブロックが無効になります。やむを得ない場合はprefix_mismatch_behavior: "drop_block"を付けたままにし、間引き以降のthinkingを持たずに応答させます。
サーバー側のtool result clearing
サーバー側のコンテキスト管理はthinkingブロックを無効にしません。古いスクリーンショットはclear_tool_uses_20250919に任せ、手元ではリサイズだけを行います。
drop_blockの設定には、ベータヘッダーthinking-binding-controls-2026-08-01が要ります。このヘッダーを付けると、レスポンスにinput_transformationsの配列が加わり、APIが落としたthinkingブロックが一覧で返ります。2026年8月31日0時(UTC)以降に作ったアカウントは、プレフィックスの検査が既定で有効です。それ以前のアカウントでは、block_binding.prefix_mismatch_behaviorを指定したリクエストだけが検査の対象になります。
Claude Sonnet 5.5では、block_bindingはthinking: {"type": "adaptive"}のときだけ使えます。between_toolsのときにblock_bindingを送ると400エラーです。この場合は履歴を追記専用に保つか、編集したターン以降のthinkingブロックを取り除きます。
サーバー側clearingでもキャッシュは切れる
thinkingは守られても、プレフィックスは変わります。tool result clearingは、内容を消した位置からキャッシュを無効にします。消すたびにキャッシュ書き込みコストがかかり、そのあとのリクエストは作り直されたプレフィックスを再利用します。
消した効果が書き込みコストに見合うよう、clear_at_leastで1回に消す最小トークン数を指定できます。クライアント側の「25ターンごとにまとめて」と同じ考え方を、サーバー側の設定で表したものです。
context editingには、thinkingブロックを消すthinking block clearing(clear_thinking_20251015)も別にあります。消すのはClaudeが生成したthinkingの中身で、tool_resultの中身ではありません。thinkingを残す(keep)間はキャッシュが保たれ、消すとその位置から無効になります。どちらもベータ機能で、context-management-2025-06-27ベータヘッダーが必要です。
実装例: 25ターンごとのバッチ間引き
会話履歴のスクリーンショットを25ターンごとにまとめて間引く最小実装です。Fable 5.1・Opus 5.5・Sonnet 5.5以外のモデル向けです。
def prune_screenshot_history(messages, turn_count, keep_last=3, prune_interval=25):
"""25ターンごとにスクリーンショットをまとめて間引く。
毎ターン呼ぶのではなく、turn_countが間隔の倍数のときだけ実行する。
"""
if turn_count % prune_interval != 0:
return messages # 間引きのタイミングでなければプレフィックスをそのまま保つ
# 画像入りのtool_resultを持つuserメッセージの位置(判定を簡略化している)
image_positions = [
i for i, m in enumerate(messages)
if m["role"] == "user"
and any(block.get("type") == "tool_result" for block in m["content"])
]
keep_from = image_positions[-keep_last:] if len(image_positions) > keep_last else image_positions
pruned = []
for i, m in enumerate(messages):
if i in image_positions and i not in keep_from:
# strip_imagesは自前のヘルパー: imageブロックを除き、テキストは保持する
pruned.append(strip_images(m))
else:
pruned.append(m)
return prunedcache_controlのブレークポイントは、この間引き処理とは別に、system promptとツール定義の直後、および直近3ターンのtool_result末尾に立て直します。
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A9r5kQm2LxWc7vT3nZ4bJs",
"content": [{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "..."}}],
"cache_control": {"type": "ephemeral"}
}
]
}キャッシュを無効にする、スクリーンショット以外の変更
キャッシュはtools、system、messagesの順にプレフィックスでつながっていて、ある階層の変更はそれ以降をすべて無効にします。間引きを正しく入れても、次の変更でヒット率は落ちます。
- ツール定義を書き換える: キャッシュ全体(tools・system・messages)が無効になります。
zoomの有効と無効を途中で切り替えるような変更も、ここに入ります tool_choiceやdisable_parallel_tool_useを変える: messagesのキャッシュが無効になります。ターンの途中で変える必要があるなら、変える位置の手前にブレークポイントを置きます- 画像の有無を切り替える: リクエストに画像がある状態とない状態を行き来すると、messagesのキャッシュが無効になります。間引きで画像を全部外した直後の1リクエストが、これに当たる場合があります
- モデル世代ごとのサイズ上限を混同する: 2000pxを正確な上限と思い込むと、Opus 4.7以降の長辺2576pxを無駄に切り捨てたり、旧モデルの長辺1568pxを超えたりします
まとめ
キャッシュヒットが落ちたときの切り分けには、レスポンスのusageが使えます。1つの変更ごとに数値を見比べれば、原因が間引きの間隔かthinking設定かツール定義かを分けられます。
プロンプトキャッシュの基本的な仕組みはAnthropic APIのPrompt Cachingを理解する、自分でエージェントループを組む基礎はClaude Agent SDK入門を参照してください。