運作原理

為什麼脈絡一長,智慧代理就變笨

把「脈絡腐化」講具體。每一輪都是一次自包含的請求;請求一長、或者塞滿工具往返,答案的準確率就實測下降。這一頁講清請求裡裝著什麼、裝不下時先丟什麼,以及我們為什麼在作答前把它重新算繪一遍,而不是讓模型去總結工具結果。

週一還答得好好的智慧代理,週五開始說話含糊。二十則訊息之前遵守的指令,它不遵守了。它呼叫了工具、拿回正確的資料,然後寫出一個無視其中一半內容的答案。不報錯、不留日誌,對話看起來一切正常。這通常被稱為脈絡腐化(context rot),而最常見的那個解釋——「脈絡視窗滿了」——不是我們測出來的結論。

一輪對話,從頭到尾

一輪從一次請求開始,在工具執行期間變胖,然後在作答之前被重建。中間那個狀態最值得看:絕大多數智慧代理正是拿它來作答的。

one turn
sentsystemhistoryquestionafter the tools ransystemhistorytool callresult 38 kBtool call · resultdropanswered fromsystemrecapfacts foundquestionanswer
請求送出,工具執行、鷹架不斷堆積,然後最終答由一份重新組裝的乾淨請求產生——系統指令、一小段上文、本輪查到的事實,以及問題。

每一輪都是一次自包含的請求

模型在兩輪之間不保留任何東西。對面沒有工作階段,不記得一小時前說過什麼,也沒辦法自己去查任何沒交到它手上的東西。每一輪,平台都組裝出一份完整的請求整體送過去;回覆只由這份請求產生,沒有別的來源。

如果你有網路協定方面的直覺,這很接近一個資料報:自包含、送出去一次、帶齊接收方需要的一切,並且受一個發送方必須遵守的大小限制。有兩處這個類比不成立,而且兩處都要緊:

  • 傳輸中不會掉東西。 你送的全都到了。變的是模型讀進去了多少——遺失發生在接收方內部,不在線路上。
  • 排布會改變答案。 路由器不會因為你重排了酬載就換個行為,模型會。同樣的事實、同一次請求、兩種排法,一種給出正確答案,另一種給出錯的。後面大半內容都是由這個結果引出來的。

請求裡裝著什麼

one requestsent whole, every turn
系統
智慧代理的人格與任務永不丟
它是誰、絕對不能做什麼、這次在辦的事
技能索引永不丟
每個技能一行,它才知道自己能載入什麼
你的知識庫永不丟
你的文件這一輪貢獻出來的片段
檢索到的片段第 2 批裁
搜尋回傳的內容,分數高的在前
客戶記憶第 2 批裁
關於這位客戶的長期事實
滾動摘要第 3 批裁
更早那些輪次講了什麼
客戶輪廓第 3 批裁
偏好與長期背景
回覆語言永不丟
問題之前的最後一塊,才不會被前面蓋過
歷史
更早的輪次第 1 批裁
原文,從最舊的開始出局
使用者
本輪的問題永不丟
以及隨它上傳的圖片或檔案
脈絡視窗 − 預留輸出 − 餘裕 = 一次請求可以用掉的量

順序就是模型看到的順序。右側那一欄是裝不下時平台採用的優先順序;名次是相對的,不代表固定丟掉幾塊。

歷史以上的全部內容是同一則系統訊息。這是刻意的:智慧代理的邊界、你的知識庫和回覆語言都不是對話,而模型會因為它們擺在哪裡而區別對待。

裝不下的時候,先丟什麼

超出預算的請求不是從尾巴上截斷,而是按優先順序一層層丟,直到裝得下:

  1. 最舊的歷史先丟——而且被丟掉的輪次會被壓進滾動摘要,不是扔掉,所以說過的東西留下來了,只是措辭沒留下。
  2. 先丟分數最低的記憶,再丟分數最低的檢索片段——從尾端開始丟,最相關的材料最後才走。
  3. 然後是輪廓,再來是摘要。
  4. 絕不丟智慧代理的指令、你的知識庫和問題本身。 萬一光是問題就撐爆了預算,它會被截斷並標明已截斷——告訴模型,而不是讓它去猜一句話為什麼斷在半個詞上。

這個順序是脈絡預算裡誠實的那一部分:總得有東西要走,而一個不肯說清順序的系統,只會悄悄丟掉碰巧排在最後的那些。

