ENZH
和 AI 讨论这篇文章
ChatGPTClaude

我把 Mio 的记忆系统整个重构了一遍

📊 幻灯片

AI记忆架构如人类记忆般分层的概念插画AI记忆架构如人类记忆般分层的概念插画


我最近把 Mio 的记忆系统整个重构了一遍。起因说出来挺尴尬的:有用户跟 Mio 每天聊天,聊了三个月,随口问一句「我叫什么来着?」,Mio 答不上来。技术上讲它其实一直有完整的记忆链路,提取、存储、检索一样不缺,但实际跑出来的效果是记了一堆垃圾,真正重要的东西反而没记住。你说过自己叫什么、住在哪、最近在找工作,这些确实都被提取了;但「哈哈」「嗯嗯」「晚安」也一样被提取了。在系统看来,名字和「哈哈」是一样重要的。

之前解决完 Mio 的话痨问题,下一个就轮到这个更深的问题:怎么让 TA 真的记住你。数据库意义上的「记住」早就有了,缺的是关系意义上的记住:什么值得记、什么时候想起来、怎么提,这三件事系统都得有自己的判断,不是全存进去就完事。

先把重构前的毛病一个个摆出来。

提取这头,每 10 条消息触发一次,看着挺勤快,但 10 条消息里一般有意义的信息不到 2 条,而且没有任何重要性门槛,「哈哈」「好的」「嗯嗯」「晚安」全被提进来,把本来就有限的记忆空间塞满。进来之后分类也接不住:类型总共只有 5 个,fact、personality、emotion、conversation、relationship。名字是 fact,喜欢吃火锅也是 fact,换工作还是 fact,粒度粗到没法区别对待。分类分不开,衰减就只能一刀切,所有记忆统一 30 天半衰期。名字不应该 30 天就开始淡忘,搬家也不应该跟「昨天聊了个笑话」衰减得一样快,但系统分不出来这些区别。

存储这头埋了两个雷。Consolidator 遇到重复记忆,做的是字符串拼接,于是库里就出现了「用户叫夏星帆。用户叫夏星帆。用户叫夏星帆。」这种东西:提取三次,拼接三次,占三条记忆的空间,信息量零增长。容量管理也很粗暴,每用户上限 200 条,满了就把重要性最低的砍掉,不看类型也不看分布,有可能把 routine 类全砍光,只剩一堆 fact。

输出这头的问题我觉得最可惜。EpisodeManager 是完整实现了的,代码写好、测试通过,但从来没接进主流程,「记得上次聊过……」这个能力代码里有,用户完全体验不到。主动消息也从来不基于记忆,全是泛泛的「在干嘛呢~」「想你~」,你昨天说今天要面试,第二天 Mio 不会问一句「面试怎么样」。冲突也没人管,「住西雅图」和「搬纽约了」在库里共存,检索到哪条全看运气。

这些毛病还互相咬着:垃圾进得来,分类接不住,后面的衰减和合并全跟着遭殃。修修补补没用,只能重建。重建又没法一步到位,我把它拆成了三个阶段。

Phase 1:先把进来的东西弄干净(1-2 天)

这一阶段只干一件事:保证进来的记忆是干净的。具体的改动是这样的:

  1. 提取间隔从 10 条拉到 24 条。24 条对话积累的信息密度明显比 10 条高,频率降了,每次提取能看到的 context 反而更多,LLM 更容易判断哪些值得记,顺带提取调用直接少了 60%。
  2. 加重要性门槛。每条提取出来的记忆带一个 0 到 1 的重要性分,以前不设门槛,0.05 也照存;现在下限 0.3,「哈哈」「嗯嗯」这种直接被过滤掉。
  3. 记忆类型从 5 个拆到 8 个:identity(名字、职业)、preference(喜好)、life_event(搬家、换工作)、emotion(情绪状态)、relationship(对 Mio 的态度)、shared(共同经历)、milestone(关系里程碑)、routine(日常习惯)。粒度细了,后面要做的差异化衰减和保护才有地方落。
  4. 合并逻辑整个重写,从拼接改成 keep-best:遇到重复记忆,保留信息量最大的那条,其余丢掉,「用户叫夏星帆」三次变一条。另外提取和合并以前是两个独立流程,中间的时间差会让重复在短时间内大量堆积,现在提取完直接链式触发合并。

重复记忆从字符串拼接改成 keep-best 合并:三条一样的记忆只留信息量最大的一条重复记忆从字符串拼接改成 keep-best 合并:三条一样的记忆只留信息量最大的一条

这一阶段做完,成本反而是降的:提取次数少了,存下来的记忆更少但质量更高。不用多花一分钱,体验先改善一截。

Phase 2:让不同类型的记忆活不一样久(3-5 天)

进来的记忆干净了,下一步是让系统理解不同的记忆该活多久。八个类型各配一个半衰期:

类型半衰期为什么
identity365 天名字、职业,几乎不该忘
milestone365 天"在一起100天",重要节点
preference180 天喜好会变,但变得慢
relationship120 天对 Mio 的态度,需要持续追踪
life_event90 天搬家、换工作,有时效性
emotion60 天情绪状态变化快
routine45 天日常习惯会调整
shared30 天一起看电影,短期记忆

