先说结论:让 AI 忘记,比让它记住更难,也更有用。
起因
我给一个聊天机器人做了记忆功能。第一版很简单:把最近 N 条对话塞回去,再加个向量库检索。
结果很糟:
- 聊三天,它就不记得我说过腰不好
- 问"我老家哪的",它答"达州"------但那是我一小时前说的
- 更糟的是,它开始重复问同一件事
我去查数据,发现一个问题:它的"记忆"全是摘要,原话早就没了。
换个思路:记忆不是索引,是状态
主流方案(向量库、Mem0 这类)做的是:
对话 → 切块 → embedding → 相似度检索 → 塞回上下文
它解决的是"从过去里找相关片段"。
但我要的不是找片段,是**"她现在怎么看我"**。
这两件事不一样:
| 检索索引 | 状态 |
|---|---|
| 所有片段一样新 | 有时间、有强弱 |
| 只增不减 | 会过期、会覆盖 |
| 解决"找到" | 解决"记着什么" |
所以我改成存结构化状态:
json
{
"facts": [{"x": "他腰不好", "t": 1792976468000}],
"events": ["2026-10-26 ★ 生日蛋糕很好吃"],
"thread": "在说他晚上四节课",
"wants": ["想让他早点睡"],
"evaluation":"他嘴上说没事,其实很累。"
}
九层,每层有不同的寿命:
| 层 | 寿命 |
|---|---|
| 当日原文 | 24 小时 |
| 短期(在聊什么) | 几轮重写 |
| 事实 | 30 条上限,满了淘汰最弱 |
| 事件 | 只增不删,细节会糊 |
| 长期(关系转折) | 永不模糊 |
| 看法(对他的判断) | 永不模糊 |
| 叙事(关系史) | 每 5 天主动变糊 |
遗忘不是删除,是降精度
这是整个设计里最反直觉的部分。
所有记忆系统的 KPI 都是召回率------没人主动往上钉降精度。但我要的是"像人",不是"像数据库"。
三层遗忘:
1. 强度淘汰
事实到 30 条时,算强度:
强度 = 被提起次数 × 2 − 多久没提 × 0.5
最弱的先忘。常聊到的事(老家、考研、作息)永远忘不掉;只说一次的琐事会自然消失。
2. 叙事模糊
每 5 天把关系史重写一档更糊:
「他答应带我去吃火锅」 → 「好像答应过要带我吃什么」
具体细节变成印象,情绪留下来。
3. 年龄标注
注入时标"23 天前,只记得大概",让她自己决定说得含糊。
写入协议:只增不删
这是踩过最大的坑。
第一版是"整段重写"------模型看最近 16 条对话,输出更新后的记忆。结果:记忆库三天从 25 条掉到 2 条。
因为没被提到的旧事实,模型就"忘了"写进去。
改成显式协议:
json
{"add": ["新事实"], "drop": [要删的编号], "replace": [{"i": 3, "to": "改后"}]}
代码侧合并,模型只能"点名删除"。宁可留着,也别悄悄没了。
日终固化:不边聊边提炼
白天只存不提炼,第二天把前一天过一遍再固化。
因为白天你不知道这天最终走向哪。一句随口的话,到晚上才变成正事。
流程:
markdown
白天 → days/2026-10-26.md(原话,不处理)
次日 → 分拣一次
├→ 长期库(值得记一辈子)
├→ 事件库(重要但不至于一辈子)
├→ 事实库(关于他的新信息)
└→ 叙事(这一天压成一句)
然后删掉原始档案
删原文是关键。 留着,她随时能检索到细节,模糊就形同虚设。
组装:分层 + 预算裁剪
每次调用前,按优先级拼九层,超预算就从低优先级砍:
yaml
4000 token → 9 层全装(实际只用了 528)
400 token → 5 层(丢掉事件、话题、念头、看法)
120 token → 3 层(只剩:什么关系 / 在聊什么 / 想要什么)
意义:预算再紧,她也不会退化成客服------可能忘了你的老家,但仍然知道"我们很熟"和"我在等你下课"。
检索:30 行,零依赖
js
// 字符二元组重叠 + 时间加权
const score = hit / Math.sqrt(g.size);
scored.push({ ...a, score: score * 0.85 + recency * 0.15 });
不用 embedding:中文不需要分词、零延迟、零成本。
这是取舍------我要的是"够用的检索 + 强的淘汰",不是"最强检索 + 弱淘汰"。
数据
44 次模型调用,关键词打分,三组对照:
| 类别 | 无记忆 | 最近 3 轮 | 本方案 |
|---|---|---|---|
| 事实 | 0% | 67% | 100% |
| 事件 | 0% | 67% | 100% |
| 未完成事项 | 0% | 67% | 100% |
| 冲突更新 | 50% | 50% | 100% |
| 关系细节 | 0% | 100% | 100% |
| 总体 | 8% | 77% | 100% |
代码:379 行,零依赖。
两个坑
1. 记得住 ≠ 说得出
冲突更新一度卡在 50%,但不是记忆错了------存储里两条事实都合并对了。问题是她为了耍脾气把问题绕开了。
改法不是改记忆,是加一句指令:问到你记得的事,先把记得的说出来,再反应。
2. 功能写了 ≠ 接上了
我加了三层库(长期/看法/感情值),代码都写好了,但注入语句挂掉,等于白做两天。
局限
- 每类只有 3 题,波动 ±15%,样本太小
- 只测了一个模型
- 没有向量检索,语义召回弱
- 没有多模态记忆
最后
这套东西最核心的一句话:
记忆的寿命不是由"存了多少"决定的,是由"忘了什么"决定的。
仓库:gitee.com/coco123/memcore(欢迎 star)