AI 记忆是怎么实现的?下篇:向量检索、知识图谱和长期记忆系统
从向量检索、混合搜索、结构化记忆、知识图谱到分层上下文,拆解长期记忆如何被召回、注入、更新和遗忘。

AI 记住的信息越多,不一定越好用。
几百段对话、几千条用户反馈、多个项目里的历史决策都被保存下来以后,新的问题出现了:这次任务到底该拿哪几条出来?
如果旧规则、过期事实和其他项目的信息一起进入上下文,记忆越丰富,反而越容易干扰当前判断。
上篇讲的是信息如何被保存下来。这一篇继续看链路后半段:
Index -> Retrieve -> Inject -> Update / Forget
也就是:怎么索引,怎么检索,怎么放回上下文,又怎么更新和遗忘。
向量检索:从大量历史里找回相关记忆
向量检索,是 AI 记忆系统常用的召回方式之一。它先从记忆库里找出一批与当前任务相关的候选内容,再判断哪些真正有用。
要做向量检索,系统得先把原始历史加工成一种“可计算、可过滤、可召回”的形态。
这里先看几个基础概念。
- chunk:切分后的记忆片段。原始对话、会议记录、文件和工具结果通常太长,系统会先把它们拆成更小的片段,方便单独检索。
- metadata:记忆的结构化标签。比如来源、时间、项目、用户、作用范围、状态等,用来过滤这条记忆能不能被当前任务使用。
- embedding:把文本转换成向量的过程。它不是提取关键词,而是用 embedding model 把文本的语义特征编码成数字。
- vector:embedding 之后得到的一组数字。它代表这段文本在语义空间里的位置,系统可以用距离计算判断两段内容是否接近。
- vector database / vector index:向量库或向量索引。它负责存放向量,并根据当前查询快速找回语义上接近的记忆片段。实际系统里,它不一定是一个独立数据库,也可能是数据库或搜索引擎里的向量索引能力。
先把写入和读取的完整链路放在一起看,再逐步拆开。

举个会议记录的例子。
7 月 8 日部门例会:
本周五前,各组提交下季度预算。
客户回访记录统一用新版表格,旧模板不再使用。
新员工培训资料由李娜负责整理,下周三前发到群里。
下次例会复盘客户投诉处理进度。
系统通常会保留原始会议记录,同时把它拆成更小的检索单元。
这段记录可以拆出多个可复用的记忆片段:
chunks:
1. 本周五前,各组提交下季度预算。
2. 客户回访记录统一用新版表格,旧模板不再使用。
3. 新员工培训资料由李娜负责整理,下周三前发到群里。
4. 下次例会复盘客户投诉处理进度。
比如第二条 chunk,可以挂上这样的标签:
metadata:
source: 7 月 8 日部门例会
type: process_rule
scope: team
status: active
接着通过 embedding model 把这段文本转换成 vector:
vector:
[0.013, -0.42, 0.87, ...]
写入阶段可以压成一条链路:
原始历史
↓
切成 chunk
↓
挂上 metadata
↓
生成 embedding / vector
↓
写入向量库 / 向量索引
这一步做完以后,历史内容就不只是“文本”了。
它变成了一批带语义坐标、带结构化标签、可以被检索的记忆片段。
写入只是让历史变得可搜索。
真正进入读取阶段,系统要先把这次任务整理成 query。
这里的 query 不只是用户刚输入的一句话。
更完整地看,它还可能综合:
- 用户当前问题;
- 系统或开发者指令;
- 当前文件、目录或项目上下文;
- 工具调用结果;
- 最近会话摘要。
如果只看用户一句话,召回结果很容易偏。
比如你说“客户回访记录用哪个模板”,真正决定该召回什么的,不只是“模板”这两个字,还包括当前团队、会议来源、流程状态和最近的规则变更。
有了 query 之后,系统会根据它生成查询向量,再去向量库或向量索引里召回语义上接近的 chunk。
但它不会只看语义距离。
它还会结合 metadata 判断:这条记忆是不是属于当前项目?是不是仍然有效?是不是适合当前任务?
所以,一个记忆系统的检索通常不只靠一种方法。
这里可以把检索方法分成两类。
Semantic Search,语义搜索,看的是语义相似度。
它会把 query 和记忆片段都转换成向量,再比较它们在语义空间里的距离。距离越近,系统越认为它们相关。
比如 query 是“客户回访记录用哪个模板”,它可能召回“客户回访记录统一用新版表格,旧模板不再使用”。
因为两者字面不完全一样,但都和“客户回访记录的流程规则”有关。
语义搜索的优点是能跨过字面表达,找到意思接近的内容。
但它也有弱点:它不擅长处理必须精确命中的信息。
比如命令、文件名、报错码、路径、配置项,差一个字符都可能完全不同。
这时候就需要关键词检索。
BM25 是一种经典关键词排序算法。它关心的不是“意思像不像”,而是 query 里的词在文档里是否出现、出现得多不多、这个词在整批文档里是否足够稀有。
所以搜 pnpm test、AGENTS.md、MemoryError、src/app/layout.tsx 这类内容时,BM25 往往比纯语义搜索更稳。
在工程实现中,语义检索常与关键词检索组合使用,也就是 Hybrid Search,混合搜索。
它不是简单把几种搜索结果堆在一起,而是把不同检索信号组合起来:
query
↓
metadata 过滤或限定范围
↓
Semantic Search + BM25 召回候选
↓
合并候选结果
↓
rerank 重排
↓
返回更相关的 memories
这里的分工很清楚。
Semantic Search 负责找语义接近的内容。
BM25 负责抓字面上必须命中的内容。
metadata 负责过滤或限定范围,比如用户、项目、时间、来源和状态。
rerank 负责在候选结果里重新排序,把更适合当前任务的记忆排到前面。
Mem0(github.com/mem0ai/mem0)可以作为这一类的代表来看。
它是面向 AI Agents 的 memory layer,把记忆的写入和搜索封装成 Agent 能直接调用的能力,比如 add / search。

