Agent记忆系统实战:召回式记忆与多路融合排序提升对话续接率 📅 发布时间:2026/9/8 7:54:33 👁 浏览次数: 最近折腾的一个项目起因很朴素我们的业务线上有个智能客服 Agent用户隔天回来继续咨询它却把前一天聊的东西忘得干干净净害得用户重复描述半天需求。这个场景我想很多人不陌生LLM 本身没有持续记忆每次对话都像第一次见面所有状态只能靠“把历史记录重新塞进上下文”来解决。可会话一长、业务一复杂token 成本和效果双双崩盘。于是我们决定给 Agent 加一套真正的记忆系统后端存储直接落在了业务库里就在用的 TencentDB 上取用方式走“召回”也就是不让模型看全量记忆而是按需检索相关片段。系统上线后团队里最常被追问的一个问题是被召回的记忆真的有用吗为了回答这个问题我们做了一轮比较完整的对比评测也踩了不少坑。这篇文章就把整个设计思路、召回链路、实测数据和排查记录都摊开来聊给正在做 Agent 记忆的同学一份可以抄作业的参考。1. Agent 失忆问题与记忆系统的设计动机1.1 Agent 为什么会“失忆”先说根因。Agent 的智能来自大模型而大模型的输入是有限的 token 上下文窗口不是无限“硬盘”。它每一次响应都只基于当前请求携带的内容——你给它多少上下文它就只知道多少事。会话结束后一切中间状态都烟消云散。这个特性决定了只要是多轮交互、跨会话复访、长时间任务跟踪的场景失忆几乎是必然发生的。实际操作中我们会看到三种典型表现单会话后期失忆对话超过一定轮数后模型反而“忘记”了最开始用户提的需求尤其是在中间夹杂了大量无关内容时。这不仅是 token 超限的问题更是注意力被稀释的结果。跨会话完全失忆用户今天来聊一半明天回来说“继续昨天的事”Agent 一脸茫然又要用户从头解释。跨任务状态丢失用户在 A 任务里确定的偏好、约束条件在 B 任务里没有被引用导致输出风格或逻辑前后矛盾。我们最初尝试过“简单粗暴”的解决办法——把历史会话全部拼进 prompt。效果呢短会话勉强能跑但两个问题立即暴露一个是钱烧得快尤其用大上下文模型时每轮请求都在反复传输大段历史另一个是响应变慢且文本越长模型越容易在无关信息里“迷路”回答质量不升反降。1.2 为什么选择“召回”而不是“全量塞入”先明确一个核心区别全量塞入是把所有记忆当作“读一遍”的文本模型必须从头到尾处理它召回则是先把记忆存进外部数据库每次请求前用检索手段挑出最相关的几条再拼入上下文。这一步和传统 RAG 很像但有一个关键差异。传统 RAG 检索的是外部知识库内容是相对静态的文档Agent Memory 检索的是用户会话状态、偏好、决策记录等动态生成的记忆语义更碎片化、时效性也很强。所以它不能只做一次向量检索还需要叠加标量过滤、关键词匹配、时效衰减等策略。选“召回”而不是“全量塞入”动机非常直接成本可控只注入 Top-K 条相关记忆每轮请求的 token 消耗基本稳定而不是随会话长度无限增长。效果更稳上下文里只有相关度高的记忆注意力更加集中回答时的背景噪声大幅减少。存储解耦所有记忆落到数据库会话结束也不丢下一次会话随时可以继续。当然代价是引入了一套新的检索链路记忆怎么写、怎么建索引、怎么召回、怎么排序每一步都可能出问题。这也是“被召回的记忆到底有没有用”这个争议的根源。1.3 为什么后端存储选 TencentDB老实说最开始也纠结过要不要引入独立的向量数据库。后来认真评估了一下发现我们团队的项目里 TencentDB 早就承担着业务主库的角色用户表、订单表、会话表都在里面再为记忆单独搭一套向量库等于凭空多出一个需要运维的数据组件还要处理两库之间的数据同步成本和故障面都增加了。TencentDB 本身是云数据库服务体系兼容 MySQL 和 PostgreSQL 生态。我们用的实例支持标准 SQL也具备向量检索能力记忆数据的 metadata用户 ID、会话 ID、时间戳和 embedding 向量可以共存于一张表里标量过滤和向量检索一次性完成这个能力对我们来说非常合适。而且从实际架构上看记忆不是一个孤立功能。它天然跟业务数据绑定推荐什么内容要看用户的近期操作续接任务要知道当前订单状态。如果记忆库和业务库分离每次召回完还要再查一次业务库链路长、延迟高。把记忆放在 TencentDB 里业务数据和记忆数据之间可以直接关联查询逻辑上顺理成章。提示如果你已经有了稳定的 PostgreSQL 业务库用它的 pgvector 扩展也能达到类似效果如果项目特别大、向量规模千万级以上再考虑独立的向量数据库。选型不必盲目追新关键是看现有基础设施能不能平滑扩展。2. 记忆召回链路设计与核心细节2.1 记忆写入什么内容值得记召回的前提是“有东西可以召回”。记忆不是把用户说的每个字都存下来那样只会存储爆炸、噪声巨大检索时全是不相关的碎片。我们在写入环节设计了一套过滤策略简单说就是扔掉废话留下有用的事。具体操作上每次 Agent 完成一轮对话后会追加一个“记忆提炼”的轻量模型调用让模型从对话中提取结构化记忆项。我们定义了几种记忆类型事实记忆Fact用户的姓名、地区、会员等级、设备型号等稳定属性。偏好记忆Preference用户明确表达过的偏好比如“我喜欢邮件回复”“推荐时别带生肉”。任务进展Progress当前进行到哪一步了下一步要做什么比如“已选好套餐等待用户确认合同”。决策结果Decision双方确认过的事项比如“已同意退款 50 元不再进行二次申诉”。写入前还会做一次“去重”判断避免同一个信息反复写入。比如用户在第一轮说“我在上海”第二轮又说了一次我们应该更新已有记忆的时间戳而不是再插入一条。这一步对后续召回质量影响很大——重复的记忆会在检索结果里互相挤压反而淹没真正重要的信息。每条记忆最终的存储结构大致是{ memory_id: uuid, user_id: u_12345, biz_id: biz_order_678, memory_type: Preference, content: 用户偏好通过邮件接收账单不接受短信通知, embedding: [0.012, -0.034, ...], created_at: 2025-01-10 10:00:00, last_accessed_at: 2025-01-10 10:32:00, access_count: 3, importance: 0.8 }last_accessed_at和access_count是为了做时效衰减和热点记忆加权这个后面会展开讲。2.2 多路召回与融合排序不只是向量检索很多 Agent 记忆项目只做“Embedding 向量相似度召回”就是拿用户当前的问题去和记忆向量算 cos 相似度取最相似的 Top-K。实测下来这条路单独走并不够用。原因是用户对话里的语义经常是隐含的直白的向量相似度匹配不到那些“指代性”很强的记忆。举个例子用户说“那个套餐再便宜点行吗”如果只做向量召回“套餐”这个词的向量可能匹配到一堆介绍性文本但真正有价值的记忆——上次用户选了哪个套餐、纠结过哪几个价位——可能因为语义表达方式不同反而排在后面。所以我们的召回链路采用了多路召回 RRF 融合排序向量召回通过 Embedding 模型把当前问题向量化用 TencentDB 的向量检索能力找 Top-50 语义相近的记忆。关键词召回对用户输入做分词和关键词提取利用数据库全文索引或倒排索引找包含这些关键词的记忆偏好里提到“邮件”就先把和邮件相关的记忆捞出来。标量过滤这是最容易被忽略但性价比最高的一路。根据当前用户 ID、业务订单 ID、时间范围直接过滤出关联记忆。比如用户目前在退换货流程里那就把退货相关的决策记忆全部拉出来不管语义相似度如何。时效加权对每一条候选记忆用一个简单公式计算最终得分基础分乘以时间衰减系数score (0.6 * vector_score 0.3 * keyword_score 0.1 * 类别权重) * 时间衰减因子 时间衰减因子 exp(-λ * (now - last_accessed_at) / 天数)这里的核心思路是相关性和时效性一起打分。一个三个月前的偏好和一个三天前的偏好对当前任务的影响力显然不同。2.3 记忆注入上下文的方式召回只是上半场怎么把记忆喂给 Agent 同样关键。直接往 prompt 最后塞一坨记忆文本模型很可能忽略它们。我们的做法是把记忆按类型填入 prompt 的固定位置【当前任务】 【用户关键事实】 - 用户位于上海会员等级是黄金会员 【任务进展】 - 用户已选择标准版套餐下一步是确认付款方式 【历史决策】 - 用户曾要求所有发票通过邮件发送放在 system 层和用户问题之间原因是大模型对上下文开头部分的关注度通常高于中间部分。这样每次请求的记忆注入量轻松控制在 500~800 token 以内比硬塞整段会话历史节省了七八成成本。注意记忆注入后要记得在 prompt 中加一条“约束指令”让模型“只使用提供的记忆辅助回答不要虚构未提及的细节”否则模型可能把召回的记忆和当前对话混淆产生幻觉性确认。3. 实测被召回的记忆到底提升了多少3.1 实验设计与指标选择为了回答“到底有没有用”我们搭了三套对照方案对照组 A无记忆。所有请求只携带当前对话不读取任何历史记忆。对照组 B全量历史截断。读取全部历史会话超过模型上限就从头截断这是最原始的做法。实验组 C召回式记忆。使用本文描述的完整链路。评测场景选了一个典型的业务客服续接 多轮商品咨询。模拟了 200 条真实会话每条 8~15 轮且设定跨天回访的特定任务。判定指标四个任务完成率、关键信息命中率、响应相关性人工打分 1~5 分、单轮平均延迟。3.2 结果数据一览方案任务完成率关键信息命中率响应相关性评分单轮平均延迟秒对照组 A无记忆41.2%22.8%2.71.2对照组 B全量历史截断63.5%58.3%3.53.8实验组 C召回式记忆78.4%76.9%4.21.9先说不意外的事召回式记忆全面吊打无记忆这基本是预期内的。真正有意思的是和全量历史截断的对比——召回式记忆在任务完成率和相关性上优势明显关键是延迟还低了一半。全量历史虽然信息全但模型处理长文本耗时严重而且历史里大量无关寒暄会干扰关键信息的提取。不过评测也发现一个反直觉的现象在单轮、无需背景的任务比如问一句“你们营业时间”上召回式记忆不但没帮助反而因为系统额外做了一次检索和注入延迟比无记忆多了 0.5 秒。这个结论很重要记忆召回的价值高度依赖场景不是所有请求都值得做。3.3 什么情况下被召回的记忆会“帮倒忙”评测过程中我还记录了三种典型失败模式都是真实踩过的坑一是召回出错误记忆。向量相似度只负责“像不像”不负责“对不对”。有一次用户问“上次提到的那款降噪耳机”向量召回了耳机介绍类记忆却漏掉了用户其实已经退货的真实状态模型直接顺着耳机继续推荐。这种错误对用户体验的伤害比“没记忆”还严重因为用户以为你记得结果你记错了。二是过期记忆干扰当前意图。用户三天前说过一句“有空再研究一下套餐 C”这周来直接问“帮我推荐一个适合通勤的”系统把套餐 C 的记忆召回来导致模型反复推荐一个用户当下并不想要的方案。时效分数太低但语义相似度高这种冲突在召回排序里非常棘手。三是上下文挤占。召回条数设太高比如 Top-K10注入内容比当前问题还长模型注意力被记忆牵着走反而忽略了用户本轮核心诉求。我们后来把 Top-K 调低到 3~5效果反而更好。这些失败模式汇总一下都指向同一个核心记忆召回系统真正要优化的不只是“召回率”而是“被召回后真正产生正面作用的有效命中率”。4. 常见问题与排查技巧实录4.1 召回率上去了效果反而变差这是个非常经典的误区。上线初期我们一度盲目追求召回率高以为“捞得越多命中越多”结果实测任务完成率不升反降。原因前面也提到了召回的条目里混入了太多“相似但并不相关”或者“相关但已过期”的记忆模型不知道该信哪条。后来我们把优化重心从召回率转向了“有效命中率”也就是注入 Top-K 记忆后模型真正利用并帮助完成任务的比例。具体调整包括降低 Top-K 值从 10 降到 5再降到 3观察任务指标拐点提高相关性阈值向量相似度低于 0.6 的记忆直接丢弃不参与融合排序对记忆按质量加权主动确认过的重要记忆如用户明确说“记住这一点”提高基础权重。4.2 记忆重复与冲突更新怎么处理记忆系统跑久了必然出现重复项。用户的同一个偏好可能在不同的会话里被记录成两种不同的说法“用户喜欢简洁回复”和“用户要求回复不要太啰嗦”。虽然人眼一看是同一个意思但向量相似度未必能识别得出来。我们的做法是写入前做三步检查文本归一化把意思相近的口语表达转换成语义标签如“简洁”统一存成reply_styleconcise。向量相似度查重新记忆和已有记忆 embedding 相似度超过 0.85就视为重复不插入新记录只更新旧记录的时间戳。冲突检测新记忆和旧记忆属于同一类型但内容矛盾比如“用户要求短信通知”和“用户要求别发短信”先保留两条记录并标记conflicttrue召回时由重排层判断哪条更靠近当前场景优先选最近更新的那条。4.3 如何快速定位召回质量问题排查记忆召回问题不能靠猜。我们给系统加了一套完整的“召回日志”每次请求都会记录召回了哪些记忆、记忆的来源、相似度分数、最终排序分数、以及模型实际回答使用了哪条记忆。复盘时直接在日志平台里搜请求 ID就能看到“为什么这条被召回”“为什么那条没被召回”。实践里最常用的两个排查思路看召回但没被模型使用的记忆说明召回的数还算相关但 prompt 里记忆呈现方式有问题或者被其他记忆挤掉了。看模型答错但日志里有正确记忆说明召回和注入都没问题但模型忽略了这部分信息这时需要在 prompt 约束里更强地引导模型使用可用记忆。4.4 运行环境与内存资源异常排查最后提一个容易被忽略的运维问题。记忆召回系统跑在 Agent 服务旁边每天要处理大量向量计算和文本处理内存和 CPU 占用比普通接口明显偏高。我们线上就遇到过几次进程崩溃错误码类似于process exited with code 3221225477或报out of memory这类问题大多不是代码 bug而是资源分配不足。经验值供参考一个 2C4G 的 Pod 支撑日均 10 万次记忆召回请求已经很紧张建议至少 4C8G 起步Embedding 模型如果常驻内存单独部署或者和业务服务分离否则很容易因为一次大 batch 计算把内存打爆。另外连接池参数也要注意TencentDB 的连接数上限需要根据 Pod 数和并发量提前调大否则高并发时段会出现大量连接等待超时表现成响应变慢排查起来很坑。写在最后回到开头的那个问题被召回的记忆真的有用吗我的答案是看你怎么做。盲目堆召回记忆只会让系统更慢、错误更多但如果能在写入环节严格过滤、在召回环节做多路融合、在注入环节控制预算并且在场景上明确只服务“需要历史背景”的对话那召回式记忆带来的提升是非常可观的。我们线上最明显的改变是跨天回访的用户不再需要重复描述需求任务续接率从四成直接跳到接近八成这已经足够说明问题了。如果让我给正在做类似项目的同学一条建议那就是别急着上向量库先把“什么值得记、什么时候需要记、记了之后怎么用”想清楚。记忆系统设计里真正难的从来不是存而是取更准确地说是每次只在恰当的时机取出恰当的那一点点。用 TencentDB 做存储只是一种顺手的选择关键还是你愿意在这条召回链路上花多少心思去打磨。