Claude Media
Compactionで直近ターンを残す — 切れ目の選び方とtool_useの分断

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. 1

    切れ目を決める

    履歴を、要約する側(older)と残す側(recent)に割ります。APIに切れ目を指定するパラメータは無く、位置は自分のコードで決めます。

  2. 2

    olderだけをcompactionに送る

    compaction パラメータ付きで送ります。recentは入れません。入れた分はすべて要約されます。

  3. 3

    履歴を差し替える

    stop_reason が "compaction" なら、履歴を「返ってきたブロック + recent」に置き換えます。

  4. 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メッセージが安全な候補です。残す量は、原文で持たせたい直近の作業と、要約後に確保したい空きの交換になります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. platform.claude.com/docs/en/build-with-claude/compaction-keep-recent-turns
  2. platform.claude.com/docs/en/build-with-claude/compaction-on-demand
  3. platform.claude.com/docs/en/build-with-claude/compaction-thinking-blocks
この記事を共有:XはてブLinkedIn