设计一个 AI 的长期记忆系统,听起来像学术课题,但真正做过 AI 产品的人都知道,它是产品体验的胜负手。没有记忆的 AI 像一条只有 7 秒记忆的鱼,聊完就忘;有记忆的 AI 才像一个能连续工作的搭档。Mindbase 这个项目,核心就是解决“让 AI 记住该记住的、忘掉该忘掉的”,同时让记忆可以被检索、被更新、被沉淀成长期经验。这篇文章会把我在设计这套长期记忆系统时的完整思路、分层方案、写入检索流程和踩过的坑全部摊开讲,适合正在做 AI 助手、智能体、对话机器人的开发者,也适合想给自己的模型加“人设连续性”的产品经理。
1. 先想清楚:AI 的“长期记忆”到底指什么
1.1 记忆不是聊天记录,更不只是 RAG
很多团队一开始会把长期记忆直接等同于“把历史聊天记录存下来,下次提问时拼进 Prompt”。这么做确实能解决一部分问题,比如用户上次问了什么、你上次回答了什么。但它的局限非常明显:聊天记录是流水账,不是记忆。流水账里没有重点,没有实体关系,没有时间维度的变化,也没有“哪些信息已经过时”。
RAG 经常被拿来和记忆混在一起。RAG 解决的是“让模型知道外部知识库里的内容”,它本质上是一种可检索的外部事实库。而长期记忆要解决的是“让模型形成对用户、对任务、对自己行为的持续理解”。RAG 的知识是相对静态的,记忆是动态演化的;RAG 的检索目标是答案,记忆的检索目标是上下文和行为依据。Mindbase 在设计之初就把两者区分开:知识库挂在系统外部,记忆系统则是贴着对话和任务动态生长的内部状态。
我见过一个很典型的反面例子:某项目把用户的全部历史对话都塞进 Prompt,结果 token 消耗暴涨,响应变慢,而且模型经常被几条早期无关对话带偏,回答质量反而下降。原因很简单——没有记忆结构的原始文本,对模型来说是噪声大于信号。
1.2 记忆的分类:情景、语义、程序
人的记忆分很多种,AI 的记忆系统也可以借鉴这套分类:
- 情景记忆(Episodic Memory):记录“过去发生的具体事件”。比如用户昨天说“我下周要去上海出差”,这就是一条情景记忆。
- 语义记忆(Semantic Memory):记录“提炼出来的事实和概念”。比如从上面那句话里,我们可以提炼出“用户经常出差”“用户近期行程涉及上海”。
- 程序性记忆(Procedural Memory):记录“怎么做某件事的流程和偏好”。比如“用户喜欢先看结论再看细节”“用户习惯用中文技术术语”。
Mindbase 采用这套分类不是单纯模仿认知科学,而是因为它对存储和检索策略有直接的指导意义。情景记忆适合按时间和事件索引,语义记忆适合按实体和关系索引,程序性记忆适合按触发场景绑定行为偏好。如果只用一个扁平列表存记忆,检索时会很难决定该优先命中哪类信息。
1.3 长期记忆系统的核心能力模型
在设计 Mindbase 之前,我把长期记忆系统拆成五个能力:写入、保存、检索、更新、遗忘。这五个词看起来简单,落到实现上各有难点:
- 写入:如何决定哪些信息值得记录,而不是把每句话都存下来。
- 保存:用什么数据结构、放在什么存储引擎里,才能支撑后续的高效检索。
- 检索:如何在用户发出请求时,从海量记忆中捞出最相关的几条。
- 更新:当新信息和旧记忆冲突时,怎么合并、怎么修正。
- 遗忘:如何让过时信息自然降权,而不是永远占据检索位。
这五个能力不是独立的。写入策略直接决定保存形态,保存形态又反过来限制检索方式。所以我建议先从系统结构层面做统一设计,而不是零散地加功能。Mindbase 的完整版本会把这五条链路全部串起来,下面我会按设计顺序逐层展开。
2. Mindbase 的整体设计思路与存储架构
2.1 分层记忆模型:工作区、短期缓存、长期库
我不建议只做一个记忆库,更合理的是分成三层:
第一层是工作区(Working Memory)。这是当前对话正在使用的上下文窗口,包括最新的几条消息、当前任务状态、临时推理结果。工作区的内容不是记忆,而是“正在处理的信息”。每次对话开始时,工作区从长期库中加载相关记忆;对话结束后,工作区里值得保留的信息会被清洗、压缩、写入下一层。
第二层是短期缓存(Short-term Cache)。它的作用是承接工作区和长期库之间的缓冲。比如用户在一个会话里连续聊了十件事,其中一些可能只是寒暄,不必立刻写入长期库;但也不能直接丢弃,因为可能在同一次会话后面还会用到。短期缓存通常用带 TTL 的键值存储实现,数据保留几个小时或几天。
第三层是长期库(Long-term Store)。只有经过筛选、压缩、结构化的记忆才会进入这一层。长期库里的数据需要具备良好的组织性,通常用向量索引 + 结构化字段组合存储。
这套分层的最大优势是降低了记忆写入的噪声。如果所有信息都直接进入长期库,系统很容易记了一堆无关内容,最后检索效果极差。短期缓存承担了“待定”状态,让长期库只保留高置信度的长期信息。
2.2 为什么用向量库做记忆载体,又不能只用向量
长期记忆的前提是“相似语义能被召回”,所以向量化是基础。每条记忆可以用 Embedding 模型转成向量,然后在向量数据库中做相似度检索。这个方案很成熟,但它有一个陷阱:向量相似度不等于用户当前真正需要的记忆。
举个例子,用户以前说过“我喜欢喝美式咖啡”,这次说“推荐一家咖啡馆”。向量检索确实能召回那条咖啡偏好,但它没有考虑时间衰减、偏好强度、当前对话的额外约束。如果同时存在“最近在戒咖啡”这条新记忆,纯向量检索就很可能把旧记忆也带出来。
Mindbase 的做法是“向量 + 结构化字段”双通道存储。每一条记忆记录除了有向量字段,还有类型、时间戳、重要度、来源事件 ID、关联实体等基础字段。检索时先按结构化条件做粗筛,再用向量做精排。这样既利用了向量的语义匹配能力,又保留了记忆生命周期管理需要的元数据。
2.3 记忆单元设计:事件、实体、关系、摘要
长期库里存什么?这是整个系统里最关键的设计决策。Mindbase 把记忆单元分成四类:
- 事件(Event):一条带时间戳的实际发生记录。例如“2025-05-10 用户提到计划去杭州出差”。事件是最原始的记忆素材。
- 实体(Entity):用户、项目、地点、事物等核心对象的抽象。例如“用户 A”“杭州”“出差项目”都是实体。
- 关系(Relation):描述实体之间的联系。例如“用户 A 计划前往杭州(出行)”“用户 A 负责某项目(工作)”。
- 摘要(Summary):对一段时期或一类主题的提炼。例如“近一个月用户频繁出差,偏好住在离高铁站近的酒店”。
这四个维度的关系是:事件经过抽取变成实体和关系;实体和关系经过聚合变成摘要;摘要反过来可以作为更高层级的记忆索引。这样设计的好处是,系统既能回答“用户上次什么时候说要去杭州”这种具体问题,也能回答“用户出差时一般有什么偏好”这种抽象问题。
2.4 记忆索引:从标签到知识图谱
只靠向量索引还不够。Mindbase 在记忆库外层维护一个轻量级的实体图谱,用来记录实体之间的关联。为什么需要这个?因为很多记忆检索用例是“沿着关系找记忆”的。
比如用户问“我之前提过的那个喜欢喝手冲咖啡的同事叫什么来着?”如果只做向量检索,模型需要把“同事”“手冲咖啡”两个语义同时匹配到同一条记忆,难度很大。但实体图谱可以直接提供一个路径:先找到“用户提到的同事”相关实体,再筛选“咖啡偏好”属性,答案就出来了。
实体图谱不需要做得很重,也用不着在记忆库里完全复刻知识图谱的推理能力。我只用邻接表维护“实体-实体-关系类型-权重”的四元组,支撑一跳或两跳的查询就够了。这个轻量索引在实践中已经能覆盖大多数“回忆型”问题。
3. 写入与更新的实操细节
3.1 什么内容值得记住:重要性打分机制
记忆系统最怕的是“什么都记”,所以写入环节必须有一道闸门。Mindbase 用一套规则 + 模型打分混合机制来决定一条信息是否进入长期库。
规则层面:凡是满足以下条件之一,强制提升记忆优先级——包含明确时间/地点的行程安排;包含用户偏好(我喜欢、我不喜欢、我习惯);包含任务状态变化(已完成、进行中、失败);包含明确的身份信息;包含需要后续跟进的事项。
模型层面:我把当前对话的文本片段和已有的用户画像摘要一起交给一个轻量评分模型,让它输出 0 到 1 的重要度得分。通常我会同时要求模型输出“记忆类型”和“触发更新关联的实体 ID”,这样一次调用就能同时完成关键信息抽取和重要性判断。
提示:不要只依赖大模型的“自觉”。规则层可以兜底,确保重要事实不会被模型漏掉;模型层负责捕捉语义层面的抽象信息。两者配合,写入精度才会稳定。
3.2 如何从对话流中抽取结构化记忆
从对话流中抽记忆,本质上是一个信息抽取任务。我建议按以下流程操作:
- 把最新一轮用户输入和助手输出拼成一段“候选记忆文本”。
- 调用抽取模型,输出一个 JSON 对象,包含
events、entities、relations、summary四个字段。 - 对抽取结果做合法性校验:时间是否正确、实体是否对应已有实体、内容是否与已有记忆重复。
- 如果抽取置信度较低,先放入短期缓存;如果多轮对话后仍被反复提及,再提升到长期库。
这里有个很容易踩的坑:不要每次对话都立即抽取整段文本。更合理的做法是按“信息块”切分,比如以用户每轮完整发言为单位,而不是按句子切。因为同一句话里的信息往往是关联的,切碎了会丢失上下文。
3.3 记忆压缩与反思:定期摘要与重写
原始记忆会随时间膨胀。就算每条都做过重要性过滤,长期运行后还是会累积大量事件记录。Mindbase 借鉴了反思(Reflection)机制——系统定期对某一主题或某一时间段的记忆做一次压缩,生成更高层级的摘要,并将冗余的原始事件降权。
具体执行时我会设定一个时间触发器:每天晚上对当天新增的记忆做一次轻量聚合;每周对重点实体相关记忆做一次跨时间线摘要;每月做一次全局性的用户画像重写。
重写摘要时要注意的一点:摘要不能取代原始事件,它只是更高层级的索引。如果用户在摘要生成后又问了“我上个月具体哪天说要去的杭州”,系统还是要能定位到原始事件。所以摘要和原始事件是共存关系,只是在检索排序时会优先使用更具体的命中记录。
3.4 冲突检测与新旧记忆合并
长期记忆系统最容易被忽略的问题是“记忆冲突”。用户上个月说“我每周三去健身房”,这个月说“我换到周末锻炼了”。如果两条记忆同时存在,检索时会很困惑。
Mindbase 对冲突的处理分为三步:
- 检测:写入新记忆时,先找同一实体下是否存在同类型、但取值不同的记忆。
- 对比:比较新旧记忆的时间戳和来源。如果新记忆时间更新,通常以新记忆为主,旧记忆标记为“历史状态”并降低权重。
- 合并:如果冲突不是非此即彼,而是条件变化,则把条件信息写入原记忆。例如“周末锻炼(从某月某日起)”,这样系统既能理解当前状态,也不会丢掉历史变化。
这个更新机制很重要。AI 的记忆如果只能增、不能改,时间一长就会变成一团互相矛盾的浆糊。我强烈建议在记忆存储层预留valid_from和valid_to两个时间字段,而不是简单更新覆盖。
4. 检索侧:怎么让 AI 在需要时“想起来”
4.1 从“一问一查”到多路召回
很多初版记忆系统只做一次向量查询,把 top 5 结果拼进 Prompt。这种方式在测试环境里看似能跑,但真实场景下召回质量非常不稳定。Mindbase 采用了多路召回策略:同一时刻,分别从不同维度取记忆候选集。
典型的三路召回包括:
- 语义召回:用当前 Query 生成向量,从长期库中检索相似度前 20 条。
- 实体召回:从当前对话中抽取实体,在图谱索引中命中与实体相关的记忆,取前 10 条。
- 时间召回:检索近期发生的事、最近用户提及的偏好变化,取前 10 条。
多路召回的目的不是“取并集”,而是“别漏掉任何可能性”。召回完再统一做重排,保证最终进入 Prompt 的记忆是最有用的。
4.2 混合检索排序:时间衰减、相关性、重要度
召回之后要排序。排序函数里至少包含三项:语义相似度、重要度得分、时间衰减系数。
时间衰减不能等比例衰减,而是按记忆类型区分。行程安排和用户偏好这类信息的半衰期很长,三周前的偏好依然值得参考;而任务状态类信息半衰期很短,三天前的任务状态也可能已经失效。Mindbase 会给每条记忆打上一个decay_rate字段,排序时用指数衰减函数计算有效分数。
公式可以简单写成:
score = semantic_similarity * 0.6 + importance * 0.3 + time_decay * 0.1这个权重不是固定的,需要在不同业务场景里调。比如做客服机器人,时间衰减的权重可以更高;做个人助理,重要度和语义相似度要更均衡。
我实际跑下来,最有效的做法是先做一次硬过滤:把valid_to早于当前时间、且is_active为假的记忆直接排除,再做软排序。硬过滤能避免模型被已失效信息误导。
4.3 上下文组装:把记忆注入 Prompt 的两种方式
记忆不是越多越好,注入方式也有讲究。Mindbase 里我主要用两种注入方式:
第一种是“显式记忆块”。把选中的记忆整理成带编号的文本块,放在系统提示词后面,像是给模型一份参考资料。这种方式适合需要精确引用记忆的场景,比如“以下是关于用户的已知信息:1. ... 2. ...”。
第二种是“行为约束注入”。不直接给模型罗列记忆,而是把记忆转化成一两条行为指导,比如“用户偏好简洁回答,请用不超过三句话回复”。这种方式适合程序性记忆和偏好类记忆,因为它让模型直接执行,而不是做信息拼接。
在实际系统中我会混合使用:事实类记忆用显式块,偏好类和行为类记忆用约束注入。这样能避免 Prompt 里出现一长串无用的“记忆”,也更容易控制输出风格。
4.4 记忆上限与滑动窗口的取舍
即使有了长期库,每次对话能携带的记忆上下文仍有上限。Mindbase 的策略是设定一个“记忆预算”,比如每次最多注入 12 条记忆,超过的部分宁可不注入,也不能强行塞进 Prompt。
预算怎么分配?我通常按比例分配:语义命中的 top 5、实体命中的 top 5、时间命中的 top 2。这个比例可以根据对话阶段动态调整:如果用户在询问过去的细节,实体和时间命中的比例上调;如果用户在表达新需求,语义命中的比例上调。
这里有个很实际的经验:不要试图在每轮对话都带上完整的用户画像。用户画像只适合在“需要连续身份感”的场景中注入,比如长期陪伴型对话或角色扮演;在单次任务型问答中,清晰的任务指令比画像重要得多。
5. 实际跑通过程中踩过的坑
5.1 记忆重复与冗余
最常遇到的问题是记忆库在几周后出现大量重复。用户可能用不同的表达方式讲过同一件事,比如“我下周去上海”“下周三我要出差去上海”“上海出差,周三出发”。如果没有去重,这三条会被当成三条记忆,检索时全被召回,既浪费 token,又让模型困惑。
解决办法是写入阶段做一轮语义相似度去重:新记忆生成后,先在已有记忆中找同一实体下相似度超过阈值的候选;如果存在,就把新信息合并进旧记忆,而不是新增一条。这个阈值我一般设得比较保守,比如 0.86 以上才合并,避免误伤真正的新事件。
5.2 幻觉式记忆污染
记忆系统最大的风险不是记不住,而是记错。模型在抽取信息时可能“脑补”出用户没说过的话,比如把助手的表述当成用户偏好存下来。这种幻觉一旦进入长期库,会污染后续所有对话。
我后来加了两道防线:第一道是抽取阶段只允许从“用户侧输入”抽取强偏好信息,助手侧内容只能作为补充背景;第二道是写入长期库之前,增加一次“事实一致性校验”的调用,让模型对比原始输入和抽取结果,打出一个一致性分数,低于阈值就降权到短期缓存。这道防线能把大部分幻觉式记忆拦截在长期库之外。
5.3 隐私与删除控制
长期记忆系统天然涉及用户隐私,所以删除能力必须从一开始就设计好,而不是后期补丁。Mindbase 里每条记忆都有user_id和source_event_id字段,支持按会话、按时间范围、按实体维度做级联删除。
另外一个容易被忽视的点:用户明确提出“不要记住某件事”时,系统需要在记忆库中标记删除之外,还要把已经聚合到摘要里的相关内容一并剔除。如果摘要里残留旧信息,即使原始记忆删了,模型还是可能从摘要中“想起来”。所以我给摘要也加了溯源列表,删除时会沿溯源链反向清理。
5.4 评估:怎么知道记忆系统做得“好”
长期记忆系统不像普通模型指标那样好评估。我用的最有效的方法是“记忆问答评测集”——构造一组只有靠长期记忆才能回答的问题,比如“用户上周说过最想去的城市是哪里?”“用户对项目管理工具的最新偏好是什么?”。然后跑一轮完整的对话和检索流程,计算回答准确率和命中记忆的准确率。
除了离线评测,线上还要看三个辅助指标:记忆写入率(每轮对话平均写入几条)、记忆召回利用率(注入 Prompt 的记忆中被模型实际引用的比例)、用户主动修正次数(用户是否经常纠正 AI 的错误记忆)。这三个指标能反映记忆系统是否“既不过度打扰,又真正有用”。
6. 常见问题速查与下一步扩展
6.1 长期记忆系统常见问题速查
| 问题 | 可能原因 | 建议处理方式 |
|---|---|---|
| 模型总是用旧记忆回答新问题 | 时间衰减权重太低,或失效记忆未硬过滤 | 检查valid_to字段,强制过期记忆不参与排序 |
| 记忆内容琐碎、不聚焦 | 写入闸门太松 | 调高重要度阈值,强化规则层过滤 |
| 用户明确说“不是这样” | 冲突检测没生效或摘要残留旧信息 | 检查摘要溯源链,及时更新冲突记忆状态 |
| 记忆库越来越大但命中率降低 | 缺少定期摘要和降权 | 增加每日/每周压缩任务,降低冗余事件权重 |
| 检索结果看起来相关但不解决当前问题 | 只做向量召回,缺少行为类约束 | 切换一部分预算给程序性记忆和用户偏好约束 |
6.2 后续可以做的扩展方向
Mindbase 目前的基础框架已经能把长期记忆跑通,但还有几个方向可以继续深化:
- 多模态记忆:不只是文本,还可以把图片、语音、操作记录纳入记忆单元。很多用户偏好是藏在非文本行为里的。
- 跨设备同步:长期记忆如果只存在单机环境,价值会受限。设计成云端同步后,AI 助手才能在不同场景中保持一致的记忆。
- 主动记忆:目前记忆是被动写入和被动检索的。更进阶的玩法是让 AI 在某些场景下主动向用户确认:“这个问题涉及你上周说的项目,需要我调出当时的细节吗?”这会大幅提升用户对记忆系统的信任。
- 记忆可视化:做一个记忆面板,让用户能查看 AI 记住了什么、修改或删除特定记忆。这既是产品功能,也是一种透明化治理手段。
6.3 我个人的实操心得
设计长期记忆系统时,我最大的体会是:不要一上来就追求“完美记忆”,先跑通最小闭环。把“写入什么、存成什么、怎么拿出来”这三个问题想清楚,比堆一堆高级算法重要得多。
另外,记忆系统不是一个一次就能建成的模块,它需要持续调优。每次发现模型因为记忆出错,都要能回溯到“是写入错了、检索错了,还是更新策略错了”这三条链路中的哪一环。把这三条链路的日志打通,排障效率会高很多。
如果你也准备给你的 AI 加长期记忆,建议先从最简单的分层存储和重要度过滤开始,跑通一轮完整对话,再逐步加入摘要、冲突检测和知识图谱索引。长期记忆的最终目标不是“记得多”,而是“在关键的时候,想起来刚刚好”。