Claude APIのオンデマンドcompactionで好きな時点に要約させる
compaction: {type: summarize} を付けた1リクエストで、返信の代わりに要約ブロックだけが返る。ベータヘッダー、履歴の差し替え、ループ化、要約が返らないときの原因別対処まで実装で示します。
Claude APIのオンデマンドcompactionで好きな時点に要約させる
オンデマンドcompactionは、要約するタイミングをアプリケーション側が決める方式です。compaction: {"type": "summarize"} を付けたリクエストを1回送ると、Claudeは返信を書かず、要約だけを compaction ブロックとして返します。しきい値を超えたらAPIが自動で要約するサーバー側の方式とは、主導権の置き場所が逆です。
要点は3つです。
- ベータヘッダー
compact-2026-09-04が、要約を頼むリクエストと、署名付きブロックを含む以後のすべてのリクエストに要る - 応答の
stop_reasonは"compaction"になり、contentにはブロックが1つだけ入る - 履歴は「追記」でなく「差し替え」で更新する。要約済みのメッセージは消し、ブロックを先頭に置く
オンデマンドcompactionとは何か
オンデマンドcompactionは、会話をいまの状態のまま送って要約だけを返してもらう、独立したリクエストです。会話のターンとは別に走ります。応答のブロックには、人間が読める要約テキストと署名(signature)が入ります。
以後の会話では、このブロックが要約済みメッセージの代わりに messages の先頭へ入ります。Claudeは、元のメッセージがあった場所に要約を見ます。
| 方式 | 要約のきっかけ | ブロックの位置 | 履歴の整理 |
|---|---|---|---|
| オンデマンド(本記事) | 要約のきっかけアプリが compaction 付きで要求 | ブロックの位置先頭に置く | 履歴の整理アプリが要約済みメッセージを消す |
| しきい値型 | 要約のきっかけトークン数が閾値を超える | ブロックの位置要約対象の後ろ | 履歴の整理APIが古いメッセージを落とす |
しきい値型の設定はCompactionの発動しきい値の決め方で扱っています。ブロックの位置が逆になる点は、両方式を混在させるときのつまずきどころです。
どのモデルとプラットフォームで使えるか
オンデマンドcompactionはベータ機能で、ページのメタデータには次の対応が載っています。
- 対応モデル: Claude Fable 5.1 / Mythos 5.1 / Fable 5 / Mythos 5 / Mythos Preview / Opus 5.5 / Opus 5 / Opus 4.8 / Opus 4.7 / Opus 4.6 / Sonnet 5.5 / Sonnet 5 / Sonnet 4.6
- ベータ提供: Claude API / Claude Platform on AWS / Google Cloud / Microsoft Foundry
- 提供なし: Amazon Bedrock
手元のモデルが対応しているかは、Models APIにベータヘッダーを付けて呼び、各モデルの capabilities.compaction を読めば確かめられます。
要約を頼むリクエストを送る
要約を頼むリクエストは、普通の messages 呼び出しに compaction パラメータを足すだけです。ヘッダーは anthropic-beta: compact-2026-09-04 を付けます。
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: compact-2026-09-04" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 4096,
"messages": [
{"role": "user", "content": "レシピアプリのデータモデルを考えたい"},
{"role": "assistant", "content": "Recipe, Ingredient, Stepから始めましょう"},
{"role": "user", "content": "Recipeのフィールド名を提案して"}
],
"compaction": {"type": "summarize"}
}'上はドキュメントの英語の会話例を日本語に置き換え、短く整えた形です。Pythonでは client.beta.messages.create(..., betas=["compact-2026-09-04"], compaction={"type": "summarize"}) と書きます。
応答は次のような形で返ります(signature は省略)。
{
"role": "assistant",
"content": [
{"type": "compaction", "content": "Summary of the conversation: ...",
"signature": "EuYBCkQY..."}
],
"stop_reason": "compaction",
"usage": {
"input_tokens": 0,
"output_tokens": 0,
"iterations": [{"type": "compaction",
"input_tokens": 144, "output_tokens": 276}]
}
}トップレベルの input_tokens と output_tokens は0です。返信を生成していないためで、要約に使ったトークンは usage.iterations の compaction 要素に載ります。
送るときに合わせておく条件
要約を頼むリクエストには、守る条件がいくつかあります。
- 会話の途中と同じ
systemとtoolsを送る。要約役のモデルがどちらも読むため max_tokensは数千トークン確保する。要約の前に行うthinkingも含めて、呼び出し全体の上限になるcontext_managementとは同じリクエストに同居できないstop_sequences、構造化出力のoutput_config.format、tool_choiceのanyとtoolは付けない。効果がなく、APIが拒否する- 最後の
assistantターンがツール呼び出しで終わり、結果がまだ無いとAPIが拒否する。先にツール結果を送る - 会話全体がモデルのコンテキストウィンドウに収まっている必要がある。溢れてからでなく、溢れる前に要約する
ツールについては、要約役はツール定義を読みますが実行はしません。応答にthinkingも含まれません。
ストリーミングの場合、ブロックは分割されず丸ごと届きます。content_block_start に完全なブロックが載り、続く content_block_stop までの間に content_block_delta は来ません。ping が前後に挟まることはあります。
履歴を要約に差し替える
返ってきたメッセージは、送った全メッセージの代わりに履歴へ入れます。ブロックは signature を含めて、返ってきた形のまま保持します。
{
"model": "claude-opus-5-5",
"max_tokens": 2048,
"messages": [
{"role": "assistant", "content": [
{"type": "compaction",
"content": "Summary of the conversation: ...",
"signature": "EuYBCkQY..."}
]},
{"role": "user", "content": "Ingredientについても同じようにして"}
]
}差し替えのルールは次のとおりです。
- ブロックを
messagesの先頭に置く。assistantメッセージとして単独で置いても、先頭メッセージの最初のコンテンツブロックにしてもよい - 要約済みのメッセージは消す。ブロックの前に1つでも残ると、400エラー(
compaction_block_misplaced)になる - ブロックはリクエストごとに1つだけ送り、以後のすべてのリクエストにも付け続ける
要約の作成中に会話が進んだ場合も、要約に含まれなかった後続のターンは、ブロックの後ろにそのまま並べます。例では、要約対象の最後のユーザーターンへの返信が要約作成中に届き、assistant が2連続になっていますが、ブロックが先頭にあれば問題ありません。
エラーにならない2つの失敗
差し替えには、エラーが出ずに挙動だけが狂う失敗があります。
- 要約済みのメッセージをブロックの後ろに残すと、APIはそれを再びClaudeに渡す。要約と原文が二重になる
- 後続のリクエストでブロックを抜くと、Claudeは要約を受け取らない。会話の前半を知らない状態で答える
どちらも400にならないため、履歴を組み立てるコードのテストで確かめておく価値があります。ブロックを戻す実装パターンの全体像はCompaction blocksを会話に戻す実装パターンにあります。
Pythonで書くときの落とし穴
PythonのSDKは client.beta.messages を使います。client.messages を使ってブロックを自分でシリアライズするなら、to_dict() か model_dump(exclude_none=True) を使います。素の model_dump() は citations: null と text: null を足してしまい、APIに拒否されます。同種の400として、返っていないフィールドを足したブロックを送ると Extra inputs are not permitted という検証エラーが出ます。
もう一度要約する
すでにブロックで始まる会話をさらに縮めたいときも、同じ compaction を送ります。新しいブロックは、古い要約とその後のやり取りをまとめて要約します。以後は最新のブロックだけを送ります。
会話のループに組み込む
実際のアプリでは、ターンごとにトークン量を見て、上限を超えたら要約を頼むループにします。サンプルは次の流れです。
- 各ターンの後、直近の応答の
input_tokensとoutput_tokensを足す。次のリクエストは返信も一緒に送るため - 合計が上限を超え、まだ次のターンが残っているなら、同じモデル・同じ
systemでcompactionリクエストを送る stop_reasonを確認し、"compaction"なら履歴を返ってきたメッセージ1つに置き換える
COMPACT_AT_TOKENS = 2500 # 実運用では入力予算の近くに置く
# ...各ターンの応答を history に追加した後...
tokens = response.usage.input_tokens + response.usage.output_tokens
if tokens > COMPACT_AT_TOKENS and turn < len(QUESTIONS):
summary = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
system=SYSTEM,
betas=["compact-2026-09-04"],
messages=history,
compaction={"type": "summarize"},
)
if summary.stop_reason == "compaction":
history = [{"role": "assistant", "content": summary.content}]サンプルの上限2,500トークンは、短い会話でも要約が走るように低くしてあるだけです。自分のアプリでは、実際の入力予算の近くに置きます。
stop_reason の確認を、ブロックを探す前に置いている点が要です。要約が作られなかったときも応答は200で、content が空になるためです。要約が返らなかったループは、履歴をそのまま保ち、次のターンの後にもう一度頼みます。
上限の測り方
サンプルは input_tokens + output_tokens で足していますが、プロンプトキャッシュを使っているときは、input_tokens に最後のキャッシュブレークポイント以降のトークンしか入りません。その場合は cache_read_input_tokens と cache_creation_input_tokens も足します。トークンカウントのエンドポイントに同じメッセージを送る方法もあります。ただしこのエンドポイントは compaction パラメータを無視します。
比べる相手は、コンテキストウィンドウより低く自分で決めた上限です。閾値をモデルの窓に対する比率で決める考え方はしきい値の記事で扱っています。
SDKのツールランナーに任せる
Python / TypeScript / C# / Go / Java / PHP / RubyのSDKにあるツールランナーは、要約リクエストの送信までやってくれます。要約したくなった時点で、ランナーの compact_before_next_turn()(TypeScript・Java・PHPは compactBeforeNextTurn()、C#・Goは CompactBeforeNextTurn())を呼びます。いまのターンとそのツール呼び出しが終わると、ランナーが要約を送り、履歴を返ってきたメッセージに置き換えます。
- ランナーはベータヘッダーを足さないので、ランナーを作るときに
compact-2026-09-04を指定する - Python 1.8.0、TypeScript 0.128.0、C# 12.50.0、Go 1.75.0、Java 2.65.0より前のSDKは、
stop_sequencesなどの拒否されるパラメータを要約リクエストにも載せる。その場合、該当パラメータを設定したランナーは400になる - ランナーの
context_managementにcompactionの編集が入っていると、ランナーは要約を拒否する。1つのランナーでは1種類のcompactionだけを使う
いつ要約するかを決める
要約は、完了したターンの後ならいつでも送れます。判断基準はアプリ側のコードに置きます。
- 直近の応答の使用量から、次のリクエストの大きさを見積もる
- 見積もりを、コンテキストウィンドウより低い自前の上限と比べる
- 超えたら、次のターンの前に要約を頼む
- 上限のほかに、タスクの節目のようにアプリ側で区切りが分かる場所を条件に足してもよい
ツール呼び出しの途中は避けます。呼び出しと結果は同じ側に置く必要があるためです。
要約プロンプトを自分で書く
instructions を渡さないと、APIは組み込みの要約プロンプトを使います。空白でない instructions 文字列(上限16,384文字)を渡すと、その組み込みプロンプトが丸ごと置き換わります。
{
"compaction": {
"type": "summarize",
"instructions": "レシピアプリの設計会話を要約する。合意済みの全エンティティ名とフィールド名、ユーザーの最新の未解決の依頼を必ず残す。ツールは呼ばず、要約テキストだけを返す。"
}
}要約役は、instructions があってもなくても、過去のthinkingを含む会話全体を読みます。書くべきなのは、要約に残すものと、ツールを呼ばないことです。後者を書き忘れると、次に触れる "tool_use" で要約が返らなくなります。指示の書き方の考え方はカスタム要約プロンプトの実装ガイドを参照してください。
要約が返らないときの原因別対処
要約が作られるのは、要約の呼び出しがテキストで正常終了し、ツール呼び出しが無かったときだけです。それ以外は200のまま content が空で返ります。呼び出しは課金され、usage.iterations にも載ります。呼び出し自体ができなかった場合は使用量が0です。
stop_reason には、要約の呼び出しが終わった理由がそのまま入ります。
stop_reason | 原因 | 対処 |
|---|---|---|
"max_tokens" | 原因要約が途中で切れた | 対処max_tokens を増やして再送 |
"model_context_window_exceeded" | 原因要約プロンプトを入れる余地がなかった | 対処instructions を短くするか、メッセージを減らして再送 |
"tool_use" | 原因要約の代わりにツールを呼んだ | 対処ツールを呼ばないよう instructions に書いて再送 |
"refusal" | 原因リクエストが拒否された | 対処要約なしで続ける |
"end_turn" | 原因テキストが返らなかった | 対処要約なしで続ける |
どのケースでも、要約なしで会話を続け、あとでもう一度要約できます。"refusal" のときは、stop_details に拒否のポリシー区分が出ます。
HTTPエラーが返るとき
要約リクエストや、ブロックを含むリクエストは、200でなくエラーで失敗することもあります。多くの400はメッセージに直し方が書かれていて、一部は error.details.error_code が compaction_ で始まります。
| エラー | 原因 | 対処 |
|---|---|---|
529 overloaded_error(compaction_unavailable) | 原因ブロックの生成や読み込み中の一時的なサーバー問題 | 対処リクエストを再試行 |
400 compaction_block_misplaced | 原因要約済みメッセージがブロックの前に残っている | 対処消してブロックを先頭にする |
400 compaction_signature_invalid / compaction_content_mismatch | 原因返却後に signature か content を変えた | 対処返ってきた形のまま送る |
400 compaction_nothing_to_summarize | 原因messages にユーザー・アシスタントの内容がない | 対処1つ以上のメッセージを送る |
400(メッセージが requires anthropic-beta と言う) | 原因要約リクエストにベータヘッダーがない | 対処ヘッダーを足す |
400(compaction が想定外のコンテンツブロックだと言う) | 原因ブロックを含む後続リクエストにヘッダーがない | 対処全リクエストにヘッダーを付ける |
ヘッダー漏れの2つ目は要注意です。メッセージにヘッダーの話が出ないため、ブロックの型が悪いように読めます。ブロックを含む後続リクエストに1本でもヘッダーを付け忘れると、この形の400が出ます。
このほか、ブロックが2つ以上あるリクエストと、ツール結果が未送信のリクエストも、メッセージ付きの400になります。
課金と使用量の数え方
要約の呼び出しは、通常のリクエストと同じく課金され、レート制限にも数えられます。会話全体の消費量は、トップレベルの数字でなく usage.iterations を合算して数えます。ブロックを後続のリクエストで送り返しても、要約分の追加費用は発生しません。
ほかの機能との組み合わせで気をつける点
組み合わせの制約は次のとおりです。
- 同じリクエストに
compactionとcontext_managementは載せられない。しきい値型(compact_20260112)は、署名付きブロックを含むリクエストでは動かない cache_controlをブロックに付けると、要約の後ろにキャッシュのブレークポイントが置かれる。詳しくはCompactionとプロンプトキャッシュを両立させるcache_controlの置き方- 要約範囲にある
role: "system"メッセージも一緒に要約される。効かせたい指示は、次の新しいユーザーターンの直後に、role: "system"メッセージで書き直す - タスクバジェットの
output_config.task_budget.remainingは、compaction付きのリクエストにも、ブロックを含むリクエストにも付けない。付けると400になる。詳細はtask_budgetのremainingでcompaction後の予算を引き継ぐ方法 - 要約対象の画像・ドキュメント・
container_uploadブロック・取得したURLは、ブロックに差し替えた時点で失われる。後のターンで要るものは、書き直すか、再アップロードする
最後の項目は見落としやすい点です。要約はテキストなので、画像の中身を文字で残してくれるとは限りません。必要な画像は、要約後のターンで送り直します。
直近のターンを残す方法とバックグラウンドで要約する方法
同じ方式を土台にした派生パターンが、2つ紹介されています。
- 直近のターンをそのまま残す: 要約リクエストには古いターンだけを送り、残すターンはブロックの後ろにそのまま並べる。ツール呼び出しと結果が別々の側に分かれないよう、区切りを決める
- バックグラウンドで要約する: 会話を完全な履歴のまま続けながら要約を頼み、ブロックが届いてから差し替える
どちらも、この記事の差し替えルールと signature の扱いを前提にしています。まずここで書いた単純なループを動かしてから、必要に応じて足すと確認しやすくなります。
まとめ
オンデマンドcompactionは、compact-2026-09-04 のヘッダーと compaction: {"type": "summarize"} だけで動く、履歴の圧縮手段です。実装で外せないのは、返ってきたブロックをそのまま先頭に置き、要約済みのメッセージを消すことです。エラーにならない失敗が2つあるので、履歴の組み立てはテストで固定しておきます。
ループでは、stop_reason を先に見ます。"compaction" 以外なら履歴を変えず、原因別に max_tokens や instructions を直して再送します。要約タイミングをコードで持てる分、しきい値型より判断は増えますが、ツール呼び出しの区切りやタスクの節目など、アプリの文脈に合わせた圧縮ができます。