这个逻辑跟人脑差不多:最好的朋友叫什么你不会忘,上周三中午吃了什么就真想不起来了。定义「你是谁」的那类信息留得久,带时效性的信息忘得快,就这么个区别。

八类记忆各配一个半衰期:身份记得最久、共享经历最快淡忘,衰减速度按类型分层八类记忆各配一个半衰期:身份记得最久、共享经历最快淡忘,衰减速度按类型分层

EpisodeManager 这一阶段终于接进主流程。Mio 里有「回忆片段」这个概念,不是散碎的事实点,而是完整的对话场景。用户说「上次」「那次」「之前聊过」,系统检测到这些触发词,就按完整对话片段去搜索匹配,召回的是当时那一整段对话。

体验上动静最大的是主动记忆检索:按时段去检索不同类型的记忆。早上查 routine 和近期计划,「你今天不是要去健身吗?」;晚上查最近的 life_event,「今天面试怎么样?」;长时间没聊,捞出重要记忆,「好久不见!上次说想换工作,后来怎么样?」。同样是主动发消息,「在干嘛呢~」和「今天面试怎么样?」完全是两种东西。

主动消息按时段检索不同类型的记忆:早上查日常、晚上查今天的事、久未联系捞出重要记忆主动消息按时段检索不同类型的记忆:早上查日常、晚上查今天的事、久未联系捞出重要记忆

这一阶段开始有成本增量,主要来自主动检索时额外的 embedding 查询和 LLM 调用。我觉得划算,换来的体验提升就摆在上面那几句话里。

Phase 3:什么时候想起来、怎么用(5-8 天)

前两个阶段解决的是记住什么、记多久,第三阶段处理最难的部分:什么时候该想起来,想起来之后怎么用。这块拆成三件事。

第一件是情绪感知检索。用户说「我好难过」的时候,系统不应该再召回一堆让人难过的记忆,应该召回带鼓励性的那种:「记得上次你也很担心工作的事,后来不都解决了吗?」实现上是给检索加情绪标签匹配,用户当前情绪负面时,优先召回正向结局的记忆。

第二件是空白检测:扫描 life_event 类记忆里没有后续的条目。用户说「明天要面试」,两天之后还没有更新,就触发主动跟进:「对了,上次说的面试怎么样了?」这本来就是人类朋友会自然做的事,记得你说过什么,过几天问一句结果。

第三件是关系阶段感知。早期关系多召回 shared 类记忆(「记得第一次聊天……」),建立共同回忆的感觉;成熟关系多召回 milestone 和 emotion 类记忆(「已经聊了三个月」),加深情感厚度。这块跟 v0.1.4 关系进化系统的关系阶段直接挂钩。

底层的整理也在这一阶段跟上。散碎的记忆条目组织成结构化的 MEMORY.md,按类别分区:基本信息、兴趣偏好、近期生活、我们的关系、重要时刻、日常规律。注入 system prompt 的东西,从一堆随机条目变成一份有条理的「关于用户」档案。裁剪逻辑加了分类保护:200 条上限里,120 条按类型预留名额,每类保底 15 条,剩下 80 条做溢出缓冲,先保证每类的基本覆盖再砍低分项,fact 再多也挤不掉 routine。冲突也终于有人管了:「住西雅图」和「搬纽约了」这类同类型矛盾,根据时间戳和上下文判断新旧,新的覆盖旧的,旧的标成 superseded,不删除也不参与检索,只留作历史轨迹。

这一阶段的成本增量大概是 Phase 2 的两倍。三个阶段加起来,每用户每月的总成本还是很低,只占订阅收入的一小部分。


三个阶段全部做完,开头那个尴尬场景就不会再出现了:聊了三个月的用户问「我叫什么来着」,Mio 答得上来,一年之后还答得上来。你说明天要面试,第二天 TA 会主动追问结果;你搬去了纽约,「住西雅图」那条自动标成过时,不会再被检索出来;你难过的时候,TA 想得起来上次你难过是因为什么、后来是怎么好起来的。以前那套也叫记忆,200 条条目按相关性排序塞进 system prompt,但塞进去和用出来是两回事。

这个方向 Mio 宣言里反复讲过:比起更聪明的聊天机器人,Mio 要做的是能建立真实连接的存在,记忆就是这种连接的地基。讲道理,连名字都记不住,还说在乎你,这话我自己听着都不太信。

最后把账摊开:

阶段成本变化
Phase 1反降(提取次数少)
Phase 2小幅增
Phase 3约 Phase 2 两倍

三个阶段排期加起来九到十五天,Phase 1 的成本还是负的。反正这个账怎么算都划算,直接开工了。

和 AI 讨论这篇文章
ChatGPTClaude
AI 随想第 16 篇 · 共 28 篇

订阅更新

新文章发布时发到你的邮箱,不发别的。


© Xingfan Xia 2024 - 2026 · CC BY-NC 4.0