AI长期记忆系统设计:Mindbase记忆分层与向量检索全解析 做 AI 应用这一年多最让我崩溃的永远是同一件事模型明明很聪明却“记不住事”。用户上个星期刚说过“我妈下个月过生日”这周再来问礼物推荐它一脸茫然Agent 跑第五个步骤的时候已经忘了第一步定下的目标。长期记忆系统这四个字听起来像教科书概念落到产品上就是这种一字一句的崩溃现场。Mindbase 就是奔着这个痛点去的——设计一套 AI 能反复使用的记忆基础设施把对话里值得沉淀的信息分层存下来在需要的时候自动找回来让 AI 不再是金鱼脑。这篇文章会用一套完整的设计笔记把 Mindbase 从“为什么要分层”“数据模型怎么设计”“向量怎么存”“检索怎么调”到“记忆怎么更新和遗忘”全部拆开讲一遍。适合正在做 AI 助手、智能客服、Agent 工作流和个性化推荐的朋友参考也适合想给 Bot 加“连续人设”的独立开发者。懂一点 Python、能理解 Embedding 大概是什么就够用了。1. 动手前想清楚AI 长期记忆到底该存什么1.1 上下文窗口和记忆是两回事很多团队有个惯性思路模型记不住那我换一个上下文窗口更大的模型不就行了这个思路在早期确实能缓解症状但解决不了本质问题。先说大模型本身的口径问题。上下文窗口再大也是“给当前这一次请求看的临时内容”。你传给它的 prompt 里写了什么它就看到什么prompt 清空下一条请求就什么都想不起来。这就好比一个人面前摆着一张巨大的白板白板上写满了他上个星期做过的所有笔记但只要有人把这块白板擦掉他依然什么都不记得。白板大不代表他有记忆力。更麻烦的是窗口是有成本的。超大上下文会把每条请求的推理时间拉长token 费用跟着涨而且论文和实际项目里都有共识上下文越长模型对中间位置的注意力越容易衰减。你塞进去一万行历史记录模型大概率只记住开头和结尾。这已经是“窗口内”的注意力退化跟外部记忆系统是两回事。所以得换一个角度来思考如果 AI 真的想做到“上次聊过的事这次能接上”它需要的不是一块更大的白板而是一个外挂的、可以随时写入和查询的“笔记本”。上下文窗口负责工作记忆笔记本负责长期记忆。Mindbase 一上来就把这个边界划得特别清楚。1.2 记忆不是聊天记录而是“值得记住的东西”我见过不少项目的所谓“记忆系统”就是把聊天记录全部堆进数据库再在每次请求前拉最近几十条塞进 prompt。这不叫记忆这叫日志回放。结果就是跑上一周库里全是“嗯嗯”“好的”“哈哈哈哈”真正有价值的信息被淹没在噪音里。Mindbase 的第一步是把“记忆”从“原始记录”里抽象出来。聊天记录是原料记忆是经过提炼的产物。一条消息只有通过了触发规则才可能变成记忆。我实际用的判断标准是三类问题这条信息里有没有实体比如某人、某地、某个时间点、某个物品。这条信息是否影响以后的行为比如用户偏好、禁忌、承诺过的任务。这条信息是否在修正旧信息比如用户改口了、搬迁了、换工作了。满足其一才进入记忆管线。否则宁可丢掉。丢掉几段废话不心疼把废话当成记忆存进去才要命——它会在检索阶段消耗你的上下文配额还会把真正关键的记忆挤掉。2. Mindbase 的记忆分层四层架构与流转机制2.1 四个记忆层各自的职责Mindbase 的核心设计就是把记忆分成四层每一层的读法、写法和生命周期都完全不一样。记忆层存什么典型例子读取方式工作记忆当前会话正在处理的临时上下文“用户正在让我对比三款相机”直接放在当前大模型上下文窗口情景记忆具体发生过的一次事件、一段对话摘要“上周三用户和张姐确认了项目截止时间”向量检索 时间过滤语义记忆从事件里抽出来的稳定事实、偏好、人物关系“用户讨厌香菜”“用户习惯先看方案再谈价格”高优先级直读 实体过滤程序记忆反复出现过的任务流程、用户偏好的处理方式“用户做采购决策时要求先给对比表再给推荐”匹配触发条件后作为模板注入换成人话说情景记忆是“我记得昨天中午去了一家川菜馆”语义记忆是“我发现自己喜欢吃辣”程序记忆是“下次想约我吃饭找川菜馆成功的概率更高”。分层最大的价值是让每一条记忆用最合适的路径进出。工作记忆不需要检索刚说完的话就摆在那儿语义记忆稳定且量少可以每次请求都带几条情景记忆量大、增长快必须走检索不能全量塞程序记忆则在特定的任务触发时才拿出来。四层各有脾气混在一锅里炖必然互相污染。2.2 记忆在层之间的流动四层不是孤立的它们之间有明确的“固化”方向。一条对话产生的工作记忆在会话进行中是没有必要写库的。等到 session 结束或者轮数达到某个阈值就可以做一次固化把这一轮对话交给大模型摘要成几条情景记忆再从情景记忆里抽取实体和偏好沉淀为语义记忆如果同一类任务和流程反复出现再抽象成程序记忆。我对固化时机的建议是尽量在 session 结束后做而不是实时每轮都做。原因是会话进行中上下文是连续的中间夹着大量试探性内容实时抽取容易抽到半截话。等对话完整结束再回头看这整个 session目标、结果、用户的语气都清楚得多摘要准确率高不少。固化管线是异步的不要在用户等待回答的同步链路里做。平时用户问一句你等两秒才有响应体验已经打折扣若再挂一个摘要抽取的流程延迟会更难看。2.3 为什么不能只做一个“大列表记忆”有朋友看完会说你又是情景又是语义又是程序太绕了我把所有记忆打成一条条文本全部丢进一个向量数据库每次检索 top10 不就行了能跑但很难用好。单一列表最直接的问题是检索噪音。你想查“用户对香菜的偏好”向量数据库很可能把“用户昨天说想吃香菜饺子”和“用户朋友小名叫香菜”全捞出来。因为它们都和你刚才的话题相关但它们的价值和稳定度完全不同。另一个问题是无法表达新旧和重要度。一个“大列表”里五分钟前的对话和三个月前的承诺地位平等模型很难判断哪个更该被优先遵循。如果没有分层你就得依赖一个很复杂的全局打分公式去调和所有因素。有了分层语义记忆天然高权重情景记忆天然低权重程序记忆只有在匹配到任务时才出现——这个“天然”就是架构带来的不需要靠 prompt 里加一句“请优先考虑重要记忆”去硬撑。3. 记忆如何落库数据模型、向量化与存储选型3.1 记忆条目的数据模型不只是一个字符串我最早犯过的错误是把记忆只存成一个字符串。虽然向量检索能跑但后面做过滤、衰减、冲突检测时完全没有抓手。后来我把每一条记忆都做成一个结构化的条目大约长这样{ memory_id: mem_sem_9f2c1a, type: semantic, content: 用户对香菜有强烈反感点餐时需要提醒不添加香菜, entities: [ {name: 香菜, type: food}, {name: 用户, type: person} ], importance: 0.9, created_at: 1735689600, last_access_at: 1735689600, access_count: 1, source_session: session_7f21ab, embedding: [0.101, -0.042, 0.692], status: active }逐个讲一下设计逻辑。type对应四层记忆决定了这条记录走哪条读写策略。content是记忆的自然语言表述。注意这里存的是“提炼后的记忆”不是原始聊天文本。entities是结构化的实体列表。语义记忆做冲突检测时靠的就是它。没有实体索引你怎么知道两条记录都在说同一个人的“香菜偏好”importance是重要度0 到 1。可以交给大模型生成也可以人工标注。它会在检索打分和遗忘机制里反复用到。created_at、last_access_at、access_count这组字段是给“时间衰减”用的。后面讲生命周期时你会看到它们就是记忆的“使用轨迹”。source_session是溯源。用户问“你为什么觉得我喜欢吃辣”时能翻回来源会话。embedding是语义向量整个记忆的检索基础。status是软删除状态。active 是可以被检索到的superseded 是指被新记忆取代archived 是已经沉底permanent_deleted 是用户明确要求删除的记忆任何查询都不能再命中。3.2 存储选型向量检索 结构化过滤缺一不可Mindbase 在存储上的取舍经历了一个过程。第一次实现我图方便把记忆全丢进一个纯向量数据库。结果发现一个很实际的问题快是真的快但结构化过滤很弱。我想查“最近七天内、重要度 0.7 以上、类型为 semantic 的记忆”在纯向量库里要么不支持要么性能很差。而这类过滤在记忆系统里是高频操作不是边角需求。第二次换成“关系数据库 向量扩展”体验一下子顺了。元数据过滤和向量检索可以在同一个 SQL 查询里完成事务一致性也有保障。实验阶段用 SQLite 加向量扩展就够了几百万条记忆以内完全不需要上分布式。方案语义召回结构化过滤工程复杂度适用阶段纯向量数据库强弱需要额外维护偏高记忆规模大、场景单一关系库 向量扩展够用强联合过滤方便低单机可跑绝大多数项目和原型验证我的建议很简单第一版别上重组件。先把你的数据模型和召回策略跑通攒够真实数据再考虑迁移。分布式那是百万级、千万级记忆才需要考虑的事。3.3 Embedding 与记忆切分策略向量化是记忆系统的地基。但这里最容易踩的坑不是选哪个模型而是“拿什么去做向量”。我强烈建议不要直接把聊天原文向量化。聊天原文里充满语气词、废话、指代不清的话向量会被细节噪音拉偏。正确做法是先把聊天内容摘要成一段清晰、独立的记忆描述再对这段描述做向量化。也就是说content字段的文本就是做 Embedding 的对象。记忆文本的长度也值得控制。太短了信息不足太长了一个向量装不下。我一般控制在 100 到 300 字之间。超过这个长度就先拆成几条独立的记忆而不是硬塞。Embedding 模型方面通用的开源中文向量模型就够用不必追求超大模型。距离度量建议用余弦相似度它对向量模长不敏感语义相似度表现更稳定。另外有一个工程细节必须记下来在记忆条目里记录embedding_model版本。因为你之后很可能要升级向量模型新旧模型的向量分布不一定一致不记录版本的话新旧向量混在一起检索效果说崩就崩。提示写库的时候顺手把content的全文也建个全文索引。向量召回负责“语义相近”全文索引负责“关键词精确命中”后面做混合打分时这两个信号都拿得到。4. 记忆怎么想得起来混合检索与上下文组装4.1 别迷信纯向量检索混合打分公式做不完检索就给模型塞一堆记忆这会害了模型。我一直把向量检索认定是“候选生成”不是“最终排序”。它会把萝卜土豆一股脑捞出来最后选什么得靠多信号加权打分。Mindbase 里一条候选记忆的最终得分大约是这样的score 0.45 * cosine_sim 0.25 * recency_factor 0.20 * importance 0.10 * keyword_match四个信号的含义分别是cosine_sim是用户问题向量与记忆向量的语义相似度主信号。recency_factor是时间新鲜度基于last_access_at做一个衰减。比如exp(-(now - last_access_at) / half_life)。最近读过的记忆理应更容易被想起来。importance是记忆本身的重要度语义记忆天然分数高。keyword_match是实体或关键词的命中率用来兜底语义之外精确匹配的场景。不要把这个权重当成定死的标准。不同场景差异很大。我做客服类 Bot 时候importance权重就拉得非常高因为用户禁忌一个都不能漏做闲聊陪伴类时recency_factor权重更高因为用户更在意“你记不记得我昨天跟你说的事”。每接入一个场景花半天时间调权重比换个模型有效。4.2 工作记忆与检索记忆的组合注入检索完成之后还有一个大问题怎么把记忆塞进 prompt我见过最粗暴的做法是把检索到的记忆每条都原样丢进去结果 prompt 里一半是“上周三天气不错”这种废话。Mindbase 的做法是对记忆做两件事过滤和改造。过滤很简单按分值截断每层只取 topN。接着是改造我不把原始content直接丢给模型而是组装成一段带来源说明的“历史记忆上下文”像这样【历史记忆】 来自 25 天前 - 用户母亲生日是 12 月 20 日想准备一份礼物 - 用户偏好简洁的礼品建议不喜欢过度包装 来自 3 天前 - 用户提到今年生日想换个风格建议先送一份体验型礼物为什么要标时间和来源因为模型需要有“这条信息可信到什么程度”的上下文。25 天前的偏好可能已经被用户后来的话推翻模型看到时间戳会自然降低采信度。这个细节对减少“模型死守旧记忆”的毛病很有帮助。组装顺序也有讲究。当前对话内容工作记忆紧贴问题本身历史记忆放在中间知识库等通用资料放在后部。并且尽量控制历史记忆有关的信息量。我习惯把检索出来的记忆压到 top6 到 top10 条再多会让模型注意力发散。4.3 检索过程中的三个常见陷阱第一记忆重复。用户同一句话换了个说法讲两次就可能在库里留下两条意思相近的语义记忆。检索时它们同时命中白白占了 topK 的位置。解决办法在写入阶段做去重别拖到检索阶段。第二查询漂移。用户问“上次你说的那个推荐还有吗”这句话里根本没有可检索的实体和主题。如果直接拿这句话去做向量检索几乎什么都捞不到。正确做法是先把工作记忆里的指代还原识别出“上次那个推荐”是什么再拿还原后的查询去检索。第三动态 topK。别把召回数量写成写死的。用户这个问题如果摆明了是在找历史信息就多给几条如果是闲聊给两三条就够。我用一个简单规则先看当前消息里有没有“上次”“之前”“还记得吗”这类关键词有就给高 topK没有就给低 topK。简单但有效。5. 记忆的生命周期更新、遗忘与冲突处理5.1 三种更新方式追加、重写、合并长期记忆不是写一次就完结的。真实世界里用户的偏好会变目标会变行为模式也会变。Mindbase 把记忆更新分成三种操作。追加发生在同一实体新增了新属性。比如用户“讨厌香菜”之外又补充“也不吃海鲜”。这种情况我倾向于新增一条语义记忆而不是去修改旧记忆。旧记忆保留新记忆并列这样事件溯源完整以后用户改变时追溯起来也方便。重写发生在旧信息被完全推翻时。比如用户之前住在城市 A现在搬到城市 B。旧地址对后续行为已经没有参考价值如果继续保留就会出现矛盾检索。此时把旧记忆的status改成superseded同时新建一条当前生效的记忆。合并是把零散多条记忆凝固成一条高层概括。比如用户连续三次在报价环节说“先别细化给我一个总价”三条情景记忆应该合并成一条语义记忆“用户采购决策时习惯先看总价范围再决定是否深入细节”。合并后情景记忆可以降权归档语义记忆负责长期供读。5.2 遗忘机制记忆库不是垃圾场记忆系统做得越久越会发现“遗忘”比“记住”更重要。没有遗忘机制的记忆库最终会变成一个检索噪音池什么都能搜到也就什么都搜不准。Mindbase 的遗忘不靠随机丢数据靠的是定期扫描和打分。每天或每隔几小时跑一次定时任务对所有 active 记忆重新算一遍“价值分”final_value importance * freshness * access_boost freshness exp(-(now - last_access_at) / half_life) access_boost 1 log(access_count 1)这条公式的核心逻辑是记忆的重要度是底子但如果不被调用时间一长新鲜度就衰减如果经常被调取access_count会给一个小幅增益相当于页面访问次数。这跟人类记忆规律是一致的——总被想起的事越来越牢固从不想起的事慢慢就模糊了。低于阈值的记忆按类型区别处理情景记忆直接归档变成archived语义记忆则降低它的importance或者标记为低置信度。这里要强调我用软删除而不是物理删除原因有两个一是用户可能某天反悔需要旧记忆来纠正二是你永远不知道用户会不会说一句“你之前可不是这么说的”来找你溯源。但有一种情况必须硬删除用户明确要求删除记忆也就是“被遗忘权”。这种permanent_deleted标志必须写在查询层的强制过滤里任何一条路径都不能绕过。注意遗忘任务执行时要做读写隔离。检索链路如果正在读一条正在被更新状态的记录很可能拿到半旧半新的数据给用户的回复就会变得非常奇怪。5.3 冲突检测新输入和旧记忆打起来了怎么办最常见的冲突场景是用户自己改口。我实测中最典型的是“我特别喜欢吃辣” → 三个月后 →“最近胃不好不吃辣了”。如果系统只做追加更新两条矛盾记忆会同时存在检索的时候都命中模型就精分了这一句说吃辣下一句说不吃辣。Mindbase 在写入管线上加了一层实体级冲突检测当新记忆的实体组合与旧记忆高度重叠时交给大模型判断是否矛盾。如果判断为矛盾旧记忆的status标记为superseded新记忆标记为active。这里有一个细节必须讲清楚不要立刻物理删除旧记忆。用户可能只是一时兴起改口没过两天又说“算了还是吃辣吧”。软标记保证了系统可以回溯。我一般会保留superseded状态记忆 7 到 30 天之后才归档。冲突检测的效果很大程度取决于实体抽取质量。实体都提不准新旧记忆连“在说同一件事”都无法识别矛盾检测就是摆设。所以 Mindbase 把实体抽取单独做成一个高优先级环节没有实体就不进语义层。6. Mindbase 核心流程落地写入、召回与定时整理6.1 记忆写入管线从闲聊到结构化记忆写入管线是记忆系统的入口也是决定整个系统质量的第一道关卡。Mindbase 的写入流程用伪代码看大概是这样的def write_memory(session_text, session_id): if not should_store(session_text): return None summary summarize(session_text) entities extract_entities(summary) mem_type classify_memory_type(summary) entry build_entry( mem_typemem_type, contentsummary, entitiesentities, session_idsession_id, ) duplicate find_duplicate(entry) if duplicate: merge_or_skip(duplicate, entry) else: store(entry)第一步should_store就是前面说的触发规则先判断这段对话值不值得进入记忆管线。第二步是摘要把闲聊浓缩成结构化描述。第三步实体抽取给后面的冲突检测打下数据基础。第四步分类决定这条记忆进入哪一个层。第五步去重判断库里是否有同义或近似内容。每一步都不复杂但都要走得稳。尤其要注意分类错误的问题明明是一条语义记忆被分进情景记忆那么它在检索时的权重就会偏低用户的长期偏好就会“不受重视”。宁可在分类模型上多调几次也不要随手丢给默认值。6.2 召回管线给定一句新问题返回可用记忆召回管线的目标不是把最相似的记忆找到而是把当前最有价值的记忆放到模型面前。Mindbase 的召回管线是这样设计的def recall(user_id, query): query_vec embed(query) candidates vector_search( user_id, query_vec, top_k30 ) candidates metadata_rerank(candidates) # 过滤 superseded / archived / permanent_deleted candidates hybrid_score(query, candidates) return top_n(candidates, n8)这里有个关键设计先召回 30 条再缩到 8 条。为什么不让向量检索直接返回 8 条因为向量检索只看语义相似它不知道这些东西是不是矛盾记忆、是不是过期记忆。先用大一圈的召回池把可能的候选都捞出来再做元数据过滤和混合打分最终返回的 8 条才是满足“语义相关 时间合格 重要度高 不互相打架”的记忆。top_n并不是写死的。根据当前请求的类型动态调整比如用户明显在追溯历史时可以把n提到 10只是闲聊时降到 5。多实验几次你会找到适合自己业务的那组数字。6.3 定时整理任务让记忆自己进化记忆系统不怕写就怕没有整理。Mindbase 跑起来三周后我明显感觉到检索质量在下降原因就是记忆库在没经过整理的情况下不断膨胀旧的情景记忆越积越多把语义记忆的空间挤压得没法看。后来我加了一个定时整理任务有三个核心动作。合并扫描同实体、近语义的多条记忆合并成更高层次的概括。衰减重算每条记忆的价值分决定是否降权或归档。冲突复查重新检查那些长期处于superseded状态的旧记忆超过过渡期的进行归档。整理任务可以用一个简单的定时脚本跑每隔 24 小时执行一次。执行时注意加好锁或者使用独立的队列别跟用户请求的主链路抢资源。我个人的习惯是整理任务跑完后输出一份简报今天合并了多少条、降权了多少条、归档了多少条。这能让我一眼看出记忆库是不是健康。如果连续几天合并数量都很大说明我的写入去重没做好如果降权数量异常多说明衰减阈值设得太激进。这些信号比单纯看用户反馈更快暴露问题。7. 高频问题排查与避坑经验7.1 问题速查表把我在 Mindbase 实际运行中遇到的高频问题整理成一张速查表遇到症状直接对照排查。症状可能原因排查与解决思路用户昨天说过的事第二天 AI 全忘写入管线触发条件过严记忆没进库查看写入口志确认should_store和摘要是否成功调低触发阈值检索回来的记忆全是“吃饭”“天气”这种无关内容混合打分中语义权重过大或 topK 过高提高importance权重降低 topK增加实体关键词命中权重记忆库越来越大检索越来越慢没有定时整理或碎片记忆太多启用合并任务把碎片内容合并后再向量化定期归档用户改口后模型回答时依然用旧偏好冲突检测没启用或superseded记忆没过滤补实体级冲突检测在metadata_rerank里强制过滤superseded状态Prompt 里塞满过期废话上下文组装没有按时间和类型过滤把情景记忆按时间限制过期情节一律不进当前上下文用户要求删除记忆但检索还能命中只做了软删除没有硬过滤设置permanent_deleted标志并在所有查询入口强制过滤升级向量模型后检索质量骤降新旧 embedding 向量混用记录embedding_model版本升级后做一次全量回填向量7.2 我踩过的几个实在经验第一把“绝对不能错”的记忆单独提出来直接放进系统级 prompt不要走检索。比如用户对某种食物过敏、坚决反对的价值观、账号绑定信息。检索可以召回一百条相关记忆但如果它把“不能加香菜”给漏了那就是一次实打实的翻车。我在 Mindbase 里给这类记忆加了一个pinned字段每次请求都带上不走检索逻辑。第二每次命中记忆就立刻更新last_access_at和access_count。这个动作看似无足轻重其实是整个衰减机制能跑起来的生命线。我在第一版里把这两个字段当成“写入时设置一次即可”结果三个月后所有旧记忆都因为没有last_access_at更新而全部衰减成零检索效果变得一塌糊涂。记忆被调用一次相当于被强化一次你不更新时间戳算法永远没有机会表达“这条很常用要留着”。第三不要在代码里手工维护记忆的重要度。把“这条记忆值多少分”交回给大模型去判断但必须用强结构化约束。我让模型按固定 JSON 格式输出importance0~1和lifespan标签short/medium/long再用代码做校验和清洗。第一批数据跑下来重要度打分的合理性就超过了我之前的手工规则。第四调试检索时一定要把分数打出来看。初期别把检索当黑盒每次争议回答就把命中的 top5 记忆连同它们的cosine_sim、importance、recency_factor打印出来一眼就能看出是哪一路信号在作怪。我调权重时全靠这份日志比对着用户反馈猜快得多。最后再分享一个我个人的使用习惯Mindbase 第一版上线时我用它管理了一个给朋友做科普问答的 Bot。头一个星期Bot 连续两次把朋友的名字和资料记错了我一度怀疑是向量库的问题。后来查日志才发现是写入管线里摘要步骤的 prompt 太弱把“小明喜欢天文”摘要成了“用户关注科技领域”。问题不在检索而在写入端的信息损失。从那时候起我就把所有管线环节的日志都留全了。现在每次调优第一件事永远是看日志而不是猜模型。说回 Mindbase 本身。跑通这个闭环后我最强烈的感受是AI 的“聪明”大家都能堆但“记得你”才是产品的差异化。用户不会记得你的模型参数量是多少但会记得头天跟他聊过的事第二天还能接上。第一版别贪多就上面这套——四层记忆、一个向量存储、一条写入管线、一条召回管线、一个定时整理——足够撑起大多数对话型产品。我做的时候因为求快一上来就铺了一堆方案结果没有一个是好用的。所以如果你想动手从最容易跑通的“情景记忆 语义记忆”两层开始先让记忆录得进、查得出再去折腾衰减和冲突。这套系统我用到现在检索的准确率和体验都是实打实的。