RAG 和 Memory 是 Agent 开发里最容易被混在一起的两个概念。很多人做知识库问答时第一反应是:我用了 RAG,是不是就不需要 Memory 了?也有人反过来,把会话历史一股脑拼进系统提示词,然后说这就是 Memory。先说结论:这两个东西解决的问题根本不一样。RAG 解决的是“Agent 不知道外部知识”的问题,Memory 解决的是“Agent 记不住刚才说过什么、做过什么、用户偏好是什么”的问题。它们可以配合,但不能互相替代。
这篇文章适合正在选型 Agent 框架、准备做知识库问答、或者想优化多轮对话体验的开发者。我会先帮你分清这两个概念,再给一套实际可用的选型判断路径,最后说落地时最容易踩的坑。重点不是堆功能,而是让你能在自己的场景里判断:现在该加 RAG,该加 Memory,还是两个都加。
1. 先分清问题:RAG 解决“信息缺失”,Memory 解决“状态遗忘”
1.1 RAG 的本质是给 Agent 装一个可检索的外部知识库
RAG 全称是 Retrieval-Augmented Generation,检索增强生成。它的核心思路不是把知识写进模型参数,而是先从外部文档里检索出和用户问题相关的片段,再把片段作为上下文交给语言模型,让模型基于这些片段生成回答。
典型场景是企业内部文档问答、产品手册查询、法律法规检索、学术论文阅读。这些内容要么不在模型训练数据里,要么更新很快。比如你的产品上周刚改了价格,模型知识还停留在半年前,这时候就算模型本身很聪明,它也会答错。RAG 的价值就在于:把答案的“依据”从模型内部搬到外部知识库,每次回答前先查最新文档。
所以判断一个场景要不要用 RAG,可以问自己一个问题:用户问题的答案,是否需要依赖某个具体文档里的准确信息?如果需要,那就要考虑 RAG。如果只是常识性问答,模型本身就能回答,没有必要上来就做一套检索链路。
RAG 的关键组件包括文档加载、切块、向量化、向量存储、检索、重排序、生成。每一步都有参数要调,后面我会专门讲。
1.2 Memory 的本质是让 Agent 记住交互历史和状态
Memory 关注的是时间维度的信息。它包含短期的会话上下文,也包含长期的用户画像、偏好和任务状态。
举几个具体例子:
- 用户上一轮说了“我要订明天早上九点的会议室”,这一轮说“改到十点”,Agent 需要知道“这间会议室”指的是哪一间。
- 用户连续问了五个问题,最后说“结合前面几条给我一个总结”,Agent 不能只看到最后一句话。
- 用户已经在系统里绑定过企业 ID 和部门,Agent 在后续回答中应该默认记住,而不是每次重新问。
这些都是 Memory 的职责。实现方式也很多样:把最近几轮对话拼进系统提示词、用摘要压缩历史、用向量库存记忆条目、用外部数据库记录用户属性,或者用专门的记忆模块。
判断场景是否需要 Memory 同样可以问一个问题:用户问题的答案,是否依赖刚才聊过的内容,或者用户长期信息?如果是,就需要 Memory。光靠 RAG 检索外部文档是不够的,因为外部文档里没有“用户刚刚说过什么”这个信息。
1.3 最容易出现的误区:把 RAG 和 Memory 混着用
我在实际项目里见过两种典型误区。
第一种,把 RAG 库当成 Memory 用。有人把历史对话也切块存进向量库,用户提问时同时检索文档和历史。这么做不是完全不行,但很容易失控。历史对话噪声多,相关性不稳定,检索出来的片段可能互相矛盾,而且会快速消耗上下文空间。更关键的问题是,历史对话里的很多信息根本不需要向量化,比如“用户刚说了 OK”,这句话没有知识价值,存进向量库只会干扰检索。
第二种,把 Memory 当成 RAG 用。有人为了省事,把产品文档全文塞进记忆模块,每次请求都把文档拼进提示词。如果文档很短,还能凑合;文档一长,很快超过上下文窗口,模型反而抓不住重点,延迟也上去了。
正确思路应该是:RAG 的检索对象是稳定的知识型内容,Memory 的检索对象是动态的交互状态和用户事实。两者可以在同一个 Agent 里共存,但定位完全不同。
2. 从 Agent 工作流程看 RAG 和 Memory 到底放在哪个环节
2.1 Agent 的典型执行循环
一个 Agent 执行任务时,通常会经历这样的循环:接收用户输入,结合上下文和记忆,判断是否需要外部知识,调用工具或检索,生成回复,最后更新记忆。
在这个循环里,Memory 是贯穿始终的。Agent 每一次做出决策,都需要知道当前处于什么状态;而 RAG 通常是在“需要外部知识”这个节点触发,不是每次提问都要走一遍检索。
用伪代码表示会更容易理解:
user_input = receive() context = memory.build_context(user_id, session_id) if need_external_knowledge(user_input): docs = rag.retrieve(user_input) context += docs response = llm.generate(user_input, context) memory.save(user_id, session_id, user_input, response)这里 Memory 负责提供背景,RAG 负责补充检索片段。两者都拼进上下文,但来源不同,用途也不同。
2.2 Memory 在整个会话中的角色
Memory 在系统中通常分成短期记忆和长期记忆两层。
短期记忆主要保存当前会话的对话轮次、工具调用结果、临时变量。它的特点是时效性强,但也容易被新消息覆盖。实现时最常用的是维护一个消息列表,每次请求把最近 N 轮拼进提示词。N 一般由上下文窗口、模型能力和成本共同决定。
长期记忆则保存跨会话的稳定信息。比如用户姓名、公司、偏好、历史订单、订阅状态。这类信息往往需要结构化存储,或者以记忆条目的形式写入独立数据库。长期记忆要注意一个问题:用户偏好会变化。不能把用户半年前的选择当成永远不变的偏好,需要给记忆条目加时间戳或者版本。
配置 Memory 时,常见的参数有:会话 ID、保留最近对话轮数、摘要触发阈值、记忆写入条件。不同框架参数名不一样,但你要关心的核心问题是:这些信息应该在什么时候写入,什么时候被清理。
2.3 RAG 在任务中的触发时机
RAG 不是每次用户提问都要触发。如果用户只是在闲聊,或者问题本身在模型知识范围内,走 RAG 反而增加延迟和噪声。
比较常见的做法是增加一个路由判断:先判断用户问题是否需要外部知识。可以用规则、分类器,也可以让模型自己决定。比如“查一下最新的政策文件”明显需要走检索;“帮我写一段欢迎语”通常不需要。
一旦确定要走 RAG,流程一般是这样:
- 理解用户问题,必要时扩展成多个子查询。
- 对查询做向量化。
- 在向量库中检索最相似的片段。
- 对结果做重排序,把真正相关的排名提前。
- 把筛选后的片段拼接到提示词中。
- 让模型基于片段生成答案,并尽量给出引用来源。
整个过程里,切块策略对效果影响最大。固定长度切块简单,但容易把语义切断;按段落和标题切块更合理,但不同文档结构差异很大。常见做法是先用文档结构粗切,再对特别大的块做二次切分。
3. 到底怎么选:先回答五个关键问题,再看决策表
3.1 选型之前,先问自己五个问题
与其直接问“RAG 和 Memory 用哪个”,不如先把场景拆开。我一般会先回答这五个问题:
- 用户的问题是否依赖外部文档或实时数据?
- 用户的问题是否依赖刚才的对话历史或用户长期信息?
- 外部知识的更新频率高不高?
- 当前模型的上下文窗口能容纳多少内容?
- 你更在意回答准确率,还是更在意交互的连续性和个性化?
第一个问题指向 RAG,第二个问题指向 Memory,第三个问题决定你要不要做文档同步,第四个问题影响你的方案复杂度,第五个问题决定你的优化优先级。
举个例子。一个产品手册问答机器人,用户的问题大多数依赖外部文档,但是单轮问答为主,连续追问的情况很少。这种情况 RAG 优先,Memory 只需要保留很浅的会话历史就够了。
另一个例子。一个招聘助手,用户会连续交流自己的经历、求职意向、薪资期望。这些信息不是外部文档里的知识,而是用户动态提供的状态。这种情况 Memory 优先,RAG 只在需要查询岗位信息时触发。
3.2 一张表帮你快速选型
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 产品文档问答 | RAG 优先,Memory 辅助 | 答案依赖文档,知识需要及时更新 |
| 多轮对话中的个性化推荐 | Memory 优先,RAG 按需 | 需要记住用户偏好,商品库查询适合走 RAG |
| Agent 执行复杂任务 | 两者结合 | Memory 记录任务状态,RAG 检索操作手册或规则 |
| 客服机器人 | 两者结合 | Memory 保存用户订单和上下文,RAG 检索政策文档 |
| 纯闲聊机器人 | 只上 Memory | 不需要外部知识,重点在上下文连贯 |
| 单轮知识问答 | 只上 RAG | 没有跨轮依赖,Memory 成本可以省掉 |
这张表是经验判断,不是绝对规则。如果你的场景比较特殊,按照自己的数据分布来调整。
3.3 两种常见组合模式
除了上面这种表格,我再给你一个更简单的版本。
极简模式:只使用 Memory,加上模型自带知识。适合简单客服、闲聊、任务型对话。优点是开发成本低,缺点是没有私有知识时模型容易编造答案。
知识库模式:只使用 RAG,做单轮问答。适合文档检索场景,比如搜政策、搜手册。优点是不用维护复杂历史,缺点是连续追问效果很差,用户问“刚才说的那个条款呢”,模型根本不知道。
混合模式:RAG 和 Memory 一起用。这是真实 Agent 场景中最常见的选择。Agent 既需要私有知识,又需要跨轮状态。混合模式不等于把所有功能堆一起,而是让两条链路各司其职。
这里有一个很容易被忽视的判断:不要看到一个 Agent 框架自带 RAG 和 Memory 模块,就以为两个都用一定更好。如果场景真的只需要文档检索,强行加 Memory 反而会让问题复杂化。选型的第一步永远是看需求,而不是看功能列表。
4. 实际落地:先从最小例子跑通,再做混合方案
4.1 环境准备
动手之前,先确认你的运行环境。不用一步到位,但下面几个条件要尽量提前准备好。
- 语言和框架:Python 生态下常用 LangChain、LlamaIndex,也可以自己用 FastAPI 搭接口。如果用的是 Dify、Spring AI 这类框架,模块会更多一些,但核心思路一致。
- 向量库:常见选择有 Qdrant、Chroma、Milvus、pgvector。学习阶段用 Chroma 最轻量,生产环境再考虑 Qdrant 或 Milvus。
- 嵌入模型和语言模型:可以调云端 API,也可以用本地模型。本地模型要额外关注显存和内存,如果机器配置不高,先把模型体积选小一点。
- 输入输出格式:明确你要处理的是 txt、Markdown、PDF 还是 Word 文档,以及对话结果的返回格式。
注意,原始材料里没有给出版本号,落地时请先确认你所用的框架和依赖版本。不同版本的 API 变化很大,不要拿旧教程直接套。
4.2 先跑通纯 RAG 的最小样例
我建议不要一上来就做混合方案。先把 RAG 单条链路跑通,再考虑 Memory。
最小步骤是:
- 加载一个文档,比如一份产品说明的 txt 文件。
- 按长度切片,比如每块 500 字符,重叠 50 字符。
- 调用嵌入模型,把每个切片转成向量。
- 把向量存入向量库。
- 输入一个测试问题,检索相似片段。
- 把片段拼进提示词,让模型生成回答。
# 伪代码示例,实际参数以你的环境为准 chunks = split_text(text, chunk_size=500, chunk_overlap=50) embeddings = embed_model.encode(chunks) vector_store.add(chunks, embeddings) question = "这个产品的价格调整政策是什么?" results = vector_store.search(question, top_k=3) prompt = f"基于以下资料回答:\n{results}\n问题:{question}" answer = llm.invoke(prompt)跑通之后,先验证三件事:检索结果是否相关,回答是否基于检索片段,以及会不会出现“编造文档中没有的内容”。如果回答看起来像模型在自由发挥,说明提示词约束不够,或者检索片段根本没有被模型认真使用。
4.3 再跑通 Memory 的最小会话
RAG 稳定之后,再单独加 Memory。最简单的做法,就是维护一个消息列表,每次请求把最近几轮拼进提示词。
messages = memory.get_recent(session_id, max_turns=10) user_input = "我叫张三" prompt = build_prompt(messages, user_input) answer = llm.invoke(prompt) memory.add(session_id, user_input, answer)测试时用一组连续问题:先问“我叫张三”,再问“我叫什么”,最后问“我刚才说了什么”。如果模型都能回答,说明最小会话记忆是通的。
这里先不要急着做摘要。先用原始消息跑通,观察上下文长度对回答质量的影响,再决定要不要把早期消息压缩成摘要。摘要会省 token,但也会丢失细节,需要权衡。
4.4 混合后的最小流程
两条链路都稳定后,再合并成一个流程:
# 伪代码示例 messages = memory.get_recent(session_id) if should_use_rag(question): docs = rag.search(question) context = format_docs(docs) + format_messages(messages) else: context = format_messages(messages) answer = llm.invoke(question, context) memory.add(session_id, question, answer)这已经是一个非常够用的混合方案。它没有复杂的路由模型,没有长期用户画像,但能覆盖大部分场景:有知识时检索文档,有历史时参考历史,两者都满足时一起用。
4.5 判断标准
混合方案跑起来之后,不要只看“能回答”就结束,还要盯几个指标:
- 单条响应延迟。如果响应变慢,优先看检索耗时和上下文长度。
- 检索命中质量。随机抽 20 个问题,看检索结果是否真的覆盖答案。
- 上下文占用。连续对话到第 10 轮后,提示词占了多少 token;如果超过模型窗口的 80%,就要做摘要或限制轮数。
- 是否重复引用同一段文档。如果 top_k 里三块内容高度重复,说明切块重叠太多,需要调整。
- 记忆是否准确。用户上一轮说的事情,这一轮是否被正确复用。
5. 混合使用时的架构要点和常见坑
5.1 Memory 和 RAG 不要抢同一块上下文
混合方案最常出的问题不是功能不够,而是上下文不够用。
RAG 检索出来的片段可能很长,Memory 里又有十几轮历史。两者都塞进提示词之后,二十分钟过去,模型看到的大部分内容都是历史对话和检索片段,反而找不到当前用户问题在哪。
解决思路是给上下文做一个预算。比如模型上下文窗口是 8000 token,你可以规定:RAG 片段最多占 3000,Memory 最多占 2500,留给模型发挥的空间至少 2000。比例不一定固定,但一定要有预算意识。
Memory 侧可以压缩:最近 2-3 轮保留原始消息,更早的用摘要代替。RAG 侧可以限制:top_k 设成 3 或 5,每段长度通过切块参数控制。不要以为检索出 10 块都有用,很多时候 3 块高质量片段比 10 块噪声更有价值。
5.2 记忆的写入和清理
记忆不是存得越多越好。把所有对话原样存下来,很快会把存储和上下文都拖垮。
写入策略上,我会优先保存有状态价值的信息。比如用户明确说过“我偏好某个品牌”“我下周要出差”“我已经完成了第一步”,这些信息值得写入记忆。而“好的”“嗯”“谢谢”这类消息,价值很低,不写反而更干净。
清理策略同样重要。长期记忆里要给每条记录加时间戳,当用户说“我改主意了”时,新记录要能覆盖旧记录,而不是让模型同时看到矛盾信息。会话记忆里,超过最大轮数的消息要么丢弃,要么摘要,不能无限累积。
5.3 RAG 检索质量和切块策略
RAG 最容易翻车的点不是模型,而是文档加载和切块。
常见问题包括:PDF 里表格提取后乱掉、页眉页脚成为噪声、扫描件没有 OCR、文档编码不是 UTF-8。这些都会让后续检索质量大打折扣。处理文档时,先人工抽查几个片段,别急着全部灌进向量库。
切块也不是只看字符数。固定长度切块最容易把一句话切成两半,导致检索结果语义不完整。更好的办法是按标题、段落、列表先拆分,再对超大块二次切分。切块长度和重叠率是互相影响的,一般先设 500-800 字符、10% 重叠,再根据检索结果调整。
另一个容易忽略的点是引用溯源。企业级 RAG 不能只给答案,还要能说明答案来自哪份文档的哪一段。否则答错了没法排查,用户也不敢采信。热词里提到的“RAG 的引用溯源与 groundedness”,就是这个意思。
5.4 混合方案排查链路
混合方案出了问题时,按这个顺序排查,不要一上来就怀疑模型能力。
- 回答像没读过文档:先看检索片段是否相关。如果片段本来就不对,模型再强也答不对。
- 连续对话答错:先看 Memory 是否覆盖了关键信息。有时候是保存了,但模型没有用上;有时候是根本没保存。
- 提示词超长或响应很慢:先看上下文预算、检索片段数量、历史轮数。超长通常不是模型问题,是策略问题。
- 回答前后矛盾:先看 Memory 里的旧信息和新信息是否冲突,有没有清理机制。
- RAG 命中但生成乱编:先看提示词是否明确要求“只能基于资料回答”,并且允许模型在资料不足时说不知道。
5.5 值得关注的进阶方向
如果你已经跑通了基础的 RAG + Memory,下一步可以关注几个更细的方向。
Agentic RAG 是一个思路:不让每次请求都固定走检索流程,而是让 Agent 自己判断什么时候需要调用检索工具。这样能减少无效检索,节省成本和时间。
Memory Channel 也是一种参考:把记忆拆分成不同通道,比如会话记忆、实体记忆、任务记忆。每个通道存不同类型的信息,检索时按需取用。对复杂 Agent 场景来说,比单一大列表更稳定。
还可以考虑加入知识图谱或 MCP 这类外部模块,但前提是你的基础链路已经稳定。否则一次堆太多功能,出了问题很难定位。我的建议是:先跑通最小闭环,再逐步加能力。
6. 几个可以直接拿去用的选型经验
6.1 先区分“知识”和“状态”
这是整个选型最核心的一句话。
如果信息是一个事实,比如“发货政策是 24 小时内”“这座城市的面积是 1000 平方公里”,往 RAG 方向走。 如果信息是动态状态,比如“用户刚刚选择了 5 号商品”“用户当前登录的是企业账号”,往 Memory 方向走。 如果一个信息既有知识属性又有状态属性,比如用户选择的产品型号是动态状态,而该型号的官方技术参数是外部知识,就要拆成两段处理:状态放 Memory,技术参数走 RAG。
6.2 不要一开始就追求大而全
很多 AI Agent 框架自带 Memory 和 RAG 模块,默认配置不一定适合你的场景。
我见过不少团队,上来就配置了长期记忆、向量数据库、重排序、多轮摘要、工具调用,结果系统看起来能力很强,但用户随便问几个问题就开始乱答。原因就是链路太长,任何一个环节的噪声都会被放大。
更稳的做法是先用最简单的方式跑通:一个列表存历史,一个向量库查文档。验证核心链路之后,再逐步加摘要、重排序、权限控制。
6.3 用问题反推方案
我不太推荐先选框架再想场景。反过来,先收集真实用户问题,再做分类,会更靠谱。
把用户问题分成四类:
- 事实查询:答案能在外部文档里找到,典型如“这个品类的退货标准是什么”。这类走 RAG。
- 连续追问:答案依赖上一轮语气和上下文,典型如“刚才说的那个方案,预算改成 2 万怎么调整”。这类走 Memory。
- 任务执行:需要 Agent 记住步骤和状态,同时可能要查操作手册。这类 RAG + Memory 都要。
- 个性化推荐:需要记住用户偏好,同时检索内容库。这类也是混合方案。
分类之后你会发现,很多场景其实只需要其中一个,不需要一上来就把所有能力全开。
6.4 一定要留可观测性
混合方案最怕“看起来能用,但出了问题不知道从哪里排查”。
我建议每个请求都记录三份信息:这次有没有触发 RAG,检索了哪些片段,命中分数多少;Memory 带了哪些历史,是否做了摘要;最终模型生成了什么答案,有没有引用来源。这些日志存在本地文件或数据库里都可以,关键是别省略。
有了日志,你才能判断回答错误是检索问题、记忆问题还是模型问题。否则用户反馈一句“你回答错了”,你连从哪里开始查都不知道。
我个人更建议把项目拆成两个阶段:第一阶段跑通单文档 RAG 加会话 Memory,确认两条链路都稳定;第二阶段再做路由、摘要、长期记忆和更复杂的 Agentic RAG。不要一开始就把所有高级概念都堆上去。很多问题不是工具能力不够,而是前置环境和输入材料没有处理干净。先把基础链路做扎实,后面加什么功能都更容易判断值不值。