Agent 記憶不是一個向量庫:Session、State、Profile 與 Memory Bank
Agent 記憶不是接一個向量庫就結束。把記憶拆成 Session、State、Profile、Memory Bank 四層,才能分清對話暫存、結構化中間結果、跨對話使用者事實,以及可語意檢索的長期經驗——也可治理、可忘記。
一講到「給 Agent 加記憶」,很多人第一反應是接一個向量資料庫。向量檢索很有用,但它只是長期記憶裡的一種技術選項,不是記憶系統的全貌。若你把所有東西——當輪對話、結構化中間結果、使用者偏好、跨週的經驗——都塞進同一坨 embedding,系統會在三個地方同時失敗:上下文膨脹、狀態不可追溯、以及「明明說過是素食者,新對話又推薦牛排」。
這篇文章是「Agent 系統工程」系列的第一篇。我想先把記憶拆成四層可實作的概念:Session、State、Profile、Memory Bank。框架可以是 Google ADK 或你自己的 runtime;分層邏輯是通用的。
為什麼「只有聊天紀錄」不夠
聊天機器人把歷史訊息往 context 裡堆,短期看起來很直覺。Agent 一旦開始呼叫工具、改狀態、跨步驟執行,就會暴露三個問題:
- 重讀成本:模型不該為了找一個確認碼,重讀十五則訊息。
- 邊界模糊:哪些是「這次行程的暫存」,哪些是「這個人長期是素食者」?
- 重啟即失憶:行程重開、容器重啟、新開一個對話 ID,若沒有持久層,一切歸零。
所以記憶不該是單一儲存,而是依生命週期與讀取型態分層。
第一層:Session——對話串本身
Session 就是一段連續對話,像手機裡的一個聊天室。同一 Session 內,Agent 應看得到先前輪次;新開 Session 則預設是陌生人——除非你另外接了跨 Session 的層。
常見翻車 Demo:使用者說「幫我規劃兩天里斯本之旅」,代理排好第一天;第二輪「規劃第二天」若開了新 Session,代理會忘掉城市與第一天內容。修好的方式往往不是換更大模型,而是在多輪之間重用同一個 Session ID。
Session 裡通常有兩類東西:
| 元件 | 比喻 | 內容 |
|---|---|---|
| Events | 逐字稿 | 使用者訊息、代理回覆、工具結果、甚至圖片等媒體輸入 |
| State | 便利貼 | 結構化、可快速讀取的中間結果(餐廳名、確認碼、已選項目) |
Events 提供完整上下文;State 提供高效索引。你不會希望每次推理都從長篇對話裡「考古」一個欄位。
寫 State 的硬規則
很多框架會給 tool 一個 context。正確做法是透過框架提供的 API 寫入 state,讓變更進入事件日誌,之後才能被持久化與還原。
反模式:在工具函式裡直接改 session 物件。變更可能當下看得到,卻沒進 log,重啟或換儲存引擎後就消失,除錯時像鬧鬼。
短期 Session 記憶很像 RAM:快、暫存、預設不保證跨行程存活。
第二層:可插拔的 Session 儲存——讓對話活過重啟
「同一對話不要斷」與「同一個人換聊天室還認得你」是兩件事。先解決前者:把 In-memory Session Service 換成寫進磁碟或資料庫的 Session Service。
| 儲存型態 | 特性 | 典型用途 |
|---|---|---|
| 記憶體內 | 快、重啟清空 | 本機實驗 |
| 資料庫/檔案 | 自管、重啟可載入完整歷史與最新 state | 要可靠的產品雛形 |
| 雲端托管 Session | 與企業記憶/合規能力綁定 | 正式環境 |
工程上理想狀態是:代理邏輯不變,只抽換底下的 Session Service。用 get_or_create_session(session_id):有就接續,沒有就新建。使用者帶著同一個 Session ID 回來時,歷史與 state 一併載入,對話從斷點繼續。
這層回答的問題是:「這個對話關掉重開,還在不在?」
第三層:Profile——跨對話的結構化事實
新的 Session ID、下週再來一句「嗨,幫我規劃行程」——此時沒有舊對話可讀。你需要的不是把所有舊 chat 塞進 prompt,而是一個精簡、可查詢的使用者檔案:
- 飲食:素食
- 偏好:博物館多於夜店
- 交通:寧可公共交通
實作上可以很樸素:一張表,(user_id, key, value)。代理配兩個工具:
- Recall:對話開始先讀該使用者全部(或相關)偏好。
- Save:使用者說出新事實時,結束前寫回。
再用 instruction 把行為寫死成腳本:先 recall → 規劃時使用 → 呈現方案 → 問是否有新偏好要記 → 有則 save。
Demo 很有說服力:第一輪沒有偏好,規劃完主動問;使用者說「請記住我是素食者」→ save;模擬重啟、全新 Session ID 再來,代理一開場 recall 到 dietary=vegetarian,直接個人化。
這層回答的問題是:「這個人換了一個對話,我是否仍知道不該忘記的事實?」
Profile 適合穩定、低基數、高確定性的鍵值;不適合把整段旅遊日記當 value 亂塞。
第四層:Memory Bank——跨時間、跨媒體的語意封存
Session 管活躍對話;Profile 管結構化真相;當你需要的是:
- 三週前那張建築照片裡的細節
- 某次直播音訊裡提過的約束
- 「兩輪車」能搜到腳踏車相關筆記(語意,不只關鍵字)
你需要的是 Memory Service/Memory Bank 這類能力:從對話與媒體中抽取事實、做 embedding、語意檢索,並在新對話用預載工具把相關記憶帶進來。
| Session Service | Memory Service | |
|---|---|---|
| 角色 | 活躍對話的現場 | 長期封存的檔案櫃 |
| 讀法 | 依 Session ID 載入時間序 | 依查詢語意/主題檢索 |
| 失敗模式 | 錯 ID、未持久化 | 檢索噪音、抽事實錯誤、隱私邊界不清 |
簡易 in-memory memory 只適合本機測;生產環境通常要雲端儲存、權限、以及比關鍵字更好的檢索。重點是心智模型:這不是「再貼一張 raw chat 表」,而是「處理內容 → 抽出可重用事實 → 可搜尋」的服務。
四層怎麼一起用(避免疊床架屋)
一個可執行的預設策略:
當輪推理
← Session Events(必要上下文)
← State(結構化中間結果,優先於重讀長對話)
開場 / 需要個人化時
← Profile.recall(user_id)
需要「以前某次經驗」或媒體細節時
← Memory Bank 語意檢索(可預載進 system/tool 結果)
對話中出現穩定新事實
→ Profile.save
步驟完成、工具結果
→ 經 context 寫入 State,並進 Event log
對話結束或檢查點
→ Session 持久化;可選擇將摘要/事實匯入 Memory Bank
反模式速查
- 只用向量庫裝當輪 state → 延遲高、一致性差
- 只靠超長 context 當長期記憶 → 貴、易丟中間、重啟即死
- Profile 與 Memory 不分 → 素食者這種硬約束被語意檢索「差不多」掉
- Tool 偷改 session → 幽靈 bug
與治理的關係(為什麼這對你很重要)
在多代理或人機協作裡,記憶分層也是責任分層:
- Session/State:這次任務的可追溯過程
- Profile:對使用者的承諾式事實(改了要可審計)
- Memory Bank:可檢索的經驗,必須可過期、可刪、可標敏感級
「Agent 有記憶」若沒有分層,最後通常變成不可治理的黑盒子:說不清記得什麼、從哪來、能否忘記。
結語
向量庫可以是 Memory Bank 的引擎之一,但它不是記憶架構的全部。先問四個問題:
- 這是這次對話還需要的嗎?→ Session/Events
- 這是步驟間要快速讀的結構化值嗎?→ State
- 這是換對話仍應成立的使用者事實嗎?→ Profile
- 這是跨時間、需語意找回的經驗嗎?→ Memory Bank
把這四層劃清,Agent 才從「會聊天的模型」變成「可連續服務的系統」。
系列下一篇,我們會談另一個正交維度:當任務不是一輪對話,而是跨天、等人、等 webhook 的長流程——如何休眠、打檢查點、以及為什麼不能讓代理自己打滿分。
延伸閱讀
我是江中喬,專注於 AI Agent Architecture、Memory Governance 與 Cognitive Diversity,持續研究如何打造能夠長期協作、可信任且可治理的 AI 系統。