SynapseWire

Claude API 上下文壓縮教學:自己決定何時摘要

9 月 14 日上線的 on-demand compaction。教你怎麼發 compact-2026-09-04、怎麼 swap 簽名區塊,以及它跟記憶體層差在哪。

發布於:
淺色書桌上兩疊紙卡與剪刀,長卷紙被剪短壓在夾板下,冷色日光,沒有商標。
FIG. 01 // 淺色書桌上兩疊紙卡與剪刀,長卷紙被剪短壓在夾板下,冷色日光,沒有商標。

長跑的 Agent 很少死在第一個 tool call。比較常見的死法是:history 越堆越長,每輪都把同樣一段失敗紀錄再送進模型,延遲變差,帳單變肥,回覆開始『忘記』三十分鐘前講過的檔名。

本站寫過應用層記憶系統怎麼搭,那是你自己管向量庫、檔案與檢索。這篇講的是另一件事:Anthropic Messages API 把『把舊對話壓成摘要』做成官方能力。9 月 14 日左右,文件又補上你能主動觸發的 on-demand 路徑(beta compact-2026-09-04)。ML Pills 當週整理把它形容成「狀態管理開始變成模型平台的一部分」。行銷會講得很大,實務上你還是要自己寫 swap,還是要測摘要有沒有漏單號。

兩種壓縮,誰決定時機

官方現在並列兩種 beta,別混在同一個 request 裡。

Threshold compaction(beta compact-2026-01-12)掛在 context_management.edits,edit type 是 compact_20260112。你設一個輸入 token 門檻,預設 15 萬、最低 5 萬。某個普通對話 request 跨過門檻時,API 在這次請求中途自己寫摘要,必要時還能接著回你要的答案。門檻說明寫得很直:適合聊天產品、長時間同一則 thread、或不想自己管『何時該壓』的應用。

On-demand compaction(beta compact-2026-09-04)用頂層參數 "compaction": {"type": "summarize"}。這次請求只回摘要,不回一般助手文字。stop_reason 成功時是 "compaction"。你拿到一個帶簽名的 compaction block,之後用這個 block 取代被摘要掉的那段 history。AI Codex 的實作筆記把差別講清楚:時機由你決定,摘要請求可以跟對話並行。你也可以刻意把最近幾輪原文留在摘要後面。

經驗法則很簡單。聊天室、客服、『使用者自己拖很久』的產品,threshold 通常夠用。你自己寫 Agent loop、不能在摘要時卡住、又想保留最近幾輪的 thinking,用 on-demand。

平台差異也要先看。文件寫 on-demand 可用於 Claude API、Claude Platform on AWS、Microsoft Foundry;Amazon Bedrock 與 Google Cloud 這條路徑目前不可用。模型清單涵蓋 Fable / Mythos 5.1 與 5、Opus 5 與 4.x 系列、Sonnet 5 / 4.6 等;正式上線前用 Models API 看 capabilities.compaction 比抄部落格可靠。

On-demand:發請求、換區塊

最小流程只有兩步:要摘要,再 swap。

import anthropic

client = anthropic.Anthropic()
SYSTEM = "你是食譜 App 的資料模型助理。"

history = [
    {"role": "user", "content": "幫我定 Recipe、Ingredient 的主實體。"},
    {"role": "assistant", "content": "先留 Recipe、Ingredient、Step,中間用 RecipeIngredient 記份量。"},
    {"role": "user", "content": "好,接著建議 Recipe 的欄位名。"},
]

summary = client.beta.messages.create(
    model="claude-opus-5",
    max_tokens=8000,
    system=SYSTEM,
    messages=history,
    betas=["compact-2026-09-04"],
    extra_body={"compaction": {"type": "summarize"}},
)

assert summary.stop_reason == "compaction"
# 之後每個 request 都把 summary.content 原樣放回,簽名不能改
history = [{"role": "assistant", "content": summary.content}]

幾條 API 會直接拒的規則,寫進程式比讀到錯誤再猜快:

  • 最後一輪 assistant 還停在未完成的 tool call,先送 tool result 再壓縮。
  • 同一個 request 不能同時帶 context_management(兩種壓縮互斥)。
  • 某些 tool_choice(any / 指定工具)、結構化輸出格式、stop_sequences 會擋這次摘要請求。
  • 對話本身還是得塞進當下模型的 context window;等到爆窗再壓,已經來不及。

Swap 時最容易犯的錯是『摘要還在跑,Agent 又多講了兩輪』。正確做法是記下送進壓縮請求時的訊息長度 sent_count,回來後:

block = {"role": "assistant", "content": summary.content}
history = [block] + history[sent_count:]

只丟掉當初被摘要的那段;壓縮期間新長出來的 turns 留著。官方也強調:compaction block 必須出現在 messages 最前面;前面還留舊訊息會拿到 compaction_block_misplaced。反過來,若把已摘要的原文仍留在 block 後面,API 不一定報錯,你只是再付一次那些 token。

再次壓縮時,對『已經以 block 開頭』的 history 再發一次 compaction。新 block 會吃掉舊摘要加上後面新增內容;之後只保留最新那個 block。

留尾巴、背景壓、自訂摘要詞

