返回文章

AI 记忆是怎么实现的?下篇:向量检索、知识图谱和长期记忆系统

从向量检索、混合搜索、结构化记忆、知识图谱到分层上下文,拆解长期记忆如何被召回、注入、更新和遗忘。

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 testAGENTS.mdMemoryErrorsrc/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:Memory For AI Agents

从检索角度看,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

这些结构信息可以来自当前用户、项目和会话上下文,也可以由规则或模型在抽取记忆时补充。

到了读取阶段,系统再利用它们限定检索范围。

比如当前任务属于客服组,系统可以先排除财务组的记录;只需要现行流程,就继续过滤已经失效或被替代的旧规则。

如果任务只查询某段时间或指定资料来源,还可以继续按 timestampsource 过滤。

筛选完成后,再在剩下的候选中执行向量检索、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 中的相同主题。

MemPalace 记忆宫殿结构

字段和层级不需要一一对应。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 框架。

它可以理解成一种时间感知的上下文图谱。

Graphiti:Build Real-Time Knowledge Graphs for AI Agents

它会把持续进入系统的对话、文档或业务数据保存为 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。

它们要回答的往往不只是“哪段文字相似”。

更重要的是:客户最近一次投诉是什么、谁跟进过、合同现在是什么状态、这条事实来自哪份记录。

Graphiti 时间感知图谱

但图谱也不是越用越好。

同名实体可能被错误合并,关系抽取或有效期更新也可能出错。

schema / ontology,也就是实体和关系需要遵守的类型规则,同样需要设计和维护。

图谱本身、向量索引和全文索引也会增加存储与维护成本。

所以图谱更适合关系复杂、事实变化快、来源追踪重要的系统。

如果只是个人写作偏好、常用命令、项目规则,文件、自动记忆、向量检索和结构化过滤,已经能解决很多问题。

分层记忆:管理上下文预算

前面几种方案主要解决“怎么存、怎么找”。

但找回来以后,还有一个问题:

放在哪里?

模型真正能用到的东西,必须进入当前上下文。

可上下文预算有限。

你可以把它想成一张桌面。

资料柜里可以放很多文件,但你真正工作时,桌面上不能摊满所有东西。该放在桌面的,是当前任务马上要用的资料。

分层 / 自管理式记忆解决的,就是如何在有限的上下文里安排这些资料。

它不是一种新的存储格式,而是一套上下文管理策略。文件、向量库和知识图谱里的信息,都可以成为不同层级的记忆来源。

不同系统的叫法不一样,从使用方式看,大致可以分成三类:

  • 工作上下文:当前任务、最近对话、正在处理的材料和最新反馈,模型可以直接看到,但空间最有限。
  • 常驻记忆:用户偏好、项目约束等需要持续可见的信息,跨多轮保留,也会占用上下文预算。
  • 按需记忆:历史对话、归档记录、文件和外部数据库,平时不全部放进上下文,需要时再搜索并取回相关片段。

举个例子,你让 AI 持续协助修改一份工作方案。

这一轮的修改要求、最新草稿和反馈,放在工作上下文里;项目目标、预算限制和写作规范,放在常驻记忆里;之前的会议记录、历史版本和被否掉的方案,则留在按需记忆中。

当 AI 需要判断某个方案为什么被否掉时,再找回相关会议记录,而不是把所有历史资料都塞进上下文。

这才是分层的核心:重要信息不是简单地“存着”,而是根据使用频率和当前任务,决定哪些始终可见,哪些需要时再取。

MemGPT(Memory-GPT)是 2023 年提出的一种 Agent 记忆管理方法。它把模型有限的上下文看作“工作内存”,把上下文之外的存储看作“长期内存”,再让 Agent 通过工具在两者之间读写信息。

MemGPT 后来发展为 Letta。现在,MemGPT 主要指论文提出的这套记忆管理方法,Letta(github.com/letta-ai/letta)则是实现并继续发展这一思路的 Agent 框架。

MemGPT 原始分层记忆架构

在 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 索引。

EverOS 项目概览

其中,Markdown 是可读、可编辑、可版本控制的事实源;SQLite 管理状态、任务队列和变更;LanceDB 提供向量、BM25 和标量过滤。三者分别承担保存、管理和检索职责。

所以设计记忆系统时,问题不只是“选哪个框架”,而是:

这条记忆是什么类型?
它属于哪个范围?
它会不会变化?
它需要怎么检索?
哪些内容应该进入当前上下文?
它错了以后怎么纠正?

这些问题,比框架名更重要。

真正难的不是记住,而是忘掉

前面的方案已经解决了很多“怎么记住”的问题。

但一条记忆从写入到删除,还要经历选择、更新、纠错和权限控制。真正容易出问题的,往往是这段生命周期。

第一道关是:该不该写入?

用户说“这次写正式一点”,可能只是当前任务要求。

用户反复说“以后不要写客服话术”,才更像长期偏好。

第二道关是:写入以后怎么更新?

一份工作方案最初的预算是 20 万,后来调整为 15 万。旧预算曾经是对的,但继续把它当成当前事实,就会误导 AI。

如果 AI 一开始就理解错了,问题更麻烦。错误一旦进入长期记忆,后面的任务可能反复受到影响。

第三道关是:谁可以读取、修改和删除?

记忆里可能包含账号、客户信息、业务资料和内部流程。个人记忆不一定能团队共享,某个项目的信息也不应该被带到另一个项目。

AI 记忆的生命周期管理

对记忆系统来说,“忘掉”不只等于删除。

一次性要求可以不写入长期记忆;被新事实替代的旧记录可以标记为失效;长期不再相关的内容可以降权;涉及用户、团队和项目的信息需要限制召回范围。

在用户明确要求删除,或者数据不应该继续保留时,系统还需要从存储和检索索引中清除相关内容,并避免它从旧摘要或其他副本中再次写回。

这些操作依赖来源、时间、状态、版本和作用范围等信息,也依赖系统提供可检查、可纠正、可删除的入口。

所以,遗忘不是记忆系统的反面。

它本来就是记忆生命周期的一部分。

回到最初的问题

AI 记忆不是让模型“变成人”,也不是把所有聊天记录无限塞进上下文。

它是上下文工程、信息检索、知识管理和 Agent 工作流共同组成的一套记忆层。

上篇讲信息如何留下来,下篇讲这些信息如何被找回、组织、注入、更新和遗忘。

这些机制最终服务于同一件事:让模型在当前任务里拿到正确、有效,并且属于当前范围的信息。

我们当然不希望 AI 每次都像第一次认识你。

但真正可靠的记忆,也不该把过去的信息,永远当成现在的事实。

同标签文章