前幾天分享的Write、Select、Compress,都屬於「一個context window裡該放什麼、放多少」。不去想辦法讓一個 context window 塞進所有東西,而是從架構上把內容或執行過程拆到不同地方,可以是把任務拆給不同的 agent 各自處理,也可以是把執行環境跟模型的 context 分開,這就是 Isolate。
第一種做法,是把一個大任務拆成幾個子任務,交給各自獨立的子代理處理,每個子代理有自己的一份 context window,同時平行處理不同面向的問題。
例如一個「研究某個主題」的任務,可以拆成好幾個子代理,各自負責查不同的子方向,最後再把各自的結論彙整起來。每個子代理只需要看到自己那部分的 context,不需要背負整個任務的所有細節。
這個做法的代價也很直接:總 token 用量可能大幅增加,每個子代理都要各自處理一次自己的 context,加總起來可能是單一模型處理同一件事的好幾倍。這是拿成本換取「任務可以被拆解、平行處理」的能力,什麼時候值得這樣做,取決於任務本身複不複雜、平行處理省下的時間划不划算。
第二種做法,是把體積大、但模型不需要「逐字讀」的資料(例如圖片、音訊、大量原始檔案),丟到一個獨立的執行環境裡處理,模型的 context 裡只留一個「指標」,例如一個變數名稱或檔案路徑,而不是把整包資料塞進對話。
# 概念示意:資料留在沙箱裡,context 裡只留參照
sandbox_state = {"result_image": "/sandbox/output_1.png"}
# 模型看到的只是一句話,不是整張圖片的原始資料
context_note = f"圖片已產生,儲存於 {sandbox_state['result_image']}"
模型不需要看到圖片的原始位元組,只需要知道「有這個東西、放在哪裡」,真正需要用到這份資料時,再由執行環境去讀取、處理。
第三種做法比較細緻:把不同性質的資訊放進獨立的欄位(例如「使用者設定」「任務進度」「工具執行結果」各自獨立),每一步只把當下真正需要的欄位讀給模型看,其他欄位先擱著不動。這其實是把 Select 的概念用在「設計資料結構」這一層,不是每次都把整包 state 攤開給模型看,而是設計成可以按欄位挑選的形式。
Write、Select、Compress 處理的是「單一個 context window 裡的內容」,Isolate 處理的是「一開始就別讓所有東西擠在同一個窗口裡」。四個動作合起來,才是完整的 context 管理思路:先決定什麼要存到外面(Write)、需要時挑對的拉回來(Select)、留在 context 裡的東西盡量精簡(Compress)、從架構上避免所有東西擠在同一個窗口裡(Isolate)。
Isolate 是四個動作裡最偏「架構設計」的,多代理架構、獨立沙箱、state 欄位隔離,都是在一開始設計時就把問題拆解開來處理的做法。
這也跟之後的 Part 4 有關,當 harness 要決定「工具能碰到什麼、不能碰到什麼」時,隔離的概念會再次出現,只是這次談的是安全邊界,不只是 context 的大小。
明天要談一個更貼近成本面的技巧:Write、Select 產出的內容,如果常常是穩定不變的,有沒有辦法不用每次都花全額 token 重新處理一次?