从检索角度看,Mem0 的代表性在于它使用的是多信号召回:semantic search、BM25 keyword、entity matching,以及和时间相关的 retrieval 判断。
这正好对应前面讲的 Hybrid Search:向量负责语义相似,BM25 负责关键词命中,entity / metadata / time 负责缩小范围和判断上下文。Agent 最后拿到的,不是“所有历史”,而是一批被召回、过滤、排序后的候选记忆。
到这里先把边界说清楚。
向量检索解决的是“从大量历史里召回候选记忆”。
它不等于完整的 AI 记忆系统。
召回结果只是候选内容,后面还要经过范围判断、有效性判断、排序和上下文注入。
它能帮你从大量历史里找回可能相关的记忆,但不保证这些记忆一定该用。
语义相似不等于任务相关。
检索成功也不等于注入成功。
真正影响模型输出的,是被放进当前上下文的那几条记忆。
结构化记忆:给记忆划定边界
向量检索擅长找“意思相近”的内容,但相似不等于适用。
如果把用户偏好、会议纪要、工作流程和历史决策全部放进同一个检索空间,问题就来了。
系统可能找到了语义很像的记忆,却用错了部门、项目或时间范围。
举个常见的例子。
财务组的会议记录里写着:
报销单统一使用新版模板,周五前提交给财务负责人审核。
客服组的会议记录里也写着:
客户回访记录统一使用新版表格,旧模板不再使用。
两条记忆都在讲新版表格或模板的使用规则。
如果只按关键词或语义相似度搜索,当你问“客户回访记录用哪个模板”时,财务组的报销规则也可能进入候选结果。
问题不是系统没有搜到,而是它搜索的范围太大了。
结构化记忆要解决的,就是记忆之间的边界:这条信息属于谁、来自哪里、适用于什么任务、现在是否还有效。
写入记忆时,系统不只保存正文,还会把上篇 Extract 阶段提到的归属、来源、时间和状态一起记录下来。
content: 客户回访记录统一使用新版表格,旧模板不再使用
scope: 客服组
project: 客户回访项目
topic: 记录模板
source: 部门例会记录
timestamp: 7 月 8 日(会议发生时间)
status: active
这些结构信息可以来自当前用户、项目和会话上下文,也可以由规则或模型在抽取记忆时补充。
到了读取阶段,系统再利用它们限定检索范围。
比如当前任务属于客服组,系统可以先排除财务组的记录;只需要现行流程,就继续过滤已经失效或被替代的旧规则。
如果任务只查询某段时间或指定资料来源,还可以继续按 timestamp 和 source 过滤。
筛选完成后,再在剩下的候选中执行向量检索、BM25 或混合搜索。
根据当前用户、会话或项目上下文确定查询范围
↓
根据 scope、source、timestamp、status 缩小候选范围
↓
在候选范围内做语义或关键词检索
↓
返回可以用于当前任务的记忆
工程上,这种范围约束可以通过命名空间(namespace)、元数据过滤(metadata filter)或层级目录实现。
这些方式可以组合使用:结构负责确定“应该去哪里找”,检索算法负责判断“哪条记忆更相关”。
MemPalace(github.com/mempalace/mempalace)就是这种思路的代表。
前面的例子用字段表达边界,MemPalace 则把记忆组织成一套可以导航的层级:
Wing:以人物、项目或主题划分的顶层空间;Room:Wing 中的具体主题;Closet:指向原文的压缩索引;Drawer:保存单个原始文本 chunk。
Hall 用来连接同一 Wing 内相关的 Room,Tunnel 则连接不同 Wing 中的相同主题。

