大语言模型看起来能够 "记住" 用户,真正保存信息的却不是模型本身;一次模型调用只能依据当前收到的上下文进行推理,跨会话记忆通常由 Agent 外部的文件、数据库、检索器和更新流程共同实现
因此,设计 Agent 记忆系统不能只问 "该用哪个向量数据库",而应该依次回答四个问题:
需要记住什么?记忆以什么结构保存?怎样找回当前真正需要的信息?新事实出现后,旧记忆如何更新、失效和追溯?
记忆类型决定 "存什么",存储介质决定 "放哪里",检索策略决定 "怎么找",维护策略决定 "它是否长期可信"
上下文窗口不等于长期记忆:
上下文窗口是模型本次推理时能够看到的信息;长期记忆则保存在模型之外,需要在下一次运行时重新取回并放入上下文
一个典型 Agent 的记忆流程如下:
用户请求
↓
判断是否需要历史信息
↓
按用户、时间和记忆类型过滤
↓
关键词 / 向量 / 图检索
↓
重排并组装相关上下文
↓
模型推理与工具调用
↓
提取值得长期保存的新信息
↓
写入、替代或整理记忆
这也解释了为什么不能把全部聊天记录直接塞给模型;历史越长,Token 成本越高,噪声越多,真正重要的信息反而越容易被淹没
先区分短期记忆与长期记忆:
短期记忆:维持当前任务连续性
短期记忆通常绑定到某个会话或任务线程,包括:
- 最近几轮对话
- 当前任务状态
- 已调用的工具及其结果
- 尚未完成的计划
- 临时变量和中间结论
它的作用类似人的工作记忆;任务结束后,其中大部分内容不需要永久保存
长期记忆:跨会话保留重要信息
长期记忆能够跨任务和会话复用;按照 LangGraph 等系统采用的认知分类,可以进一步分为三类:
| 类型 | 保存内容 | 示例 |
|---|---|---|
| 语义记忆 | 稳定事实和概念 | 用户偏好深色模式;客户要求纯素认证 |
| 情景记忆 | 带时间和上下文的经历 | 上周参加了会议;某次工具调用失败 |
| 程序记忆 | 规则、流程和操作方法 | 如何生成周报;如何完成一次市场调研 |
三种记忆的写入和读取方式并不相同;语义记忆适合提炼为事实,情景记忆需要保留时间和来源,程序记忆则更适合保存为可编辑的规则、提示词或 Skill
四种主要存储结构:
Markdown:最简单、最透明
Agent 可以通过 memory.md 保存用户事实,通过 soul.md 保存角色和行为规则,再通过单独的 Skill 文件保存操作流程;Hermes 等 Agent Harness 都采用了类似思路
Markdown 的优势是容易阅读、编辑、审计和版本管理;如果信息很少,还可以在每次运行时直接加载到上下文
它的问题是规模扩展能力有限;文件越来越长以后,全量加载会增加成本,模型也难以持续关注所有内容;因此,Markdown 更适合少量高价值事实、核心规则和人工维护的程序记忆
SQLite:结构化记录与全文检索
当记忆需要时间、来源、用户和状态等字段时,关系数据库会更合适;一个实用的数据结构可以包含:
| 字段 | 用途 |
|---|---|
user_id |
隔离不同用户的记忆 |
memory_type |
区分语义、情景和程序记忆 |
content |
保存事实或事件内容 |
source |
记录信息来自用户、网页还是工具 |
valid_from |
事实开始生效的时间 |
valid_to |
事实失效的时间 |
supersedes_id |
指向被当前事实替代的旧记录 |
created_at |
记忆写入时间 |
SQLite 的 FTS5 是官方提供的全文检索虚拟表模块;基于倒排索引实现高速文本检索,支持词项、短语、前缀、邻近查询、布尔条件组合以及 BM25 相关性打分排序;原生缺少中文分词能力,适合读多写少场景,可搭配外部分词器适配中文业务
关键词检索速度快、成本低、结果容易解释,尤其适合姓名、编号、日期、日志和工具调用记录;但它依赖分词与词面匹配,跨语言、同义表达和模糊意图可能降低召回率;中文数据还需要特别验证分词器或考虑 trigram 等索引方案
向量存储:按语义寻找相似内容
向量检索会先使用嵌入模型,把一段文本转换成高维向量;查询也经过相同处理,然后通过余弦距离、内积或欧氏距离寻找语义接近的记录
这里需要纠正一个常见误解:系统通常为句子、事实或文本块生成向量,而不是简单地 "把每一个单词分别存成向量"
pgvector 是 PostgreSQL 的扩展,不是 SQLite 扩展;它允许向量与普通业务字段存放在同一张 PostgreSQL 表中,并支持:
- 精确近邻搜索
- HNSW 和 IVFFlat 近似近邻索引
- 余弦距离、内积、L2 和 L1 等距离计算
- 使用普通 SQL 对用户、时间、权限和类别进行过滤
精确搜索能获得完整召回,但数据规模增大后成本会上升;近似索引以部分召回率换取速度,其中 HNSW 通常拥有较好的速度与召回率平衡,但建索引更慢、占用更多内存;IVFFlat 构建更快、内存较少,却更依赖数据量和参数设置
向量相似只代表 "表达接近",不代表 "事实正确";生产系统不能只取相似度最高的几条记录,还应结合用户范围、有效时间、来源、权限和后续重排
图结构:保存实体之间的关系
当问题的重点不再是某条独立事实,而是事实之间的关系时,可以使用图结构;图中的节点表示人物、组织、产品或事件,边表示它们之间的关系:
Alex
├── 参加 → Lisbon AI Meetup
├── 创办 → 机器人项目
└── 项目 → 原定五月发布
↓
推迟到六月
图能够直接表达 "谁参与了什么" "某个决定影响了哪个项目" "一条信息为何与另一条信息相关";它适合多跳查询、关系密集数据和需要追溯历史变化的场景
四种记忆检索策略:
| 检索方式 | 工作方式 | 优点 | 主要局限 |
|---|---|---|---|
| 直接预加载 | 把记忆直接放入上下文 | 简单、不会漏检 | 占用上下文,难以扩展 |
| FTS/关键词 | 匹配词项、短语和布尔条件 | 快、便宜、可解释 | 不擅长同义词和跨语言 |
| 向量检索 | 查找语义相似的文本 | 表达灵活、召回能力强 | 相似不等于正确 |
| 图与混合检索 | 结合实体、关系、时间和语义 | 支持多跳与关系推理 | 索引和维护成本较高 |
成熟系统通常使用混合检索,而不是四选一;例如先根据 user_id 和有效时间过滤,再并行执行 BM25 与向量搜索,最后融合分数、重排结果并组装上下文
RAG、GraphRAG 与 Agent Memory:
RAG 把语言模型的参数化知识与外部非参数化记忆结合起来;系统先从外部知识库检索相关内容,再让模型依据这些内容生成答案;
RAG 解决的是 "怎样从外部知识中取回证据";Agent Memory 解决的则是 "怎样长期保存并管理与用户、任务和 Agent 自身有关的动态信息"
两者可以重叠,但不能画等号:
- 企业文档库属于 RAG 知识源,却不一定是用户记忆
- 用户偏好属于 Agent Memory,也可以通过向量 RAG 取回
- 一次工具调用记录属于情景记忆,通常不会进入公共知识库
- 程序记忆可能以 Skill 文件保存,根本不需要向量检索
Microsoft GraphRAG:
Microsoft GraphRAG 不是简单地 "给节点和边生成向量";它会从非结构化文本中抽取实体、关系和相关描述,建立知识图谱与社区层次,并生成社区报告
其查询方式包括:
- Local Search:结合图中的实体关系和原始文本块,回答围绕具体实体的问题
- Global Search:在社区报告上执行 Map-Reduce,回答关于整个数据集的宏观问题
- DRIFT Search:从社区信息出发扩展局部检索,并生成更细致的后续问题
- Basic Search:提供基础向量 RAG,便于比较不同检索方式
Microsoft 官方明确提示 GraphRAG 的索引可能成本较高;它更适合复杂、叙事性强、需要跨文档理解的数据集,而不是几条用户偏好这样的小型记忆
Graphiti 与时间上下文图:
Graphiti 是 Zep 体系中的开源时间上下文图引擎,更偏向持续变化的 Agent 记忆;它把信息组织为:
- Entity:人物、产品、组织等实体节点
- Fact/Relationship:带有效时间窗口的事实或关系边
- Episode:产生这些事实的原始对话、文本或 JSON 数据
- Provenance:从派生事实回溯到原始 Episode 的来源关系
Graphiti 支持增量更新,并将语义搜索、BM25 和图遍历组合为混合检索;旧事实被新事实替代时,可以失效而不必删除,因此既能回答 "现在是什么",也能回答 "过去是什么";
Zep 是托管式产品,Graphiti 是需要自行部署和运维的开源引擎;二者不能直接当作同一个软件版本理解
写入记忆不能等同于保存聊天记录:
一次完整对话可能包含寒暄、重复表达、推测、纠正和工具输出;如果全部写入长期记忆,数据库很快会变成无法使用的聊天垃圾场
写入前至少需要完成三步判断:
重要性判断:这条信息未来是否还会使用?
类型判断:它属于事实、事件还是程序?
冲突判断:它是新事实、重复事实,还是对旧事实的修正?
随后再选择具体动作:
| 操作 | 含义 |
|---|---|
| Add | 添加新事实 |
| Update | 修正现有记录 |
| Delete | 删除错误或依法必须清除的信息 |
| No-op | 信息重复,不做处理 |
| Supersede | 新事实生效,旧事实保留但失效 |
例如,用户先说 "访客将在晚上七点到达",随后改为 "晚上九点才能到";删除七点这条记录会丢失变化历史,直接覆盖又无法解释过去的安排;更好的方法是让旧记录在更新时刻失效,再新增一条从该时刻开始生效的九点记录
需要注意的是,产品的内部算法会随版本变化;当前,Mem0 的新一代托管算法在自动提取阶段采用 ADD-only 策略,不再让模型直接执行 UPDATE/DELETE,同时融合语义、BM25、实体匹配与时间推理;这不代表所有历史版本或人工管理 API 都遵循同一策略,因此选型时应以实际部署版本的文档为准
在线写入与后台整理:
记忆可以在两条路径上形成:
- 在线路径(hot path):Agent 在对话过程中立即决定查询或写入记忆
- 后台路径(background):任务完成后异步提取、合并和更新知识
LangMem 同时提供了对话中的记忆管理工具和后台记忆管理器;前者响应及时,但会增加当前请求的延迟;后者不会阻塞回答,更适合批量去重、摘要和一致性检查
一种实用架构是先把原始对话和工具结果写入 SQLite,再异步提取高价值事实;短小且必须始终生效的规则写入 Markdown,语义事实进入向量索引,复杂实体关系再进入时间图;
后台整理有时被形象地称为 "反思" 或 "做梦",但它不是统一技术标准;工程上真正需要的是明确的去重、合并、冲突检测、失效和来源追踪流程
如何选择:从最小可用架构逐步升级
| 场景 | 推荐起点 |
|---|---|
| 原型或个人助理 | Markdown + SQLite |
| 需要保存完整运行轨迹 | SQLite + FTS5 |
| 用户表达多变或存在跨语言查询 | 结构化过滤 + 向量检索 |
| 大量文档问答 | 向量 RAG 或混合检索 |
| 复杂实体关系与多跳问题 | 知识图谱 / GraphRAG |
| 高频变化且需要历史追溯 | 时间上下文图 |
| 希望直接使用记忆管理能力 | Mem0、LangMem、Zep 等现成组件 |
最稳妥的演进路线通常是:
Markdown
↓
SQLite + FTS5
↓
元数据过滤 + 向量检索
↓
BM25 与向量混合召回
↓
仅在关系复杂时引入图和时间语义
图数据库并不会自动让 Agent 更聪明;如果业务问题只涉及少量偏好、日期和联系人,引入时间图可能增加写入延迟、索引成本和运维复杂度,却没有带来同等价值
怎样正确评估记忆系统:
使用同一组事实测试 SQLite、Mem0、LangMem、Zep 和无记忆对照组能展示关键词检索、跨语言查询、事实替代和图构建延迟之间的差异,但单次运行时间不能当作严格性能排名
更可靠的评估至少应包含:
- 召回率:需要的记忆是否被找到
- 精确率:返回内容中有多少真正相关
- 事实一致性:回答是否忠于存储记录
- 时间正确性:能否区分当前事实与历史事实
- 跨语言能力:不同语言提问能否找到同一事实
- 更新正确性:新信息能否替代旧信息
- 来源可追溯性:答案能否回到原始对话或工具结果
- 延迟与成本:写入、索引、检索和生成分别消耗多少资源
- 隔离与删除:不同用户的数据是否隔离,删除请求是否彻底执行
测试集还应包含无答案问题;一个优秀的记忆系统不仅要在有记录时回答正确,也要在没有证据时明确表示不知道
一个可靠的最终架构:
对多数 Agent 项目而言,合理的默认设计不是直接采用最复杂的 GraphRAG,而是分层组合:
- Markdown 保存少量核心规则和程序记忆
- SQLite 保存对话、事件、工具结果和审计记录
- FTS5 负责名称、编号、短语和日志检索
- 向量索引负责同义表达和跨语言语义召回
- 元数据过滤负责用户、权限、类型和时间边界
- 时间字段与替代关系负责处理事实变化
- 后台任务负责提取、去重、合并和失效
- 只有在多跳关系具有明确价值时才建立图结构
AI Agent 的长期价值不在于积累了多少聊天记录,而在于能否把经历转化为少量、准确、可检索、可更新并可追溯的知识;先明确记忆内容,再选择存储和检索方式,最后建立可靠的生命周期管理,才是一套真正可持续的 Agent 记忆系统