# 讀懂 Pi 的上下文壓縮機制

> 📖 本站完整內容索引（documentation index）：[llms.txt](/llms.txt)

> 原作者：LanLance (@LanLance24) · 策展與摘要：EasyVibeCoding · 平台：X (Twitter) · 熱度：🔥🔥 · 日期：2026-08-18

> 原始來源：https://x.com/LanLance24/status/2089288099876548757

## 證據與延伸閱讀

- [# 讀懂 Pi 的上下文壓縮機制](https://x.com/LanLance24/status/2089288099876548757)
- [Compaction in Pi](https://earendil.com/posts/compaction-in-pi) — 官方文件
- [Context Rot](https://trychroma.com/research/context-rot) — 一手來源
- [Pi Compaction 文件](https://pi.dev/docs/latest/compaction) — 官方文件
- [Pi Repository](https://github.com/earendil-works/pi) — 官方 Repository

## 中文摘要

# 讀懂 Pi 的上下文壓縮機制

長時間使用 Coding Agent 的人都會碰到同一個問題：上下文越滾越大，效果越來越差。模型每讀一個檔案、每執行一次搜尋、每修改一處程式碼，這些過程和結果都會追加進對話歷史，之後的每一次請求都會完整帶上它們。

上下文變長首先會降低品質。各家模型在大海撈針（NIAH）這類 Benchmark 裡都能拿到接近滿分，長上下文看起來已經被解決了，但 Chroma 的 context rot 研究指出，這類測試只考字面檢索：把一句已知的話埋進一堆無關文字裡，再讓模型把它找出來；而真實任務需要的是語意層面的理解和推理。Chroma 固定任務難度、只增加輸入長度後，測試的 18 個模型效能全部都會隨著輸入變長而下降，而且下降幅度並不均勻。

研究裡有兩個結論，對 Coding Agent 場景尤其致命：

- 一個是干擾項：內容和問題主題相關，但無法回答問題。哪怕只放一條，效能就會明顯下滑；輸入越長，下滑越明顯，而長會話的歷史裡充滿了這種半相關片段。

- 另一個是 LongMemEval 的對比：只提供相關片段時，模型表現很好；但換成完整的 11 萬 token 歷史後，效能就大幅下滑。因為模型被迫在同一輪裡同時做兩件事：先從長歷史中找出相關部分，再根據找到的內容進行推理；檢索和推理會互相擠占資源。

就在前幾天，Pi 發布了一篇部落格，詳細說明他們的壓縮機制（compaction）設計，深入分析上下文如何膨脹、壓縮如何觸發，以及快取需要付出什麼代價等問題。

## 上下文是怎麼一步步膨脹的

Pi 發送給模型的每次請求，都會帶上 system prompt、AGENTS.md、工具定義和完整的對話歷史。舉個具體的例子：你讓模型修正一個 bug，它先呼叫 read 工具查看檔案，Agent 執行完後，會把檔案內容拼回請求裡再送出一次；模型看完後，可能又呼叫一個 grep，來回幾輪才給出答案。一個 turn 裡的每次請求，都會帶上先前的一切，包括你的檔案內容、搜尋結果、模型自己的中間想法等等。

歷史只增不減，越到後面就越昂貴、也越慢。總有一次，下一個請求會超出上下文視窗，返回類似 `Request exceeds the maximum size` 的錯誤；到了這個時候，會話就無法繼續了。

![顯示 Agent 在多輪 request 互動中累積上下文直至超出 context window 限制的結構示意圖](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/5b98748b163d9ac7.jpg)
> 顯示 Agent 在多輪對話與 tool call 互動中，上下文累積導致超出 context window 限制的文字結構圖。

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">request 1:
[system][tools][user]

after request 1:
[system][tools][user][assistant: tool call][tool result][assistant]

request 2:
[system][tools][user][assistant: tool call][tool result][assistant][user]

...

[system][tools][user][assistant][....][tool result][user]
^
exceeds context window</div></details>

## 上下文滿了之後的兩種動作

一種是直接開啟新會話。歷史歸零，先前的決策和還沒完成的事情全部丟失。聽起來損失很大，但結合前面提到的 context rot，糟糕的上下文比沒有上下文更傷害模型的狀態；有時候丟掉歷史反而是更正確的選擇。

另一種就是 compaction：把舊歷史交給一次 LLM 請求壓縮成一份摘要，再用摘要取代原本的內容，為新訊息騰出空間，讓會話得以繼續。

![compaction 前後的訊息結構差異圖](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/819f0dff13c41179.png)
> compaction 前後的訊息結構對比圖，深色背景上顯示兩行橘黃色文字，分別標示 compaction 執行前包含 system + tools、older turns 與 recent retained messages 的訊息排列，以及 compaction 執行後依序為 system、tools、summary、recent turns 與 new user message 的結構。

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">before compaction:
[system + tools][older turns][recent retained messages]

after compaction:
[system][tools][summary][recent turns][new user message]</div></details>

## Pi 的壓縮流程

Pi 會在每個 turn 結束後檢查上下文使用量，接近視窗上限時就會自動觸發壓縮；手動輸入 `/compact` 也可以隨時觸發。除此之外，還有一種兜底機制：當 turn 執行到一半遇到溢出錯誤時，Pi 會當場壓縮後再繼續，避免任務直接報廢。

把觸發時機選在 turn 結束是有原因的。turn 還在執行時，每次請求都是在前一次的基礎上追加，LLM 的快取前綴可以持續重複使用；此時進行壓縮，快取損失最小。

壓縮並不是全部刪除。Pi 會保留最近一段訊息原樣不動，保留多少由可設定的 token 預算控制，預設為 2 萬，約相當於 5 到 20 個 turn。在這之前的內容則全部序列化，交給一次獨立的 LLM 請求進行摘要。這個請求和平常的對話有三處不同：

- system prompt 會變更，告訴模型它是一個上下文摘要助手。

- user message 會變更，要求模型輸出一份結構化摘要，涵蓋目標、進度和關鍵決策。

- 請求本身獨立於會話歷史，因此可以換成較便宜的模型來執行，不會占用主要對話的成本。

摘要會以純文字儲存回會話中。這一點讓摘要具備可移植性：此時即使在 Pi 裡更換模型，新模型也能接著使用同一份摘要，不需要重新壓縮一次。

Pi 本身具備擴充能力，作者在文章最後留下了一個實驗入口：讓 Pi 自己撰寫一個擴充功能來替換預設的壓縮 prompt，就可以測試自己的摘要策略。我把這段理解為作者的態度：compaction 的做法沒有標準答案，目前的實作只是作者認為的最佳實務。

> The ideal outcome of a good summarization for a coding agent is like a handoff briefing from one shift to the next.

我很認同原文這句類比。好的壓縮摘要就像換班交接：舊上下文裡大部分的內容都已經和接下來的工作無關，只需要把仍然有效的部分交代清楚。

## 壓縮的隱藏代價是快取全部失效

Prompt caching 靠前綴精確比對來節省成本，LLM Provider 會對命中快取的部分提供非常低的折扣價格；例如 DeepSeek V4 Flash 命中快取時，基本上就等於不用錢。壓縮之後，保留的那段 turn 內容雖然沒有變，但它前面的前綴變了，從 system prompt 直接接上摘要。前綴一旦改變，之前累積的快取就全部失效；壓縮後的第一個請求需要重新計算，之後的新請求則要重新累積快取。

![快取壓縮前後的 token 結構與重新計算點示意圖](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/e05651174c44fb39.jpg)
> 比較 compaction 前後快取狀態與 token 重算機制的文字流程圖，標示出 cached prefix、reusable、first changed token 以及需要重新計算的位置。

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">cached before compaction:
[system][tools][older history][recent retained turns]
&lt;---------------------- cached prefix ----------------------&gt;

first request after compaction:
[system][tools][summary][recent retained turns][new user message]
&lt;-- reusable --&gt;^
                |
        first changed token
                |
        +-- everything after this point must be recomputed</div></details>

所以壓縮是有隱性成本的：壓縮越頻繁，重建快取的成本就越高。

## 我的取捨

上下文壓縮中有三個彼此牽制的變數：資訊保真度、上下文長度和快取命中率。壓縮得頻繁，上下文確實變短了，但快取會反覆失效，摘要過程也必然會丟失一部分細節；壓縮頻率低，快取命中率雖然提高了，但模型會長時間處在超長上下文中，能力下降和幻覺也會更加明顯。

我的做法是看前後內容的相關性。前面聊過的事情後面還用得上，compaction 才值得做；如果前後話題已經不相關，我會直接 clear，開啟新的會話，連摘要都不需要。摘要丟失的細節，在壓縮時沒有人知道哪些之後還會派上用場；這本質上是一次有損壓縮，細節一旦消失就無法復原。

## 參考

- earendil.com/posts/compaction-in-pi

- trychroma.com/research/context-rot

- pi.dev/docs/latest/compaction

- github.com/earendil-works/pi

🚀歡迎按讚、追蹤，後續持續更新 AI 前沿內容

## 標籤

Agent, LLM, 研究論文, Pi, Chroma