字段和层级不需要一一对应。project / topic 可以变成层级,scope / source / timestamp / status 仍然作为元数据参与过滤。
当你查询客户回访模板时,系统先限定到对应的 Wing 和 Room。
Closet 用压缩指针帮助相关 Drawer 提升排序,但不是必须经过的入口;向量或关键词检索仍然可以直接搜索 Drawer 中的原文。
需要跨主题或跨项目查找时,系统还可以沿 Hall 或 Tunnel 扩大范围。
层级结构负责导航和限定范围,检索算法仍然负责判断哪些原文更相关。
对 MemPalace 来说,保存原文还有一个好处:减少摘要造成的信息损失。
某个方案为什么被否掉、某条命令为什么不能用、某个限制只适用于哪个目录,这些边界一压缩就容易丢。
当然,结构化记忆也有成本。
分类错了,检索就会错。
结构太细,维护成本高;结构太粗,又会退回“大池子”问题。
所以它更适合长期项目、多人协作、多项目切换,以及记忆边界很重要的场景。
个人轻量使用,不一定一上来就需要这么重。
知识图谱:追踪关系和事实变化
向量擅长找相似文本。
结构化擅长缩小范围。
但真实记忆里,还有一类信息更麻烦:实体之间的关系会不断变化。
比如项目 X 最初采用方案 B,后来发现权限模型不满足,于是在 7 月 8 日的会议中改用方案 C。
如果只把两次会议记录切成 chunk 存进向量库,系统可能同时找回“使用方案 B”和“使用方案 C”,却不知道哪条仍然有效。
知识图谱式记忆要处理的,就是关系、变化和历史状态。
Graphiti(github.com/getzep/graphiti)是面向 AI Agents 的开源 temporal context graph 框架。
它可以理解成一种时间感知的上下文图谱。

它会把持续进入系统的对话、文档或业务数据保存为 episode,再从中抽取实体和关系事实:
entity: 人、项目、产品、规则等实体
fact / relation: 实体之间的关系事实
episode: 产生事实的原始对话、文档或业务事件
validity window: 事实从何时生效、何时失效
provenance: 事实来自哪个 episode
Provenance 负责追溯事实来源,不会自动判断某个来源更可信。
写入新信息时,系统不只是追加一段文本:
新 episode
↓
抽取实体和关系事实
↓
与图中已有事实对比
↓
关闭旧事实的有效期,写入新事实
↓
保留新旧事实与原始 episode 的关联
只有新 episode 明确表示方案 C 取代方案 B,或者新旧事实被识别为冲突时,系统才会关闭旧事实的有效期。
如果两条关系可以同时成立,就应该继续保留。
前面的方案变化,可以表示成:
项目 X --使用--> 方案 B
有效期:截至 7 月 8 日
项目 X --使用--> 方案 C
有效期:自 7 月 8 日起
来源:7 月 8 日会议记录
方案 B 没有被简单删除,而是变成一条已经失效的历史事实。
这样系统既能回答“现在使用哪个方案”,也能追溯“之前用过什么、为什么更换”。
读取时,Graphiti 会组合图遍历、语义检索和关键词检索:
当前问题
↓
识别相关实体和时间条件
↓
图遍历 + 向量 / BM25 检索
↓
返回当前事实、历史关系和原始来源
图遍历负责沿实体关系找到相关路径,向量检索负责召回语义接近的事实,BM25 负责命中名称、编号等准确关键词。
有效时间再决定返回当前事实,还是某个时间点的历史状态。
这类方案适合企业知识、客户关系和业务流程 Agent。
它们要回答的往往不只是“哪段文字相似”。
更重要的是:客户最近一次投诉是什么、谁跟进过、合同现在是什么状态、这条事实来自哪份记录。

