先讲一个我经历过的翻车现场。去年我负责一个7×24小时连续服务的客服智能体,上线第一周看着一切正常,直到有个用户问:“我昨天说过的那台设备报修,你们到底派单了没有?”智能体沉默了。不是权限问题,不是模型能力不够,是它压根不记得昨天有这件事。那一刻我才反应过来,我之前精心搭的RAG方案,解决的是“不知道”,却完全没解决“记不住”。
后来我把那套系统从“知识外挂”一路改造成“带记忆的智能体”,中间踩了无数坑。这篇文章就是我梳理出来的演进路线——从RAG到智能体记忆到底发生了什么,每一步解决了什么、遗留下了什么问题,以及我沉淀下来的一套可复制的记忆模块设计。如果你正在做Agent应用,被“多轮对话怎么搞”“记忆到底该存什么”“存了怎么让它想起来”这些问题折磨,这篇文章应该能帮上忙。
1. 先捅破窗户纸:RAG不是记忆,是外挂知识库
1.1 RAG解决的是信息缺位,不是记忆缺失
RAG的基本套路大家都很熟:用户提问,先从外部知识库里检索最相关的文档片段,把片段拼进上下文,再让模型生成回答。本质上,它是给模型配了一个随时可以查阅的“书架”,书架上的内容可以持续更新,模型不需要背下所有细节,只需要知道去哪一页找。
这个设计非常优雅,它一次性解决了三个问题:模型知识截止日期的问题、私有知识进不了模型参数的问题、以及模型凭空编造的问题。所以过去两年,几乎所有知识问答类应用都是这个架构,我也这么干过,而且效果确实立竿见影。某客服场景的答案准确率从60%出头提到了85%以上,胡编乱造的幻觉明显减少。
但注意,RAG从始至终都是无状态的。每一次请求,对智能体来说都是第一次见面——它不知道刚才聊了什么,不知道三个月前你提过什么需求,更不知道你对回答方式有没有偏好。有人会说,没问题啊,我把历史消息一起传给模型不就行了?行,也不是不行,但那是另一套机制,后面会细说。这里先把结论放在这儿:RAG负责的是“信息缺位”,像给一个失忆的人配了书架,每次需要知识时去翻书;但它没承担“记忆缺失”这件事——记不住你是谁、记不住你们之间发生过什么、记不住哪些知识你已经知道。
1.2 三个让RAG当场失灵的典型场景
我总结了三个“RAG再强也白搭”的场景,都是实际遇到过的。
第一个是多轮连续性。用户开口说“我们公司最近在升级权限体系”,中间隔了几个问题,然后问“刚才说的权限体系你给个方案”。RAG只会把他最后这句话拿去检索“权限体系方案”,根本不知道“刚才说的”具体指什么、背景是什么。哪怕你把整段历史都塞给它,它也只是把聊天记录当成普通文本,没有“重点事件”的概念,不会主动把关键上下文捞出来。
第二个是用户画像与偏好。客服场景里,一个VIP客户第三次进线,说“还是老规矩,简洁点,别给我发一堆附件”。RAG检索库里有这个人的消费记录、工单记录,但检索返回的是“文档片段”,不是“这个人的核心特征”。除非你专门做了画像抽取,否则模型依然看不懂“老规矩”是什么。
第三个是状态变化。技术支持场景,用户上周反馈了一个bug,这周问“那个问题怎么样了”。问题的状态是动态的,是事件流,甚至跨会话。RAG检索静态文档可以,检索“这个变量当前的值是多少”就非常窘迫,你甚至不知道该往哪个文档里写“这个bug当前已修复”——因为它是持续演进的临时状态,不是固化知识。
这三个场景指向同一个结论:记忆不是单纯的“信息检索”,而是对个体、时间、状态和关系的持续追踪。RAG把知识当作一次性快照处理,智能体记忆却要把用户当作一个不断变化的对象去经营。
1.3 RAG隐含的假设,恰恰是记忆最不认同的
顺着这个逻辑往深挖,你会发现RAG背后有一条很隐蔽的假设链:信息是可以显式文本化的;检索见到相似就算成功;信息是静态的,版本更新是低频事件;用户和知识之间没有关系。
这些假设在小范围知识问答里成立,在智能体记忆里全部不成立。人类记忆是隐式的,很多偏好根本没有直说;是关联的,A事件和B事件互相解释;是动态的,昨天喜欢的东西今天可能就不喜欢了。
所以我后来跟团队讲了一句大实话:别再继续把RAG当记忆用,它是外挂知识库,是记忆的外部补给站。两者是上下游关系,不是替代关系。想通这一点,后面所有设计都顺了。
2. 回望来路:我们是怎么一步步逼近“记忆”的
2.1 最原始的方案:把历史全文塞进上下文窗口
在没有专门记忆机制之前,几乎所有Agent项目都用同一个土办法:把最近几轮的对话历史全部拼在用户输入前面。这也是很多框架的默认行为。它的优点是零成本、零延迟、语义无损,适合会话比较短的场景。但撑到30轮以上就麻烦了。
我做过一次压力测试,一个调试用的Agent连续对话40轮,每轮模型输出平均800字,光历史就超过三万字,token费用拉满,响应时间从2秒涨到8秒以上。更关键的是注意力稀释:模型确实能看见所有历史,但三万字里重要信息和闲聊混在一起,它在生成的时候已经分不清哪句是重点。这就像让你一口气听完一整天的会议录音再回答一个问题,答得出来才怪。
2.2 第一次妥协:滑动窗口加摘要压缩
上下文窗口有限,那就控制放进去的内容,这就是滑动窗口。保留最近的N轮,老的部分直接扔掉。效果立竿见影,token开销稳定,响应速度回来,代价是老信息彻底消失,用户上午说过的事下午就不认了。
后来大家又发现,与其直接扔掉,不如用模型把老对话“压”一下——每过一段时间,把前面的对话总结成一段摘要,存成一个“过去的事”。再调用的时候,把摘要和历史末尾一起拼进上下文。这个东西其实就是记忆的雏形:有选择、有压缩、有沉淀。
但摘要的问题很隐蔽:信息损失不可控。“用户偏好简洁回答”这种关键信息,很可能被淹没在一大段“该用户主要咨询了购买流程、退换货政策、物流时效……”的流水账里。摘要是对整段的平均,不是对重点的加权,它天然抓不住细节但长期有用的内容。
我当时还写过一个挺复杂的摘要记忆脚本,运行一阵子之后发现,它记住的“用户画像”基本是错的,因为它是从被截断的历史里总结的,真正重要的用户偏好往往被更长的闲聊盖过去了。于是我开始怀疑:摘要不是不行,而是需要一个更结构化的东西来承载记忆。
2.3 分水岭:从“上下文工程”进入“记忆工程”
转折点是我想通了一件事:上下文窗口的本质是一个“工作台”。人脑不会把所有记忆都摆在桌面上,只会把当前任务需要的资料拿上来用。那为什么Agent非要在一个有限的窗口里同时处理“当前思考”和“全部历史”?完全可以建一个外部仓库,让Agent在需要时按需取用、用完放回。
这个思路在业内有非常成熟的理论对应,就是常说的“把操作系统内存分页的思路搬给AI”:上下文是RAM,外部存储是磁盘,记忆管理系统负责分页、换入换出、淘汰、缓存。它的真正价值不是多存点东西,而是把“放什么进上下文”这个决策从人手里拿走,变成系统运转的一部分。
从这一刻起,问题从“我怎么把更多历史塞给模型”变成了“我该怎么组织、筛选、更新Agent的经验”。前者是上下文工程,后者才是记忆工程。这个转变,我愿称之为智能体从“工具”走向“协作者”的分水岭。
3. 智能体记忆到底在记什么:四类记忆拆解
我到现在还会跟人强调一句话:先别急着上向量库,先想清楚你的Agent需要哪几种记忆。很多项目一上来就喊“上记忆”,结果把全部对话一股脑塞进向量库,效果自然很差。人的记忆是有分类的,Agent也应该有。
3.1 工作记忆:正在思考的事
工作记忆对应上下文窗口和推理过程,它不需要长期存储,用完就可以丢。工程上对应两个东西:一个是对话上下文,最近几轮的输入输出;另一个是Agent的scratchpad,就是多步推理过程中的中间结果、待办事项、临时假设。
设计时要注意给工作记忆设预算。我一般会把上下文窗口的一半以上留给当前任务,因为当前任务没想清楚,记忆再多也没用。这跟人一样,你手头正算着一道复杂的题,旁边有个人不停跟你说昨天的事,你肯定会烦。
3.2 情景记忆:发生过的事
情景记忆是“事件流”,谁在什么时候做了什么事。对话历史只是情景记忆的一小部分,工具调用结果、后台任务状态、用户行为日志,都是情景记忆。它的特点是强时序、高噪音、必须追加写而非覆盖写。
存储上我倾向于用事件流水表,每条记录带事件ID、时间戳、参与者、事件类型、原始内容,永远不UPDATE。为什么?因为事件是历史事实,修改历史等于篡改记忆。想纠正一个错误认知,应该在语义记忆层处理,而不是倒回去改事件。
比如用户昨天投诉发货慢,今天气消了说“其实也能理解”,你不能把昨天那条“用户非常生气”改成“用户表示理解”,那样就丢失了真正的情绪变化轨迹。正确做法是保留昨天的负面情绪事件,再增加一条今天的缓和事件。历史事件越忠实,后面的语义抽取越准确。
3.3 语义记忆:沉淀下来的事实和偏好
从大量情景记忆里,可以抽象出这个用户的固定特征:“用户是采购负责人,决策权很高”“用户偏好Markdown格式回复”“这个项目最怕变更需求”。这些都是语义记忆,不是某次事件,而是“发生了什么之后得到的结论”。
语义记忆适合结构化存储,比如三元组或带属性的标签,配合实体关联可以做“用户-项目-偏好”这类图结构查询。语义记忆最大的坑是冲突:用户上周说“我对这个功能没兴趣”,这周突然要立项。这时候不能直接覆盖旧记忆,应该把旧记忆标成“被新记忆取代”,保留一条变更历史。
人也是这么干的。你不会因为朋友改变了立场就否认他以前的立场,你只是更新了“当前状态”。记忆系统里保留这条变更链,Agent在推理“用户为什么前后不一致”时才能做出更合理的判断,而不是傻乎乎地用新记忆否定一切。
3.4 程序记忆:怎么把事情做成
程序记忆是“如何做”的知识,比如工具的使用流程、任务的固定套路、回答客服问题的标准动作。起初这些是写在提示词里的,再往后可以沉淀为技能库、工具集甚至微调样本。
我一般会把高频任务流程抽出来作为程序记忆模板,在执行对应任务时固定注入;低频的就留在提示词里。它的价值在于减少重复推理,让Agent越用越“熟”。就像一个老客服不需要每次重新学怎么处理退款,脑子里已经有一整套标准动作了。
3.5 记忆的完整生命周期:写入、固化、回忆、更新、遗忘
单有分类还不够,记忆还得跑起来,它有五个环节:写入、固化、回忆、更新、遗忘。
写入是把原始经验记录成可检索的记忆项;固化是把零散事件抽象成稳定的语义记忆;回忆是按需把相关记忆召回并注入上下文;更新是处理新事实与旧结论的冲突;遗忘是按重要性、时效性和存储预算淘汰不再有价值的记忆。
遗忘不是缺陷,恰恰是记忆系统健康的标志。没有遗忘的记忆系统会越来越慢、越来越矛盾,还会不断污染上下文。我在项目里给记忆管理系统写过一句注释:记忆的目标不是记住一切,而是在正确的时间想起正确的事。这句话听着像口号,但工程含义非常具体——你需要为每条记忆定义“它什么时候该被想起来”,这就是召回策略要做的事。
4. 一套可直接照抄的智能体记忆模块设计
下面这部分是我在项目里反复迭代后沉淀下来的通用架构,不依赖任何特定框架,你可以直接在这个骨架上改。
4.1 存储架构:事件流加记忆索引,再加可选图谱
我的做法是三层存储。
第一层是事件流水,用普通关系表存,追加写,基本不更新。每条事件记录原始对话或行为,是所有记忆的源头。第二层是记忆索引,每条记忆都是从事件里抽取出来的结构化条目,带向量、实体、重要性、置信度这些元数据。第三层是可选的实体关系图,把人物、项目、组织、偏好这些实体连起来,用于处理“谁和谁什么关系”这类问题。
前两层是必须项,第三层可以后续再加。下面是记忆索引表的精简设计,我直接用SQL描述,具体数据库你可以按需替换:
CREATE TABLE memory_items ( memory_id TEXT PRIMARY KEY, owner_id TEXT NOT NULL, -- 归属用户,做隔离和删除 memory_type TEXT NOT NULL, -- episodic | semantic | procedural content TEXT NOT NULL, -- 记忆内容 embedding VECTOR, -- 向量,用于语义召回 entities TEXT[], -- 关联实体,用于过滤召回 importance REAL DEFAULT 0.5, -- 重要性 0~1 confidence REAL DEFAULT 0.9, -- 置信度,越低越需要核实 source_event_id TEXT, -- 来源事件,可追溯 status TEXT DEFAULT 'active', -- active | superseded | forgotten superseded_by TEXT, -- 被哪条新记忆取代 created_at TIMESTAMP, last_access_at TIMESTAMP, access_count INT DEFAULT 0 );为什么每条记忆一定要带owner_id?因为智能体记忆默认是面向特定用户或特定项目的,没做隔离的记忆系统,最后一定会在多用户场景下把A的偏好泄露给B。这一步不仅是技术问题,更是合规问题。
4.2 写入管线:让模型当“记忆提取器”
原始对话不可能原封不动进记忆索引,噪声太多。我的做法是双轨制:事件流里存原文,记忆索引里存抽取结果。抽取这步用模型完成,可以每轮对话调用一次,也可以攒几条批量调用一次。下面是一个抽取提示词模板,可以直接改:
任务:从下面的对话片段中提取值得长期记忆的信息。 只提取对理解用户、跟踪项目、维护关系有帮助的内容,忽略寒暄和即时性问答。 要求: 1. memory_type 只允许 episodic(事实性事件)或 semantic(习得的偏好/特征/关系); 2. content 用陈述句描述,避免指向代词; 3. importance 按 0~1 打分,影响后续行为的打高分; 4. confidence 低于0.6的不要输出; 5. 输出 JSON 数组,不要输出其他内容。 对话片段: {{conversation_text}}实际用的时候,我会在抽取结果后面再套一个校验层:实体对齐、去重、冲突检测。去重用“实体加embedding相似度”双重判定。比如同一个用户已经有了两条“偏好简洁回复”的记忆,就合并成一条,把历史版本标成superseded。这一步不做的话,记忆库不用一个月就会充满重复和互相矛盾的条目,后面召回时上下文会被这种垃圾记忆塞满。
4.3 召回策略:别只靠向量相似度
很多项目把记忆召回做成“拿当前query去向量库找最像的top-k”,这其实是不够的。向量相似度适合找语义接近的,但记忆召回还需要考虑三个因素:时效性、重要性、实体上下文。
时效性很好理解,越近的记忆越可能相关;重要性是说同等条件下,影响决策的关键记忆应该优先被想起;实体上下文是当前正在聊的事涉及哪个用户、哪个项目,这往往比语义相似度更硬。我把它们加权组合,实际效果比纯向量检索好不少,下面是一个可参考的评分伪代码:
def recall_score(memory, query_embedding, entity_score, now): semantic_sim = cosine_sim(memory.embedding, query_embedding) entity_score = 1.0 if current_entity in memory.entities else 0.0 entity_weight = 0.3 recency = 0.9 ** ((now - memory.last_access_at).days / 7) return ( 0.4 * semantic_sim + entity_weight * entity_score + 0.2 * recency + 0.1 * memory.importance )这只是一个起点,实际还要加业务规则的硬过滤:过期未触达的记忆不召回,被标记为superseded的记忆默认不召回。召回之后还有一个“排列和注入”的问题:召回的几条记忆怎么放。我建议单独放在一个记忆前缀区,明确告诉模型“以下是回忆到的背景信息,如果与当前对话冲突,以当前对话为准”。这么一句简单的标注,能显著减少模型把记忆当事实直接复述造成的记忆污染。
4.4 遗忘和更新:主动做减法
记忆系统最容易被忽视的就是遗忘。我给每条记忆打三个分:重要性上限、自然衰减速率、是否属于关键属性。关键属性比如用户身份、安全偏好这类,永不衰减;普通记忆则随时间自然降权。淘汰策略按“低重要性加长时间未访问”优先回收,同时控制每个时间窗口内的写入量上限。
更新方面,我的原则是“版本化”而不是“覆盖”。当新记忆与旧记忆冲突时,新记忆标记active,旧记忆标记superseded并指向新记忆。这样Agent在回忆时能看出“用户曾经说过A,后来改为B”,推理会更有连贯性,也方便排查记忆系统本身的问题。
这里补充一个工程细节:不要试图在每次对话结束后都立刻做全量固化,成本太高。我一般用定时任务,比如每五分钟批量处理一次事件流,把新增事件转成语义记忆。这样既不阻塞对话响应,又能保证记忆不会滞后太多。
4.5 与RAG的关系:记忆负责内,RAG负责外
最后一步,把记忆和RAG放进同一个体系。我实践中是两个检索入口并存:RAG检索外部知识库,处理“世界知识是什么”;记忆库检索内部经验库,处理“你和我之间发生过什么”。
输出时,先走RAG拿到事实,再走记忆拿到上下文和个性化,然后一起注入模型。顺序上我习惯先注入记忆前缀,再给RAG片段,最后给当前对话,让模型在生成时优先看到与个体相关的约束。不要为了“上档次”把RAG砍掉,一个纯记忆系统面对“你们产品的退换货政策是什么”这种问题根本没法回答。知识库和记忆库是两条腿,缺一不可。
5. 实战复盘:这些坑我基本都踩过
5.1 记忆污染:模型把记忆当成事实
最典型的翻车现象是:用户这次说“我想了解一下你们的安全策略”,模型因为记忆里有一条“用户上周说过要整改权限体系”,就直接回答“根据您上周提到的权限整改需求……”然后开始长篇大论。问题是用户上周说的和今天问的根本不是一回事。
排查下来根子是两条:一是召回逻辑太宽,把相关性不高的记忆也捞出来了;二是注入时没有清楚标识“这是回忆,不一定是当前事实”。我的修正方案是:召回增加实体硬门槛;注入时用专门标签把记忆区隔开;在系统提示里加一句“记忆仅供参考,请以当前对话为唯一事实来源”。这套组合打下来,记忆污染导致的答非所问下降了六成以上。
5.2 粒度两难:存得多还是存得精
这个问题我纠结了挺久。存原文,信息全、好追溯,但噪声大、token费;只存抽取结果,干净、省token,但细节会丢,比如用户某个语气背后的态度变化就没了。
现在的做法就是前面说的双轨制:事件流保留原文,记忆索引放抽取结果,两者通过source_event_id关联。需要核查“这条偏好是哪次聊出来的”时,可以回溯到原文,不牺牲精度也不牺牲可解释性。代价是多一份原文存储,但对几乎所有的对话式智能体来说,这个成本都可以接受。
5.3 token预算失控:记忆越多,生成越慢
记忆系统的最大开销不在存储,而在每次请求时把记忆注入上下文。我见过有的团队一次注入10条记忆、每条一两百字,加上RAG片段,prompt快顶到窗口上限,响应速度慢到用户骂人。
我的做法是给记忆注入设置显式预算:默认top 5条,800 token封顶,超出的宁可不注入。这个优化比调模型推理快多了,而且是最容易见效的。记住,记忆系统的目标不是“把能想起的都想起”,而是“想起当前最该想起的”。
5.4 记忆系统怎么评估
记忆系统没有单点指标,不能只看“向量检索top-k准不准”,要看它是否提升了下游任务。我用的方法分三层:第一层是记忆召回命中率,人工抽样标注“系统回忆出的内容与该轮对话是否相关”;第二层是下游任务指标,比如客服场景的“用户重复提问率”“单次解决率”;第三层是记忆污染率,统计回答中是否引用了与当前问题无关的记忆内容。
三个指标配合同一版本下的A/B测试,比凭感觉调参靠谱得多。我有一点踩出来的经验:千万不要在没做污染率统计之前盲目加记忆条数,很可能是“多记了80%错误的东西,只多对了20%”。
5.5 隐私和“被遗忘权”
如果用户说“请删除我的所有数据”,记忆系统必须真的能删,而不是只在逻辑层标成forgotten。这个需求听起来很基础,但我见过不少方案逻辑层删了、存储层还有残留,出问题就是大事。
所以架构上每个记忆项都带owner_id,提供按owner_id清除的物理删除接口;事件流和索引表都做级联删除;备份和导出也要同步处理。这不是技术选型问题,是产品底线问题。
还有一个高频问题:用户改口后Agent还在用旧偏好。解法就是前面说的版本化加superseded标记,让模型识别出“曾经如此,现已改变”。下面是我整理的一张问题速查表,基本覆盖了常见症状、根因和修法:
| 现象 | 典型根因 | 我的修法 |
|---|---|---|
| 答非所问 | 召回过宽,记忆污染 | 实体硬过滤 + 记忆独立注入区 |
| 重复错误答案 | 记忆与当前事实冲突 | superseded版本化,保留变更链 |
| 响应明显变慢 | 记忆注入token超预算 | 显式top 5、800 token预算 |
| 用户隐私泄露 | 记忆未做owner隔离 | 强制owner_id物理隔离 |
| 记忆无限膨胀 | 没有遗忘机制 | 重要性 + 时效 + 淘汰策略 |
6. 演进的本质:从“查文档”到“经营关系”
回看这条演进路线,RAG时代我们把智能体当成一个“博学的陌生人”:它知道很多书上的知识,但跟用户没有任何交情。智能体记忆要解决的是让它变成一个“懂你的老朋友”:知道你关心什么、经历过什么、有哪些雷不能踩。
这是从“信息处理”到“关系经营”的范式切换。所以我不太建议大家把智能体记忆简单理解成“更大更强的RAG”,两者解决的问题不同,评估方式也不同:RAG用“答案正确率”评价,记忆用“持续相处的体验”评价。前者是工具,后者是关系。
关于接下来的方向,有几点我觉得比较务实:第一,记忆压缩,把大量低价值事件折叠成少量高价值模式,这比堆向量库更值得投入;第二,跨会话的长程一致性,让Agent在100轮之后的对话中依然能保持一致的人设和事实认知;第三,多智能体共享记忆,多个Agent协作时如何共享一个不冲突的记忆层。这些都不是加模型、加参数能解决的,都得在记忆架构层面去设计。
我自己每次动手调记忆系统,都会提醒自己一句话:先做减法,再做加法。记忆系统最忌讳一上来就塞满。先把事件流建好,把抽取做好,把召回和注入的预算管住,模型立刻就会有“长脑子”的感觉。等体验稳定了,再慢慢加图谱、加反思、加自动遗忘。
最后再分享一个调试小技巧:给所有记忆项加一个debug字段,把每次注入记忆的条数和来源记录到日志里。这样当用户抱怨“它怎么突然变傻了”的时候,你能立刻查出来是记忆污染、召回失败还是抽取错了。记忆系统是个黑盒,但这个黑盒必须留一扇观察窗。做智能体记忆最迷人的地方,不是它让模型记住了多少,而是它逼着你去思考:一段关系里,什么才是值得被记住的。这个问题没有标准答案,但它值得每个做Agent的人认真想一次。