AI News HubLIVE
站內改寫5 分鐘閱讀

向量嵌入作為AI代理記憶的預設方案是錯誤的

向量嵌入已成為AI代理記憶的預設選擇,但在實際應用中常因檢索漂移、低效和寫入複雜性而表現不佳。結構化鍵值儲存配合MCP風格的記憶體伺服器為大多數代理記憶工作負載提供了更好的基礎,向量資料庫應僅用於大規模語義檢索。

來源Hacker News AI作者: supercrafthost

在過去的兩年裡,對於“如何為我的LLM代理提供記憶”這一問題的預設答案,幾乎都是某種變體的“設定向量資料庫並嵌入對話片段”。作者在多個生產級代理中實踐了這一模式,卻發現它每一次都表現不佳。本文旨在說明,對於代理實際需要的記憶任務,結構化鍵值回憶(key-value recall)配合MCP風格的記憶體伺服器,通常優於向量嵌入,並指出了導致作者放棄首先使用向量資料庫的特定失敗模式。

論點比聽起來更狹窄:向量嵌入在其設計領域——即大規模非結構化語料庫的語義檢索——表現出色,但常被誤用於代理記憶,因為相關工具已成熟,且會議演講中的代理架構總是強調“RAG一切”。實際上,代理大多數時候需要的是一種更簡單但也更困難的東西:具有可靠、一致回憶能力的結構化持久狀態。

作者列舉了在多個部署中遇到的三個具體失敗模式。

失敗一:高召回率查詢的漂移

經典的向量資料庫記憶模式是:嵌入每一條對話訊息,檢索與當前輪次最相似的top-k結果,然後將它們塞入系統提示。這在演示中有效,但在生產環境中卻產生了所謂的“漂移幻覺”:代理自信地引用來自與當前輪次僅有鬆散關聯的向量命中結果。

具體案例:一個客戶支援代理擁有過往工單的向量記憶。使用者開啟一個新工單,說“我無法登入”。向量搜尋返回了三條包含“登入”的過往工單,其中包括一年前關於另一個已重新命名功能的工單。代理自信地告訴使用者“檢查工作流標誌,這在上次工單中解決了問題。”然而該標誌已被棄用九個月。使用者花了二十分鐘尋找一個已不存在的UI元素。

向量資料庫完美履行了其設計職能——檢索語義相似的專案。但問題在於,“該使用者最後一次登入嘗試是在五天前”和“該使用者的已棄用工作流標誌在2024年2月是相關的”這兩個檢索結果都是有效的,代理卻無法區分“當前相關事實”和“歷史產物”。

一個帶有明確新鮮度標記和事實生命週期(“此事實在2024年2月為真,此事實是當前的”)的結構化KV儲存則不會出現這種失敗模式。你按鍵查詢,而非按相似度查詢。如果事實不是當前的,它就不存在。

失敗二:小型高頻狀態

大多數代理記憶每個使用者僅有幾百位元組:使用者名稱、當前偏好設定、最近幾次互動摘要、正在進行的專案上下文。向量資料庫對於這種規模的資料完全是錯誤的工具。你用1536維的浮點數來索引200位元組的結構化狀態,付出30倍的儲存開銷和100倍的查詢延遲,僅僅為了獲取一個鍵值查詢就能在微秒內返回的資訊。

這聽起來顯而易見,但作者不斷發現生產環境中的代理,其最常查詢的記憶事實(如“使用者偏好的響應格式是什麼”)竟然隱藏在向量搜尋之後,而非鍵之後。向量資料庫的辯護者會說:“你可以將其作為嵌入的後設資料儲存。”沒錯,但這相當於一個多了一步驟的鍵值儲存,外加一個你並不需要的向量索引。

失敗三:寫入過程比讀取更混亂

代理需要頻繁寫入記憶:每次使用者表達偏好、每次事實發生變化、每次會話結束。向量資料庫處理寫入,但其生命週期管理卻相當尷尬。以一個簡單情況為例:使用者說“實際上,我的郵箱是[email protected],不是我之前說的[email protected]。”向量資料庫會將兩者都儲存為嵌入。未來的檢索會返回兩者,代理現在擁有兩個矛盾的事實,卻沒有一流的方法來標記哪一個是最新的、哪一個是歷史的。你可以透過工程手段繞過(新增“superseded_by”欄位,在檢索時過濾,編寫自己的衝突解決邏輯),但這時你已經用向量索引笨拙地重新發明了一個結構化資料庫。

結構化KV儲存自然處理這種情況:user:jane:email被覆寫,只有一個當前值。舊值是明確的歷史記錄,而非檢索噪聲。

“代理記憶”的實際含義

