運作原理

一段對話在兩輪之間存在哪裡

既然每一輪都是一次自包含的請求,對話其餘的部分在哪?每一輪存下什麼、什麼永遠不存,逐字視窗是什麼,更早的輪次如何被恰好折進滾動摘要一次,單輪仍然裝不下時會發生什麼,以及哪些事實活得比工作階段久。

每一輪都是一次自包含的請求:模型在兩輪之間不保留任何東西,沒進這份請求的,對它而言就不存在。於是有個擺明的問題——如果模型什麼都不記得,那對話在哪?

在我們這邊,分三層,三種不同的壽命。每一輪都是從這三層裡組裝出來的,而層與層的差別不在重要性,在於它們往回搆多遠

三層

one conversation over time
durable memoryfacts about this customer · survives the session, follows them across channelsships to Rotterdamrolling summaryeverything older,folded in oncecursor: never twicerecent turns, verbatim
最近的幾輪逐字進入請求。滑出這個視窗的部分被折進滾動摘要,只折一次——一個游標保證同一筆不會被摘要兩遍。值得留下的事實會被單獨提取出來,活得比整個工作階段還久。

存下什麼,以及什麼永遠不存

每一輪恰好寫兩列:訪客說了什麼,智慧代理答了什麼。兩列都用該使用者自己的金鑰做欄位級加密存放,和他其餘內容的處理方式一致(見隱私即設計)。

不寫的那部分同樣是刻意的。模型發起的工具呼叫、這些工具回傳的原始酬載、它一路做的中間推理——一概不落資料庫。它們只為一輪、在一份請求裡存在過,然後就沒了。留下的對話記錄就是真正說出口的話——這也是為什麼一段存下來的對話能被人直接讀,不必在機器的草稿紙裡跋涉。

逐字視窗

最近的若干輪逐字進入請求——目前是最後十二則訊息。正是這一層讓智慧代理感覺像在跟你對話:指代能解析,你的更正它記得住,「第二個」是有所指的。

視窗用則數而不是 token 預算來劃,是刻意的。則數是可預期的——你能推斷出智慧代理眼前還剩什麼。當這個視窗本身也裝不下時,請求層級的優先順序接管(見裝不下時先丟什麼),這些訊息裡最舊的先走。

滾動摘要

比視窗更早的內容不會被丟掉。一旦累積的訊息超過視窗容量,多出來的那些——最舊的——就被折進這個工作階段的摘要,同時一個游標向前推過它們

游標是這裡值得知道的部分。摘要不是每次都從整段對話重新產生,而是增量續寫:既有的摘要,加上剛剛滑出視窗的那幾則,產生新的摘要,而那幾則再也不會被摘要第二遍。正是它讓長對話的維護成本不隨長度上漲,也解釋了摘要為什麼是逐漸沉澱而不是被反覆改寫——早期的脈絡保持著它第一次被記下的樣子。

摘要有上限(幾百個 token),存在工作階段上,和其餘內容一樣加密。在請求裡它是歷史上方的一塊。

單輪仍然裝不下的時候

上面兩個機制是穩態下的。單獨一輪仍然可能太大——一個很長的問題、貼進來一份大文件、知識庫這一輪貢獻得格外多。

這時請求層級的預算會先丟最舊的歷史,而被它丟掉的那些輪次會被壓進這一輪的摘要區塊,而不是單純移除。這個差別要緊:丟棄是悄悄失去內容,壓縮只失去措辭。沒有任何東西為了讓請求裝下而被悄悄刪掉。

什麼活得比工作階段久

上面這三層都屬於同一段對話。有兩樣東西不屬於:

  • 關於這位客戶的長期事實——隨著對話發生被提煉出來,去重之後同一筆事實是被更新而不是不斷堆積,取用時按相關度而不是新舊。它們跟著這個人跨工作階段、跨通路。怎麼提煉、回想什麼,見每位客戶專屬的記憶
  • 一份簡短輪廓——定期從近期訊息重新整理,攜帶的是長期背景而不是具體事實。

所以新開一個工作階段確實是一段全新的對話——沒有歷史、沒有摘要——但它不是失憶的。智慧代理關於這個人學到的東西還在。

這對你意味著什麼

  • 工作階段就是「保持同一個 session id」的那一段。 網頁小工具為每位訪客保留一個;API 呼叫方自己決定。開一個新的,就是在不丟掉客戶資訊的前提下,主動放下當前這條線索。
  • 長對話是逐漸退化,不是突然失效。 最舊的輪次先失去措辭,再失去內容要點,最後才徹底消失。
  • 匯出和記錄看到的是對話,不是機器的運轉過程。 因為工具往返從來沒有落資料庫,你讀回來的就是一個人會讀到的東西。