當使用者問完「台北明天會下雨嗎?」接著說「那高雄呢?」,助理若要求他重講一次,模型能力再強都顯得不聰明。對話記憶解決的是這個落差,但它也同時引入隱私、成本、失效處理與測試污染等新問題。
Twinco 原本是完全單輪的 LINE 助理。加入記憶時,我們沒有把對話落進永久資料庫,而是使用 Redis 保存有限輪數、有限時間的文字歷史。這不是單純的技術選型,而是一組產品承諾:記憶是短期、揮發、可以清除,而且不需要永久保存。
為什麼不是永久資料庫
永久資料庫很方便查詢、分析與備份,但「方便」也正是風險。若產品不需要回看數月前的對話,就沒有理由承擔長期保存使用者內容的責任。
Redis 配合 TTL 提供一個更貼近需求的模型:
- 系統只記住最近幾輪,而不是完整聊天紀錄。
- 使用者停止互動一段時間後,內容自動失效。
- Redis 壓力或淘汰發生時,最壞結果是少了上下文,不是服務停止。
- 永久遙測只記錄 token、延遲、工具狀態等觀測值,不保存訊息全文。
這樣可以把「產品需要的上下文」和「公司想分析的營運資料」分開。前者短暫且含內容,後者長期但不含內容。
TTL 是對話段落的邊界
我們讓每次成功寫入都重設 TTL,因此 TTL 表示的是閒置多久後忘記,而不是一段記憶從建立起最多能活多久。
這個差異很重要。連續聊天時,上下文應該持續有效;使用者隔很久再回來時,舊話題則不該突然影響新問題。若使用固定到期時間,一段正在進行的長對話可能在中途失憶;若完全沒有到期時間,昨天的餐廳問題又可能干擾今天的工作排程。
記憶資料的形狀可以保持很簡單:
[
{
"user": "台北明天會下雨嗎?",
"reply": "明天下午降雨機率較高,建議帶傘。",
"tool": "weather(city=台北)"
}
]
真正重要的不是 JSON 格式,而是讀寫契約清楚:誰能存取、每次保留多少輪、何時刷新 TTL,以及資料壞掉時怎麼退化。
兩層上限控制提示詞成本
只限制「保留幾輪」仍不夠。一輪可以是十個字,也可以是一整篇文章;如果每輪都很長,模型輸入仍然會快速膨脹。
我們使用兩種彼此獨立的限制:
- 單則文字上限:限制使用者訊息與 AI 回覆各自能帶進歷史的長度。
- 整份歷史上限:超過總量時,從最舊的輪次開始移除。
輪數負責讓產品方案容易理解,字元數負責讓工程成本有硬邊界。兩者缺一不可。
量測也顯示,對話記憶的主要成本通常不是使用者輸入,而是 AI 自己的回覆。使用者可能只打十幾個字,模型卻回數百字;因此若要壓低歷史成本,控制回覆長度往往比截斷使用者問題更有效。
工具結果只留下可接續的摘要
工具型助理還有另一個選擇:要不要把完整的 function call 與 function response 重播給模型?我們選擇不重播,只保存工具名稱、少量關鍵參數與狀態。
原因有兩個:
- 完整搜尋結果、行程清單或外部 API payload 很容易超過真正需要的資訊量。
- 歷史中的工具呼叫識別資訊若配對不完整,模型可能把已結束的操作誤認為仍在等待處理。
使用者下一句問「那高雄呢?」時,模型需要知道上一輪查的是台北天氣,不需要重讀整份氣象資料。
工具摘要也應該放在使用者那一側的歷史,而不是模型回覆裡。若內部註記看起來像模型曾經說過的話,語言模型很可能模仿那個格式,把觀測資訊原樣輸出給使用者。
記憶服務必須 fail-open
Redis 可能逾時、斷線、資料損壞,或因記憶體壓力淘汰 key。對話記憶不該因此成為整個助理的單點故障。
我們的策略是:讀取或寫入失敗時記錄警告,這一輪視為沒有歷史,繼續回答使用者。這稱為 fail-open。
這個選擇不能套用到所有 Redis 功能。例如用量配額若 fail-open,可能直接變成無上限使用;那種情況通常應該 fail-closed。判斷標準不是「Redis 掛了怎麼辦」,而是各功能失敗後的業務代價:
| Redis 用途 | 故障時的代價 | 合理策略 |
|---|---|---|
| 對話記憶 | 使用者要多說一次 | fail-open |
| 金流或用量硬限制 | 可能產生無上限成本 | fail-closed |
| 可重建快取 | 回源較慢 | fail-open |
「重新開始」是必要的產品出口
自動過期仍不夠。只要系統可能記錯,使用者就必須有立即清除上下文的方式。
清除指令應該在配額檢查之前執行,因為它不需要呼叫模型,也不該消耗 token。更重要的是,清除不能只處理文字歷史;若系統另有待處理照片、位置或表單狀態,也必須一起清掉。否則畫面說「已經忘記」,下一輪卻仍被舊座標影響,會比沒有清除功能更難理解。
測試如何避免被記憶污染
加入記憶後,原本獨立的 E2E 情境可能開始互相影響。測試帳號通常會重複使用,而 Redis 記憶可能跨部署存活;上一個天氣情境留下的城市,就可能改變下一個情境的工具選擇。
我們讓每個 E2E 情境開始前都透過公開的重設指令清場,而不是新增只有測試知道的內部端點。這同時驗證使用者真正依賴的出口,也避免多一個需要保護的攻擊面。
此外,至少要有兩條專門情境驗證:下一輪確實使用上一輪上下文,以及重設後確實不再使用。記憶無聲失效通常不會讓既有單輪測試變紅,只會讓使用者覺得助理突然變笨。
可以帶走的原則
對話記憶不是把聊天紀錄塞進 prompt,而是建立一個有明確邊界的短期狀態系統:
- 不需要永久保存的內容,就不要落進永久資料庫。
- 用閒置 TTL 定義自然的對話段落。
- 同時限制輪數、單則長度與歷史總量。
- 工具往返只保留下一輪真正需要的語意摘要。
- 記憶故障時讓服務退化,不讓服務中斷。
- 提供使用者可理解、可立即執行的清除出口。
模型負責理解語言;記憶系統負責決定哪些語言值得被帶到下一輪。兩者分工越清楚,產品越容易控制成本,也越能對隱私承諾負責。