hanhan的BLOG
← 返回文章

关于 agent 记忆系统的一些初步理解

2026-05-28
agent

初步开始学习agent开发,就觉得关于记忆系统相关的设计很有意思,因为是要做一些给自己长期使用的小玩具,那肯定是想要高度定制化,或者说越用越顺手,这时候就不得不去思考这个玩具的记忆系统到底怎么设计又能轻量化的同时还能满足前面提到的特性。

记忆系统本质在做什么

LLM模型本身是无状态的,每次调用之间互相独立。 有一个非常简单的记忆系统实现,就是在调用模型之后把对话历史保存下来,然后下一次调用把对话历史全量拼接在prompt里面,让模型在多轮对话中具备了上下文连贯性,我们由此可知,记忆系统本质就是给模型做一个外部记忆存储,而怎么存,存在哪儿,怎么拿,就是需要设计的地方。

简单记忆系统的简单优化

通过把对话历史全量保存下来拼接prompt的方式可以满足会话级的记忆,但是这样的设计会导致一旦会话过长,对话历史超出上下文窗口,

所以我们必须要优化这个粗糙的设计。

很自然的会想到,我们可以通过滑动窗口的方式来避免对话历史的无限制增长,不导入全量对话历史,而且只导入最新十轮或者二十轮的对话历史来保证上下文不被填满,但是这时候又有一个新的问题——模型丧失了早期对话的记忆,这就违背了初衷了。

咋整。 那就压缩。

既要保留早期对话记忆,又不能全量导入,最简单的办法就是压缩,没有人不喜欢ai总结,你说对吧。通过对设定轮数之前的对话记录进行压缩再加上设定轮数之后全量对话记录,这样就能既让模型有对话的全部记忆,又能避开上下文窗口的限制。(这里的设定轮数也可以是区间,设定的也可以不是轮数而是token数)

持久化存储

如果只把对话历史存在变量里面,那么关闭会话窗口之后,这部分记忆就永久消失了,而且也不方便对已有对话历史做进一步处理。所以我们需要对对话历史进行持久化存储,这样下次打开同一个会话也能继续上次的对话,后续对对话历史处理也会更加方便。

长期记忆的引入

很显然,这个简单记忆系统甚至不能给玩具用,因为根本没有办法获取跨会话的记忆,只有单次会话内的记忆,这时候我们就需要引入长期记忆了。

先抛开原来的想法,现在从短期记忆和长期记忆两个模块来看记忆系统。

短期记忆模块需要维持当前会话的连贯性,让模型能听懂和记住刚刚说过的话。 用人的记忆来举例就是,你听到一个电话号码,需要立刻拨出去,短期记忆就负责在那几秒钟里把号码挂在脑海里。

长期记忆模块需要构建对用户的深度认知,让AI能跨会话地记住关于你、关于世界的一切。 用人的记忆来举例就是,你通过学习知道了地球是圆的,以后看到地球相关的知识你都会想起来地球是圆的。

简单来说,短期记忆负责“当下的交流”,而长期记忆负责“积累的认知”

而记忆系统的设计就是围绕这两个模块展开的。

以Mem0为例简单窥探记忆系统设计(不包含Mem0g)

Mem0通过一个向量数据库 + 精炼的事实,存跨会话的记忆系统。 整个系统就做两件事,写记忆和读记忆。

写记忆

写记忆负责将原始对话转化为精炼的事实并持久化。它是一个两阶段管道:先提取,再更新。 每次触发写操作时,输入是一个三元组: P = (S, {m_(t-m), …, m_(t-2)}, m_(t-1), m_t) 三个部分的角色:

  • S:整个对话的全局摘要。由一个异步模块在后台周期性刷新,不阻塞主流程。
  • {m_(t-m), …, m_(t-2)}:最近 m 条历史消息(论文取 m=10),提供细粒度的时序语境。
  • m_(t-1), m_t:新到达的消息对,是本次提取的目标。 S 和最近消息是辅助上下文,提取目标仅限于新消息对。
第一阶段:Extraction(提取)

通过LLM模型从新消息对提取一组候选事实输出自然语言,S和最近消息负责辅助理解语境以及判断哪些信息是新的、值得存储的。

第二阶段:Update(更新)

对于一阶段的每条候选事实w做三件事

  • 用w的向量嵌入从数据库中检索 top-s 条语义最相似的已有记忆(论文取 s=10)
  • 将 wi 与检索到的相似记忆一起提交给 LLM。LLM 通过 function-calling 接口自主选择以下四种操作之一:ADD UPDATE DELETE NOOP
  • 执行LLM选定的操作,更新数据库

ADD:w 与已有记忆语义不相似,创建新记忆分配唯一 ID
UPDATE:w 增强/补充了某条已有记忆,更新修正旧记忆
DELETE:w 与某条已有记忆矛盾,物理删除旧记忆
NOOP:w 已在库中或不构成事实,跳过

读记忆

给定查询query,用向量相似度从记忆库中检索最相关的记忆作为上下文