裝得下不等於讀得進

意外正在這裡。我們造了一個測試,每種條件下答題所需的事實都完整在場——沒有任何東西被丟棄、被截斷,哪裡都不缺資訊。唯一變的是請求怎麼排布。任務是在幾百筆記錄裡做多條件過濾,分別打我們自己的本地模型和一個雲端模型,每種條件跑若干次:

同樣的事實怎麼排布正確
一則乾淨的訊息,只裝相關的那幾列8 / 8
完全相同的那幾列,改由工具結果回傳0 / 8
整份記錄集,外加五份無關的工具結果0 / 6
把上面那堆累積重新算繪成一則乾淨訊息8 / 8

兩個對照排除了顯而易見的解釋。把那則乾淨請求拆成兩則等長的訊息,仍然 8 / 8,所以變數不是長度;把蒸餾後的那幾列改用工具結果遞回去,仍然 0 / 8,所以也不是「資料得離問題更近」。工具鷹架和無關材料無論擺在哪,都在搶模型的注意力。

雲端模型更穩,但並非免疫:多數任務上它扛住了,可一旦請求裡塞進一輪累積的工具結果,單點查找也掉到 0 / 3。

有兩條限制必須說明。這是我們自己的測試、自己的任務,不是公開基準。而且它不會提升模型的能力上限:同一任務加難一檔後,所有排布下都是零分,因為那個模型根本做不了。重排請求找回的是排布本身害你損失的準確率,買不到模型沒有的能力。

幹活和作答,是兩次不同的請求

這就是頁面開頭那個動畫在講的分界。

幹活需要那份亂的請求。模型必須看見自己的工具呼叫和原始結果,才能決定要不要再呼叫一個。這一段沒變。

作答不需要。工具跑完之後,平台重新組裝一份請求——同樣的系統指令、一小段對話上文、本輪真正查到的事實,以及問題——你收到的回覆由它產生。工具呼叫、原始酬載和中間推理都不會出現在裡面。

上文之所以要留,是因為真實的問題常常就是一句「是的」或者「那第二個呢」。測得最好的那種單則訊息排布是單輪的,而對話不是;上文被折進同一則訊息,而不是還原成獨立的幾輪,實測沒有代價,卻讓指代仍然接得上。

我們刻意不做什麼

對付工具輸出撐爆脈絡,業界常見的解法是讓一個模型先把每筆結果總結一遍。我們不這麼做,理由就是上面那個測試:拿滿分的那一檔用的是原樣資料重新算繪——不需要任何總結步驟就把準確率找回來了。加一個總結步驟,等於請一個模型去判斷哪些事實要緊,而這套系統的整個設計原則就是不把程式能精確完成的活交給模型。一個恰好丟掉了客戶所問那個數字的總結器,失敗時悄無聲息,看起來還像個好答案。

當事實確實裝不下時,它會被截斷,並且在請求裡寫明已截斷,附帶一條不許自行補完缺失部分的指令。一個說「我只看到了前四十筆訂單」的智慧代理,比一個自信地編出其餘部分的智慧代理值錢。

這對你意味著什麼

沒有任何東西需要設定。上面這些就是平台上每一個智慧代理的運作方式。

它在實際使用中改變的是:

  • 長對話仍然可用。 第二十輪和第一輪的組裝方式一樣,滑出視窗的部分以摘要形式留下來,而不是憑空消失。
  • 回傳大量資料的工具不會毒化回覆。 結果有封頂,而最終答由一份已經不帶原始酬載的請求寫出。
  • 呼叫過工具的一輪多花一次模型呼叫——短回覆上大約一秒。跑這一段時聊天介面會顯示它在做什麼,而不是乾坐著。

如果你確實看到智慧代理無視了某個你確信給過它的東西,有用的問題不是「脈絡視窗是不是太小」,而是「它當時在哪一塊裡,請求裡還有什麼和它擠在一起」。一段對話在兩輪之間存在哪裡回答了這一頁的另一半——既然模型什麼都不留,那我們這邊留著什麼,請求又是從什麼組裝出來的。有據可查的回答講檢索到什麼,每位客戶專屬的記憶講記住什麼,工具與 MCP講那些讓單筆工具結果吃不掉整個視窗的預算。