最近有做 Agent 的朋友问我:AI数据库怎么给 Agent 做记忆底座?他自己把用户历史对话全部塞进向量库,结果 Agent 上线后依然答错,甚至比不记的时候更让人哭笑不得:上一秒刚记住用户不喜欢辣,下一秒就在推荐川菜。这个问题非常有代表性。我自己的团队也踩过同样的坑,今天就把这套“记忆底座”从设计到落地,再到纠错排查的过程完整拆一遍。
先说结论:Agent 的记忆不是简单的“读文件 + 塞上下文”,而是一个包括写入、存储、召回、校验、覆盖、遗忘的复杂系统。AI数据库在这个系统里扮演的也不是普通容量角色,而是负责把不可控的经验,变成可查询、可更新、可评估的结构化知识。可一旦设计失当,就会出现“记住了却用错”的诡异情况。下面从原理到实战一步步聊。
1. 先理解“记忆底座”到底在解决什么问题
1.1 Agent 的短期记忆和长期记忆,本质上是两套系统
很多人以为 Agent 记不住事,是因为上下文窗口不够大。于是把对话历史全部塞进提示词,或者把所有历史写入向量库,然后无脑 top-k 召回。这两种做法都会遇到同一个根因:把“短期记忆”和“长期记忆”混为一谈。
大模型的上下文窗口,本质上就是 Agent 的工作记忆。它适合存放当前任务正在用的信息,比如用户这次对话的目标、刚拿到的工具返回结果、正在执行的步骤。它的特点是容量有限、生命周期短。真正能沉淀下来的长期记忆,必须落到外部系统里,这就轮到 AI数据库出场。
但这里说的 AI数据库,不是单纯指某个向量引擎或者 Redis,而是一整套为 Agent 设计的记忆基础设施。它的职责包括:保存事实、保存用户偏好、保存历史决策、保存实体之间的关系,并且支持按语义召回、按时间过滤、按来源评估。没有这套底座,Agent 只能停留在“问一句答一句”的聊天机器人水平。
1.2 AI数据库和传统数据库的区别
我经常用一个比喻:传统数据库像仓库,你存什么取什么,靠的是精确的箱子和编号;AI数据库更像图书馆 + 检索员的组合,你存的不仅是一本书的内容,还包括这本书大概讲了什么、和哪些书关联、什么时候更新过。
从工程形态看,两者有明显差异:
| 维度 | 传统数据库 | Agent记忆底座(AI数据库) |
|---|---|---|
| 查询方式 | 精确匹配、SQL 条件 | 向量相似度 + 关键词 + 元数据过滤 |
| 数据模型 | 结构化表为主 | 文本切片、实体图谱、状态记录并存 |
| 数据变更 | UPDATE 覆盖 | 版本化追加、冲突消解、时间衰减 |
| 核心诉求 | 保证事务和一致性 | 保证相关性、可追溯、可评估 |
| 使用主体 | 人 / 应用代码 | 大模型 Agent 的推理循环 |
这也是为什么很多团队直接把 MySQL 表里的一堆历史记录拼给 Agent,效果很差。因为 Agent 需要的不是原始流水,而是“在这个场景下,哪几条记忆和当前问题相关,哪几条记忆已经过时,哪几条记忆之间存在矛盾”。这些是传统数据库不太擅长的。
2. 给 Agent 做记忆底座的四种主流方案
2.1 向量数据库:语义记忆的首选
Agent 记忆里占比最大的通常是文本类经验,比如用户说过的话、文档知识、历史摘要。这类数据天然适合转成 embedding,存进向量数据库。
向量数据库做的事很简单:把文本切块,用 embedding 模型转成一个多维向量,查询的时候也把用户问题转成向量,然后通过余弦相似度或内积算出最接近的几条记录。常用的有 Milvus、Qdrant、Weaviate,或者直接用 pgvector、SQLite-VSS 这类插件。
但向量数据库解决的是“模糊召回”,不是“精确事实”。你可以把用户偏好“不吃辣”存成一条记忆,然后当用户问“晚上吃什么”时,通过相似度召回它。可问题是,如果用户又问“我看那家川菜馆评分很高,要试试吗”,向量召回很可能因为“川菜”这两个字把这个偏好一起抓回来,结果 Agent 反而推了辣椒菜。原因就在于相似度不等于相关性,后面细讲。
2.2 图数据库:实体关系的记忆底座
如果 Agent 需要记忆大量实体和关系,比如“张三是谁”“张三和李四是什么关系”“张三参与过哪个项目”,单纯靠向量文本就不够稳。这种情况我强烈建议引入图数据库。
图数据库里的节点表示实体,边表示关系。比如用户说过“我领导叫王姐,她负责市场部”,你会得到两个节点和一个关系:用户-领导→王姐,王姐-负责→市场部。下次用户说“王姐找我”,Agent 能精确推导出“领导的领导或王姐”之间的联系。
图数据库还有一个好处:支持矛盾检测。当新记忆说“王姐调去销售部了”,图结构上只需要更新边的属性,并且保留旧关系的时间戳。查询时天然按照最新关系走,不会同时存在“负责市场部”和“负责销售部”两条平级记忆。
2.3 键值存储与短期状态:记忆的“草稿纸”
长期记忆不可能每次都做向量检索,有些临时状态更适合用最简单的 KV 存储。比如 Redis。Agent 在执行一个多步骤任务时,需要记住当前做到第几步、临时变量是什么、某个工具调用结果是否重复、用户是否已经授权。这些并不需要语义检索,用 key 精确读写反而更快更可靠。
这一层我称之为“工作记忆”。它不需要持久化太久,但必须和长期记忆分开。如果混在一个库里,很快就会被无关的临时信息污染,导致长期记忆检索时返回垃圾。
一个比较稳的架构是:Redis 存会话临时状态,向量库存语义长期记忆,图数据库存实体关系。必要时用关系型数据库记录记忆的来源、更新日志、权限范围,方便排查问题。
2.4 关系型数据库在记忆底座里的兜底作用
不要忽略传统数据库。Agent 的记忆系统里,元数据经常比向量本身更有价值。比如这条记忆是谁在什么时候写的,来自哪个文档,置信度有多高,被命中过多少次,是否已被用户纠正。这些都应该放在可靠的事务型数据库里。
我见过一个生产事故:因为只存向量,不存来源,Agent 回答问题时引用了一条错误记忆,而且这条记忆来自三个月前的某次代码变更记录,跟当前问题毫无关系。由于没有来源字段、没有时间戳,排查时根本不知道它哪来的。后来强制加上 metadata,问题立刻清晰很多。
3. 记忆写入:不做好写入,检索再强也白费
3.1 把原始信息拆成“可用于决策”的记忆单元
很多人把用户整段聊天记录扔进向量库,然后祈祷检索能用,这是记忆系统最大的败笔。大段文本经过 embedding 后,语义会被压缩,细节容易丢失。你以为存的是“用户不吃辣”,实际存进去的可能是一整段包含“偶尔吃辣”、“微辣”、“火锅”的混合文本。
正确的做法是:在写入前,用一个提取层把原始信息加工成规整的记忆单元。典型单元可以包含:
- 实体:这个记忆涉及谁、哪个项目、哪类事物。
- 属性:具体的偏好、事实、限制条件。
- 上下文:什么时间、什么场景下成立。
- 来源:来自用户直接说,还是根据工具结果推断。
- 置信度:是肯定句,还是猜测。
这一步可以靠一个大模型 call 完成,也可以靠规则提取。成本不低,但必须做。因为后面所有查询的质量都取决于写入端的质量。有一句话很直白:烂数据进,烂数据出。
3.2 记忆的合并、覆盖和冲突消解
用户会修改自己的偏好。昨天说“我不吃辣”,今天可能说“微辣可以接受”。如果两句话都存成独立向量,召回时可能两条都返回,导致 Agent 不知道听谁的。
所以在写入时,就要做合并和覆盖。常见方案是:给实体 + 属性建唯一键。例如 user_id + preference_taste。新记忆写入时,不是直接 append,而是先查询旧记录,把旧记录标记为“已被新记忆替代”,同时保留历史版本。这样向量库里只保留有效版本,但审计日志里还能看到历史变化。
对于没有明确实体的语义记忆,可以用 embedding 相似度 + LLM 判断来决定是否合并。比如旧记忆“喜欢吃辣火锅”,新记忆“最近在控制饮食,少吃辣”,可以让模型判断这是同一主题的更新,而不是新增。
3.3 遗忘机制为什么是刚需
Agent 的记忆如果只会增加不会减少,系统迟早会被垃圾信息淹没。用户三天前随口说的一句话,和今天决策完全无关,但向量检索依然可能把它召回。这时候要有遗忘机制。
遗忘不一定是物理删除。我常用两种软遗忘:第一种是时效衰减,给每条记忆加一个 freshness 值,越近的越容易被召回;第二种是重要性权重,用户明确强调过的事,比如“记住,我绝对不要香菜”,权重很高,召回时能看到;普通闲聊信息权重很低,可以被过滤。
这套机制听起来复杂,实现起来其实很简单。在 metadata 里加上 created_at、last_accessed、importance,查询时做加权。如果一条记忆长期未被命中,可以定期归档。Agent 记忆的目标不是“什么都记得”,而是“该记得的记得,不该记得的及时消失”。
4. 记忆召回:把“记住了”变成“用得对”
4.1 混合检索加元数据过滤,才是生产级做法
只靠向量相似度召回记忆,线上大概率出问题。原因在于 embedding 模型只理解语义,不理解时间、权限、任务边界、实体身份。所以生产级召回一定要做混合检索。
所谓混合检索,就是同时用向量相似度、关键词匹配、元数据过滤做召回,再做融合排序。关键词匹配能保证像“香菜”这种明确的字面信息不会被向量模型带偏;元数据过滤能确保“张三的问题”不会通过“李四的记忆”得到答案。
实际操作可以参考这个流程:
- 先根据当前 Agent 的 user_id、session_id、task_type 缩小检索范围。
- 并行执行向量检索和 BM25 关键词检索。
- 对两路结果做合并和去重。
- 用 RRF 或重排序模型重新打分。
- 只保留分数高于阈值的记录。
这里的重排序我建议用一个专门的 lightweight reranker,或者直接用大模型做一次快速判断。否则 top-5 里可能只有 2 条真正相关,其他都是干扰项。
4.2 上下文注入位置与数量控制
有的 Agent 虽然检索到了正确记忆,但模型生成时根本没用上,或者被其他无关上下文淹没了。这里有个常见误区:把 top-20 条记忆全部塞进 system prompt。
上下文窗口再大也经不住这么塞。记忆之间可能互相矛盾,还可能与当前指令冲突。正确做法是分优先级注入:
- 高优:当前任务必须的最关键事实,如用户明确偏好、当前项目状态。
- 中优:背景资料,比如用户历史偏好、团队信息。
- 低优:通用知识,这类根本不需要从数据库里召回。
同时还要控制数量。常规经验是:召回的记忆控制在 5~10 条,每条提炼到一两句话。与其给模型 50 条残缺记忆,不如给 8 条精确记忆。记忆不是越多越聪明,越精越稳。
4.3 记忆的可信度与引用机制
Agent 使用记忆时,很容易把记忆内容当成“事实”。但如果这条记忆本身来自一次错误的工具返回,或者用户随口说的玩笑话,Agent 就会一本正经地用错误信息回答。
所以我在这类系统里强制给每条记忆加两个字段:computed_confidence 和 source_type。source_type 分为 user_stated、user_confirmed、tool_result、inferred。比如用户明确说“我叫张伟”,这是 user_stated,可信度高;模型根据上下文推断“张伟可能喜欢篮球”,这是 inferred,可信度低。
同时,Agent 生成答案时,如果依赖了某条记忆,最好在后台记录“这一回答引用了哪条记忆”。这样既能做评估,也能在后续排查时知道为什么错。很多团队忽略这一步,等用户投诉时完全无从下手。
5. 记住了为什么还会用错:故障层拆解
5.1 存储时信息已经被污染
很多时候 Agent 用错记忆,问题压根不在检索,而在写入。举个我实际遇到的例子:一次对话里用户说“我儿子现在读高一”,结果写入层在压缩时把整段聊天记录变成“用户有一个孩子,读高中”,然后跟另一个用户的记忆合并到一个 embedding chunk 里。等后续询问“孩子多大”时,召回结果张冠李戴。
这种污染来自两个源头。一是信息切块粒度没有对齐实体,导致一条记忆里混杂了多个主题;二是用大模型做抽取时,模型强行补全了不存在的信息。这里要强调一个原则:记忆系统只记录高置信度事实,少用模型补全。宁可少写,不能错写。
5.2 检索时返回了相似但不相关的记忆
这是“记住了还错”最常见的形态。语义相似度是连续向量空间里的距离,不是现实逻辑里的相关。用户问的是“今天晚饭吃什么”,你召回的是“上次火锅店在哪”,这俩在 embedding 空间里可能很近,但它们在当前决策里并不相关。
解决办法除了混合检索,还可以在召回后加一个“相关性检查提示”。具体做法:把检索结果和当前问题一起交给一个小模型,让模型判断每一条记忆是否真的与当前用户意图有关。这一步能过滤掉大量伪相关记忆。注意,这个检查模型不需要多强,能判断主题是否一致就够。
5.3 Agent 生成时没有真正推理记忆
还有一类错误比较隐蔽:记忆明明召回了,Agent 却视而不见。比如系统提示里说“用户不吃辣”,用户问题却是“推荐一家川菜馆”,Agent 为了迎合问题直接推荐了水煮鱼。这是模型推理偏好压过了记忆指令。
对策是在 prompt 里把记忆的优先级写得很明确,甚至可以在生成前做一次显式推理:先让模型输出“我参考了哪些记忆”,再输出最终答案。如果模型列举出来的记忆和当前问题无关,那就强制重新检索。这种“先回忆、后回答”的机制很有效。
5.4 多 Agent 共享记忆时的干扰
多 Agent 系统越来越常见,比如一个 Agent 负责销售,一个负责技术支持。如果两者共享同一个记忆库而没有任何隔离,就会出现严重干扰:销售 Agent 写入的用户预算信息,被技术支持 Agent 当成了技术参数;或者一个 Agent 刚更新了记忆,另一个 Agent 还在旧的快照里做决策。
这里需要引入两个概念:一是按角色/命名空间隔离记忆,比如 sales_memory、support_memory;二是按会话快照隔离,每次 Agent 执行任务时抓取当时的记忆版本,避免执行过程中被其他 Agent 改动。用 AI 数据库做底座时,隔离不是可选项,是必选项。
6. 实战:一个最小可靠记忆系统怎么设计
6.1 核心结构代码示例
下面是一个简化但可落地的记忆管理器。它把向量检索、元数据过滤、置信度评估放在一起,能覆盖大多数业务场景。用 Python 写,存储层可以替换成 pgvector、Milvus 或任何支持向量检索的库。
from dataclasses import dataclass, field from datetime import datetime, timezone from typing import Optional, List # 定义一条记忆的数据结构 @dataclass class MemoryItem: memory_id: str user_id: str role: str = "memory" # 记忆所属角色命名空间 entity: Optional[str] = None # 关联实体,比如用户ID content: str = "" # 规范化后的记忆内容 source_type: str = "user_stated" # user_stated/tool_result/inferred confidence: float = 0.9 importance: float = 0.5 created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat()) last_accessed: Optional[str] = None expires_at: Optional[str] = None vector: Optional[List[float]] = None这里最核心的设计是source_type和confidence。千万不要省。很多人写记忆系统只存 content 和 embedding,结果后续出现问题时完全没有改进依据。
写入记忆的逻辑:
def remember(memory_store, item: MemoryItem): # 1. 判断是否需要合并更新 old = memory_store.find_similar_existing(item) if old and needs_update(old, item): # 将旧记忆标记为 superseded memory_store.mark_superseded(old.memory_id) # 写入新版本 item.memory_id = new_id(item.user_id, item.entity) else: # 全新记忆直接写入 item.memory_id = new_id(item.user_id, item.entity) item.vector = embed(item.content) memory_store.insert(item)needs_update可以用一个轻量 LLM 调用判断,也可以简单看实体的唯一键。比如user_id + preference_taste这类明确属性,直接用唯一键覆盖即可。对于非结构化记忆,建议加一次模型判断,避免误删旧记忆。
召回记忆的逻辑:
def recall(memory_store, user_id: str, query: str, top_k: int = 8): query_vec = embed(query) candidates = memory_store.vector_search( query_vec, top_k=20, user_id=user_id, exclude_superseded=True, role=current_agent_role, ) # 混合检索:关键词过滤 keyword_ids = memory_store.keyword_search(query, user_id=user_id) merged = merge_and_dedupe(candidates, keyword_ids) # 重排序 + 过滤低相关 reranked = rerank(merged, query) return [m for m in reranked if m.score >= threshold][:top_k]这里的threshold很关键。建议初期设一个较高的值,比如 0.6 到 0.7,宁可不召回,也不召回到完全无关的东西。线上跑一段时间后,根据用户反馈调整阈值。
6.2 实操参数与经验总结
切块大小:记忆单元不要超过 100 到 200 字。太短语义不全,太长互相污染。如果一段信息超过 200 字,先做摘要,再存摘要和分段原文。
召回数量:top_k 一般取 5 到 10。超过 10 条之后,额外记忆给模型带来的收益极低,反而增加推理错误率。如果你发现 Agent 总答错,先检查是不是 top_k 开得太大。
注入位置:关键记忆放进 system prompt,辅助记忆放在对话历史的上文。不要全部堆在 context 末尾。实验下来,记忆放在靠前的位置,模型遵从度更高。
记忆冲突规则:当新旧记忆冲突时,user_confirmed>user_stated>tool_result>inferred。用户刚刚明确纠正过的事,优先级最高,其他记忆一律暂时不返回。
7. 排查手册:Agent “记错/用错” 的检查清单
7.1 直接对照这几个问题排查
| 现象 | 检查点 | 解决方案 |
|---|---|---|
| 完全想不起来 | 是否写入时没有抽取实体/主题 | 检查写入层是否做了语义整理和摘要 |
| 回答总是过时 | 是否没有时间衰减/版本更新 | 加入 freshness 权重,更新时标记 superseded |
| 答非所问 | 是否只用了纯向量检索 | 切换成混合检索 + 重排序 |
| 自说自话不参考记忆 | 是否把记忆放在上下文末尾 | 把高优记忆放到 system 前缀,增加“先回忆后回答”指令 |
| 不同 Agent 互相干扰 | 是否没有隔离命名空间 | 按 role/session 做记忆隔离 |
| 引用了不存在的信息 | 是否写入时模型补全过多 | 降低 inferred 类型记忆的置信度,必要时只存原文事实 |
| 用户纠正后仍然再犯 | 是否没有冲突消解 | 实现覆盖逻辑,纠正信息必须标记为最高优先级 |
7.2 一个排查实例
之前我们上线过一个客服 Agent,用户昨天刚投诉“不要再发送促销短信”,第二天 Agent 又在回复中推荐优惠活动。最开始我们以为是检索没召回这条记忆,查了之后发现其实召回了,但和另一条“用户对折扣敏感”的记忆同时进入了上下文,模型在两条冲突记忆面前选择了旧行为。
最后处理方式有三步:第一,给“投诉/反对”类型的记忆加高权重;第二,当存在冲突记忆时,强制让模型先输出“我注意到两条冲突记忆,按最新时间优先”,再给答复;第三,对用户明确禁止的事项,设为“禁止清单”,检索时直接过滤掉所有相关旧推送记录。
这套组合拳下来,问题基本消失。如果你也遇到“记住了还错”,不要只盯检索,一定要把写入质量、冲突消解、生成推理顺序一起检查。
7.3 最后分享一个调试技巧
我自己的习惯是给每条记忆都加上trace_id。每次 Agent 回答完,后台记录它的答案、召回了哪些记忆、每条记忆的分数、版本号。出错时把这次 trace 回放一遍,基本一眼就能看出是写入端、检索端还是生成端的问题。没有 trace,你只能靠猜,而猜往往猜不对。
做完上面这些,回头再看标题里那个问题:AI数据库怎么给 Agent 做记忆底座?答案是:把它当一个严肃的数据系统来做,而不是一个笨拙的存储桶。记住是一次写入和不断维护,用对是一次召回和推理校验的完整闭环。最终记住不是难点,真正的难点是让 Agent 在正确的时间、用正确的置信度、依赖正确的记忆做出正确决策。