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

向量嵌入作為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就足夠了。