「コンテキストが軽くなったのに、むしろ遅くなった」という違和感
長く走らせるエージェントのコンテキスト肥大に手を焼いて、Context Editing の clear_tool_uses_20250919 を一行足したときのことです。トークン数のグラフは確かに下がりました。ところが体感の応答は速くならず、月末のトークン請求はむしろ増えていました。
ログを並べて分かったのは、エージェントが「さっき検索して読んだはずの内容」を忘れ、同じ web_search をもう一度呼び直していたことです。クリアされた結果のプレースホルダーだけが残り、Claude は「ここに何かあったが消えた」と判断して、足りない情報を取りに行き直していました。つまりクリアで減らしたトークンを、呼び直しの新しいツール結果で取り戻していたわけです。
個人開発で複数サイトの記事生成や監視を無人で回していると、こうした「数字は良くなったのに実害は悪化している」状態が一番やっかいでした。派手なエラーは出ません。静かにコストと品質だけが削れていきます。この記事は、その沈黙する劣化を計測で表に出し、Context Editing を入れて損をしない設定に落とすまでの手順をまとめたものです。
まず疑うべきは「クリア境界が会話の意味境界とズレている」こと
clear_tool_uses_20250919 は、古いツール結果から順にクリアします。問題は、Claude にとって「もう不要な結果」と「後続の判断にまだ効いている結果」の境界は、トークン数では測れないことです。keep(保持するツール使用回数)が小さすぎると、まだ参照したい結果まで消えます。
呼び直しループに入っているかどうかは、次の2系列を時系列で並べるだけで判定できます。ひとつは同一ツール・同一引数の重複呼び出し回数、もうひとつはクリア発動の前後でのキャッシュヒット率です。重複呼び出しが増え、かつキャッシュが頻繁に作り直されているなら、クリアが攻撃的すぎます。
# Context Editing 適用時のレスポンスから、クリア発動とキャッシュの実測値を取り出す
# 目的: 「クリアが効いた量」と「キャッシュが壊れたコスト」を同じ行で観測する
import anthropic, json, hashlib
client = anthropic.Anthropic( api_key = "YOUR_API_KEY" )
def call_with_context_editing (messages, tools):
resp = client.beta.messages.create(
model = "claude-sonnet-4-6" ,
max_tokens = 4096 ,
messages = messages,
tools = tools,
betas = [ "context-management-2025-06-27" ],
context_management = {
"edits" : [{
"type" : "clear_tool_uses_20250919" ,
"trigger" : { "type" : "input_tokens" , "value" : 30000 },
"keep" : { "type" : "tool_uses" , "value" : 3 },
"clear_at_least" : { "type" : "input_tokens" , "value" : 5000 },
}]
},
)
u = resp.usage
# context_management の適用結果は usage 配下のサーバ報告値で確認する
applied = getattr (u, "context_management" , None )
print (json.dumps({
"input_tokens" : u.input_tokens,
"cache_read" : getattr (u, "cache_read_input_tokens" , 0 ),
"cache_write" : getattr (u, "cache_creation_input_tokens" , 0 ),
"context_edits" : str (applied),
}, ensure_ascii = False ))
return resp
cache_read_input_tokens がほぼゼロのまま cache_creation_input_tokens ばかり積み上がっていれば、クリアのたびにキャッシュプレフィックスが壊れています。これが「軽くなったのに高くなる」の正体です。
呼び直しループを数値で確定させる
体感ではなく数で確定させます。ツール呼び出しを引数のハッシュで束ね、同じ呼び出しが何回出たかを数えるだけで十分です。
# 1回のエージェント実行ログから「同じ調査を何度やり直したか」を数える
# 目的: クリア設定の前後で重複呼び出し数を比較し、攻撃的すぎる設定を検出する
from collections import Counter
def count_redundant_tool_calls (transcript_blocks):
seen = Counter()
redundant = 0
for b in transcript_blocks:
if b.get( "type" ) != "tool_use" :
continue
key = hashlib.sha1(
(b[ "name" ] + json.dumps(b[ "input" ], sort_keys = True )).encode()
).hexdigest()
seen[key] += 1
if seen[key] > 1 :
redundant += 1
total = sum (seen.values())
rate = redundant / total if total else 0.0
return { "total_tool_calls" : total, "redundant" : redundant, "redundant_rate" : round (rate, 3 )}
# 判断の目安: redundant_rate が clearing 無効時より 0.1 以上跳ね上がったら keep を増やす
私自身の運用でも、keep を 3 から 6 に上げただけで重複率が 0.28 から 0.05 に落ちた回がありました。トークンは少し増えますが、呼び直しのツール結果が消えるぶん、最終的な入力トークンは横ばいで、応答品質は明確に戻りました。減らすべきは「古い結果」であって「まだ効いている結果」ではない、という当たり前のことが、数字で初めて腑に落ちました。
trigger と clear_at_least は実測のトークン分布から決める
trigger を低く置くと、まだ余裕のあるうちからクリアが走り、キャッシュ無効化だけが先に発生します。逆に高すぎると肥大を抑えられません。ここは推測で置かず、実際のセッションの入力トークン推移を一度プロットしてから決めます。
判断の順序は次のとおりです。
クリアを無効にしたまま代表的なワークロードを数本流し、各ターンの input_tokens を記録する
トークンが頭打ちになる手前のターンの値を見て、その8〜9割を trigger の初期値にする
clear_at_least は、キャッシュ再作成で書き込まれる cache_creation_input_tokens の実測平均より大きく設定する。これでクリア1回の節約がキャッシュ作り直しコストを上回る
exclude_tools に、長期的に参照し続ける検索系・仕様参照系のツールを入れて保護する
clear_at_least がキャッシュ書き込み量を下回ると、「少し消してはキャッシュを作り直す」を延々と繰り返し、最も損をします。ここだけは保守的に大きく取るのが安全です。トークン計測の基礎は Claude API Token Counting でコストを実測する に整理しています。
Extended Thinking を併用するときの追加の落とし穴
ツール結果のクリアに気を取られていると見落とすのが、Extended Thinking 側の挙動です。Thinking を有効にして clear_thinking_20251015 を指定しないと、既定で直近1ターン分の思考しか残りません。複数ステップの推論を要するエージェントだと、これが「途中まで考えた筋道を忘れて、また最初から考え直す」原因になります。
# ツール結果と思考ブロックを別々の保持方針で管理する
# 目的: 調査結果は浅く、推論の連続性は深く保つ、という非対称な設計を明示する
resp = client.beta.messages.create(
model = "claude-opus-4-6" ,
max_tokens = 16000 ,
temperature = 1 , # Extended Thinking 使用時は 1 が必須
thinking = { "type" : "enabled" , "budget_tokens" : 10000 },
messages = conversation_messages,
tools = my_tools,
betas = [ "context-management-2025-06-27" ],
context_management = {
"edits" : [
{
"type" : "clear_tool_uses_20250919" ,
"trigger" : { "type" : "input_tokens" , "value" : 50000 },
"keep" : { "type" : "tool_uses" , "value" : 6 },
"clear_at_least" : { "type" : "input_tokens" , "value" : 8000 },
"exclude_tools" : [ "web_search" ],
},
{
"type" : "clear_thinking_20251015" ,
"keep" : { "type" : "thinking_turns" , "value" : 2 },
},
]
},
)
# 効果: 調査結果は直近6回ぶんに絞りつつ、推論の連続性は直近2ターン維持される
ツール結果と思考ブロックでは、保持すべき理由が違います。ツール結果は「事実の在庫」なので過去ぶんは消えてよいことが多い一方、思考ブロックは「判断の連続性」なので消すと推論がリセットされます。同じ keep の感覚で両方を絞ると、Thinking 側を削りすぎます。
キャッシュと相性が悪い設定を事前に弾く
最後に、入れる前に損得を一度だけ見積もります。ざっくり、クリア1回あたりの節約トークン(次ターン以降に効く)と、その時に発生するキャッシュ再作成トークンを比べます。
観測した状態 意味 取るべき対応
cache_read が高いまま安定 クリア境界が安定し、プレフィックスが維持できている 現状維持。trigger を少しずつ下げて節約余地を探る
cache_creation ばかり増える クリアのたびにキャッシュが壊れている clear_at_least を上げる 。クリア頻度を下げる
redundant_rate が上昇 まだ効く結果まで消している keep を増やす 。exclude_tools に保護対象を追加
トークンは減るが応答品質が低下 Thinking を削りすぎ clear_thinking の keep を増やす、または all にする
判断の核心はひとつです。Context Editing は「コンテキストを減らす機能」ではなく「いつ・何を・どれだけ減らすかを制御する機能」です。減らすこと自体が目的になると、呼び直しとキャッシュ破壊で必ず裏目に出ます。本番でのコスト設計全般は Claude API のコスト最適化を本番運用で詰める もあわせてご覧ください。
次の一手
まずはクリアを無効にしたまま代表ワークロードを3本流し、input_tokens の推移と cache_read / cache_creation の比を1枚のログに出してください。そのうえで trigger をトークン頭打ちの8割、clear_at_least をキャッシュ書き込み平均の上に置き、keep は重複率が跳ねない最小値から始める。この順で入れれば、「軽くなったのに高くなる」を踏まずに済みます。同じ落とし穴に時間を溶かさずに済めば幸いです。