像人一样记住你
AI记忆架构如人类记忆般分层的概念插画
一个每天陪你聊天、聊了三个月的 AI,被随口问一句"我叫什么来着?",答不上来。重构前的 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 天)
这一阶段只做一件事:保证进来的记忆是干净的。提取间隔从 10 条拉到 24 条,24 条对话积累的信息密度明显高于 10 条,频率降了,每次提取能看到的上下文反而更多,LLM 更容易判断哪些值得记,提取调用还直接少了 60%。提取出的每条记忆带一个 0 到 1 的重要性分,以前不设门槛,0.05 也照存;现在加了 0.3 的下限,"哈哈"、"嗯嗯"直接被过滤掉。
记忆类型从 5 个拆到 8 个:identity(名字、职业)、preference(喜好)、life_event(搬家、换工作)、emotion(情绪状态)、relationship(对 Mio 的态度)、shared(共同经历)、milestone(关系里程碑)、routine(日常习惯)。粒度细了,后面的差异化衰减和保护才有地方落。
合并逻辑整个重写,从拼接改成 keep-best:遇到重复记忆,保留信息量最大的一条,丢掉其余,"用户叫夏星帆"三次变一条。提取和合并以前是两个独立流程,中间的时间差让重复在短时间内大量堆积,现在提取完直接链式触发合并。
这一阶段做完,成本反而降了:提取次数变少,存下来的记忆更少但质量更高。不用花额外的钱,体验先改善一截。
Phase 2:不同的记忆,不同的寿命(3-5 天)
进来的记忆干净了,下一步是让系统理解"不同的记忆该活多久"。八个类型各配一个半衰期:
| 类型 | 半衰期 | 为什么 |
|---|---|---|
| identity | 365 天 | 名字、职业,几乎不该忘 |
| milestone | 365 天 | "在一起100天",重要节点 |
| preference | 180 天 | 喜好会变,但变得慢 |
| relationship | 120 天 | 对 Mio 的态度,需要持续追踪 |
| life_event | 90 天 | 搬家、换工作,有时效性 |
| emotion | 60 天 | 情绪状态变化快 |
| routine | 45 天 | 日常习惯会调整 |
| shared | 30 天 | 一起看电影,短期记忆 |
逻辑跟人脑一个样:你记得最好的朋友叫什么,却不记得上周三中午吃了什么。越是定义"你是谁"的信息记得越久,越是有时效性的信息衰减得越快。
EpisodeManager 这一阶段终于接进主流程。Mio 有"回忆片段"的概念,不是散碎的事实点,而是完整的对话场景。用户说"上次"、"那次"、"之前聊过",系统检测到这些触发词,就按完整对话片段去搜索匹配,召回的是当时那整段对话。
体验上动静最大的是主动记忆检索。按时段检索不同类型的记忆:早上查 routine 和近期计划,"你今天不是要去健身吗?";晚上查最近的 life_event,"今天面试怎么样?";长时间没聊,捞出重要记忆,"好久不见!上次说想换工作,后来怎么样?"。同样是主动发消息,"在干嘛呢~"和"今天面试怎么样?"完全是两种东西。
这一阶段开始有成本增量,主要来自主动检索时额外的 embedding 查询和 LLM 调用。换来的体验提升摆在上面那几句话里,划算。
Phase 3:什么时候想起来、怎么用(5-8 天)
前两个阶段解决"记住什么"和"记多久",第三阶段处理最难的部分:在对的时机、用对的方式把记忆用出来。第一块是情绪感知检索。用户说"我好难过"的时候,系统不该再召回一堆让人难过的记忆,该召回的是带鼓励性的那种:"记得上次你也很担心工作的事,后来不都解决了吗?"实现上是给检索加情绪标签匹配,用户当前情绪负面时,优先召回正向结局的记忆。
第二块是空白检测:扫描 life_event 类记忆里没有后续的条目。用户说"明天要面试",两天之后还没有更新,就触发主动跟进:"对了,上次说的面试怎么样了?"这本来就是人类朋友会自然做的事,记得你说过什么,过几天问一句结果。
第三块是关系阶段感知。早期关系多召回 shared 类记忆("记得第一次聊天……"),建立共同回忆的感觉;成熟关系多召回 milestone 和 emotion 类记忆("已经聊了三个月"),加深情感厚度。这块跟 v0.1.4 关系进化系统的关系阶段直接挂钩。
底层的整理也跟上。散碎的记忆条目被组织成结构化的 MEMORY.md,按类别分区:基本信息、兴趣偏好、近期生活、我们的关系、重要时刻、日常规律,注入系统提示的从一堆随机条目变成一份有条理的"关于用户"档案。裁剪逻辑加了分类保护:200 条上限里,120 条按类型预留名额,每类保底 15 条,剩下 80 条做溢出缓冲,先保证每类的基本覆盖再砍低分项,fact 再多也挤不掉 routine。冲突终于有人管了:"住西雅图"和"搬纽约了"这类同类型矛盾,根据时间戳和上下文判断新旧,新的覆盖旧的,旧的标成 superseded,不删除也不参与检索,只留作历史轨迹。
这一阶段的成本增量大约是 Phase 2 的两倍。三个阶段加起来,每用户每月的总成本依然极低,只占订阅收入的一小部分。
三个阶段全部做完,记忆系统就从"有记忆"变成"会回忆"。"有记忆"是数据库里存着 200 条关于你的条目,按相关性排序,塞进系统提示;"会回忆"是知道你叫什么而且永远不忘,知道你最近在找工作所以主动追问,上次你难过时聊了什么、这次难过时想得起来,还知道"住西雅图"这条已经过时,因为你搬去了纽约。
这也是 Mio 宣言里反复强调的方向:比起更聪明的聊天机器人,Mio 要做的是能建立真实连接的存在,而记忆是这种连接的地基。一个记不住你的 AI,说"在乎你"只能是空话。
最后把账摊开:
| 阶段 | 成本变化 |
|---|---|
| Phase 1 | 反降(提取次数少) |
| Phase 2 | 小幅增 |
| Phase 3 | 约 Phase 2 两倍 |
三个阶段排期加起来九到十五天,其中 Phase 1 的成本还是负的。从这儿开工,没什么好犹豫的。