該短語涵蓋四種不同的工作負載,每種都有其最優儲存方式:

  • 情景回憶:“使用者在上次對話中說了什麼?”——順序性、近期加權,通常由滑動上下文視窗或結構化會話日誌處理,而非檢索。
  • 知識庫的語義回憶:“我們的產品是做什麼的?”——向量資料庫在此處大放異彩:文件、程式碼、工單的RAG。大規模語料庫、模糊查詢、語義相似性是正確的原語。
  • 結構化狀態:“使用者當前的偏好是什麼?”——顯式鍵、當前值、寫覆蓋舊值。屬於KV領域。
  • 跨會話連續性:“過去六週使用者與我在什麼上合作?”——這是真正困難的部分,混合了情景、語義和結構化。不同事實老化速度不同。天真的“嵌入每一輪”模式在這種場景下失敗最嚴重。

錯誤在於將四種工作負載視為同一類,並直接使用向量資料庫。實踐中,應對不同的記憶型別使用不同的儲存,並由代理的工具層進行路由。

MCP伺服器模式

模型上下文協議(MCP)提供了一種乾淨的方式,將記憶暴露給代理,而無需將其耦合到特定儲存。一個記憶MCP伺服器是一個輕量級工具層,代理透過它呼叫memory.recall、memory.set、memory.list_recent等函式,底層由適合資料的儲存支撐:KV用於結構化、向量用於語義、追加日誌用於情景。

這種模式相比“嵌入並檢索”有兩個實際好處:

  1. 代理擁有顯式的操作能力。代理決定“我需要查詢使用者的已儲存偏好”,然後呼叫memory.recall("preferences")。決策在代理的推理中,而非嵌入相似度的黑箱中。
  2. 你可以混合使用儲存後端,而無需更改代理。MCP層隱藏了實現細節。作者曾參與一個專案,其中結構化使用者狀態採用Postgres表,情景會話摘要採用Redis有序集合,語義文件檢索採用Qdrant索引,全部透過一個MCP記憶伺服器暴露。代理毫不在意。

有一個名為Memnode(作者的另一個專案)的伺服器,專注於結構化和情景端。但重點不是Memnode,而是MCP伺服器模式允許你為每種工作負載選擇正確的儲存,而不是強制一切透過向量資料庫。

向量資料庫何時是正確工具

明確一點:作者並非否定向量資料庫,而是反對將其作為代理記憶的預設選項。向量檢索確實是最好原語的場景包括:

  • 文件RAG:數千文件,模糊語義查詢,“關於X的政策是什麼?”
  • 大型程式碼庫的程式碼搜尋:當符號名搜尋不足時,透過實現模式進行相似性檢索。
  • 對話語料庫的長期語義記憶:當真正需要“查詢六個月間涉及類似概念的對話”時,且僅當代理的推理明確證明需要該檢索,而非作為預設的第一站查詢。

原則是:當語料庫大、查詢模糊、且精確鍵查詢不可行(因為鍵未知)時,使用向量資料庫。如果你的代理記憶是“每個使用者的小型結構化狀態”或“會話的最後50條訊息”,那並不是向量資料庫的用途。

如何判斷你需要哪種記憶

一個有用的診斷問題:寫下代理最常問自己的五個記憶相關問題。如果它們像:

  • “使用者名稱是什麼?”
  • “他們上次操作是什麼?”
  • “我們正在看哪個文件?”
  • “最後一條錯誤訊息是什麼?”
  • “當前任務狀態是什麼?”

→ 結構化KV記憶,不要用向量資料庫。

如果它們像:

  • “找到與這個問題相關的政策文本”
  • “檢索與此程式碼片段相似的程式碼模式”
  • “拉取與這個工單相似的工單”

→ 向量檢索,使用向量資料庫。

如果它們像:

  • “三次會話前我們關於遷移計劃討論了什麼?”
  • “總結我過去一週的上下文”

→ 混合方案:情景日誌加按需摘要,可能對摘要建立小型向量索引(而非原始輪次)。採用MCP伺服器模式,以便根據查詢型別調整後端。

作者現在的做法

對於新的代理專案,預設棧為:

  • 結構化使用者狀態使用Postgres或SQLite,透過MCP記憶伺服器暴露,具有顯式的set/recall/delete語義。
  • 情景會話記憶作為追加日誌,也在MCP伺服器中,帶有近期視窗檢索。
  • 向量RAG僅在有大型外部語料庫且查詢確實為語義時使用。

好處包括:成本更低(無需執行向量資料庫)、失敗模式更可預測(代理不再引用三個月前的工作流標誌)、除錯更容易(可以讀取結構化記憶,但無法讀取嵌入向量)。

如果今天開始一個代理專案,建議跳過向量資料庫優先的慣性,從結構化KV和MCP記憶層開始。僅在特定工作負載證明需要時,再新增向量檢索,而非作為預設架構。預設已經錯了很長時間,會議演講還沒跟上。

如需具體檢視結構化記憶的實踐,Claude Code記憶演示展示了安裝、記錄、回憶和譜系迴圈四個步驟。如果僅需要無MCP語義的普通鍵值查詢(快取、特性標誌、會話金鑰),像basekv這樣的通用KV就足夠了。