资讯动态

Agent与Milvus联手:为AI助手构建动态记忆,突破静态知识库天花板

发布时间:2026/10/20 4:42:47 来源:尧图企业网站定制
被静态知识库折腾过的人一定懂那种感觉文档喂了几百份问答效果还是“看似会答实则空洞”。问文档里有的东西答得还行一旦用户问题换了个说法或者需要结合上一次聊天的上下文它就原形毕露——只会从固定知识库里搜索拼接完全没有“记性”。我这次想聊的不是又一份eRAG增强检索生成教程而是把 Agent 和 Milvus 向量数据库搭在一起给AI助手真正装上“动态记忆”的过程。这套思路不只在做文档问答而是让助手能自己判断“哪些信息值得记住、哪些该忘掉、哪些需要跨会话复用”从一个只会读资料的问答机器人变成一个“越用越懂你”的长期陪伴型助手。如果你正在做智能客服、私域运营助手、个人AI知识管家这类项目这篇内容大概率对你有用。1. 静态知识库的天花板为什么“喂再多文档”也救不了助手先定义一下我口中的静态知识库把一堆文档切块、向量化导入向量数据库用户提问时就做一次相似度检索再把最相关的若干个片段丢给大模型生成答案。这是目前绝大多数RAG应用的默认形态也是大部分人理解中的“知识库问答”。它确实解决了“大模型不掌握私有知识”的问题但解决不了另外三个关键问题。1.1 知识时效性的死结文档切块入库之后这个知识库就“定格”了。今天导入了产品手册明天产品功能升级后天价格调整如果没有重新跑一遍拆分和嵌入的流程助手回答的永远是旧信息。有人会说不就是重新导入吗可现实项目里知识库动辄几万个片段每次全量重建嵌入的时间和成本都不小更别提很多团队连“哪个文件更新了”都难定位。我在之前某项目里就看到过这样的尴尬助手被问到新版本是否支持某项功能它拿着旧手册信誓旦旦说支持客户照做后翻车最后一堆人排查了半天才发现知识库三个月没更新。1.2 会话型需求才是真正的“记忆黑洞”静态知识库的另一个硬伤是它的检索上下文完全来自“用户当次提问”。它不关心用户昨天问过什么、上次反馈过什么偏好、之前是否已经否定了某个方案。举个很现实的例子一位用户昨天让助手帮忙筛选数据库选型方案明确说了“不考虑需要额外购买License的商业产品”。今天他再问“帮我推荐几个数据库”静态RAG会老老实实地把文档里所有主流数据库都列出来包括他昨天已经排除的商业产品。助手完全“失忆”用户感受到的就是这AI根本不懂我。1.3 静态RAG为什么解决不了上下文个性化有些团队会试图通过拼长上下文来缓解这个问题比如把历史对话全部塞进大模型上下文窗口。这种方法短期内有效但窗口再大也有上限对话一长要么截断、要么费用暴涨、要么模型被无关历史带偏。更关键的是历史消息里真正重要的往往是“用户偏好”“待办事项”“关键约束”这类结构化记忆而不是一堆原始对话流水账。这也是我后来转向 Agent 向量数据库这套架构的直接原因我们需要的不只是一个“能查资料的搜索引擎”而是一个能对记忆进行写入、更新、召回、遗忘的“记忆管理系统”。而向量数据库在其中扮演的角色远不止存几个embedding这么简单。动态记忆本质上是把“记忆”当成一种可以持续写入、检索、重组的数据资产。这句话听起来抽象拆开了就三条每个对话里抽取出值得长期保留的事实片段写入记忆库用户每次提问时除了查静态文档还要查“历史记忆”把相关的过往结论一起交给模型记忆有生命周期会过期、会被修正、会被覆盖而不是只增不减。这三条对应到技术上就是Agent负责“判断什么值得记”Milvus这类向量数据库负责“存得下、查得快”而大模型负责综合静态知识与动态记忆生成答案。下面我把这套架构拆开讲。2. 动态记忆的分层模型Milvus在Agent里的真实位置很多人一听“动态记忆”就去翻专门的记忆框架其实没必要。只要理解了分层的思路用通用技术栈完全能搭出来。我习惯把助手的记忆分成三层短期工作记忆、长期情节记忆、语义偏好记忆。2.1 把记忆分成短期、长期和语义记忆短期工作记忆对应的是当前会话内需要随时引用的信息比如用户刚才说“我预算大概20万以内”。它通常不落地到向量库放在Agent的会话状态里就好属于“用完即走”的临时变量。长期情节记忆对应的是跨会话的关键事件比如“用户上次确认了登录模块用统一身份认证方案”“用户已经否决过某款云服务”。这类信息时间跨度大不立即生效于当前所有问题但会在后续决策中反复用到。这类记忆最适合向量化存储。语义偏好记忆则更稳定比如“用户团队以Java为主”“用户偏好开源方案”“汇报要求带成本对比表”。它类似于“画像”会在几乎所有交互中影响Agent的风格和推荐倾向。在设计上我不建议把所有记忆塞进一个collection里。混在一起会让检索噪声变大、权限控制变难。更合理的做法是按类型拆collection或者至少用标量字段区分memory_type后面检索时强制过滤。这个细节直接决定了召回质量后面调优部分还会细讲。2.2 Milvus负责的部分向量化、索引与可回溯检索把记忆抽象成“记忆碎片”之后下一个问题是谁来管这些碎片。有人会图省事直接用临时文件或者Redis里的数组来存embedding几百条记录时确实能跑但一旦到了几万条、几十万条并且要支持多租户、标量过滤、实时写入就完全不是一回事了。这轮我选择Milvus原因有几个第一是它把向量检索和元数据管理放到了同一套体系里。记忆碎片不仅是一个embedding数组还伴随用户ID、场景标签、时间戳、记忆类型、来源会话ID等标量字段。Milvus可以在这套结构上直接做“标量过滤 向量相似度检索”不需要在业务代码里自己维护两套索引再合并。第二是它的索引类型完善尤其HNSW和IVF系列在中小规模数据上表现稳定兼顾召回率和查询延迟。对记忆场景来说通常在百万级以内HNSW基本是首选后面给参数建议。第三是支持写入后近实时可见。动态记忆要求“本轮对话结束后写入的内容下一轮提问最好能立刻被召回”Milvus的可调一致性级别正好覆盖这个需求不至于为了等数据可见而阻塞对话流程。2.3 写入路径与读取路径的分离设计真正让记忆系统变复杂的不是某一条记忆的存取而是写入与读取两条链路互不干扰。写入链路发生在每轮对话结束后。Agent拿到完整对话先做信息抽取哪些是新事实、哪些是对旧事实的修正、哪些只是寒暄不值得记。抽取出来的结果经过摘要压缩、实体归一化再生成embedding写入Milvus。这个过程不需要用户等待属于异步任务。读取链路发生在用户提问时。Agent把当前问题embedding化先到记忆库检索相关历史记忆再结合静态知识库结果组合成上下文交给大模型。读取链路对延迟敏感通常控制在200毫秒以内所以要把写入和读取分离开避免写入时的索引构建拖慢检索。我当时在架构图里画得很简单一条异步写入管道一条同步读取管道中间用Milvus连接。看起来简单但真正落地时两条链路各自都有不少细节要注意下面从写读两侧分别展开。3. 从零搭建带记忆的Agent摄入、写入与召回3.1 选型为什么用Milvus而不是旁路搞个HashMap这里先给我的选型逻辑。如果你只是做个Demo用个Python列表或者Redis都行但真实项目一定会遇到几个逃不掉的问题数据量增长后检索效率下降、需要按用户隔离数据、需要按时间或类型过滤、需要高频写入的同时还要保证查询稳定。HashMap或简单Redis方案在这些需求面前会迅速变成“手写数据库”项目光维护成本就够呛。Milvus在这条路上的优势是向量存储、索引构建、标量过滤、动态schema都原生支持而且开源版部署门槛不算高。对Agent场景来说我们不需要分布式集群单机模式加上合适的索引参数就足够支撑中小型应用。另外一个细节选型要点embedding模型和向量库的维度要提前定好。我自己踩过坑一开始用了某个模型的768维向量后来想换一个效果更好的1024维模型结果要么重建collection要么在兼容性上费一番功夫。建议初期就选定一个主用embedding模型维度、距离度量一般用余弦距离都固定下来后续只升级模型版本并做“新旧向量共存 离线迁移”。3.2 记忆写入链路对话摘要、实体抽取与向量入库写入链路是动态记忆系统的“输入闸门”做得不好后面检索再快也白搭。我会给每轮对话加一个异步的“记忆归纳”步骤核心动作有三步。第一步是抽取核心记忆项。最简单的方式是让大模型按固定JSON模板输出记忆候选比如[ { memory_type: preference, subject: 数据库选型, content: 用户明确排除需要额外购买License的商业数据库, importance: 0.9, expire_at: 2025-12-31 }, { memory_type: event, subject: 登录模块, content: 用户确认采用统一身份认证方案预计下月实施, importance: 0.8, expire_at: null } ]这个“半结构化抽取”比直接把整段对话切块入库聪明得多。它能去掉重复废话只留关键约束和决策而且后续做冲突检测时有subject字段可以精确关联。第二步是生成embedding并入库。这里我建议对content做embedding而不是对subject做。原因很简单用户下次提问时表达方式大概率不会和subject完全一致但语义上会和content更贴近。入库时记得把user_id、memory_type、created_at、source_session_id这些标量字段一起写进去后面按用户隔离和按时间过滤全靠它们。第三步是同步处理记忆冲突。比如用户这轮说“预算调整为30万以内”而库里已经有一条“预算20万以内”。直接append会让检索同时召回两条矛盾记忆模型就会迷糊。冲突检测的简单方案是按subject字段先搜索旧记忆存在则做更新或标记过期而不是无脑插入新记录。这个逻辑早期可以放进同一个大模型调用里完成不一定要单独搞复杂的规则引擎。3.3 记忆召回链路相关性检索与Agent工具调用召回链路的核心任务是在用户提问时从记忆库里找出“和当前问题相关且靠谱”的旧记忆并把它们组织成可用的上下文。我建议把这步设计成Agent的一个工具调用而不是直接把所有记忆都塞进prompt。例如在Agent的function列表里加上两个工具search_user_memory(query, memory_type, user_id, limit)search_document_knowledge(query, collection, limit)大模型会根据用户问题判断该调哪个工具、以及怎么组合。为什么要这样设计因为直接无差别召回往往会让模型被不相关的旧记忆干扰而 If the 系统性调用可以为问题归类避免额外噪声。比如用户问“MySQL兼容性如何”Agent应该优先检索文档知识而不必把“用户偏好开源”这类画像记忆也拉出来。召回策略还有个细节记忆检索的embedding可以做一些轻量改写。用户口语化提问“上次说那个数据库选型排除了什么来着”直接做向量检索可能效果不理想因为句式太绕。可以在召回前让大模型把问题转成“查询关键词”或“陈述性查询”比如转成“数据库选型中被排除的方案”再去检索命中率会明显提升。3.4 一段可复现的最小化代码骨架下面给一段用Python实现的最小写入/召回逻辑方便理解整体流程。这个示例做了简化不依赖具体Agent框架只演示Milvus和embedding模型的核心配合。from pymilvus import MilvusClient, DataType client MilvusClient(memory.db) schema MilvusClient.create_schema(auto_idTrue, enable_dynamic_fieldFalse) schema.add_field(pk, DataType.VAR_CHAR, is_primaryTrue, max_length64) schema.add_field(user_id, DataType.VAR_CHAR, max_length64) schema.add_field(memory_type, DataType.VAR_CHAR, max_length32) schema.add_field(subject, DataType.VAR_CHAR, max_length255) schema.add_field(content, DataType.VAR_CHAR, max_length2048) schema.add_field(importance, DataType.FLOAT) schema.add_field(created_at, DataType.INT64) schema.add_field(embedding, DataType.FLOAT_VECTOR, dim768) index_params client.prepare_index_params() index_params.add_index( field_nameembedding, index_typeHNSW, metric_typeCOSINE, params{M: 16, efConstruction: 100} ) client.create_collection(user_memory, schemaschema, index_paramsindex_params) def write_memory(user_id, memory_type, subject, content, importance, embedding): row { pk: f{user_id}_{subject}_{int(time.time()*1000)}, user_id: user_id, memory_type: memory_type, subject: subject, content: content, importance: importance, created_at: int(time.time()), embedding: embedding, } client.insert(user_memory, [row]) def recall_memory(query_embedding, user_id, memory_typeNone, top_k5): expr fuser_id {user_id} if memory_type: expr f and memory_type {memory_type} res client.search( collection_nameuser_memory, data[query_embedding], filterexpr, limittop_k, output_fields[content, subject, memory_type, importance, created_at], ) return res从这段骨架出发再接入Agent的function calling就很自然了。关键是要理解搜索时的过滤条件和索引设计必须在建表时就想好否则后面加字段、改类型都要重建collection线上项目会非常折腾。4. 调优不是玄学标量过滤、一致性、索引参数这三板斧跑通Demo只是开始。真正要上线上环境或者要在评测里拿高分你得把三个细节调明白。很多人觉得向量数据库调优是个黑盒其实不是核心就三件事。4.1 标量过滤千万级向量里“先筛后搜”的威力记忆库和通用文档库最大的不同是它天然带着很多强过滤条件user_id、memory_type、expire状态。如果不做过滤每一次检索都在整个库里跑不仅慢而且容易把别人的记忆或者已经过期的记忆捞出来。Milvus里的filter可以像SQL一样组合表达式比如user_id u_1001 and memory_type preference and expire_at now这里有一个性能技巧user_id这类高基数、高频过滤的字段建议单独建Inverted Index或使用支持filtering的索引类型否则过滤本身会成为瓶颈。我在实测中发现同一个collection给user_id加上标量索引之后过滤查询延迟能从300毫秒降到50毫秒左右提升非常明显。还有一点容易忽略先过滤再向量检索和先向量检索再过滤的结果在精排上有区别。Milvus支持在向量检索时直接带filter它会先做向量候选集的粗筛再根据scalar条件过滤。想要更稳可以把filter写成强条件配合较小范围的候选集参数确保返回的都是合法记录。4.2 索引参数HNSW的M值与efConstruction到底怎么设HNSW是目前中小数据量下最稳妥的索引类型。它的核心参数有两个M和efConstruction查询时还有ef参数。M是每个节点的最大连接数。M越大图越稠密召回率越高但内存占用和构建时间也越高。我的建议是16到32之间数据量在百万级以下选16足够。efConstruction是建图时的动态候选集大小。越大建图越慢但图质量更好。100到200是个甜点区间太低了召回率掉得厉害。查询时的ef是每次检索的候选集大小。它不参与建索引在每次查询时控制。线上建议从64开始根据延迟和召回率调优。不要一上来就追求100%召回率那是离线评测的事。在线场景下90%以上召回率配合低延迟往往体验更好。我自己通常把目标设定为p95 延迟在80毫秒以内召回率不低于95%在这个前提下尽量调小ef。4.3 一致性级别写入后多久能被查到动态记忆要求“刚记住的内容很快能用上”但Milvus这种分布式架构里写入和查询可能落到不同节点默认强一致性会带来额外延迟。我看到大多数项目都选Bounded一致性或Eventually即可在几十到几百毫秒内看到新增记录。实际建议是记忆写入本身不是高频操作平均每秒几十条已经算很多了所以不必为了吞吐牺牲一致性。我一般用Bounded把staleness控制在2秒以内既不会让用户体验到“刚说完就忘了”也不会让写入链路变慢。这里还有一个经验如果对话结束后的记忆写入和用户的下一轮提问几乎同时发生偶尔会出现“这轮没检索到上轮记忆”的情况。缓解办法是在前端对话状态里带上最新几轮的关键提取信息而不是完全依赖向量库即时查询。5. 真正麻烦的是“遗忘”和“冲突”记忆管理的实战避坑很多人觉得动态记忆最难的是写入和检索真做起来以后你会发现最难的是“如何让系统不越记越乱”。记忆不是越多越好甚至很多时候“懂得忘”比“记得牢”更关键。5.1 过期记忆不会自己消失需要衰减与归档用户偏好会变、项目状态会推进、临时约定会过期。如果所有记忆一视同仁永久存储召回时就会频繁出现“过时信息误导答案”的情况。我的做法是给每条记忆加三层控制显式过期时间expire_at。比如“用户下月进行系统切换”这类临时性记忆到点就自动标记失效活跃度衰减。每次被成功召回时给last_access_at更新长时间未被访问的高龄记忆逐渐降低检索权重定期归档任务。每天或每周扫描一次将过期且低价值的记忆从主collection中移除或迁移到冷存储。这样设计的目的很简单让高频使用的记忆始终保持在检索质量最好的区域不让冷门旧数据干扰新决策。5.2 同主题多轮冲突不能只靠“最新优先”用户的偏好不是一成不变的。今天说“优先考虑开源”明天可能看到某商业产品后说“这个也可以看看”。如果系统简单粗暴地“最新写入覆盖旧记忆”还是会有问题——因为有些旧约束只是暂时被搁置不是被推翻。我后来采用的办法是不物理删除旧记忆而是给记忆加“状态”字段active表示当前有效superseded表示被新记忆替代但仍然可追溯。写入新记忆时把同subject的旧记忆标记为superseded检索时默认只召回active记忆。同时Agent在最终回答之前会做一个简单的“记忆自检”步骤如果召回的多个记忆之间存在明显矛盾就把这些矛盾点一并呈现给大模型让它结合当前问题判断用哪一条必要时主动向用户求证。这个设计虽然多一次模型调用但对客服类场景特别值钱能避免自作主张的翻车。5.3 隐私与删改问题向量库里删一条记录有多贵如果做的是C端产品用户有一天要求“删除我的所有数据”你的动态记忆系统得能快速响应。向量库删数据本身不复杂按主键删除即可但有两个坑。一个坑是embedding本身不可逆。即便删了原始记录embedding向量仍然保留着语义痕迹严格来说无法彻底“遗忘”。纯合规视角下如果隐私要求极高可能需要连embedding一起删除并考虑索引碎片的清理。这个讨论有点复杂但在产品层面至少要做到底层数据可删、索引可重建。另一个坑是缓存和日志。如果记忆在被删除之前已经进入了某些对话的历史响应这些信息残留在服务日志、模型调用记录里单靠删库是清不干净的。我的建议是在系统设计之初就约定“记忆数据只作为运行时上下文不做长期日志落库”避免后续疲于应付删改请求。6. 实测效果与后续扩展从“能记住”到“记得准”6.1 一组针对会话型场景的实测数据我在某个模拟会话式导购场景里跑了约两周的动态记忆版Agent对比之前的静态RAG方案结果很有参考意义。最初用静态RAG方案时用户多轮对话成功率大概在六成左右经常出现“前面刚确认过预算后面推荐超预算方案”的情况。换成AgentMilvus动态记忆后这类同主题一致性错误大幅下降连续对话场景下助手对用户偏好的命中率接近九成。数据只是参考但它验证了一件事动态记忆解决的其实不是大模型“变聪明”的问题而是“信息在不同轮次之间保真传递”的问题。模型还是同一个模型但因为每一次决策都能基于历史约束整体表现自然就上去了。6.2 再往前走一步分层记忆TTL、知识图谱融合与多用户隔离这套架构跑顺之后我考虑过几个扩展方向也建议你按这个顺序去升级。第一是更细致的TTL策略。现在很多记忆是统一过期策略其实可以分类型用户画像类记忆有效期最长项目事件类记忆跟随项目周期临时意图类记忆只存活几分钟。目前Milvus在标量字段上做过期标记并不难真正需要投入的是确定各类记忆的合理生命周期。第二是与知识图谱融合。向量记忆擅长相似语义召回但在“实体之间的关系链”上比较弱。比如“用户A关联的项目B使用了方案C而方案C与用户偏好冲突”这需要图结构才能推出来。我目前的做法是把关系型关键信息额外维护成三元组只在需要多跳推理时启用日常单跳问题还是走向量库。第三是多用户隔离。前面代码里已经体现了按user_id过滤但真做多租户还得考虑collection隔离还是数据隔离。小规模场景一个collection加过滤字段就够用户量上来后更推荐按租户拆collection避免查询性能互相干扰。6.3 最后分享几点让我少走弯路的心得走到这里这套“AgentMilvus”的动态记忆方案基本成型了。最后分享三条从实际部署中总结出的经验。一不要一上来就追求复杂。先用一个最小闭环对话结束异步抽取记忆、写入向量库、下次提问带过滤召回。跑通再逐步加冲突检测、过期归档、记忆自检。很多人一开始就设计五六个模块结果调了半个月还在原地。二评估指标一定要包含“记忆是否被正确复用”。不要只看最终回答是否合理要单独统计“系统召回的记忆里有多少是真正相关的”。我把这个概念叫做“记忆采纳率”它才是动态记忆系统本身的质量指标。三embedding模型值得单独投入时间。向量库和索引参数能调的空间有限真正决定召回质量上限的是embedding模型对中文长句、口语化表达的支持程度。预算允许的话用领域微调过的embedding模型效果提升非常明显。动态记忆不是一个神奇开关打开就万事大吉。它更像一个长期维护的数据工程问题要想让助手“懂你”就得让它有写入、有检索、有遗忘而这些每一环都需要认真设计。希望这篇内容能帮你少踩一些我踩过的坑把AI助手从“查得着资料”真正推到“记得住用户”。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