向量嵌入作为AI代理记忆的默认方案是错误的
向量嵌入已成为AI代理记忆的默认选择,但在实际应用中常因检索漂移、低效和写入复杂性而表现不佳。结构化键值存储配合MCP风格的内存服务器为大多数代理记忆工作负载提供了更好的基础,向量数据库应仅用于大规模语义检索。
在过去的两年里,对于“如何为我的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用于结构化、向量用于语义、追加日志用于情景。
这种模式相比“嵌入并检索”有两个实际好处:
- 代理拥有显式的操作能力。代理决定“我需要查找用户的已存储偏好”,然后调用memory.recall("preferences")。决策在代理的推理中,而非嵌入相似度的黑箱中。
- 你可以混合使用存储后端,而无需更改代理。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就足够了。