但图谱也不是越用越好。
同名实体可能被错误合并,关系抽取或有效期更新也可能出错。
schema / ontology,也就是实体和关系需要遵守的类型规则,同样需要设计和维护。
图谱本身、向量索引和全文索引也会增加存储与维护成本。
所以图谱更适合关系复杂、事实变化快、来源追踪重要的系统。
如果只是个人写作偏好、常用命令、项目规则,文件、自动记忆、向量检索和结构化过滤,已经能解决很多问题。
分层记忆:管理上下文预算
前面几种方案主要解决“怎么存、怎么找”。
但找回来以后,还有一个问题:
放在哪里?
模型真正能用到的东西,必须进入当前上下文。
可上下文预算有限。
你可以把它想成一张桌面。
资料柜里可以放很多文件,但你真正工作时,桌面上不能摊满所有东西。该放在桌面的,是当前任务马上要用的资料。
分层 / 自管理式记忆解决的,就是如何在有限的上下文里安排这些资料。
它不是一种新的存储格式,而是一套上下文管理策略。文件、向量库和知识图谱里的信息,都可以成为不同层级的记忆来源。
不同系统的叫法不一样,从使用方式看,大致可以分成三类:
- 工作上下文:当前任务、最近对话、正在处理的材料和最新反馈,模型可以直接看到,但空间最有限。
- 常驻记忆:用户偏好、项目约束等需要持续可见的信息,跨多轮保留,也会占用上下文预算。
- 按需记忆:历史对话、归档记录、文件和外部数据库,平时不全部放进上下文,需要时再搜索并取回相关片段。
举个例子,你让 AI 持续协助修改一份工作方案。
这一轮的修改要求、最新草稿和反馈,放在工作上下文里;项目目标、预算限制和写作规范,放在常驻记忆里;之前的会议记录、历史版本和被否掉的方案,则留在按需记忆中。
当 AI 需要判断某个方案为什么被否掉时,再找回相关会议记录,而不是把所有历史资料都塞进上下文。
这才是分层的核心:重要信息不是简单地“存着”,而是根据使用频率和当前任务,决定哪些始终可见,哪些需要时再取。
MemGPT(Memory-GPT)是 2023 年提出的一种 Agent 记忆管理方法。它把模型有限的上下文看作“工作内存”,把上下文之外的存储看作“长期内存”,再让 Agent 通过工具在两者之间读写信息。
MemGPT 后来发展为 Letta。现在,MemGPT 主要指论文提出的这套记忆管理方法,Letta(github.com/letta-ai/letta)则是实现并继续发展这一思路的 Agent 框架。

