Compactionで直近ターンを残す — 切れ目の選び方とtool_useの分断
要約対象から末尾ターンを外し、要約ブロックの後ろに原文のまま送るkeep-tail方式の手順。切れ目の位置、tool callを分けたときの400エラー、thinkingの扱いをまとめます。
Compactionで直近ターンを残す — 切れ目の選び方とtool_useの分断
オンデマンドcompactionは、会話全体を1つの要約ブロックに置き換えます。直近のやり取りまで要約に溶かしたくないときは、末尾の数ターンを外して要約ブロックの後ろへ原文のまま送ります。これがkeep-tail方式です。
決めることは1つだけです。履歴のどこで切るか。切れ目を選ぶパラメータはAPIに無く、自分のコードで決めます。ここを誤ると、tool callが宙に浮いてAPIが要求を拒否します。
keep-tailは何を変えて何を変えないか
keep-tailが変えるのは2点です。compactionリクエストに入れるメッセージと、要約ブロックの後ろに送るメッセージ。ブロックを返して履歴を差し替える流れは、通常のオンデマンドcompactionと同じです。
keep-tailの1周
- 1
切れ目を決める
履歴を、要約する側(older)と残す側(recent)に割ります。APIに切れ目を指定するパラメータは無く、位置は自分のコードで決めます。
- 2
olderだけをcompactionに送る
compactionパラメータ付きで送ります。recentは入れません。入れた分はすべて要約されます。 - 3
履歴を差し替える
stop_reasonが"compaction"なら、履歴を「返ってきたブロック + recent」に置き換えます。 - 4
次のリクエストを送る
ブロック、recent、新しいuserメッセージの順です。recentは履歴にあるとおりに送ります。
前提は次のとおりです。
- ベータ機能で、ベータヘッダー
compact-2026-09-04が要る - 対応プラットフォームはベータのClaude API、Claude Platform on AWS、Google Cloud、Microsoft Foundry。Amazon Bedrockは対象外
- 対応モデルはOpus 5.5 / Sonnet 5.5 / Fable 5.1のほか、Mythos 5.1、Opus 4.6〜4.8、Sonnet 4.6など。モデルごとの対応はModels APIにベータヘッダーを付けて呼び、各モデルの
capabilities.compactionで読める
しきい値方式でブロックを会話へ戻す実装はCompaction blocksを会話に戻す実装パターンで扱っています。ここでは、切れ目の設計に絞ります。
切れ目の選び方 — どこに切ればよいか
切れ目より前のメッセージがcompactionリクエストに入り、切れ目以降が「残すターン」になります。APIは送られたメッセージをすべて要約するので、残したい分を要約リクエストに混ぜてはいけません。
公式の例では、1ターンをuserメッセージ1件とその返答1件と数えています。2ターン残すなら、履歴を末尾から4メッセージの位置で割ります。残す側は必ずuserメッセージから始まります。
KEEP_TURNS = 2
split = -2 * KEEP_TURNS
older, recent = history[:split], history[split:]この方法が成り立つのは、ツールを使わない単純な往復だからです。ツールを呼ぶ会話では1ターンが何メッセージにもなるため、末尾から固定数で割ると切れ目がtool callの途中に落ちます。
切れ目の判定は1つの規則にまとまる
公式が示す規則は1つです。tool callを開いたままにせず、tool callとその結果を同じ側に置きます。
切れ目を置ける場所と置けない場所
置ける
assistantの返答の直後で、次のuserメッセージの前。tool callとその結果が、どちらも同じ側に収まります。
置けない
tool callを含むassistantメッセージと、その結果を持つuserメッセージの間。要約側が結果の付かないtool callで終わると、APIはcompactionリクエストを400で拒否します。
ツール呼び出しを含む履歴での切れ目の探し方
ここからは公式の記述を前提にした、記事側の実装例です。tool_resultはuserロールで返るため、「userメッセージなら切れ目にできる」とは言えません。人間の入力(テキスト)が先頭に来るuserメッセージだけを切れ目の候補にします。
def is_turn_start(msg) -> bool:
"""人間の入力で始まる user メッセージか(tool_result を含む
user メッセージは、直前の tool_use と対になるので除く)。"""
if msg["role"] != "user":
return False
content = msg["content"]
if isinstance(content, str):
return True
return not any(block.get("type") == "tool_result" for block in content)
def find_split(history, keep_turns: int):
starts = [i for i, m in enumerate(history) if is_turn_start(m)]
if len(starts) <= keep_turns:
return None # 要約する側が空になるので compaction しない
return starts[-keep_turns]この実装では、残す側が必ず人間の入力から始まり、その直前の要約側はassistantの返答で終わります。tool_useとtool_resultの組は、途中で分かれません。
None を返す分岐は、公式サンプルの条件(会話のターン数が残すターン数を超えること)と同じ意図です。olderが空のままcompactionリクエストを送ると、送る意味がありません。
要約側と残す側を別々に送る
切れ目が決まったら、olderだけをcompactionリクエストに入れます。
older, recent = history[:split], history[split:]
summary = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
system=SYSTEM,
betas=["compact-2026-09-04"],
messages=older,
compaction={"type": "summarize"},
)
if summary.stop_reason == "compaction":
history = [{"role": "assistant", "content": summary.content}, *recent]stop_reason の確認は省けません。要約が作られるのは、要約の呼び出しがテキストで正常終了し、tool callを含まないときだけです。それ以外は200のまま content が空で返ります。
stop_reason | 原因 | 対処 |
|---|---|---|
"max_tokens" | 原因要約が途中で切れた | 対処max_tokens を増やして再送 |
"model_context_window_exceeded" | 原因要約用のプロンプトが入る余地が無い | 対処instructions を短くするかメッセージを減らして再送 |
"tool_use" | 原因要約の代わりにツールを呼んだ | 対処ツールを呼ばないよう instructions に書いて再送 |
"refusal" / "end_turn" | 原因拒否された / テキストが無かった | 対処要約なしで続け、後でもう一度compactionする |
compactionリクエストでは、stop_sequences、structured outputsの output_config.format、tool_choice の any と tool が400になります。
要約の呼び出しは、リクエストのモデル・system・tools・thinkingの設定・max_tokens をそのまま使います。max_tokens は要約の前のthinkingも含めた上限なので、数千トークンの余裕が要ります。system と tools は、会話の他のリクエストと同じものを送ります。
残したターンにはthinkingブロックも含め、編集や再構成はしません。
要約を書くのは要約用のモデル(summarizer)です。ツール定義は読みますが、ツールは実行しません。要約に何を残させるかは instructions(16,384文字まで)で指定できます。空でない文字列を渡すと、API既定の要約プロンプトは丸ごと置き換わります。書き方はCompactionの要約指示をカスタマイズする方法が参考になります。
残した分だけ空きは減る
残したターンは原文の長さのままClaudeに戻ります。多く残すほど、compactionで空く枠は小さくなります。
たとえば、ツールの出力が長い会話で3ターン残すと、要約で削ったつもりの分を末尾が食い返します。残すターン数は、次の2つを見て決めます。
- 直近のやり取りを原文で持たせる価値(直前の指示、書きかけのコード、途中の判断)
- 要約後に確保したい空き
残す側の大きさは、見積もりで先に測れます。次のリクエストの大きさは、ブロックの要約とrecentの合計です。トークン数エンドポイントはメッセージを数えられますが、compaction パラメータは無視します。キャッシュを使う会話では、cache_read_input_tokens と cache_creation_input_tokens も足します。
要約の呼び出しの料金は、通常のリクエストと同じ形で課金されます。usage.iterations の compaction 項目に出ます。会話全体の消費量は、最上位の input_tokens と output_tokens ではなく、iterations の合計で数えます。最上位の2つは、返答が生成されないためゼロです。ブロックを後続のリクエストに載せ続けても、compactionの追加費用は発生しません。
要約を走らせる閾値の決め方はCompactionのトークン閾値の設計で扱っています。閾値と残すターン数は組で調整する値です。残す側が大きければ、閾値を下げても要約後の空きは狭いままです。
分断が起きたとき何が返るか
残す側の先頭がtool_resultだけのuserメッセージで、対応するtool_useが要約側に入ると、compactionリクエストは400で拒否されます。下のエラー表で、要約側の最後のassistantターンが結果の付かないtool callで終わる行にあたります。tool_useとtool_resultの整合性の一般規則は、tool_resultとtool_useの400エラー — 正しい整形ルールにまとまっています。
ブロックを戻すときに返る400
差し替えを誤ると、次のようなエラーで拒否されます。一部は error.details.error_code が compaction_ で始まります。
| エラー | 原因 | 対処 |
|---|---|---|
400 compaction_block_misplaced | 原因要約済みのメッセージが、ブロックの前に残っている | 対処要約側を履歴から消し、ブロックを先頭にする |
400 compaction_signature_invalid / compaction_content_mismatch | 原因ブロックの signature か中身を、返却後に変えた | 対処返ってきたまま、signature ごと送る |
| 400(コード無し) | 原因1リクエストに compaction ブロックが2つ以上ある | 対処最新の1つだけ送る |
400 compaction_nothing_to_summarize | 原因messages に user / assistant の中身が無い | 対処空のolderでcompactionを送らない |
400(compaction が想定外のブロック型という検証エラー) | 原因ブロックを載せた後続のリクエストで、ベータヘッダーを付け忘れた | 対処ブロックを載せるすべてのリクエストにヘッダーを付ける |
| 400(コード無し) | 原因要約側の最後のassistantターンが、結果の付かないtool callで終わっている | 対処そのターンのtool_resultを送ってからcompactionする |
400(requires anthropic-beta: compact-2026-09-04) | 原因compactionリクエストでベータヘッダーを付け忘れた | 対処compactionリクエストにもヘッダーを付ける |
529 compaction_unavailable | 原因ブロックの生成か読み取りで、サーバー側の一時的な不具合 | 対処リトライする |
後続のリクエストでのヘッダー漏れは、エラー文がヘッダーに触れません。Pythonでは、ブロックを自分でシリアライズするときの model_dump() も落とし穴です。citations: null と text: null が加わり、compaction.citations: Extra inputs are not permitted のような検証エラーで拒否されます。client.beta.messages の戻り値をそのまま渡すか、model_dump(exclude_none=True) を使います。
compaction_nothing_to_summarize は、上の find_split が None を返す分岐と対応します。残すターン数が会話のターン数以上なら、要約側が空になるためです。
thinkingを使う会話で切れ目に加わる制約
preserved thinkingに対応するモデルでthinkingブロックを送り返している場合、残したターンのthinkingが有効であり続けるには条件があります。条件は次のとおりです。
- 残すターンが、要約したメッセージの直後に続き、変更なしで送られること。間にメッセージを足さず、飛ばしもしない
- 残す先頭のメッセージのロールが、要約側の最後のメッセージと異なること
- 途中の
role: "system"メッセージで始まらないこと systemと、defer_loading: trueでないtoolsが変わらないこと- compactionリクエストが、preserved thinkingに対応するモデルで実行されること
ロールが同じだったり、先頭が途中のsystemメッセージだったりすると、APIは残す先頭のメッセージを要約側の最後のメッセージへ結合します。切れ目の位置に効くのは、上の1つ目から3つ目です。
守れていなくても、compaction自体は失敗しません。失敗は、そのthinkingを送る後続のリクエストで、既定では400エラーとして現れます。thinking.block_binding.prefix_mismatch_behavior を "drop_block" にすると、エラーの代わりにthinkingブロックが落とされます。Message Batches APIでは、prefix_mismatch_behavior を指定しない項目は失敗せず、thinkingブロックが落とされます。
公式は、すでに送ったリクエストの messages をそのままcompactionし、その後に増えた分を残す方法も挙げています。この場合は残す側がClaudeの返答から始まり、先頭のロールが要約側の最後(user)と異なるので、上の条件に当てはまります。
確認は、ベータヘッダー thinking-binding-controls-2026-08-01 を足し、prefix_mismatch_behavior を "error" にして次のリクエストを送ります。200で input_transformations が空なら、残したthinkingは有効でした。
system や tools を変えたい場合は、先に会話全体をcompactionして、残すターンをなくします。その次のリクエストで変更します。後続のリクエストが別の system・tools・モデルを使っても、APIはブロックを受け付けます。影響を受けるのは、残したターンのthinkingだけです。
system を触らずに指示を足したいときは、messages の末尾に追記する方法もあります。ブロックと残すターンの間にsystemメッセージを挟むと、残したターンのthinkingは無効になります。
もう一度compactionするときの扱い
keep-tailを使った会話も、再度compactionできます。新しいブロックは、古い要約と、compactionリクエストに入れたそれ以降のメッセージをまとめて覆います。そのリクエストから外したターンが、新しいブロックにとっての「残すターン」です。
前回残したターンを要約に溶かすか、さらに残すかは、そのつど切れ目で決まります。thinkingを残したまま2回compactionする場合は、両方のcompactionリクエストがpreserved thinkingに対応するモデルで走っている必要があります。thinkingブロックが作られる前に済んだcompactionは数えません。ブロックが入った後に作られたthinkingは、そのブロックに結び付き、条件を満たす限り以後のcompactionを越えて有効です。
再compactionのあとは、最新のブロックだけを送ります。
要約と一緒に消えるもの
残さなかった側にあるものは、ブロックへの置き換えで失われます。
要約側に入れると消えるもの
画像・ドキュメント・URL
画像、ドキュメント、
container_uploadブロック、取得したURLは、ブロックに置き換わると失われます。必要なら、後のターンで再送か再アップロードをします。途中のsystemメッセージ
要約側の
role: "system"の指示は、テキストとしては効かなくなります。まだ必要なら、残すターンの後ろの最初の新しいuserターンの直後に、role: "system"で再掲します。ツールの変更
要約側にあったツールの変更は、compactionリクエストにベータヘッダー
inline-tools-2026-09-15を付けると、ブロックのtool_changesに残ります。ブロックを変更せず返せば引き継がれます。tool_changesが無いブロックなら、同じ位置のsystemメッセージで言い直します。
残す側の1ターン前後に画像や指示が固まっているなら、切れ目をそこに合わせる手があります。要約の質に頼るより確実です。
併用できない設定
compaction と context_management は、同じリクエストに載せられません。しきい値方式のcompaction(compact_20260112)は、署名付きのブロックを載せたリクエストでは動きません。タスクバジェットの output_config.task_budget.remaining も、compactionのリクエストとブロックを載せるリクエストでは400になります。ブロックの cache_control は、要約の直後にキャッシュのブレークポイントを置きます。
keep-tailを使う判断の目安
| 状況 | 残す量 |
|---|---|
| 直前の指示の言い回しが、次の作業に効く | 残す量1〜2ターン |
| ツール出力が長く、原文を残すと空きが足りない | 残す量0〜1ターン |
| thinkingを送り返しており、system / toolsを変える予定がある | 残す量0ターン(全体を要約してから変更) |
残す量に固定の正解は無く、上の表は考え方の整理です。実際の値は、自分の会話のターン当たりのトークン数を測って決めます。プロンプトキャッシュとの併用はCompactionとPrompt cachingの併用を参照してください。
まとめ
切れ目の位置を決めるのは自分のコードで、tool callを使う会話では人間の入力で始まるuserメッセージが安全な候補です。残す量は、原文で持たせたい直近の作業と、要約後に確保したい空きの交換になります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。