
做Agent的人早晚会遇到一个很尴尬的场面你给Agent配了记忆库想让它记住用户偏好结果它不光把同一件事存了好几份还会在后续对话里说出前后矛盾的话。用户上周还钟意简洁风这周Agent就按花哨风推荐了一堆东西原因不是模型变笨了而是记忆系统本身“脏”了。我这边就吃过这个亏。当时我维护的一个Agent项目日常交互量不算大但用了三个月后记忆库里的条目数量膨胀到预期的五倍。检索召回Top 5里经常出现三四条重复内容偶尔还能看到两条明显冲突的记录。这种问题不解决RAG的底座再稳也白搭。后来我集中做了两轮治理先做去重再做更新策略调整前后试了六种方案才摸出一套低成本但稳定的组合打法。这篇就好好聊聊Agent记忆的去重与更新策略。会拆解每种方案的原理、适用场景、实测表现和易踩的坑尽量给到可以直接落地的代码和配置思路。不管是正在做AI助手、客服机器人还是自动化工作流的同学应该都能从中找到参考。1. 先搞清楚Agent记忆为什么越用越“脏”要想治理记忆污染得先弄明白垃圾信息是怎么进来的。我排查了一圈发现重复和矛盾这两个问题来源其实不太一样。重复记录的根源通常是写入路径缺少约束。Agent第一次从用户对话中提取出的信息是“用户偏好简洁的产品说明”隔了两天它在另一段上下文里又提取出“用户不喜欢啰嗦的说明”语义几乎一样但文本表述不同向量检索时相似度可能在0.82左右。如果你设定的入库阈值低于这个值两条就都会被写进去。更麻烦的是同一个信息在不同会话里可能被多次触发保存于是记忆库里的冗余条目指数级增长。矛盾记录的成因则复杂一些。一种是时序覆盖缺失用户说“我最近在学Python”过了一个月又说“我已经放弃Python了”旧记录没被标记为过期两条信息同时存在Agent被问起时就只能抽签式回答。另一种是事实与推断混存记忆库同时保存了“用户说周末喜欢去南山徒步”和“推测用户是户外爱好者”前者是事实后者是推断但系统对这两类信息没有区分后续使用时很可能把推断当成确定结论。再加上很多Agent项目的记忆写入是纯异步的没有设置冲突检测。后果就是等你想清理的时候数据量已经大到做全量清洗都费劲。所以我的经验是不要把记忆质量当成事后补丁架构上就得留出治理的口子。2. 六种去重与更新策略实测拆解策略名单先说清楚我按实施成本从低到高排列策略一写入门控向量相似度阈值去重策略二语义指纹去重轻量级哈希合并策略三记忆合并压缩列表式与摘要式更新策略四时间衰减与优先级更新策略五矛盾检测与版本化更新策略六周期性反思重写双网络记忆模型的朴素实现2.1 策略一写入门控最便宜的第一道防线这个思路很直白在记忆写入之前先拿新条目标识与已有记忆做一次相似度计算超过预设阈值就拒绝写入或者在原记录上做更新而不是新增。我用了两种方式实现。一种是用Embedding向量算余弦相似度阈值设在0.88到0.93之间比较合适。设太低容易把真正的新信息拦在外面设太高又起不到去重效果。这个需要看你所用的Embedding模型的区分度我用text-embedding-3-small时0.9是个不错的起点。另一种是轻量级方案不需要调Embedding接口直接对新旧文本做规范化处理后求字符级相似度。比如去掉空格、标点、转小写把“很”、“特别”、“非常”这类修饰词做归一化然后用difflib或类似的库算相似度。这种方式对“用户喜欢XX”和“用户非常喜欢XX”这类变体非常有效而且几乎零成本。实现逻辑其实很简单这是我在项目中最先落地的一段def is_duplicate(new_memory, existing_memories, threshold0.9): new_vec embed(new_memory[content]) for mem in existing_memories: old_vec mem[embedding] if cosine_similarity(new_vec, old_vec) threshold: return True return False # 写入前检查 if is_duplicate(new_mem, memory_store.latest(50)): # 可跳过写入或转入更新流程 pass else: memory_store.add(new_mem)这个策略的性价比很高因为它把大部分明显的重复挡在门外。但要注意只做写入门控是不够的。门槛只能拦“像”的拦不住“语义相同但表述差异巨大”的。比如“用户养了一只柯基”和“用户有一只短腿狗”向量相似度可能并不高但语义确实是同一条信息。这道坎需要靠语义指纹或合并策略来解。2.2 策略二语义指纹去重用轻量哈希锁定重复记忆语义指纹的核心思路是把不同文本映射成一个短字符串若两条文本指纹相同或高度相似就认定它们是同一信息的不同表达。一开始我用的是简单哈希但效果不佳。因为“用户喜欢用Notion做笔记”和“用户日常记录都放在Notion里”这两句话的哈希值完全不同。后来改成两步法先用LLM生成一句规范化摘要再对摘要做哈希。这样既能保证同义文本生成相似的摘要也能控制指纹的长度和计算成本。具体做法是每写一条记忆前用一次轻量LLM调用将原始文本压缩成不超过20个字的规范化描述然后把摘要送入一个哈希函数作为这条记忆的指纹。举几个实测例子原始表述用户说他在工作日一般不怎么看手机规范化摘要用户工作日少用手机原始表述用户工作时不喜欢被打扰规范化摘要用户工作时拒绝打扰指纹相同或相似时就可以触发更新逻辑而不是新写入。好处是精确度比纯向量相似度高坏处是每一条写入会多一次LLM调用成本略增。我个人的经验是高频写入场景可以把指纹生成结果做缓存同一个会话内的重复文本无需反复调用。我的项目在被缓存命中后指纹计算成本基本可以忽略。2.3 策略三记忆合并压缩把碎片变结构化记录去重只解决“同一条信息存多份”的问题但解决不了“多条碎片信息描述同一主题”的问题。比如记忆A用户喜欢深色模式记忆B用户经常在晚上使用产品记忆C用户希望界面能自动切换主题这三条其实是同一个主题下的不同侧面。合并压缩的策略就是把它们归拢成一条结构化记忆{ topic: 界面主题偏好, facts: { 配色: 深色模式, 使用时段: 偏好晚间使用, 期望功能: 自动切换主题 }, last_updated: 2025-06-18 }合并触发条件一般有两种一是同主题记忆数量超过阈值我设的是3条二是检索时发现Top-K结果有较大概率属于同一主题。前者适合离线批量整理后者适合在线场景在检索后做一次动态合并。这里有个容易犯的错误合并时只保留最新文本导致信息丢失。比如“用户这两天在准备面试”和“用户在准备后端岗位的面试”合并时如果图省事只留后一条那“准备面试”这个稍大范围的信息就被丢了。正确做法是保留一个主信息字段再加一个“备选细节”字段宁可多存一点上下文也不要随意裁剪。合并后还有个连锁问题原本指向这些碎片的索引、标签、关联关系都需要更新。如果记忆库用的是简单列表那还好办直接替换即可。如果已经引入了图结构或标签体系合并时就要考虑关系迁移。这块虽然操作繁琐但躲不掉不然又会形成“索引指向已删除记录”的新脏数据。2.4 策略四时间衰减与优先级更新低成本维护记忆新鲜度记忆不只要去重还要会“过期”。我的做法是给每条记忆增加两个属性重要性和时间衰减因子。重要性决定了这条记忆在冲突时是否值得保留时间衰减因子则影响它的检索权重。公式看起来是这样score base_importance * exp(-decay_rate * age_days)这里base_importance是初始重要性decay_rate决定多快衰减。对于Agent场景decay_rate可以设成0.01到0.05之间也就是记忆保留20到100天后权重明显下降。如果是客服场景用户会话型信息的衰减速度可以更快一些0.08左右比较合适。关键在于衰减不是删除只是降低排序权重。这样既能避免长期不用的记忆占据检索头部又不会因为误判把重要信息彻底清掉。实际更新时我会在两类时机触发权重刷新用户在对话中再次提及某个已存信息时直接对该记忆执行“触摸更新”把last_accessed设为当前时间让衰减重新计时每天凌晨跑一次定时任务对所有记忆做年龄计算把分数低于阈值的记录降级到冷存储在常规检索中不再召回但不物理删除。这样做的好处很明显——记忆库的活跃区始终保持在一个可控大小检索延迟和噪声都下降同时历史数据还在需要追溯时可以随时调出。简单说这就是给记忆做了个“冷热分区”。2.5 策略五矛盾检测与版本化更新像处理代码冲突一样处理记忆记忆矛盾没法完全避免所以得有一套检测和仲裁机制。先聊检测。常规做法是在写入新记忆时与同主题既往记忆做一次“事实冲突检查”。这里的冲突不是文本相似度高而是同一维度上出现了不同取值。比如“用户所在城市”这个维度旧记录是杭州新记录是上海这就是冲突。冲突检测有两个层面结构化字段冲突因为记忆按JSON结构化存储直接在字段层面做比对比如city值不同、job_title值不同很直观。非结构化语义冲突把新旧记忆同时丢给LLM让模型判断是否矛盾。这个准确性高但成本也高我一般只在结构化检测未发现冲突但语义可能矛盾时才调用。检测到冲突后怎么处理我试过几种方式最终觉得版本化更新最靠谱。{ key: user_city, current: 上海, history: [ {value: 杭州, set_at: 2025-05-10, confidence: high}, {value: 上海, set_at: 2025-06-20, confidence: high} ], conflict_pending: false }新值来了不直接覆盖旧值而是采用“新值写入旧值进历史”的策略。这样Agent在回答时默认使用current值但如果用户在后续对话中追问“我上次在哪办的卡”系统还能从history里找到杭州这条记录。如果两条矛盾的记忆置信度都很高且无法通过时间最新值判断比如用户先说自己喜欢Windows后又说喜欢Mac系统不知道该信哪个那么就把conflict_pending设为true让Agent在下一次对话中用自然语言向用户确认。这种“让用户来仲裁”的方式在真实交互中效果非常好既避免了误判又让用户感觉Agent很贴心。2.6 策略六周期性反思重写双网络记忆模型的朴素实现最后一种策略也是我实测下来维护成本最低、长期效果最稳定的一种周期性反思重写。这个思路说白了借鉴了双网络记忆模型的一些理念——一个快速写入网络负责记录近期信息一个慢速巩固网络负责定期整理和沉淀长期知识。落到Agent记忆场景里不必要整那么复杂直接用一个定时任务每隔一段时间让LLM对近期记忆做一次“回顾重写”。具体做法是每运行N轮对话我设的是20轮或者每天定时把这段时间内新增的记忆取出来让LLM扮演记忆管理员输入这些记忆和已有的长期记忆输出一组“维护指令”维护指令包括三类操作merge合并相同信息、update用新信息修正旧信息、delete删除已确认过期的内容。比如LLM看到这样一组记忆用户最近在准备面试用户投了前端岗位用户对TypeScript不太熟它可能会输出将“准备面试”和“投前端岗位”合并为“用户正在准备前端岗位面试”将“对TypeScript不太熟”更新到该用户的技能维度中。这个策略对LLM的要求不算高不涉及复杂推理只需要它做信息归纳。成本方面20轮一次整理每次消耗的token远小于对话本身的调用量完全可以接受。有一点务必注意重写任务必须使用独立的上下文窗口不要让Agent正在进行的对话干扰整理结果。我一开始图省事直接在Agent回复后追加“顺便整理下记忆”结果整理出来的结论混入了当时的对话上下文脏得不行。改成独立任务后质量立刻稳定了。3. 如何验证记忆优化效果评测集与关键指标六种策略聊完了但光有方案不行还得有标准不然你不知道改完是变好还是变差。我搭了一套轻量级评测方案核心指标有三个重复率、矛盾率、召回准确率。先说明指标定义重复率统计在记忆库中随机抽取100条记录两两计算相似度取超过阈值的比例。这个指标能直接反映去重策略的效果。矛盾率统计同样随机抽记录用LLM判断两两是否存在事实冲突冲突比例越低越好。召回准确率构造50个测试问题每个问题对应一个正确记忆。跑一遍Agent的检索流程看Top 5结果中是否包含正确记忆。我在优化前后的对比数据如下基于同一批测试集指标优化前优化后六策略全开重复率17.2%1.4%矛盾率8.6%0.9%召回准确率82%94%平均入库耗时约350ms约420ms可以看到优化后重复率和矛盾率都大幅下降召回准确率有明显提升。入库耗时略有增加主要是多出了指纹计算和冲突检测的步骤。但如果配合本地Embedding和缓存这20%左右的耗时增量完全可以用质量收益来覆盖。评测这事我建议做成自动化每改一次策略就全量跑一遍不然手动去数会让人崩溃。一套50个问题的测试集用普通配置跑完不到三分钟频率高一点也不心疼。4. 常见问题与排查技巧实录方案落地的过程中坑比想象中多。挑几个高频问题按现象、原因、解决办法的顺序整理一下给各位避个雷。问题一设了去重阈值但该去重的还是没去重。原因大概率不是阈值设高了而是Embedding模型对短文本的区分度不够。我遇到过“用户喜欢喝美式”和“用户每天喝美式加冰”两条记忆语义明显有交集但相似度就是不到0.85。解决办法是不要只用Embedding配合Fingerprint或LLM摘要做二次判断。摘要后两条都变成“用户喝美式”指纹一致就能合并了。纯向量方案的极限就在那强上不如换个思路。问题二合并压缩后Agent反而遗忘了某些细节。这个是最冤的一种问题。我之前把“用户说下个月要去日本旅行”和“用户计划去东京、大阪和京都”合并成“用户计划下个月去日本旅行”结果“东京、大阪、京都”这三个具体地点丢了。用户问“我打算去日本哪些城市”时Agent完全答不上来。所以合并原则里要强制加一条结构化字段只增不删。即使某个细节在当前的合并结果里用不到也要保留在“备选细节”字段中。信息可以降级但不能直接抹除。问题三矛盾检测误报率太高新写入经常被标记为冲突。一次用户说“我一般十点睡”另一次说“我昨晚加班到凌晨三点才睡”系统判定为矛盾把新记录拦住了。但真实情况是第一句说的是习惯第二句说的是特例。语义层面这不是事实冲突而是范围不同。解决办法是在记忆结构里加上类型标签区分规律事实和临时状态。规律事实默认优先级高临时状态只作为覆盖不作为冲突。然后矛盾检测只对比同类型记录不同类之间不做冲突判断。问题四定时反思重写占用的token量太大。反思重写如果处理不当会变成一个成本黑洞。解决方案是两个一是限制单次处理的记忆条数比如每次最多100条超出部分分批次二是把整理任务放到非高峰期执行。还有个小技巧重写的输入模板里可以明确要求LLM对未变动的记忆直接输出“no-change”这样输出token可以节省30%左右。最后分享一个小技巧六种策略各有各的适用位置但我在实际测试中体会最深的一点是不要指望单一策略能解决所有问题。写入门控挡最明显的冗余语义指纹处理同义变体合并压缩减少碎片时间衰减控制活跃区矛盾检测处理事实冲突反思重写做长期整理。它们各管一段组合起来才形成完整的记忆治理闭环。如果你现在正被Agent记忆的重复和矛盾问题困扰我的建议是先别急着堆代码。花一个下午把记忆库里的数据导出来肉眼看一下重复和矛盾的类型分布再决定优先上哪几个策略。多数项目里写入门控加合并压缩就能解决80%的问题剩下的交给周期重写性价比是最高的。后续如果想在这个方向上继续扩展还可以考虑给记忆增加置信度评分和来源追踪实现基于来源的自动纠偏。关于这块等我在新项目里跑出更多数据后再来填坑。