
上个月和一个做企业级 AI 助手的朋友聊架构他一句话点醒我“Demo 里的 Agent 什么都能干一上生产就变成了个没记性的傻子。”这话一点不夸张。早期我也干过把对话历史塞进一个 List、再整段拼进 Prompt 的操作内部演示跑得飞起。可真的接上真实用户之后问题一个接一个冒出来上下文长度被撑爆、多轮意图老是对不上、用户上个礼拜改过的偏好完全记不住。这时候才意识到Agent 的“记忆”不是往 Prompt 里多塞几轮历史记录它需要一个独立设计、可水平扩展、能治生命周期管理的服务来承载。这篇文章就是围绕“企业级 Agent Memory Service”这件事写的。我不会只推某个具体的开源项目而是把我在几个项目里做记忆模块时的完整思考链拆开记忆到底要存哪些东西、存储引擎怎么选、写入和遗忘的流程怎么设计、分片和缓存怎么做、以及真正上了生产之后踩过的几个记忆相关的事故。适合正在做 Agent 开发的工程师、正在设计记忆模块的技术负责人以及准备给 Agent 产品搭后端服务的架构师。1. 先想清楚Agent 的记忆到底需要存什么很多人第一步就做反了先兴致勃勃去选向量数据库再回来看数据模型。我建议反过来先把“记忆”这个词拆开因为 Agent 的记忆不是一块铁板不同种类的记忆生命周期、访问频率、存储要求完全不同。1.1 按用途把记忆拆成四层我在设计时习惯把 Agent 的记忆分成四类工作记忆、情景记忆、语义记忆、程序记忆。这不是学术定义是为了方便做存储设计和容量规划。工作记忆Working Memory当前会话内的临时状态比如用户上一句话说的“待办事项草稿”、正在填写的表单数据。它高频读写、生命周期极短会话结束或者一段时间不活跃就应该清理。它更适合放在进程内内存或者 Redis 这类高速缓存里连持久化都是次要的。情景记忆Episodic Memory用户和 Agent 之间发生过的事实性交互记录比如“上周二让助手生成了季度报表”。它带有时间维度回答“上次那个任务进行到哪了”这类问题时要用。这类数据量大按时间和用户维度增长适合文档型存储加向量索引。语义记忆Semantic Memory从交互中抽象出来的稳定事实和偏好比如“用户偏好简洁回复”“用户公司主营跨境电商”。它的特点是低频更新、高价值、查询频繁。这正是向量检索的主要战场通常需要抽取出结构化条目。程序记忆Procedural MemoryAgent 学习和沉淀下来的任务执行模式比如处理某类工单时的工具调用习惯。这类记忆在实践中往往直接沉淀为配置规则、工具定义或代码模板很少需要数据库更多是版本治理。这四类记忆可以整理成一张访问模式表方便后续对接存储层记忆类型生命周期访问模式一致性要求典型载体工作记忆分钟级高频读写弱一致可丢失内存 / Redis情景记忆长期追加写 按时间/条件查中文档库 向量索引语义记忆中长期低频更新 高频查询强结构库 向量索引程序记忆长期读多写少强配置中心 / 代码仓库1.2 Memory Service 的边界是什么拆完记忆类型下一个问题就是什么是“Memory Service”它绝对不只是封了一层数据库读写。我理解的 Memory Service 要承担的是记忆的完整生命周期写入、索引、查询、更新、遗忘、订正、衰减、导出。还可以打个比方如果 Agent 是人Memory Service 就是他的海马体加长期记忆系统你总不能让大脑直接拿硬盘来存东西。我见过不少项目把记忆做成服务里的一个 Repository 类这在小场景下没问题但到了企业级记忆往往是多个 Agent 共用的。A 助手写入的记忆B 助手要能读到用户通过 Web 端修改偏好Agent 侧要能感知。如果有记忆一条都散落在各个 Agent 进程里这些协作场景全都玩不转。记忆服务在组织形态上应该是一个独立的中间件有明确的 API、有数据模型、有可观测性、有权限控制。到这一步选型才有了讨论的前提。2. 存储引擎选型向量库、关系库、消息队列各管哪一层先说结论没有哪个数据库能同时承接全部记忆类型真实的 Memory Service 一定是多种存储组件组合出来的。选型的核心是搞清楚“哪个组件承担哪一层记忆”而不是找一个万能数据库。2.1 向量数据库的横向对比语义记忆的检索绕不开向量化所以向量数据库往往是选型的重头。我用过的几个主流方案一句话感受如下Milvus/Zilliz Cloud定位是大规模生产级。支持 Collection 分片、分区、混合检索向量 标量过滤生态组件完整。缺点是运维偏重它依赖 etcd、对象存储、消息组件小团队自建需要一定的运维储备。QdrantRust 写的单机性能很好Payload 过滤能力强API 设计得非常直观Docker 一条命令可以跑起来。我在中等规模项目里用得很顺手它自带 WAL数据可靠性比一般纯检索库要好。Chroma轻量、本地开发友好向量化、持久化开箱即用适合原型验证。但它是为实验场景设计的高并发生产环境里我遇到过一些一致性和性能波动不太适合直接扛企业级流量。pgvectorPostgreSQL 的向量扩展适合中小团队。最大优势是复用已有 PG 运维栈向量数据和业务元数据在同一套事务里过滤条件能走 SQL强一致性好。缺点是向量索引的构建和海量扩展能力弱于专业向量库上亿向量之后会比较吃力。方案部署复杂度生产级扩展过滤能力一致性推荐场景Milvus高强中最终一致大型独立服务Qdrant中强强WAL 保障中型生产 / 独立服务Chroma低弱中一般原型验证pgvector低中极强SQL强已有 PG 栈的小团队选型时我有一个基本判断标准如果你的记忆条目本身有大量的元数据过滤需求按用户、按时间、按业务线过滤Qdrant 的 payload 过滤和 pgvector 的 SQL 过滤都比 Milvus 早期版本的标量过滤好写很多。反过来如果你要处理的是几十亿向量、要求极致的水平扩展那 Milvus 这种分布式架构是主流选择。至于 Chroma我现在只把它当作本地调试工具用偶尔用它快速验证 embedding 效果。2.2 Redis 适合当热记忆层但不适合当记忆本体很多团队图省事用 Redis 存所有记忆每个 Key 就是一段 JSON。它做短期记忆确实很顺手TTL 自清理、读写快、数据结构灵活。但如果你把 Redis 当作唯一事实来源数据丢失风险太高了。Redis 毕竟是个缓存性质的系统持久化机制在生产抖动时不能满足企业级记忆的可靠性要求。我的做法是把 Redis 定位为“热记忆加速层”保存最近 N 小时的记忆条目服务查询时先走缓存再回落数据库。长期记忆的权威数据仍然放在向量库或关系库里。这也刚好应对第 3 章要讲的冷热分层。2.3 消息队列在这里不是“选不选”的问题而是选哪个的问题一旦记忆写入开始依赖 LLM 抽取、向量化这些耗时操作同步写入就会把用户请求拖得很慢。所以记忆服务里消息队列不是可选项而是绕不开的异步底座。我实际用过 Kafka、RabbitMQ、RocketMQ它们在这个场景下适合做的事完全不同。Kafka高吞吐、消息可回放、分区有序。记忆写入链路最适合它因为写入量大且需要消费者故障后重放。我把记忆事件流直接落在 Kafka消费者挂了可以从上一次 offset 继续不会漏掉记忆。RabbitMQ路由灵活、延迟队列好用适合任务分发和需要复杂路由的轻量场景。在记忆服务里它更适合做后台任务的调度比如“延迟 10 分钟再对某条记忆做后验证”。RocketMQ事务消息和延时消息是强项适合“本地事务和消息发送强一致”的场景。如果你的记忆服务本身要操作数据库事务又希望通过事务消息保证写入一致性RocketMQ 是很好的选择。特性KafkaRabbitMQRocketMQ吞吐量极高中高高消息重放原生支持较弱支持事务消息较弱不支持强延迟消息需自研原生支持原生支持适合记忆场景记忆事件流、异步写入任务分发、延迟处理强一致事务写入如果你刚开始搭建记忆链路我建议优先上 Kafka 做异步写入和重放兜底。后面每一步消费逻辑有问题都能靠重放来修复这个兜底能力在生产环境太值钱了。3. 记忆的写、查、忘、改核心流程才是服务的灵魂选好存储只是第一步真正决定 Memory Service 好不好用的是记忆条目怎么定义、写入链路怎么设计、检索和遗忘怎么治理。3.1 记忆条目先定义好 Schema再写代码很多团队把记忆当成自由文本嵌入向量库就撒手不管。结果检索出来一堆语义相似但没用的片段权重、时效、归属都分不清。我建议每条记忆都按结构化条目建模包含元数据、内容、权重、时间线。下面是我在一个项目里实际用过的记忆条目 JSON 结构字段可以按业务裁剪{ memory_id: mem_8f3k..., tenant_id: t_1001, user_id: u_20547, memory_type: semantic, content: { subject: user.preference, attribute: reply_style, value: concise, source_text: 以后回复我尽量简短一些 }, embedding_version: bge-m3-v1.4, importance_score: 0.87, confidence_score: 0.92, created_at: 2026-01-15T10:24:00Z, updated_at: 2026-01-15T10:24:00Z, expire_at: null, source_event_id: evt_a1b2..., tags: [user.preference, style] }这里有几个字段我特别想强调memory_type决定后续用哪条检索链路importance_score给遗忘机制做排序依据source_event_id是幂等消费的关键后面专门讲embedding_version是踩坑事故的根源第 5 章会展开。3.2 写入链路把 LLM 抽离出核心路径记忆写入不是一个同步动作。对话结束时Agent 把原始对话丢给记忆抽取服务由 LLM 提炼成结构化记忆条目再走异步写入。这套流程的完整链路大概是对话回合结束Agent 发送原始交互数据到 Memory Service 的写入 API。记忆抽取服务调用 LLM将原始文本转为候选记忆条目JSON并给重要性和置信度打分。候选条目经过校验Schema 检查、敏感词过滤、去重判断后发布到 Kafka 的memory.commands主题。记忆写入消费者从 Kafka 拉取消息生成向量对向量库执行 upsert同时更新元数据表。写入完成后向热缓存层同步最新记忆并返回确认事件。幂等在这里是底线。Kafka 消息可能被重复消费网络重试也可能带来重复请求。我坚持用source_event_id做唯一键写入一律走 upsert 而不是 insert。如果同样的source_event_id已经存在直接跳过。不然用户会看到同一条记忆出现两次检索时也会反复查到相同内容。3.3 检索策略纯向量召回是不够的检索质量决定 Agent 的“记性”好不好。纯向量检索的问题在于它只懂语义相似不懂时间、重要性和事实约束。同一个用户问你“我上次改的地址是什么”时向量上可能召回像但不对的历史片段。我实际使用的检索策略是“向量召回 元数据过滤 重排截断”的组合拳。具体流程可以用这个伪代码描述recalled vector_store.query( embeddingembed_user_query(query), filter{ tenant_id: tenant_id, user_id: user_id, memory_type: [semantic, episodic], expire_at: {$gt: now} }, top_k50 ) recalled rerank_by_time_and_importance(recalled, now) recalled deduplicate_by_subject(recalled) final truncate_to_budget(recalled, max_tokens1200)关键点有三个。第一元数据过滤必须先做把范围钉在对应的用户和租户上不然跨用户串记忆就是安全事故。第二向量只解决“像不像”重排阶段要按时间衰减和 importance_score 综合排序否则几个月前的泛泛记录会把最新偏好挤掉。第三召回结果不能全部塞给 Agent要给 token 预算并截断长篇大论的记忆会污染 Agent 的上下文。口径上我一般把对单次对话注入的记忆量控制在总上下文窗口的 20%-30% 以内。3.4 遗忘机制和写入一样重要记忆不是越多越好。塞满历史的 Agent 会变得又慢又糊涂遗忘是记忆服务里最容易偷懒但又最不该偷懒的部分。我把遗忘任务拆成三路基于 TTL 的自动过期、基于重要性和容量的策略淘汰、用户触发的强制删除。TTL 最简单适合临时性记忆直接靠数据库过期时间清理。策略淘汰需要扫描低 importance 分数的记忆定期归档或删除。用户强制删除是为了满足数据合规删掉用户明确要求遗忘的数据时要同步删缓存、删向量记录不能只删数据库里的主记录。每次遗忘操作都要落审计日志谁能删、删了什么、什么时候删的都该查得到。3.5 更新与冲突同一事实以谁为准真实环境里记忆之间经常打架。用户这个月说“我喜欢详细报告”上个月说“简短一点就行”系统里两条语义记忆都是可信的但语义上已经冲突了。直接覆盖不可取一起保留又会让 Agent 混乱。我的策略是为每条语义记忆维护updated_at和confidence_score两个排序字段。检索时优先取置信度更高的版本置信度差不多则取时间更新的一条。如果检测到新写入的记忆与旧记忆在 subject 上重复后台任务会将旧条目标记为 superseded而不是物理删除。这既保留了时间线又避免在检索时被旧记忆干扰。4. 可扩展性设计从单体一路走到分片与缓存可扩展性是企业级和 Demo 之间最本质的差别。构建 Memory Service 时我习惯先把容量模型摆出来再往里填技术方案。4.1 容量估算先做扩展策略后定假设一条语义记忆的文本约 200 字节对应的 768 维 float32 向量约 3KB再加上元数据索引整体可以按 4KB 每条估算。如果每天新增 100 万条记忆向量存储相当于每天增长 4GB一个月就是 120GB一年 1.4TB。这还没算上情景记忆的原文存储和日志开销。有了这个数字你就能判断单机支撑几个月没问题但按年规划分片和冷热分层不是可选项而是必选项。4.2 分片策略按租户 Hash还是按时间分片维度直接决定后续运维体验。按租户 Hash 分片的好处是数据分布均匀、单租户访问可以路由到固定分片坏处是大租户会产生热点。按时间分片适合情景记忆这种时序追加模型但跨时间段检索要聚合多个分片复杂度会上来。我实际采用的分片方式是复合策略主分片按tenant_id哈希每个分片内部再按时间做冷热分层。热点租户识别出来后直接把它的记忆迁移到独立分片甚至独立实例。没有一种分片策略是一劳永逸的关键是你得在写入 API 里预留重分片的能力否则数据倾斜出现时只能干瞪眼。4.3 缓存设计Read-Through 与 Write-Behind 搭配读多写少是记忆访问的常态。我给记忆服务设计了 Read-Through 缓存查询先走 Redis命中就直接返回不命中则回源向量库和元数据表再把结果写回缓存。短期记忆则用 Write-Behind 模式先更新缓存、再异步写回持久层降低写入延迟。这里要特别注意Write-Behind 意味着数据可能短暂不一致必须有补偿任务周期性地检查缓存和持久层的差异不然缓存一抖动就会丢记忆。4.4 多租户隔离三种模式按客户价值选企业级场景一定绕不开多租户。三种常见模式分别是“独立实例”、“独立 Collection/Schema”、“共享实例 行级隔离”。三者的隔离强度、成本和运维复杂度差异很大没有绝对的好坏。隔离模式隔离强度成本典型场景独立实例最强最高金融、政务级大客户独立 Collection/Schema中强中中型客户私有定制需求共享 行级 tenant_id逻辑隔离最低SaaS 标准产品我自己的经验是大部分场景用第三种起步但前提是所有读写 API 都必须强制带上tenant_id过滤索引也必须覆盖这个字段。否则一旦漏掉某个查询条件用户数据互相串这在企业级场景中是致命事故。5. 踩坑实录几个真实事故的完整排查链路选型指南写得再漂亮都不如把生产环境里的真实事故拿出来讲得透彻。这几个坑我都亲手踩过回报一下排查思路希望你能直接绕开。5.1 事故一升级 embedding 模型旧向量全部“失忆”现象某天检索召回率暴跌明明库里有高度相似的内容Agent 却查不到。日志里没有报错只是命中内容质量突然下降。排查过程先看检索日志确认查询向量没有异常再随机抽取库里的向量和查询向量做相似度对比发现距离全面变大。这时才意识到几天前我们刚升级了 embedding 模型旧数据向量还是老的维度分布新查询向量来自新模型两边根本不在同一个向量空间里。修复方案给所有记忆条目加embedding_version字段写入时按新版本生成向量同时启动离线重向量任务分批把旧向量用新模型重新生成后覆盖。这个事故给我的教训是embedding 模型升级必须当成数据库迁移一样管理要有版本记录、有回滚方案、有重放任务绝不能只改一行调用就上线。5.2 事故二记忆膨胀Prompt 被塞爆现象用户的单轮响应越来越慢Token 费用明显上涨。查了一下 Prompt发现系统把 70 多条记忆全部注入了上下文很多是几个月前的琐碎记录。排查过程追踪记忆注入逻辑发现检索阶段没有设置 top_k 上限也没有对召回结果做重要性截断。向量库把“相似的都返回了”Agent 上下文窗口又有限于是不得不强行塞入大量低质量记忆。修复方案在检索链路中增加严格的预算控制召回 50 条后按时间和重要性重排再按 token 预算截断到 1200 字以内。同时给每条记忆设置importance_score低于阈值的记忆只在特定任务时才检索。修复后响应延迟下降明显费用也回归正常。5.3 事故三Kafka 重复消费同一记忆出现两遍现象用户发现系统重复记住同一件事对话里出现“你刚才说过了”的尴尬情况。数据库里也确实查到了两条内容完全相同的记忆。排查过程先怀疑业务逻辑没有去重再看消费者日志发现确实有重复消费的情况Kafka 消费端因处理超时触发了 rebalance导致同一条消息被并发消费了两次。修复方案增加以source_event_id为唯一键的 upsert 写入逻辑重复消息直接跳过同时给消费者配置幂等表记录最近处理过的 event_id。加了这一层之后无论 Kafka 怎么重放记忆都不会重复写入。5.4 事故四热点租户打爆单个分片现象整体服务没崩但某个分片的 CPU 持续打满该租户下的记忆读写明显变慢甚至影响相邻租户的检索。排查过程看分片监控发现数据量分布严重倾斜一个大客户的写入量是其他租户的几十倍。所有记忆都按租户哈希分发流量全部都打到了同一个分片上。修复方案把热点租户迁移到独立分片同时给它配置单独的写入限流和缓存资源中间件层增加租户流控避免单个大客户把共享资源耗尽。后续我把这个逻辑做成了自动识别任务实时监测分片负载超过阈值就触发迁移。这些事故都发生在很常规的工程细节上没有一个是“高级算法”问题。正因如此它们才更值得重视——企业级可扩展性不是靠某一个惊艳设计方案实现的而是靠把每个基础环节的可靠性做扎实。6. 选型决策表按团队规模直接抄作业最后给一套可以直接套用的选型决策。这里按团队规模和业务体量分了三档你可以根据自己的实际情况选。6.1 中小团队 / 日活几千到几万推荐组合PostgreSQLpgvector Redis 定时任务队列。这个组合的好处是技术栈收敛PG 承担业务元数据和向量数据Redis 做热记忆缓存定时任务负责记忆压缩和遗忘清理。不需要专门引入 Kafka用 PG 的LISTEN/NOTIFY或简单的任务表即可。别一开始就上分布式组件运维复杂度会吃掉你的开发效率。6.2 中等规模 / 日活几十万到几百万推荐组合Qdrant Kafka Redis 独立 Memory Service。Qdrant 用 Docker 或托管服务部署维护成本远低于 MilvusKafka 负责记忆事件流和异步写入Redis 承担热记忆缓存记忆服务独立成组件对外只暴露 API内部实现写入、抽取、检索和遗忘逻辑。这个组合在扩展性和运维复杂度之间最平衡。6.3 大型多租户 SaaS / 千万级数据量推荐组合Milvus或云上的托管向量库 Kafka Redis 对象存储 Flink。Milvus 解决向量数据的大规模分布式存储对象存储承担原始对话和记忆归档Flink或类似流处理框架负责记忆的聚合、去重、压缩和跨分片统计。这个方案成本高、运维重但它是能支撑真正企业级 SLA 的形态。6.4 我最终落地的一套参考架构我最近一个中型项目是这样的组合Qdrant 存语义记忆和情景记忆的向量PostgreSQL 存记忆元数据和审计日志Redis 存工作记忆和热检索层Kafka 串联记忆抽取、写入、重放的全链路。原因是这套组合能让我用最小的运维资源覆盖最大的功能需求。选型不是给自己找最美的组件而是找最适合你团队当前运维能力的组件。我个人在多次项目里最大的体会是构建 Memory Service 的顺序应该先是定义记忆模型和访问模式再选存储引擎最后才谈扩展方案。很多人一上来就引入 K8s、Milvus、Flink 全家桶结果数据量还没起来先被组件复杂度拖垮了。先把记忆条目、写入链路、遗忘机制跑通再带着监控数据做分布式演进这条路会顺畅得多。最后一个小建议无论选哪套方案都要给记忆服务做一个“可解释”的后台能随时把某个用户的记忆列表 dump 出来人工检查。这个能力在排查事故和召回率问题的时候比任何监控面板都管用。