Appearance
對話太長怎麼辦?五層壓縮管線
先理解問題
想像你和 AI 在一間很小的辦公室裡工作。桌上擺得下大約 50 萬個中文字的資料(200K tokens)。你們聊了 300 個來回,閱讀了 50 個檔案,執行了 100 個命令——桌上已經堆滿了。
如果你繼續往桌上放東西,東西會掉到地上(API 報錯「上下文太長」),工作就中斷了。
怎麼辦? 你需要定期整理桌面:把不再需要的參考文件收起來、把散亂的筆記整理成摘要、把佔空間的大型報表丟掉。
Claude Code 把這個「整理桌面」的過程設計成一條五層管線,從最便宜到最貴,依序嘗試。
為什麼是「五層」而不是「一步到位」?
先看整體的管線流程圖:
因為每種整理方式的成本不同:
| 層次 | 操作 | 類比 | 成本 |
|---|---|---|---|
| 1. Tool Result Budget | 把大型工具結果替換為引用 | 把厚厚的列印報表換成一張「報表在第三層書架」的便條 | 免費(純文字替換) |
| 2. Snip | 直接移除最舊的訊息 | 把最早的參考文件直接丟掉 | 免費(刪除操作) |
| 3. Microcompact | 清除舊工具結果的內容 | 把已經讀過的參考文件內容擦掉,只留標題 | 免費(文字替換) |
| 4. Context Collapse | 把一段舊對話折疊成摘要 | 把三頁會議記錄縮成三行重點 | 中等(可能需要 API) |
| 5. Autocompact | 整個對話重新摘要 | 請助理重新整理所有資料,寫成一份精華報告 | 很貴(完整 API 呼叫) |
核心原則:先嘗試免費的方法。如果免費方法就能把桌面清到夠用,就不需要花錢請助理寫摘要。
第一層:Tool Result Budget(結果預算)
場景
AI 讀了一個 5000 行的檔案。這個讀取結果佔了大量空間。但 AI 已經從中提取了它需要的資訊,原始的 5000 行已經不需要了。
做法
把大型工具結果替換為一個引用:
原本:[5000 行的檔案內容]
替換後:[此結果已儲存到磁碟,需要時可重新讀取]搭配的 Prompt 設計
在系統提示詞中有這句關鍵指引:
When working with tool results, write down any important information
you might need later in your response, as the original tool result
may be cleared later.翻譯:「看到工具結果時,把重要資訊寫到你的回覆裡,因為原始結果之後可能會被清掉。」
這是一個讓 AI 主動「做筆記」的策略。 AI 看到 5000 行的檔案後,會在自己的回覆中寫下:「第 42 行有一個 TODO,第 156 行定義了 UserService,第 300-350 行是錯誤處理邏輯。」這些筆記佔的空間遠小於原始的 5000 行,而且在之後的對話中仍然可用。
實戰思考
如果你的 AI 應用有「讀取大量資料」的功能(比如讀資料庫、讀文件),試試在 system prompt 中加入類似的「做筆記」指引。這樣即使你之後清除了原始資料,AI 的筆記仍然保留了關鍵資訊。
第二層:Snip(歷史修剪)
場景
你和 AI 聊了 3 小時,前 2 小時在討論 A 功能,最後 1 小時在討論 B 功能。A 功能已經完成了,那 2 小時的對話不太可能再被用到,但它們佔了桌面的 60% 空間。
做法
Snip 像是翻書——直接把前面的章節撕掉,只保留後面還在用的部分。它不需要任何 API 呼叫,就像你把紙直接丟進垃圾桶一樣簡單快速。
一個容易忽略的細節:「釋放多少」的補償計算
這裡藏了一個很容易踩到的坑,讓我解釋。
Claude Code 有一個「token 計數器」,用來判斷「桌面還剩多少空間」。這個計數器的資料來源是最後一次 API 回應中附帶的使用量數據——API 會告訴你「這次請求的上下文用了多少 token」。
但 Snip 是在本地操作的——它直接移除了訊息,API 完全不知道發生了什麼。所以 token 計數器仍然顯示舊的(更高的)數字。
這就像你已經搬走了辦公室裡的一半家具,但登記表還寫著「辦公室已滿」,因為沒人去更新登記表。
如果不處理這個問題會怎樣?下一步的 autocompact 會看到計數器顯示「空間不足」,然後花一大筆錢去做完整壓縮——但其實 Snip 已經釋放了足夠的空間!
解法:Snip 操作後,手動記錄「我釋放了多少 token」,傳遞給 autocompact:
typescript
snipTokensFreed = snipResult.tokensFreed
// 後面的 autocompact 會在判斷時扣掉這個值這就像搬完家具後,你在登記表上寫一個註記:「已搬走 3 張桌子,空間比顯示的多 30%。」
實戰思考
在你的系統中,有沒有「多個元件共享一個計數器/狀態」但「元件各自獨立修改資料」的情況?這種情況很容易出現不一致——一個元件改了資料但沒更新計數器。確保每個修改資料的操作都能正確反映在共享狀態中。
第三層:Microcompact(微壓縮)
場景
AI 在對話早期用 Grep 搜尋了大量檔案,結果佔了好幾萬個 token。但 AI 早就處理完了,那些搜尋結果不再需要。
做法
只清除特定工具的舊結果:
typescript
// 只壓縮這些工具的結果
const COMPACTABLE_TOOLS = new Set([
'Read', // 檔案讀取結果
'Bash', // 命令執行結果
'Grep', // 搜尋結果
'Glob', // 檔案搜尋結果
'WebSearch', // 網路搜尋結果
'WebFetch', // 網頁內容
'Edit', // 檔案編輯結果
'Write', // 檔案寫入結果
])清除後的替代文字:
[Old tool result content cleared]為什麼不清除所有工具的結果?
因為有些工具的結果很小但很重要——比如 AskUserQuestion 的結果(使用者的回答)。清除使用者的回答會導致 AI 忘記使用者說了什麼,這比佔一點空間的代價大得多。
第四層和第五層:Autocompact(自動壓縮)
場景
桌面還是太滿了,前三層免費方法不夠用。需要請 AI 自己寫一份「到目前為止發生了什麼」的摘要。
什麼時候觸發?
Claude Code 有一套精確的閾值計算。用圖來看更清楚:
上下文窗口(200K tokens)
│
├── 預留 20K 給壓縮摘要的輸出空間
│ (為什麼是 20K?因為統計數據顯示
│ 99.99% 的摘要在 17,387 tokens 以內)
│
├── 有效窗口 = 180K
│ │
│ ├── 167K(有效窗口 - 13K 緩衝)
│ │ ↓ 超過這裡就觸發自動壓縮
│ │
│ └── 177K(有效窗口 - 3K 緩衝)
│ ↓ 超過這裡就完全阻塞,強制壓縮壓縮是怎麼做的?
Claude Code 把所有對話訊息送給 AI,加上一個「壓縮指令」:
你的任務是建立一份對話摘要。請包含以下 9 個部分:
1. 主要需求和意圖:使用者想做什麼?
2. 關鍵技術概念:討論了哪些技術?
3. 檔案和程式碼:看了、改了、建了哪些檔案?
4. 錯誤和修復:遇到了什麼問題、怎麼解決的?
5. 問題解決:解決了什麼問題、還在排查什麼?
6. 所有使用者訊息:使用者說了什麼(不是工具結果)?
7. 待辦任務:還有什麼沒做完?
8. 當前工作:被打斷前正在做什麼?
9. 下一步:接下來應該做什麼?一個精妙的技巧:Chain of Thought + 丟棄
壓縮指令還要求 AI 先寫一段「分析」,然後再寫「摘要」:
先在 <analysis> 標籤中整理你的思考過程...
然後在 <summary> 標籤中寫出正式摘要...但在格式化摘要時,<analysis> 部分會被丟掉:
typescript
function formatCompactSummary(summary: string): string {
// 丟掉分析部分——它只是提高摘要品質的草稿
formattedSummary = formattedSummary.replace(
/<analysis>[\s\S]*?<\/analysis>/, ''
)
}為什麼要「先寫分析再丟掉」? 因為研究表明,AI 在「先思考再回答」(Chain of Thought)時,回答品質會顯著提升。但分析本身不需要保留在上下文中——它已經完成了使命(提高摘要品質),保留只是浪費空間。
用一個比喻:這就像考試時在草稿紙上演算,然後把答案抄到答案卷上,最後把草稿紙丟掉。草稿紙幫你算對了答案,但考完後就不需要了。
壓縮的「斷路器」
這是一個從真實事故中學到的設計:
typescript
// 3 次連續失敗後停止重試
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3
// 真實數據:1,279 個會話有 50+ 次連續失敗
//(最高 3,272 次),每天浪費 ~250K API 呼叫發生了什麼? 有些對話處於一種「死鎖」狀態——太長了需要壓縮,但壓縮本身也因為太長而失敗。結果就是:嘗試壓縮 → 失敗 → 再嘗試 → 又失敗……無限循環。有一個會話連續失敗了 3,272 次。
解法就是「斷路器」——一個電力工程的概念。家裡的保險絲在電流太大時會自動斷開,防止電線燒毀。同理,autocompact 在連續失敗 3 次後就會自動停止,防止無限消耗 API 呼叫。
實戰思考
你的 AI 應用中有沒有「可能無限重試」的操作?不管是 API 呼叫、工具執行還是錯誤恢復,都應該設定一個最大重試次數。用真實數據來決定這個數字——不是拍腦袋,而是看看過去的失敗模式。
壓縮的「不要用工具」保護
壓縮是用一個子 AI 執行的。這個子 AI 繼承了主 AI 的所有工具(為了快取共享),但壓縮時不應該呼叫任何工具——它只需要寫文字。
問題是:有些模型版本會在壓縮時嘗試呼叫工具(大概是覺得「我需要讀這個檔案才能寫好摘要」)。所以壓縮指令的開頭寫了一段非常強硬的警告:
CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.
- Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.
- You already have all the context you need in the conversation above.
- Tool calls will be REJECTED and will waste your only turn —
you will fail the task.為什麼這麼強硬? 因為壓縮的子 AI 只有一次機會(maxTurns: 1)。如果它浪費這次機會去呼叫工具(然後被拒絕),就沒有第二次機會了,壓縮就失敗了。原始碼的註解記錄了這個問題在 Sonnet 4.6 上的發生率是 2.79%——在每天數百萬次壓縮的規模下,這個比率是不可接受的。
整個管線的執行順序
每次送出 API 請求前:
1. Tool Result Budget → 免費,O(n) 遍歷,替換大型結果
↓ 可能釋放了足夠空間 → 不需要後續步驟
2. Snip → 免費,O(1) 刪除最舊的訊息
↓ 可能釋放了足夠空間 → 不需要後續步驟
3. Microcompact → 免費,O(n) 遍歷,清除舊工具結果
↓ 可能釋放了足夠空間 → 不需要後續步驟
4. Context Collapse → 中等成本,可能需要 API
↓ 可能釋放了足夠空間 → 不需要後續步驟
5. Autocompact → 最貴,完整的 API 呼叫
↓ 如果這也失敗了 → 斷路器 → 停止嘗試實戰思考
想想你的 AI 應用在「上下文快滿了」的時候可以做什麼。不要只有一種策略(比如「直接壓縮」),而是設計一條從便宜到貴的管線。先清除不需要的資料,再嘗試壓縮。
整體回顧:你可以帶走的 5 個原則
| 原則 | 一句話說明 |
|---|---|
| 多層策略,成本遞增 | 先用免費方法,不夠再花錢 |
| 讓 AI 主動做筆記 | 告訴 AI「這些資料會消失,先記下重要的」 |
| 用數據定閾值 | 「20K tokens」來自 p99.99 統計,不是拍腦袋 |
| 斷路器防無限循環 | 連續失敗 N 次就停止,不要無限重試 |
| Chain of Thought + 丟棄 | 讓 AI 先思考再回答,然後丟掉思考過程 |