1. “记住”为什么这么难:从一次现场事故说起上个月我在给一个内部用的客服 Agent 调优时,碰到一件非常典型的事:同一个用户第三次来问“我之前申请退款的单子处理到哪一步了”,Agent 每次都能礼貌地回答“请您提供一下订单号”,然后用户在对话框里翻了半天,重新把订单号、姓名、申请时间全部打了一遍。那一刻我就意识到,很多人在讨论 Agent 的长期记忆时,思路还停留在“能存下来就行”的阶段,但实际上“记住了”和“存下来了”是两个完全不同的工程问题,甚至差着好几个量级。先说结论:长期记忆不是一个存储桶,而是一套从写入、清洗、压缩、检索到遗忘的完整生命周期管理。如果只是把对话历史塞进向量数据库,然后每次检索top-k,你得到的是一个看起来记得、其实经常记错的 Agent。我见过不止一个项目上线后出现这种情况:用户明明在三天前说过“我住浦东,通勤时间不超过40分钟”,结果 Agent 第二天推荐的房源全在郊区;用户说“我不吃肉”,Agent 依然在推荐牛排套餐。原因都一样——记忆能查出来,但没被正确地用起来。所以我写这篇文章,想把 Agent 的长期记忆这件事拆开来讲:它到底由哪几层组成,“存下来”之后还需要哪些环节才能真正变成可用的记忆,以及在实际项目里我们是怎么设计记忆服务、又踩了哪些坑的。适合谁看?正在做 Agent 应用开发、想把对话体验从“每次都像第一次见面”提升到“像个老朋友”的工程师和产品同学,哪怕你现在还没动手写过一行记忆代码,这篇文章也能帮你建立一套完整的认知框架。1.1 记忆不是“聊天记录备份”很多人第一次做记忆功能,直觉是把所有 session 的对话原文原封不动存起来,再在每次请求时做向量检索。这种做法很快会遇到三个问题。第一是原始文本噪声太大。用户聊天时会说大量无信息量的话:“嗯”、“好的”、“我再想想”、“你稍等”。这些内容如果全部进入向量库,检索时会被高频语义干扰。比如用户问“你上次推荐给我的那个东西叫什么来着”,结果向量检索回来的是几条“嗯嗯好的”这类没有实质内容的片段,完全没法用。第二是知识分散。用户可能在第一次对话里提到“我在准备 CFA 考试”,第二次提到“我每天有2小时学习时间”,第三次说“我容易分心,需要番茄钟式提醒”。如果只做原文检索,每次只能命中一条碎片,Agent 很难把这三条信息缝合起来形成一个完整的“用户画像”。长期记忆的核心价值恰恰在于跨会话、跨时间的知识整合,而不是单条记录的精确匹配。第三是没有时效概念。用户说“这个月月底之前我需要完成报告”,如果这条记忆没有时间戳、没有被更新,下个月再被检索出来,Agent 会一本正经地提醒用户“记得月底前完成报告”,可那时报告早就交了。这就是典型的“记住了不该记住的东西”。所以我把 Agent 的记忆定义为:经过结构化、压缩、冲突处理后,能在合适的时机被检索出来并影响后续决策的信息。存储只是其中一个环节,甚至不是最难的环节。1.2 “存下来”只是记忆的第一公里我这几年做过的项目里,凡是记忆功能翻车的,几乎都不是“存不下来”,而是死在下面这几个环节:写入时没做结构化,存了一堆非结构化聊天噪声;没有抽象和反思,长期记忆里全是流水账;检索时机不对,每条用户消息都触发检索,导致上下文被塞爆;没有更新和冲突处理机制,旧记忆覆盖新记忆,或者新记忆覆盖旧记忆之后无法回溯;忘了做遗忘策略,记忆库无限膨胀,检索越来越慢、越来越不准。你看,存储(把文本向量化插进数据库)在其中大概只占30%的工作量,而另外70%都在记忆的生命周期管理上。我后面的章节会按这个思路展开:先分清楚记忆的类型,再讲如何从原始会话里提炼记忆,然后讲检索和注入策略,最后讲遗忘与更新。每个环节我都会给出具体的做法和踩坑经验,而不是停留在概念层面。2. 长期记忆的三层分类:别把所有信息都塞进同一个向量库我在设计记忆结构时,最先做的不是写代码,而是把记忆分了个类。这个分类直接决定了后面所有存储和检索策略。借鉴认知科学里长期记忆的分类方式(语义记忆、情节记忆、程序性记忆),映射到 Agent 场景会特别清晰。2.1 语义记忆:关于用户的客观事实语义记忆是“用户是什么样的人、拥有什么、偏好什么、处于什么状态”,比如:用户在浦东工作,通勤时间上限40分钟;用户是一名产品经理,正在准备 CFA 三级考试;用户有宠物猫,租的房子要求可以养猫;用户对乳糖不耐受,不能喝牛奶。这类记忆的特点是:谁便关键事实、很少随时间变化、可以形成属性键值对。我一般建议用结构化的实体-属性-值(如 JSON、数据库行)来存储,而不是只做向量化。原因很简单:向量检索擅长“按语义模糊找”,但像“用户ID=123,租住区域=浦东”这种精确信息,用结构化存储更可靠、更容易被精确过滤和更新。当然语义记忆也可以有向量索引,方便用自然语言查询。但在我的实践里,优先保证精确字段的存在。比如数据库里有一行preference表,字段是user_id, key, value, updated_at, confidence,每次写入时做 upsert。这样当用户说“我现在搬到朝阳了”,系统可以直接把region字段从pudong更新为chaoyang,而不是再往向量库里插入一条新文本。2.2 情节记忆:发生过的事件序列情节记忆记录的是“在某个时间点发生了什么”,比如:1月10日用户申请了订单 #A123 的退款;1月12日用户问了三次物流信息;用户在上一次对话中说过“这周五前给我方案”。情节记忆的重要特征是时间性和因果性。它不是一个孤立事实,而是一个事件序列。这些事件可以帮助 Agent 理解用户当前的进展状态。比如客服 Agent 知道用户退款的诉求已经处理到“等待仓库验收”阶段,就不会再让用户重复提交申请。我在工程上通常用两类结构存情节记忆:一类是事件日志,记录event_id, user_id, event_type, payload, created_at,适合做精确时间线查询;另一类是摘要文本,比如“用户在2月第一次咨询退款流程,3月正式提交申请,4月因物流显示异常发起投诉”,这种摘要适合做向量化,用于模糊回忆。需要注意,情节记忆千万不要逐句保留,一定要做摘要。我见过一些项目把整段对话原文都当作情节记忆存下来,结果向量库越来越大,检索到了重复内容还互相矛盾。正确做法是把事件提炼成一句话摘要,保留关键实体和时间,然后定期把多句摘要再聚合成更高层的记忆。2.3 程序性记忆:Agent 学会了怎么服务这个用户程序性记忆听起来很学术,其实在 Agent 场景里指的是“针对这位用户,哪些做法有效、哪些做法无效”。例如:用户反馈条目较多,应该先总结再逐条确认,而不是每次追问很多问题;用户偏好幽默风格,称呼上喜欢“你”而不是“您”;给这个用户推荐产品时,价格敏感度高,不要推超过预算的选项;用户很少看长文本,回复尽量压缩在200字以内。程序性记忆往往不是用户直接告诉我们的,而是从多次交互中归纳出来的。它更接近“行为策略”。我的实现方式是把这类记忆做成“经验包”:每条经验里包含触发条件、建议动作、反馈结果。比如记一条“当用户询问时间节点时,同时给出日历提醒链接,点击率提高了40%”。程序设计上,这类记忆可以使用评分配机制:每次检索到经验时,如果后续交互产生了正向反馈(如用户点击了、明确说“谢谢”),就轻微上调该条记忆的权重;如果用户忽略或表达了不满,就下调。这个机制让 Agent 的行为策略能随交互动态优化,而不是永远停留在第一版。三层分类可以有效避免“迷茫地全塞进向量库”。一个务实原则是:能用精确字段的用精确字段,能用摘要的用摘要,只有确实需要语义模糊检索的才做向量化。很多人纠结向量库选型,却忽视了给记忆分层,最后向量库又当数据库又当缓存,各种难受。3. 从原始对话到可用记忆:清洗、压缩、反思三级处理管线分好类之后,真正费工夫的是怎么把一段乱七八糟的对话变成可持久化、可检索的记忆。我经历过从“原文入库”到“多级提炼”的转变,效果差距非常大。下面讲讲我目前在用的三级处理管线。3.1 第一级:清洗与结构化原始对话必须先经过一轮清洗和结构化,这一步通常在对话流中实时进行。我通常用 LLM 抽取,给定对话片段,让它提取:实体提取:人名、地点、产品、事件、订单号、日期等;事实抽取:用户明确表达的偏好、状态、约束条件;待办与承诺:Agent 答应过用户的事情,用户答应过 Agent 的事情;情感与倾向:用户对某个方案是否满意,是否有情绪波动。举个例子,用户说“这个手机颜色我不喜欢,我喜欢深色,另外价格超过4000就不要推给我了”。清洗后得到结构化片段:{ "type": "preference", "entities": {"product_category": "手机"}, "facts": [ {"attribute": "color", "value": "深色", "negated": true, "qualifier": "不喜欢浅色"}, {"attribute": "price_max", "value": 4000, "qualifier": "超过则不推荐"} ], "confidence": 0.9, "source_session": "session_123", "timestamp": "2025-01-15T20:13:22" }很多人会跳过我这一步直接全文向量化,但我强烈不推荐。结构化之后的记忆有几个巨大优势:可以精确更新,不会因为重复写入产生语义覆盖混乱;可以参与 SQL 过滤和排序;可以为后续反思提供干净的输入。代价是多一次 LLM 调用,但这笔调用绝对值得。3.2 第二级:压缩与聚合光有结构化还不够,因为同一条事实可能在多轮对话里重复出现。比如用户分别在三次对话里都提到“我住在浦东”,如果每次都写一条结构化记录,积累的是重复冗余。压缩和聚合在这里发挥作用。我采用的方法是按用户维度,定期(通常是每天一次或每N次对话一次)把新增记忆跑一个聚合任务。聚合任务的输入是当天的记忆记录和已有的历史记忆,输出是更新后的记忆集合。具体规则包括:同键值覆盖:同一用户的同一个属性,以最新时间为准,但要保留历史值用于回溯;相似事实合并:语义相似的偏好合并为一条,比如“不喜欢浅色手机”和“喜欢深色手机”合并为“偏好深色系电子产品外观”;低置信度淘汰:置信度低于0.5且从未被再次确认的记忆,直接移入遗忘候选区。聚合层还可以做所谓的“摘要记忆”。比如用户最近十次对话全部在讨论租房搬家,系统可以产生一条摘要:“用户正在准备搬到朝阳区,重点关注通勤距离、宠物友好、预算6000以下”。这条摘要是后续对话的重要背景信息,比零散十条原始记录有效得多。我在项目里把这个聚合任务做成一个异步队列任务。对话结束后不立即做聚合,而是累积到一定量或固定时间点跑批量,避免对用户请求产生额外时延。3.3 第三级:反思与冲突处理反思(reflection)是长期记忆里最容易被人忽略、又最能体现 Agent 智能的一部分。它指的是从大量具体记忆中归纳出没有直接说过的结论。比如用户从没说过“我工作很忙”,但从过去两周的对话里看出来,用户经常在晚上十一点之后咨询、提到频繁加班、多次说“没时间看长文档”。反思层可以推断出“用户工作强度高,沟通应尽量简洁高效,推荐内容应预告耗时”。反思输出的不是用户说的,而是 Agent 学到的,这一点要谨慎。一条反思记忆如果错了,影响会非常大。所以我会给反思记忆加上独立的置信度和来源。冲突处理同样重要。当新写入的记忆和旧记忆打架,系统不应该简单地“后写覆盖先写”,而应该多保留一些上下文。比如用户周一在对话里说“我喜欢住在市区,热闹方便”,周五又说“太吵了,想换到郊区安静一点的地方”。这其实不是矛盾,而是偏好发生了变化。如果直接覆盖,Agent 就丢失了对用户偏好演变过程的理解。我在设计里会让每条记忆保留一个superseded_by字段,旧记忆不删除,只是标记为“已过时”。这样当用户问“我之前是不是说过喜欢市区”,Agent 依然能给出准确回答,并且还能补充一句“但最近你倾向于郊区安静地段”。冲突处理的实际规则可以简单粗暴:显式推翻优先于隐式推断,用户明确说“我之前说的不对”时,旧记忆直接降权;时间近的偏好覆盖时间远的偏好,但保留历史版本;如果冲突双方置信度均较高,两记并存,在注入提示词时同时呈现并注明时间线。这套三级处理管线跑下来,记忆库里的信息密度和质量比直接存原文高很多倍。检索的效果也就不在一个层次了。4. 检索与注入:什么时候调用记忆,比用什么向量库更关键记忆处理得再好,如果检索策略不对,依然白搭。经验上看,检索这一侧踩的坑比写入更多。4.1 别每条消息都做全量检索早期我把所有用户消息都先做一次向量检索,然后把 top5 记忆塞进 system prompt。结果出现两个问题:一是上下文长度爆炸,每轮都带5条记忆,token 消耗极高;二是检索出来的记忆大部分与当前意图无关,反而干扰生成。正确做法是“按需检索”。具体来说,用意图分类或关键词触发两种模式:被动注入:针对高频、稳定、与当前任务关系不大的记忆,比如用户姓名、称呼偏好、回复风格、时间区、语言偏好,每次请求都自动放在 system prompt 里。这类记忆少而精,通常不超过5条。主动检索:只有在用户消息涉及历史事实、个人状态、先前决策时才触发向量检索。触发信号可以是“是否出现历史相关词汇”“是否询问之前的内容”“是否包含订单号等实体”。这套设计的核心是区分“常驻记忆”和“情境记忆”。常驻记忆像一个人的基本人设,情境记忆像针对某次谈话临时调取的工具。两种用混了,Agent 不是显得“记忆混乱”,就是显得“记忆平平无奇还费钱”。4.2 相关性排序之外,还要有时效性排序我见过不少项目用向量检索 top-k 之后就直接注入。但向量检索在语义相关性上表现尚可,在时效性上很差。一条一年前的“用户想换工作”和一周前的“用户已入职新公司”在向量空间里可能都跟“工作”相关,但后者显然更重要。我建议检索结果做二次排序,综合三个维度:语义相关分(向量余弦相似度等)时间衰减分(记忆越新,权重越高;可设半衰期,比如一周衰减50%)置信度分(前面提到的 confidence 字段)综合分 = 0.5 * 语义分 + 0.3 * 时间分 + 0.2 * 置信度分。这个权重可以根据业务调整。比如金融咨询场景,时效性权重可以提更高;兴趣推荐场景,语义分可以占主导。二次排序后只取 top3 注入,避免把无关记忆塞进去。4.3 检索结果如何注入提示词而不“喧宾夺主”把记忆注入到 prompt 不是直接拼接一段“用户偏好”,而是要打成 Agent 可以理解的上下文格式。我推荐两种形式。第一种是“事实清单式”,适合被动注入:客户背景信息(仅供参考): - 称呼偏好:本名“王岚”,不接受“亲”这类昵称 - 沟通风格:简洁、要点式,避免长篇大论 - 当前状态:正在搬家,重点关注物流时效 - 重要约束:预算上限6000元/月第二种是“提醒以事件时间线方式回顾”,适合主动检索:与该用户相关的历史事件: 1. 2025-01-10:提交订单A123退款申请 2. 2025-01-12:咨询物流异常,情绪较急躁 3. 2025-01-14:客服承诺48小时内反馈,尚未闭环 提醒:上次承诺的闭环时间是1月16日,需要优先处理。注意一个细节:注入记忆后,Agent 可能会盲目依赖记忆内容而忽略用户当轮的新消息。所以我会在 prompt 里加一句“如果用户当前消息与背景信息冲突,以用户当前消息为准”。这句话看着简单,但能避免很多“AI自作主张引用旧记忆”的翻车案例。我实测下来,加上检索触发机制和二次排序后,同样的记忆库,输出准确率能提高不少,token 消耗也明显下降。这类工程优化带来的体验提升,往往比换一个更大的模型更立竿见影。5. 实战案例:某智能客服 Agent 的长期记忆落地过程光讲理论太虚,我拿一个具体项目来说明。当时某电商平台要做智能客服,要求是同一个用户隔三天再来咨询时,客服 Agent 能记得之前处理过什么问题、承诺过什么。预算有限,不能用庞大的模型做高频推理,所以记忆部分必须设计得靠谱。我们几个开发者组了个小团队,前后迭代了两周。5.1 场景设定与记忆需求分析业务场景是售后客服,核心诉求有四个:识别用户身份后,自动调取历史工单与承诺;用户再次询问退款进度时,不用重新走一遍流程;客服 Agent 承诺过的事情不能被遗忘;用户情绪激动时,能结合历史事件给出更有针对性的安抚话术。对照我前面讲的三层记忆,这个场景里语义记忆主要是用户身份信息、购买偏好;情节记忆主要是工单流转记录、沟通记录;程序性记忆主要是针对不同情绪状态的应答策略、以及“哪些话术更容易让用户满意”。5.2 记忆存储结构设计我们没有用复杂的图数据库,就用关系型数据库加一个向量库的组合。关系库管精确记忆,向量库管模糊检索。核心表结构大致如下:CREATE TABLE user_semantic_memory ( user_id VARCHAR(64), attr_key VARCHAR(128), attr_value TEXT, confidence FLOAT DEFAULT 0.7, updated_at TIMESTAMP, superseded_by INT DEFAULT NULL, PRIMARY KEY (user_id, attr_key) ); CREATE TABLE user_episodic_memory ( event_id BIGSERIAL PRIMARY KEY, user_id VARCHAR(64), event_type VARCHAR(64), summary TEXT, payload JSONB, created_at TIMESTAMP, ); CREATE TABLE agent_policy_memory ( policy_id BIGSERIAL PRIMARY KEY, trigger_text TEXT, action_text TEXT, success_score FLOAT DEFAULT 0.5, updated_at TIMESTAMP, );向量库只存两类内容:情节记忆的 summary 字段的向量,以及由反思层生成的抽象判断的向量。精确查询用 SQL,模糊联想用向量库,各司其职。5.3 实测中的翻车点这个案例我印象最深的是三个翻车点。第一个是重复写入。当时写入管线没加“相似检查”,用户说“我订单还没到”和“物流太慢了”分别被当成两条独立情节记忆。几天后向量库里积累了大量类似记录,检索 top5 出来有四条都在抱怨物流,Agent 直接复制粘贴式回复“我理解您对物流速度的担忧”,完全没有新信息。后来加了相似度阈值去重,语义相似度超过0.85的只保留最新一条,效果立竿见影。第二个是自动承诺的记录缺失。用户问“能保证明天送到吗”,客服 Agent 为了安抚情绪,随口说“我帮您催一下,明天肯定到”。我们没有从对话里抽取“Agent 承诺”这个特殊事件类型,导致承诺没有被记录。48小时后用户回头质问,Agent 一脸茫然。后来我在抽取模型里专门加了承诺槽位,并且把承诺事件独立存入事件表,每天定时扫描“过期未兑现承诺”并升级到人工处理。第三个是上下文爆炸。用户闲聊几句我们也触发全量检索,导致一个很简单的“你好”请求也带了一堆记忆。后来参考第4节的做法,加了一个轻量意图分类器,只有识别到“状态查询”“历史询问”“情绪宣泄”等意图才做主动检索。这个分类器很小,本地跑就行,成本基本为零,但把系统 token 消耗降了接近一半。这案例让我坚定了一个观点:记忆系统设计的优先级应该是“减少错误注入”高于“增加记忆量”。太多团队把精力花在“记住更多”上,结果记住了一堆不该用的记忆,干扰比没有记忆还大。6. 遗忘与更新:好的记忆系统必须会“忘记”记忆系统还有一个反直觉的设计要点:长期记忆不等于永久记忆。把不该忘的忘了很糟糕,但把不该记的都一直留着,同样糟糕。6.1 主动遗忘的三种触发条件我在设计里明确设置了三种遗忘条件。一是置信度衰减。一条记忆如果长时间没有被再次确认或使用,置信度会随时间下降。比如某个偏好只出现过一次,之后三个月都没有相关交互支持,衰减到阈值以下后,系统不再把它注入上下文,但数据仍在库里,只是活跃度降低。二是显式覆盖。用户说“我之前说喜欢黑色,现在不要了,给我推荐白色”,系统不删除旧记忆,但标记为 superseded。活跃记忆中只保留最新偏好。这个机制保证 Agent 不会因为记性太好而让用户觉得“我都说变了你还记着旧的”。三是隐私清除。用户主动要求删除记忆,或数据合规要求保留期限到了,系统执行物理删除或匿名化。这个机制不单是合规问题,也直接影响用户对 Agent 的信任感。我在产品里加了“查看记忆、删除单条记忆、清空全部记忆”的界面以后,用户反馈好感度提升明显,有不少用户甚至主动表示“原来你还记得我上次说的,那我也愿意告诉你更多”。八成以上的团队在设计记忆系统时,完全没有考虑遗忘路径。等到记忆库膨胀到几十万条时,检索延迟上来、存储成本上去、结果质量下降,这时候再想清理就已经很被动了。6.2 隐私边界:可解释、可控制、可删除长期记忆涉及大量用户个人信息,所以记忆系统必然带有一点数据合规的属性。技术上我建议做到三件事:每次写入记忆时记录来源对话和创建时间,如果用户问“系统知道我的什么信息”,可以拿出一张可读列表;用户删除某条记忆后,写一个删除标记;至少要让记忆不被检索、不被注入;记忆系统输出的内容不允许包含未经用户确认的推断。反思层生成的结论必须标记为“推断”,不能和用户亲口说的混在一起。我见过比较糟糕的案例:Agent 把用户在某次闲聊中顺嘴提的“薪资有点低”写成了高置信度的“用户想跳槽”,然后后续对话一直围绕跳槽展开,用户非常尴尬。这种记忆系统的设计就不够谨慎。所有可覆盖的、推断性的记忆都应该默认低置信度,只有多次印证后才逐步提升。6.3 更新频率与批量机制最后聊一下更新的工程实现。长期记忆不适合每轮对话都重写全部内容。我试过同步写入,用户一旦对话比较长,额外时延就很明显。最后我采用的策略是牺牲一点实时性换取稳定。对话过程中只把原始结构化记忆写入本地队列,立即返回给用户。后台任务每五分钟批量 flush 一次,做相似检查、去重、更新用户画像。摘要和反思任务则更低频,每天一次即可。这样用户侧基本感知不到记忆处理的时延,系统也不用担心每个请求都跑一轮复杂聚合。频率上还有个经验值:主动注入的稳定偏好记忆,每次对话前刷新一次;情节记忆的摘要,每次 session 结束时刷新一次;反思层的抽象结论,每天重建一次。这个频率足够支撑绝大多数业务场景,不需要更激进。一段收尾的经验分享跑过几个长期记忆项目之后,我最深的体会是:Agent 的记忆能力不是靠堆存储堆出来的,而是靠一套克制的工程体系。该分层的分层,该压缩的压缩,该遗忘的遗忘。每次我遇到“用户觉得 Agent 记性差”的反馈,回到代码里看,八成是检索时机不对或者写入太粗暴,而不是向量库不够大、模型不够聪明。最后分享一个很实用的小技巧:给每条记忆打上“需要确认”的标签。当记忆来自用户主动表达时,置信度高;当记忆来自 Agent 的总结时,默认置信度低。每过一段时间,在对话里自然地问一句“我理解你最近比较关注搬家相关的事,如果不对的话可以告诉我”。这种轻量确认机制既避免了记忆错误带来的尴尬,又让用户觉得 Agent 真的在用心记、不是偷偷存隐私。我在实际测试中发现,带着记忆去问答的用户满意度,比那些每次都要重新重复资料的用户,高不少。长期记忆这件事,做好了就是产品口碑差异,做不好就只是多了一个没人看的数据库表。希望这篇文章能少让你走弯路。
1. “记住”为什么这么难:从一次现场事故说起上个月我在给一个内部用的客服 Agent 调优时,碰到一件非常典型的事:同一个用户第三次来问“我之前申请退款的单子处理到哪一步了”,Agent 每次都能礼貌地回答“请您提供一下订单号”,然后用户在对话框里翻了半天,重新把订单号、姓名、申请时间全部打了一遍。那一刻我就意识到,很多人在讨论 Agent 的长期记忆时,思路还停留在“能存下来就行”的阶段,但实际上“记住了”和“存下来了”是两个完全不同的工程问题,甚至差着好几个量级。先说结论:长期记忆不是一个存储桶,而是一套从写入、清洗、压缩、检索到遗忘的完整生命周期管理。如果只是把对话历史塞进向量数据库,然后每次检索top-k,你得到的是一个看起来记得、其实经常记错的 Agent。我见过不止一个项目上线后出现这种情况:用户明明在三天前说过“我住浦东,通勤时间不超过40分钟”,结果 Agent 第二天推荐的房源全在郊区;用户说“我不吃肉”,Agent 依然在推荐牛排套餐。原因都一样——记忆能查出来,但没被正确地用起来。所以我写这篇文章,想把 Agent 的长期记忆这件事拆开来讲:它到底由哪几层组成,“存下来”之后还需要哪些环节才能真正变成可用的记忆,以及在实际项目里我们是怎么设计记忆服务、又踩了哪些坑的。适合谁看?正在做 Agent 应用开发、想把对话体验从“每次都像第一次见面”提升到“像个老朋友”的工程师和产品同学,哪怕你现在还没动手写过一行记忆代码,这篇文章也能帮你建立一套完整的认知框架。1.1 记忆不是“聊天记录备份”很多人第一次做记忆功能,直觉是把所有 session 的对话原文原封不动存起来,再在每次请求时做向量检索。这种做法很快会遇到三个问题。第一是原始文本噪声太大。用户聊天时会说大量无信息量的话:“嗯”、“好的”、“我再想想”、“你稍等”。这些内容如果全部进入向量库,检索时会被高频语义干扰。比如用户问“你上次推荐给我的那个东西叫什么来着”,结果向量检索回来的是几条“嗯嗯好的”这类没有实质内容的片段,完全没法用。第二是知识分散。用户可能在第一次对话里提到“我在准备 CFA 考试”,第二次提到“我每天有2小时学习时间”,第三次说“我容易分心,需要番茄钟式提醒”。如果只做原文检索,每次只能命中一条碎片,Agent 很难把这三条信息缝合起来形成一个完整的“用户画像”。长期记忆的核心价值恰恰在于跨会话、跨时间的知识整合,而不是单条记录的精确匹配。第三是没有时效概念。用户说“这个月月底之前我需要完成报告”,如果这条记忆没有时间戳、没有被更新,下个月再被检索出来,Agent 会一本正经地提醒用户“记得月底前完成报告”,可那时报告早就交了。这就是典型的“记住了不该记住的东西”。所以我把 Agent 的记忆定义为:经过结构化、压缩、冲突处理后,能在合适的时机被检索出来并影响后续决策的信息。存储只是其中一个环节,甚至不是最难的环节。1.2 “存下来”只是记忆的第一公里我这几年做过的项目里,凡是记忆功能翻车的,几乎都不是“存不下来”,而是死在下面这几个环节:写入时没做结构化,存了一堆非结构化聊天噪声;没有抽象和反思,长期记忆里全是流水账;检索时机不对,每条用户消息都触发检索,导致上下文被塞爆;没有更新和冲突处理机制,旧记忆覆盖新记忆,或者新记忆覆盖旧记忆之后无法回溯;忘了做遗忘策略,记忆库无限膨胀,检索越来越慢、越来越不准。你看,存储(把文本向量化插进数据库)在其中大概只占30%的工作量,而另外70%都在记忆的生命周期管理上。我后面的章节会按这个思路展开:先分清楚记忆的类型,再讲如何从原始会话里提炼记忆,然后讲检索和注入策略,最后讲遗忘与更新。每个环节我都会给出具体的做法和踩坑经验,而不是停留在概念层面。2. 长期记忆的三层分类:别把所有信息都塞进同一个向量库我在设计记忆结构时,最先做的不是写代码,而是把记忆分了个类。这个分类直接决定了后面所有存储和检索策略。借鉴认知科学里长期记忆的分类方式(语义记忆、情节记忆、程序性记忆),映射到 Agent 场景会特别清晰。2.1 语义记忆:关于用户的客观事实语义记忆是“用户是什么样的人、拥有什么、偏好什么、处于什么状态”,比如:用户在浦东工作,通勤时间上限40分钟;用户是一名产品经理,正在准备 CFA 三级考试;用户有宠物猫,租的房子要求可以养猫;用户对乳糖不耐受,不能喝牛奶。这类记忆的特点是:谁便关键事实、很少随时间变化、可以形成属性键值对。我一般建议用结构化的实体-属性-值(如 JSON、数据库行)来存储,而不是只做向量化。原因很简单:向量检索擅长“按语义模糊找”,但像“用户ID=123,租住区域=浦东”这种精确信息,用结构化存储更可靠、更容易被精确过滤和更新。当然语义记忆也可以有向量索引,方便用自然语言查询。但在我的实践里,优先保证精确字段的存在。比如数据库里有一行preference表,字段是user_id, key, value, updated_at, confidence,每次写入时做 upsert。这样当用户说“我现在搬到朝阳了”,系统可以直接把region字段从pudong更新为chaoyang,而不是再往向量库里插入一条新文本。2.2 情节记忆:发生过的事件序列情节记忆记录的是“在某个时间点发生了什么”,比如:1月10日用户申请了订单 #A123 的退款;1月12日用户问了三次物流信息;用户在上一次对话中说过“这周五前给我方案”。情节记忆的重要特征是时间性和因果性。它不是一个孤立事实,而是一个事件序列。这些事件可以帮助 Agent 理解用户当前的进展状态。比如客服 Agent 知道用户退款的诉求已经处理到“等待仓库验收”阶段,就不会再让用户重复提交申请。我在工程上通常用两类结构存情节记忆:一类是事件日志,记录event_id, user_id, event_type, payload, created_at,适合做精确时间线查询;另一类是摘要文本,比如“用户在2月第一次咨询退款流程,3月正式提交申请,4月因物流显示异常发起投诉”,这种摘要适合做向量化,用于模糊回忆。需要注意,情节记忆千万不要逐句保留,一定要做摘要。我见过一些项目把整段对话原文都当作情节记忆存下来,结果向量库越来越大,检索到了重复内容还互相矛盾。正确做法是把事件提炼成一句话摘要,保留关键实体和时间,然后定期把多句摘要再聚合成更高层的记忆。2.3 程序性记忆:Agent 学会了怎么服务这个用户程序性记忆听起来很学术,其实在 Agent 场景里指的是“针对这位用户,哪些做法有效、哪些做法无效”。例如:用户反馈条目较多,应该先总结再逐条确认,而不是每次追问很多问题;用户偏好幽默风格,称呼上喜欢“你”而不是“您”;给这个用户推荐产品时,价格敏感度高,不要推超过预算的选项;用户很少看长文本,回复尽量压缩在200字以内。程序性记忆往往不是用户直接告诉我们的,而是从多次交互中归纳出来的。它更接近“行为策略”。我的实现方式是把这类记忆做成“经验包”:每条经验里包含触发条件、建议动作、反馈结果。比如记一条“当用户询问时间节点时,同时给出日历提醒链接,点击率提高了40%”。程序设计上,这类记忆可以使用评分配机制:每次检索到经验时,如果后续交互产生了正向反馈(如用户点击了、明确说“谢谢”),就轻微上调该条记忆的权重;如果用户忽略或表达了不满,就下调。这个机制让 Agent 的行为策略能随交互动态优化,而不是永远停留在第一版。三层分类可以有效避免“迷茫地全塞进向量库”。一个务实原则是:能用精确字段的用精确字段,能用摘要的用摘要,只有确实需要语义模糊检索的才做向量化。很多人纠结向量库选型,却忽视了给记忆分层,最后向量库又当数据库又当缓存,各种难受。3. 从原始对话到可用记忆:清洗、压缩、反思三级处理管线分好类之后,真正费工夫的是怎么把一段乱七八糟的对话变成可持久化、可检索的记忆。我经历过从“原文入库”到“多级提炼”的转变,效果差距非常大。下面讲讲我目前在用的三级处理管线。3.1 第一级:清洗与结构化原始对话必须先经过一轮清洗和结构化,这一步通常在对话流中实时进行。我通常用 LLM 抽取,给定对话片段,让它提取:实体提取:人名、地点、产品、事件、订单号、日期等;事实抽取:用户明确表达的偏好、状态、约束条件;待办与承诺:Agent 答应过用户的事情,用户答应过 Agent 的事情;情感与倾向:用户对某个方案是否满意,是否有情绪波动。举个例子,用户说“这个手机颜色我不喜欢,我喜欢深色,另外价格超过4000就不要推给我了”。清洗后得到结构化片段:{ "type": "preference", "entities": {"product_category": "手机"}, "facts": [ {"attribute": "color", "value": "深色", "negated": true, "qualifier": "不喜欢浅色"}, {"attribute": "price_max", "value": 4000, "qualifier": "超过则不推荐"} ], "confidence": 0.9, "source_session": "session_123", "timestamp": "2025-01-15T20:13:22" }很多人会跳过我这一步直接全文向量化,但我强烈不推荐。结构化之后的记忆有几个巨大优势:可以精确更新,不会因为重复写入产生语义覆盖混乱;可以参与 SQL 过滤和排序;可以为后续反思提供干净的输入。代价是多一次 LLM 调用,但这笔调用绝对值得。3.2 第二级:压缩与聚合光有结构化还不够,因为同一条事实可能在多轮对话里重复出现。比如用户分别在三次对话里都提到“我住在浦东”,如果每次都写一条结构化记录,积累的是重复冗余。压缩和聚合在这里发挥作用。我采用的方法是按用户维度,定期(通常是每天一次或每N次对话一次)把新增记忆跑一个聚合任务。聚合任务的输入是当天的记忆记录和已有的历史记忆,输出是更新后的记忆集合。具体规则包括:同键值覆盖:同一用户的同一个属性,以最新时间为准,但要保留历史值用于回溯;相似事实合并:语义相似的偏好合并为一条,比如“不喜欢浅色手机”和“喜欢深色手机”合并为“偏好深色系电子产品外观”;低置信度淘汰:置信度低于0.5且从未被再次确认的记忆,直接移入遗忘候选区。聚合层还可以做所谓的“摘要记忆”。比如用户最近十次对话全部在讨论租房搬家,系统可以产生一条摘要:“用户正在准备搬到朝阳区,重点关注通勤距离、宠物友好、预算6000以下”。这条摘要是后续对话的重要背景信息,比零散十条原始记录有效得多。我在项目里把这个聚合任务做成一个异步队列任务。对话结束后不立即做聚合,而是累积到一定量或固定时间点跑批量,避免对用户请求产生额外时延。3.3 第三级:反思与冲突处理反思(reflection)是长期记忆里最容易被人忽略、又最能体现 Agent 智能的一部分。它指的是从大量具体记忆中归纳出没有直接说过的结论。比如用户从没说过“我工作很忙”,但从过去两周的对话里看出来,用户经常在晚上十一点之后咨询、提到频繁加班、多次说“没时间看长文档”。反思层可以推断出“用户工作强度高,沟通应尽量简洁高效,推荐内容应预告耗时”。反思输出的不是用户说的,而是 Agent 学到的,这一点要谨慎。一条反思记忆如果错了,影响会非常大。所以我会给反思记忆加上独立的置信度和来源。冲突处理同样重要。当新写入的记忆和旧记忆打架,系统不应该简单地“后写覆盖先写”,而应该多保留一些上下文。比如用户周一在对话里说“我喜欢住在市区,热闹方便”,周五又说“太吵了,想换到郊区安静一点的地方”。这其实不是矛盾,而是偏好发生了变化。如果直接覆盖,Agent 就丢失了对用户偏好演变过程的理解。我在设计里会让每条记忆保留一个superseded_by字段,旧记忆不删除,只是标记为“已过时”。这样当用户问“我之前是不是说过喜欢市区”,Agent 依然能给出准确回答,并且还能补充一句“但最近你倾向于郊区安静地段”。冲突处理的实际规则可以简单粗暴:显式推翻优先于隐式推断,用户明确说“我之前说的不对”时,旧记忆直接降权;时间近的偏好覆盖时间远的偏好,但保留历史版本;如果冲突双方置信度均较高,两记并存,在注入提示词时同时呈现并注明时间线。这套三级处理管线跑下来,记忆库里的信息密度和质量比直接存原文高很多倍。检索的效果也就不在一个层次了。4. 检索与注入:什么时候调用记忆,比用什么向量库更关键记忆处理得再好,如果检索策略不对,依然白搭。经验上看,检索这一侧踩的坑比写入更多。4.1 别每条消息都做全量检索早期我把所有用户消息都先做一次向量检索,然后把 top5 记忆塞进 system prompt。结果出现两个问题:一是上下文长度爆炸,每轮都带5条记忆,token 消耗极高;二是检索出来的记忆大部分与当前意图无关,反而干扰生成。正确做法是“按需检索”。具体来说,用意图分类或关键词触发两种模式:被动注入:针对高频、稳定、与当前任务关系不大的记忆,比如用户姓名、称呼偏好、回复风格、时间区、语言偏好,每次请求都自动放在 system prompt 里。这类记忆少而精,通常不超过5条。主动检索:只有在用户消息涉及历史事实、个人状态、先前决策时才触发向量检索。触发信号可以是“是否出现历史相关词汇”“是否询问之前的内容”“是否包含订单号等实体”。这套设计的核心是区分“常驻记忆”和“情境记忆”。常驻记忆像一个人的基本人设,情境记忆像针对某次谈话临时调取的工具。两种用混了,Agent 不是显得“记忆混乱”,就是显得“记忆平平无奇还费钱”。4.2 相关性排序之外,还要有时效性排序我见过不少项目用向量检索 top-k 之后就直接注入。但向量检索在语义相关性上表现尚可,在时效性上很差。一条一年前的“用户想换工作”和一周前的“用户已入职新公司”在向量空间里可能都跟“工作”相关,但后者显然更重要。我建议检索结果做二次排序,综合三个维度:语义相关分(向量余弦相似度等)时间衰减分(记忆越新,权重越高;可设半衰期,比如一周衰减50%)置信度分(前面提到的 confidence 字段)综合分 = 0.5 * 语义分 + 0.3 * 时间分 + 0.2 * 置信度分。这个权重可以根据业务调整。比如金融咨询场景,时效性权重可以提更高;兴趣推荐场景,语义分可以占主导。二次排序后只取 top3 注入,避免把无关记忆塞进去。4.3 检索结果如何注入提示词而不“喧宾夺主”把记忆注入到 prompt 不是直接拼接一段“用户偏好”,而是要打成 Agent 可以理解的上下文格式。我推荐两种形式。第一种是“事实清单式”,适合被动注入:客户背景信息(仅供参考): - 称呼偏好:本名“王岚”,不接受“亲”这类昵称 - 沟通风格:简洁、要点式,避免长篇大论 - 当前状态:正在搬家,重点关注物流时效 - 重要约束:预算上限6000元/月第二种是“提醒以事件时间线方式回顾”,适合主动检索:与该用户相关的历史事件: 1. 2025-01-10:提交订单A123退款申请 2. 2025-01-12:咨询物流异常,情绪较急躁 3. 2025-01-14:客服承诺48小时内反馈,尚未闭环 提醒:上次承诺的闭环时间是1月16日,需要优先处理。注意一个细节:注入记忆后,Agent 可能会盲目依赖记忆内容而忽略用户当轮的新消息。所以我会在 prompt 里加一句“如果用户当前消息与背景信息冲突,以用户当前消息为准”。这句话看着简单,但能避免很多“AI自作主张引用旧记忆”的翻车案例。我实测下来,加上检索触发机制和二次排序后,同样的记忆库,输出准确率能提高不少,token 消耗也明显下降。这类工程优化带来的体验提升,往往比换一个更大的模型更立竿见影。5. 实战案例:某智能客服 Agent 的长期记忆落地过程光讲理论太虚,我拿一个具体项目来说明。当时某电商平台要做智能客服,要求是同一个用户隔三天再来咨询时,客服 Agent 能记得之前处理过什么问题、承诺过什么。预算有限,不能用庞大的模型做高频推理,所以记忆部分必须设计得靠谱。我们几个开发者组了个小团队,前后迭代了两周。5.1 场景设定与记忆需求分析业务场景是售后客服,核心诉求有四个:识别用户身份后,自动调取历史工单与承诺;用户再次询问退款进度时,不用重新走一遍流程;客服 Agent 承诺过的事情不能被遗忘;用户情绪激动时,能结合历史事件给出更有针对性的安抚话术。对照我前面讲的三层记忆,这个场景里语义记忆主要是用户身份信息、购买偏好;情节记忆主要是工单流转记录、沟通记录;程序性记忆主要是针对不同情绪状态的应答策略、以及“哪些话术更容易让用户满意”。5.2 记忆存储结构设计我们没有用复杂的图数据库,就用关系型数据库加一个向量库的组合。关系库管精确记忆,向量库管模糊检索。核心表结构大致如下:CREATE TABLE user_semantic_memory ( user_id VARCHAR(64), attr_key VARCHAR(128), attr_value TEXT, confidence FLOAT DEFAULT 0.7, updated_at TIMESTAMP, superseded_by INT DEFAULT NULL, PRIMARY KEY (user_id, attr_key) ); CREATE TABLE user_episodic_memory ( event_id BIGSERIAL PRIMARY KEY, user_id VARCHAR(64), event_type VARCHAR(64), summary TEXT, payload JSONB, created_at TIMESTAMP, ); CREATE TABLE agent_policy_memory ( policy_id BIGSERIAL PRIMARY KEY, trigger_text TEXT, action_text TEXT, success_score FLOAT DEFAULT 0.5, updated_at TIMESTAMP, );向量库只存两类内容:情节记忆的 summary 字段的向量,以及由反思层生成的抽象判断的向量。精确查询用 SQL,模糊联想用向量库,各司其职。5.3 实测中的翻车点这个案例我印象最深的是三个翻车点。第一个是重复写入。当时写入管线没加“相似检查”,用户说“我订单还没到”和“物流太慢了”分别被当成两条独立情节记忆。几天后向量库里积累了大量类似记录,检索 top5 出来有四条都在抱怨物流,Agent 直接复制粘贴式回复“我理解您对物流速度的担忧”,完全没有新信息。后来加了相似度阈值去重,语义相似度超过0.85的只保留最新一条,效果立竿见影。第二个是自动承诺的记录缺失。用户问“能保证明天送到吗”,客服 Agent 为了安抚情绪,随口说“我帮您催一下,明天肯定到”。我们没有从对话里抽取“Agent 承诺”这个特殊事件类型,导致承诺没有被记录。48小时后用户回头质问,Agent 一脸茫然。后来我在抽取模型里专门加了承诺槽位,并且把承诺事件独立存入事件表,每天定时扫描“过期未兑现承诺”并升级到人工处理。第三个是上下文爆炸。用户闲聊几句我们也触发全量检索,导致一个很简单的“你好”请求也带了一堆记忆。后来参考第4节的做法,加了一个轻量意图分类器,只有识别到“状态查询”“历史询问”“情绪宣泄”等意图才做主动检索。这个分类器很小,本地跑就行,成本基本为零,但把系统 token 消耗降了接近一半。这案例让我坚定了一个观点:记忆系统设计的优先级应该是“减少错误注入”高于“增加记忆量”。太多团队把精力花在“记住更多”上,结果记住了一堆不该用的记忆,干扰比没有记忆还大。6. 遗忘与更新:好的记忆系统必须会“忘记”记忆系统还有一个反直觉的设计要点:长期记忆不等于永久记忆。把不该忘的忘了很糟糕,但把不该记的都一直留着,同样糟糕。6.1 主动遗忘的三种触发条件我在设计里明确设置了三种遗忘条件。一是置信度衰减。一条记忆如果长时间没有被再次确认或使用,置信度会随时间下降。比如某个偏好只出现过一次,之后三个月都没有相关交互支持,衰减到阈值以下后,系统不再把它注入上下文,但数据仍在库里,只是活跃度降低。二是显式覆盖。用户说“我之前说喜欢黑色,现在不要了,给我推荐白色”,系统不删除旧记忆,但标记为 superseded。活跃记忆中只保留最新偏好。这个机制保证 Agent 不会因为记性太好而让用户觉得“我都说变了你还记着旧的”。三是隐私清除。用户主动要求删除记忆,或数据合规要求保留期限到了,系统执行物理删除或匿名化。这个机制不单是合规问题,也直接影响用户对 Agent 的信任感。我在产品里加了“查看记忆、删除单条记忆、清空全部记忆”的界面以后,用户反馈好感度提升明显,有不少用户甚至主动表示“原来你还记得我上次说的,那我也愿意告诉你更多”。八成以上的团队在设计记忆系统时,完全没有考虑遗忘路径。等到记忆库膨胀到几十万条时,检索延迟上来、存储成本上去、结果质量下降,这时候再想清理就已经很被动了。6.2 隐私边界:可解释、可控制、可删除长期记忆涉及大量用户个人信息,所以记忆系统必然带有一点数据合规的属性。技术上我建议做到三件事:每次写入记忆时记录来源对话和创建时间,如果用户问“系统知道我的什么信息”,可以拿出一张可读列表;用户删除某条记忆后,写一个删除标记;至少要让记忆不被检索、不被注入;记忆系统输出的内容不允许包含未经用户确认的推断。反思层生成的结论必须标记为“推断”,不能和用户亲口说的混在一起。我见过比较糟糕的案例:Agent 把用户在某次闲聊中顺嘴提的“薪资有点低”写成了高置信度的“用户想跳槽”,然后后续对话一直围绕跳槽展开,用户非常尴尬。这种记忆系统的设计就不够谨慎。所有可覆盖的、推断性的记忆都应该默认低置信度,只有多次印证后才逐步提升。6.3 更新频率与批量机制最后聊一下更新的工程实现。长期记忆不适合每轮对话都重写全部内容。我试过同步写入,用户一旦对话比较长,额外时延就很明显。最后我采用的策略是牺牲一点实时性换取稳定。对话过程中只把原始结构化记忆写入本地队列,立即返回给用户。后台任务每五分钟批量 flush 一次,做相似检查、去重、更新用户画像。摘要和反思任务则更低频,每天一次即可。这样用户侧基本感知不到记忆处理的时延,系统也不用担心每个请求都跑一轮复杂聚合。频率上还有个经验值:主动注入的稳定偏好记忆,每次对话前刷新一次;情节记忆的摘要,每次 session 结束时刷新一次;反思层的抽象结论,每天重建一次。这个频率足够支撑绝大多数业务场景,不需要更激进。一段收尾的经验分享跑过几个长期记忆项目之后,我最深的体会是:Agent 的记忆能力不是靠堆存储堆出来的,而是靠一套克制的工程体系。该分层的分层,该压缩的压缩,该遗忘的遗忘。每次我遇到“用户觉得 Agent 记性差”的反馈,回到代码里看,八成是检索时机不对或者写入太粗暴,而不是向量库不够大、模型不够聪明。最后分享一个很实用的小技巧:给每条记忆打上“需要确认”的标签。当记忆来自用户主动表达时,置信度高;当记忆来自 Agent 的总结时,默认置信度低。每过一段时间,在对话里自然地问一句“我理解你最近比较关注搬家相关的事,如果不对的话可以告诉我”。这种轻量确认机制既避免了记忆错误带来的尴尬,又让用户觉得 Agent 真的在用心记、不是偷偷存隐私。我在实际测试中发现,带着记忆去问答的用户满意度,比那些每次都要重新重复资料的用户,高不少。长期记忆这件事,做好了就是产品口碑差异,做不好就只是多了一个没人看的数据库表。希望这篇文章能少让你走弯路。