1. 从“金鱼脑”到“老搭档”:Agent 长期记忆到底在解决什么
做 Agent 开发的人,大概都经历过这样一个阶段:你精心设计了一个助手,能查天气、能订日程、能回答各种知识问题,用户第一次用觉得挺惊艳。可第二天用户回来,问一句“上次我让你帮我改的那份方案,进度怎么样了”,Agent 一脸茫然——它根本不记得昨天发生过什么。这种体验就像你找了个助理,每天上班第一件事就是失忆,你得从头到尾把背景再讲一遍。
这就是Agent 长期记忆要解决的核心问题。短期记忆(也就是上下文窗口里的对话历史)只能撑一次会话,会话一结束,所有信息烟消云散。而长期记忆要做的是:把值得留存的信息从一次性的对话流里“捞”出来,存到一个持久化的地方,下次需要的时候再精准地“捞”回来,塞进当前上下文。听起来简单,但真正落地时会发现,这里面的坑比想象中多得多。
我先把话说清楚:这篇内容适合谁看?如果你正在做对话式 Agent、个人助理类产品、客服机器人,或者任何需要“跨会话记住用户”的系统,那这篇就是写给你的。如果你只是调用一下大模型 API 做单轮问答,那暂时用不上,但了解一下也没坏处,因为迟早会碰到。关键词就三个:长期记忆、记忆抽取、记忆检索。这三个词分别对应“存什么”“怎么存”“怎么取”,整篇内容基本围绕这条主线展开。
很多人一上来就问“用什么向量数据库”,我觉得这个问法本身就偏了。工具选型是最后一步,前面还有一堆更根本的问题没想清楚:什么信息值得记?记下来之后怎么组织?检索的时候怎么保证不把无关信息塞进去污染上下文?这些问题不想明白,换什么数据库都是白搭。所以下面我会按“为什么需要—存什么—怎么存—怎么取—怎么用—怎么避坑”的顺序,把这件事从头到尾拆一遍。
2. 短期记忆的天花板:为什么上下文窗口再大也不够用
2.1 上下文窗口的物理限制与成本约束
现在主流模型的上下文窗口动辄 128K、200K token,看起来很大了对吧?但你要知道,这是“上限”,不是“免费额度”。每次调用模型,你塞进去的所有 token 都是要计费的,而且窗口越大,推理延迟越高、成本越贵。假设你做一个个人助理,用户用了三个月,每天聊 50 轮,每轮平均 200 token,那就是 90 万 token 的历史。你不可能每次都把 90 万 token 全塞进去——先不说模型吃不吃得下,光是费用就够你喝一壶的。
更关键的是,上下文窗口大不等于模型能有效利用。业界有个很著名的现象叫“lost in the middle”:当上下文很长时,模型对开头和结尾的信息记得比较牢,中间部分的信息容易被忽略。你把三个月的聊天记录一股脑塞进去,模型反而可能抓不住重点。所以“把窗口撑大”这条路,本质上是用蛮力对抗问题,既不经济也不可靠。
2.2 会话隔离带来的“记忆断层”
短期记忆的另一个硬伤是会话隔离。每次新开一个会话,上下文是全新的,模型不知道这个用户之前来过。你可能会想,那我让用户一直用同一个会话不就行了?现实是,用户会关页面、会换设备、会隔几天再来,你没法保证会话连续性。而且就算技术上能保持长连接,把几个月的对话全堆在一个会话里,也会遇到上面说的成本和有效性问题。
所以结论很明确:长期记忆不是“可选项”,而是“必选项”。只要你做的是需要持续服务的 Agent,就必须有一套独立于会话之外的记忆机制。它要能把跨会话的信息持久化,并且在需要的时候精准召回。这跟人类记忆的逻辑其实很像——你不会记住每天说的每一句话,但你会记住重要的人、事、偏好和结论。
2.3 一个生活化类比:Agent 记忆 vs 人类记忆
打个比方。短期记忆就像你手边的一张草稿纸,正在算的题写在上面,算完就扔。长期记忆就像你的笔记本,重要的结论、待办、别人的偏好,你会记到本子上,下次翻开就能用。但笔记本不能什么都记,否则翻起来太慢、太乱。所以你需要一套“记什么、怎么记、怎么查”的方法论。Agent 的长期记忆系统,本质上就是在给机器造一本这样的笔记本,而且还要让它能自动决定什么时候翻本子、翻哪一页。
理解了这层,你就明白为什么单纯堆向量库不够用了。向量库只是“笔记本的纸”,真正难的是“记笔记的习惯”和“查笔记的方法”。
3. 记忆的取舍:哪些信息值得从对话流里“捞”出来
3.1 三类值得长期留存的信息
不是所有对话内容都值得存。如果你把每句话都存下来,检索时会淹没在噪声里。根据我的实践经验,值得进长期记忆的信息大致分三类:
第一类是用户画像与偏好。比如“用户是素食主义者”“用户偏好简洁的回答”“用户所在时区是东八区”。这类信息相对稳定,一旦确认就可以长期保留,而且对后续所有交互都有影响。
第二类是事实性信息与结论。比如“用户的项目截止日期是下个月 15 号”“用户已经确认了方案 A”。这类信息有时效性,需要记录时间戳,过期后要能失效或更新。
第三类是待办与未完成事项。比如“用户让我明天提醒他开会”“用户还在等一份报价”。这类信息是跨会话连续性的关键,用户下次回来最可能问的就是这些。
反过来,寒暄、闲聊、一次性的问答(比如“今天天气怎么样”),基本没有长期留存价值。存了只会增加检索噪声。
3.2 抽取时机:实时抽取还是会话结束批量抽取
抽取时机有两种主流做法。一种是实时抽取,每轮对话结束后就跑一次抽取,判断这轮有没有值得记的信息。好处是及时,坏处是每轮都要多调一次模型,成本和延迟都上去了。另一种是会话结束批量抽取,等一次会话结束后,把整段对话过一遍,统一抽取。好处是省调用、能看到更完整的上下文,坏处是如果会话中途断了(用户直接关页面),可能来不及抽取。
我个人的建议是混合策略:对明显的偏好和事实类信息做实时抽取(可以用轻量模型或规则匹配),对需要综合判断的内容放到会话结束或定时任务里批量处理。这样兼顾了及时性和成本。具体怎么判断“明显”,可以先用规则兜底,比如检测到“我喜欢”“我偏好”“记住”“下次”这类触发词,就先标记下来,再交给模型确认。
3.3 抽取的粒度:一句话、一个事实还是一条摘要
抽取粒度也是个容易踩坑的地方。抽得太细,比如把“用户说他住在北京”和“用户说他喜欢北京”拆成两条,检索时可能只召回一条,信息不完整。抽得太粗,比如把整段对话摘要成一句“用户聊了聊生活”,那基本等于没记。
我的经验是以“原子事实”为单位,但保留上下文关联。所谓原子事实,就是一条独立成立、不依赖其他句子就能理解的信息。比如“用户偏好素食”就是一条原子事实。同时给这条事实打上标签,比如来源会话 ID、时间、涉及的主题,这样检索时能按主题过滤。粒度控制在“一句话能说清一件事”这个级别比较合适。
3.4 去重与冲突处理:同一个信息被反复提到怎么办
用户可能在不同时间反复提到同一件事,比如多次说“我住在北京”。如果你每次都存一条,检索时会召回一堆重复内容。所以需要去重机制。简单做法是用向量相似度判断,如果新信息和已有信息语义高度相似,就更新已有条目的时间戳而不是新增。
更麻烦的是冲突处理。用户上个月说“我住在北京”,这个月说“我搬到上海了”。这时候不能简单去重,而要识别出这是更新,把旧信息标记为失效或直接覆盖。我的做法是给每条记忆加一个“有效期”和“状态”字段,新信息进来时先检索相关旧信息,让模型判断是“补充”“更新”还是“矛盾”,再决定怎么处理。这一步不能省,否则 Agent 会用过时信息回答,用户体验会很差。
4. 记忆的存储:向量库不是唯一答案
4.1 向量检索的适用场景与局限
一提到长期记忆,很多人第一反应就是上向量数据库。向量检索确实好用:把记忆转成 embedding 存起来,查询时算相似度,召回语义相近的内容。它对“模糊匹配”特别擅长,比如用户问“我之前说的那个旅行计划”,向量检索能召回相关记忆,哪怕字面不完全一样。
但向量检索有局限。它不擅长精确条件过滤,比如“找出所有下周三之前的待办”,这种带时间范围和结构化条件的查询,纯向量检索很吃力。它也不擅长关系推理,比如“用户的朋友的朋友是谁”这种多跳问题。所以向量库是重要组件,但不是全部。
4.2 结构化存储与向量存储的混合架构
我的推荐是混合架构:结构化存储(比如关系型数据库或文档数据库)存记忆的元数据——时间、类型、状态、来源、标签;向量库存 embedding 用于语义检索。查询时先用结构化条件缩小范围(比如“只要最近一个月内、状态为有效的偏好类记忆”),再在这个子集里做向量检索。这样既保证了语义召回,又保证了条件精确。
具体表结构可以这样设计:一张主表存记忆内容、类型、创建时间、更新时间、状态、来源会话 ID;一张向量表存记忆 ID 和对应 embedding;再加一张标签表做多对多关联。查询时先按类型和时间过滤主表,拿到候选 ID 列表,再去向量表里算相似度排序。这套结构不复杂,但能覆盖绝大多数场景。
4.3 记忆的“冷热分层”:不是所有记忆都同等重要
还有一个容易被忽略的点:记忆应该分层。高频访问的、最近使用的记忆是“热记忆”,可以放在快速存储里,甚至缓存在内存中;很久没用的、低优先级的记忆是“冷记忆”,可以放慢速存储,检索时降低权重。
怎么判断冷热?可以记录每条记忆的“最后访问时间”和“访问次数”。检索时给热记忆更高的排序权重。这样既节省资源,又让常用信息更容易被召回。这个思路跟 CPU 的缓存分层是一个道理,本质是用访问频率来优化检索效率。
4.4 存储格式的选择:纯文本、JSON 还是图结构
存储格式上,纯文本最简单,但不利于结构化查询。JSON 灵活,能表达嵌套结构,适合大多数场景。如果你的记忆之间有复杂关系(比如“A 是 B 的同事”“C 是 A 的项目”),那可以考虑图结构,用节点表示实体、边表示关系。图结构在做多跳推理时很强,但维护成本高,除非你的场景确实需要,否则不必上。
我的建议是从 JSON 起步,字段设计得清晰一点,等发现关系查询成为瓶颈时再考虑图数据库。过早引入复杂架构,往往是自己给自己找麻烦。
5. 记忆的检索:怎么在正确的时候捞出正确的信息
5.1 检索触发:什么时候该去翻“笔记本”
检索不是每轮都做。如果每轮对话都去检索一遍,一是浪费,二是可能把无关记忆塞进上下文,反而干扰模型。所以需要一个触发判断:这轮用户输入,是否需要调用长期记忆?
判断逻辑可以分几层。第一层是规则:如果用户输入里出现“上次”“之前”“还记得”“我的偏好”这类词,基本可以确定需要检索。第二层是意图判断:如果用户问的是个性化问题(“推荐个餐厅”),可能需要检索偏好;如果是通用知识问题(“地球到月球多远”),就不需要。第三层可以让模型自己判断,给它一个“是否需要检索记忆”的分类任务。实践中,规则加轻量模型判断的组合比较稳。
5.2 查询改写:把用户的话翻译成检索友好的形式
用户的原话往往不适合直接拿去检索。比如用户问“我那个事儿办得怎么样了”,这里的“那个事儿”指代不明,直接检索大概率召回一堆无关内容。所以需要查询改写:结合当前对话上下文,把用户的话补全成明确的查询。比如根据上文推断“那个事儿”指的是“方案修改”,改写成“方案修改的进度”。
这一步很关键,但经常被忽略。我的做法是让模型在检索前先做一次改写,输出一个或多个检索 query,再用这些 query 去召回。多个 query 可以覆盖不同角度,提高召回率。
5.3 多路召回与重排序:向量、关键词、时间衰减的组合
单一检索方式都有短板,所以推荐多路召回。向量检索负责语义匹配,关键词检索(比如 BM25)负责精确匹配,时间衰减负责给近期记忆加权。三路结果合并后,再用一个重排序模型(rerank)统一打分排序。
时间衰减这块值得多说一句。记忆的价值会随时间下降,但不是线性下降。比如“用户偏好”这类信息衰减很慢,“待办事项”衰减很快(过期就没用了)。所以衰减系数应该按记忆类型区分,而不是一刀切。我一般给偏好类设很长的半衰期,给待办类设很短的半衰期。
5.4 上下文注入:召回的记忆怎么塞进 prompt
召回之后,怎么把记忆塞进 prompt 也有讲究。不能简单拼接,否则模型可能分不清哪些是记忆、哪些是当前对话。我的做法是用明确的分隔标记,比如:
[长期记忆] - 用户偏好素食(记录于 2024-01-15) - 用户项目截止日期为下月 15 号(记录于 2024-02-01) [当前对话] 用户:帮我推荐个餐厅同时要控制注入的记忆数量,一般 3 到 5 条足够,太多反而稀释注意力。如果召回结果很多,就按重排序分数取 top-k。另外,给每条记忆带上时间戳很重要,模型能据此判断信息是否可能过时。
5.5 检索失败的兜底:召回为空时怎么办
召回为空是常见情况,尤其是新用户。这时候不能让 Agent 干瞪眼,要有兜底策略。可以回复“我暂时没有相关记录,你能再说明一下吗”,或者基于通用知识先给一个回答,同时提示用户“如果你有特定偏好,告诉我,我会记住”。这样既不让对话卡住,又引导用户补充信息,顺便还能抽取新的记忆。
6. 记忆的更新与遗忘:让 Agent 的记忆保持“新鲜”
6.1 记忆更新:覆盖、追加还是版本化
前面提到冲突处理,这里展开说更新策略。三种做法:覆盖是直接用新信息替换旧的,简单但丢失历史;追加是保留旧信息再加新的,安全但会积累冗余;版本化是保留每条记忆的多个版本,标记哪个是当前有效。我推荐版本化,因为有些场景需要追溯(比如“用户之前说过什么”),而且版本化天然支持冲突处理——新版本生效,旧版本标记为历史。
实现上,给每条记忆加一个version字段和is_current标记。更新时新增一条记录,把旧的is_current置为 false。检索时只查is_current为 true 的。这样既保留了历史,又保证了检索的准确性。
6.2 遗忘机制:主动删除与被动衰减
人脑会遗忘,Agent 也需要。遗忘分两种:主动删除是用户明确要求“忘掉这个”,或者信息被判定为错误;被动衰减是长期不用、价值降低的记忆逐渐降低权重,甚至被清理。
被动衰减的实现可以结合前面说的时间衰减和访问频率。设定一个阈值,低于阈值的记忆进入“冷存储”,再低就归档或删除。但要注意,删除要谨慎,尤其是用户画像类信息,删错了会影响体验。我的做法是偏好类信息基本不主动删,只做更新;待办类信息过期后自动归档;闲聊类信息定期清理。
6.3 用户可控:让用户能查看和编辑自己的记忆
这一点很多人不做,但我觉得很重要:给用户一个查看和编辑记忆的入口。用户应该能看到 Agent 记住了自己什么,并且能修改或删除。这不仅是隐私合规的需要,也能提升信任感。实现上就是一个简单的列表页,展示记忆内容、时间,提供编辑和删除按钮。技术上不难,但对用户体验的提升很明显。
6.4 记忆质量监控:怎么知道记忆系统有没有“记歪”
上线之后要监控记忆质量。几个关键指标:召回准确率(召回的记忆是否真的相关)、召回覆盖率(该召回的时候有没有召回)、冲突率(记忆之间矛盾的比例)、用户纠正率(用户纠正 Agent 记忆的频率)。这些指标可以通过人工抽检加用户反馈来收集。如果发现召回准确率低,可能是 embedding 模型不合适或查询改写有问题;如果冲突率高,可能是更新策略有漏洞。
7. 实战中的坑:我踩过的那些记忆系统陷阱
7.1 坑一:把整段对话摘要当记忆存
早期我图省事,直接把每轮对话摘要一下存进去。结果检索时召回的全是“用户询问了 X,Agent 回答了 Y”这种流水账,真正有用的信息被淹没。后来改成抽取原子事实,效果立刻好转。教训是:摘要适合给人看,不适合给检索用。检索需要的是结构化、原子化的信息。
7.2 坑二:检索时不做时间过滤,召回一堆过期信息
有一次用户问“我下周的行程”,Agent 却召回了一个月前的旧行程,还信誓旦旦地说“你下周要去某地”。问题就出在检索时没做时间过滤,把过期的待办也召回了。后来我给待办类记忆加了严格的有效期,检索时默认只查未过期的,问题才解决。时间维度在记忆检索里太重要了,千万别忽略。
7.3 坑三:记忆注入过多,反而干扰模型
有段时间我追求“召回越多越好”,一次塞十几条记忆进 prompt。结果模型开始混淆,把不同记忆的内容串在一起,回答变得混乱。后来限制到 top-5,并且加了明确的分隔和来源标注,模型的表现稳定多了。上下文不是越多越好,精准比数量重要。
7.4 坑四:忽略 embedding 模型与业务语言的匹配
embedding 模型的选择也很关键。我一开始用了一个通用模型,结果对中文口语化的记忆召回效果很差。换成对中文优化更好的模型后,召回率明显提升。选 embedding 模型时,最好用你自己的业务数据做个小测试,别只看榜单。榜单上的高分模型,不一定适合你的场景。
7.5 坑五:没有处理“用户否认记忆”的情况
用户有时候会说“我没说过这个”或者“你记错了”。早期我的系统遇到这种情况不知道怎么处理,还是继续用那条记忆。后来加了一个机制:当用户否认某条记忆时,把该记忆标记为“待确认”或直接失效,并记录这次否认。这样 Agent 不会一错再错。用户反馈是修正记忆的宝贵信号,一定要接住。
8. 从能用到好用:记忆系统的进阶优化方向
8.1 记忆的主动回忆:让 Agent 学会“想起来”
现在大多数系统是“用户问,才检索”。进阶做法是主动回忆:Agent 在合适的时候主动提起相关记忆。比如用户说“我要去北京出差”,Agent 可以主动说“你之前提到过在北京有个朋友,需要我帮你联系吗”。这种主动性能大幅提升体验,但难度也高,需要判断“什么时候提”和“提什么”才不显得突兀。我的建议是先从低风险场景试起,比如待办提醒,别一上来就搞复杂的社交关联。
8.2 跨用户记忆隔离与共享的边界
如果你做的是多用户系统,记忆隔离是底线。每个用户的记忆必须严格隔离,不能串。技术上用 user_id 做分区,检索时强制带上 user_id 过滤。同时要考虑共享场景,比如团队协作里,某些记忆是团队共享的。这时候需要设计权限模型,区分个人记忆和团队记忆。这块涉及隐私,一定要谨慎设计,宁可保守也别越界。
8.3 记忆与 RAG 的关系:别把两件事混为一谈
很多人把长期记忆和 RAG(检索增强生成)混为一谈。它们确实都用向量检索,但目的不同。RAG 检索的是外部知识(文档、资料),长期记忆检索的是用户相关的个性化信息。两者可以共用一套检索基础设施,但存储和更新策略应该分开。知识库更新是运营驱动的,记忆更新是交互驱动的。混在一起管理,后期会很乱。
8.4 评估体系:怎么量化记忆系统的效果
最后说评估。记忆系统好不好,不能靠感觉。我一般从三个维度评:准确性(召回的记忆对不对)、完整性(该记的有没有记)、及时性(该更新的时候有没有更新)。每个维度设计几个测试用例,定期跑一遍。比如构造一个用户,模拟多轮跨会话交互,看系统能不能正确记住偏好、更新待办、处理冲突。这套评估跑下来,系统的问题基本能暴露出来。
做 Agent 长期记忆这件事,我的体会是:技术选型只占三成,剩下七成是对“什么值得记、什么时候该用”的理解。工具会过时,但这套判断逻辑不会。你把抽取、存储、检索、更新这几环的边界想清楚了,换什么数据库、什么模型都能快速搭起来。反过来,如果只盯着工具,很容易做出一个“能存不能取”或者“取出来全是噪声”的系统。希望这些经验能帮你少走点弯路。