在 Letta Agent API 的上下文层级中,这些层次对应更具体的抽象:
- Memory Blocks 是持久、可编辑的常驻信息,始终位于模型上下文中,不需要检索。
- Files 可以按段打开或搜索,不必把整份文件长期放进上下文。
- Archival Memory 位于上下文之外,通过工具写入和检索。
- 更大规模的外部知识库,也可以通过自定义工具或 MCP 接入。
它的运行过程可以简化为:
当前上下文产生新信息
↓
判断它需要立即使用、持续可见,还是按需保存
↓
保留在工作上下文 / 更新 Memory Block / 写入归档
↓
需要更多信息时搜索文件、归档或外部数据
↓
只把相关结果放回工作上下文
这类方案适合长运行 Agent。
但它也更难调试。
因为问题可能出在任何一步:该常驻的信息没有保留,该归档的信息没有写入,该召回的内容没有召回,或者过期信息仍在占用上下文并影响判断。
如果 Agent 判断错了,把关键内容放错层,用户看到的结果还是:
它怎么又忘了?
真实系统为什么会走向混合
到这里可以发现,前面讲的几种方案,并不完全是同一层面的替代品。
文件、结构化记录和图谱,主要解决记忆以什么形式保存。
自动提取、摘要和更新,解决记忆如何形成和变化。
向量、BM25、metadata filter 和图遍历,解决如何找到相关信息。
分层系统则负责控制哪些信息持续可见,哪些需要时再取回。
一套完整的 memory layer,本来就需要这些不同层面的能力配合。
继续用修改工作方案的例子来看:写作规范可以保存在文件或常驻记忆里;会议记录保留原文,同时建立向量和 BM25 索引;项目、时间和状态写进 metadata;如果还要追踪人物、决策和版本之间的关系,再引入图谱。
当你要求 AI 继续修改方案时,检索系统先找回相关记录,上下文管理层再筛选出当前真正需要的内容,交给模型处理。
整个过程可以粗略看成:
对话 / 文件 / 操作记录
↓ 提取、摘要、更新
文件 | 结构化记录 | 时间图谱
↓ 建立索引
向量 | BM25 | metadata | 图遍历
当前任务
↓ 发起检索
从上述索引召回候选
↓ 排序、压缩、分层
当前上下文
↓
Agent Runtime

这不是要求每条记忆都经过所有组件。简单的长期偏好可能只需要文件和关键词搜索;关系复杂、状态频繁变化的信息,才需要结构化记录或时间图谱。
EverOS(github.com/EverMind-AI/EverOS)则是另一种混合实现。
它是一套本地优先、可跨工具使用的记忆运行时,把对话、文件和 Agent 执行轨迹保存为可读的 Markdown,同时建立 SQLite 和 LanceDB 索引。

其中,Markdown 是可读、可编辑、可版本控制的事实源;SQLite 管理状态、任务队列和变更;LanceDB 提供向量、BM25 和标量过滤。三者分别承担保存、管理和检索职责。
所以设计记忆系统时,问题不只是“选哪个框架”,而是:
这条记忆是什么类型?
它属于哪个范围?
它会不会变化?
它需要怎么检索?
哪些内容应该进入当前上下文?
它错了以后怎么纠正?
这些问题,比框架名更重要。
真正难的不是记住,而是忘掉
前面的方案已经解决了很多“怎么记住”的问题。
但一条记忆从写入到删除,还要经历选择、更新、纠错和权限控制。真正容易出问题的,往往是这段生命周期。
第一道关是:该不该写入?
用户说“这次写正式一点”,可能只是当前任务要求。
用户反复说“以后不要写客服话术”,才更像长期偏好。
第二道关是:写入以后怎么更新?
一份工作方案最初的预算是 20 万,后来调整为 15 万。旧预算曾经是对的,但继续把它当成当前事实,就会误导 AI。
如果 AI 一开始就理解错了,问题更麻烦。错误一旦进入长期记忆,后面的任务可能反复受到影响。
第三道关是:谁可以读取、修改和删除?
记忆里可能包含账号、客户信息、业务资料和内部流程。个人记忆不一定能团队共享,某个项目的信息也不应该被带到另一个项目。

对记忆系统来说,“忘掉”不只等于删除。
一次性要求可以不写入长期记忆;被新事实替代的旧记录可以标记为失效;长期不再相关的内容可以降权;涉及用户、团队和项目的信息需要限制召回范围。
在用户明确要求删除,或者数据不应该继续保留时,系统还需要从存储和检索索引中清除相关内容,并避免它从旧摘要或其他副本中再次写回。
这些操作依赖来源、时间、状态、版本和作用范围等信息,也依赖系统提供可检查、可纠正、可删除的入口。
所以,遗忘不是记忆系统的反面。
它本来就是记忆生命周期的一部分。
回到最初的问题
AI 记忆不是让模型“变成人”,也不是把所有聊天记录无限塞进上下文。
它是上下文工程、信息检索、知识管理和 Agent 工作流共同组成的一套记忆层。
上篇讲信息如何留下来,下篇讲这些信息如何被找回、组织、注入、更新和遗忘。
这些机制最终服务于同一件事:让模型在当前任务里拿到正确、有效,并且属于当前范围的信息。
我们当然不希望 AI 每次都像第一次认识你。
但真正可靠的记忆,也不该把过去的信息,永远当成现在的事实。