長 Agent 常需要『舊工作用摘要、最近兩輪用原文』。Keep recent turns 的做法是:送進壓縮請求的訊息不要包含你想保留的尾巴;拿到 block 後,順序是 block → 保留的原文 turns → 新的 user。

這跟 preserved thinking 有關。文件說,在支援保留 thinking 的模型上,只要你保留的那幾輪原本就緊接在被摘要區段之後,且 system 與非 defer_loading 的 tools 沒改,那些 thinking block 還能過檢查。你若在壓縮請求與 swap 之間偷偷改 history、或先改 system 再硬留尾巴,thinking 驗證會炸,常見結果是 400。

背景壓縮的頁面在這裡。概念是:當 token 估算超過你的門檻,用執行緒或佇列另開一個帶 compaction 的請求,主迴圈繼續用完整 history 對話;pending 完成後再 swap。代價是壓縮期間你同時開著兩條請求,都算帳、都吃 rate limit,而且完整 history 還會繼續長。所以要在『還有餘裕』時啟動,不是在只剩幾千 token 才按。

自訂摘要用 instructions。它整份取代預設摘要提示,不是附加。實務上一定要寫『不要呼叫工具,只輸出摘要文字』,否則模型可能對你宣告過的 tools 發 tool_use,結果你拿到的是空內容或錯的 stop_reason。客服場景可以明確要求留下帳號 ID、訂單號、已嘗試步驟與未解問題;寫程式 Agent 則要求留下檔案路徑、未通過的測試名、已否決的方案。

Threshold:聊天產品的懶人路徑

若你只是想在一般 messages.create 裡讓 API 自己管,threshold 比較短:

response = client.beta.messages.create(
    betas=["compact-2026-01-12"],
    model="claude-opus-5",
    max_tokens=4096,
    messages=messages,
    context_management={
        "edits": [
            {
                "type": "compact_20260112",
                "trigger": {"type": "input_tokens", "value": 100000},
            }
        ]
    },
)
messages.append({"role": "assistant", "content": response.content})

回傳裡若出現 type: "compaction" 的 content block,下一輪照樣 append 整段 response.content。API 收到後會忽略 block 之前的內容。

需要『摘要後先插回最近幾則原文』時,開 pause_after_compaction: true。此時 stop_reason 會是 "compaction",回應裡通常只有摘要 block;你重組 messages 後再發一次繼續。官方範例甚至用壓縮次數 × 門檻去估累計消耗,超過總預算就塞一句『請收尾』,這比幻想無限 context 務實。

Prompt caching 可以一起用:把 cache_control: {type: ephemeral} 放在 system 末尾,以及 compaction block 上,避免每次壓縮把整段 system 又寫進新 cache。細節仍以門檻文件為準。

帳單、失敗樣態、摘要會丟掉什麼

壓縮是另一次取樣。Threshold 路徑下,頂層的 input_tokens / output_tokens 不含 compaction iteration;你要加總 usage.iterations 裡 type: "compaction" 與 type: "message"。On-demand 成功時頂層 token 常是 0,真實用量同樣在 iterations。還用舊邏輯只讀頂層欄位的成本儀表,會少算一截。

On-demand 失敗常常還是 HTTP 200、content 空。一定要看 stop_reason:

stop_reason常見意思
max_tokens摘要被截斷,提高 max_tokens
tool_use模型去叫工具了,instructions 加禁止
model_context_window_exceeded連摘要提示都塞不下
refusal / end_turn拒答或沒產出,重試或暫時不壓

還有 529 + compaction_unavailable 這種可重試錯誤。忘記帶 beta header 時,錯誤訊息可能只說 compaction: Extra inputs are not permitted,完全不提醒你缺 header,這點值得寫進 on-call 手冊。

摘要範圍內的圖片、文件、container_upload、抓過的 URL,換成 block 之後就沒了。那段裡的 mid-conversation system 訊息也被摘要吃掉,之後不再『生效』。後面還要用的識別碼,請在下一個 user turn 重貼,或根本不要把它們放進可被摘要的區段。

什麼時候仍要自己的記憶層

Compaction 解決的是『這則對話 / 這次 Agent run 怎麼繼續塞進窗口』。它不是向量記憶,不是審計日誌,也不是跨 session 的使用者偏好庫。本站記憶管理教學裡那種『重要事實寫進可查的 store』,該留就留。壓縮跟記憶可以並存:壓縮管 prompt 長度,記憶管『三天後同一個使用者回來』。

同樣地,Herdr 那類終端多工管的是 process 活著;Coder Agent Relay 管的是檔案系統落點。這些都不取代 Messages API 裡的 history 會計。別因為官方多了一個參數,就把 eval 關掉。用同一條長 workflow 壓兩三輪,比對任務成功率、工具正確率、延遲與 token,再決定要不要扔掉自寫 summarizer。

先做這五件事就夠:選對 beta;on-demand 檢查 stop_reason;swap 時 block 放最前、簽名原樣;帳單加總 iterations;摘要丟了什麼用固定評測集驗,別靠感覺。

分享文章 //
READER LETTERS

讀者回應与討論

[ 0 則評論 ]

暫無讀者回應,歡迎發表你的見解。

FURTHER READING

相關文章