在 AI Agent 开发中,RAG 和 Memory 是出现频率最高的两个词,也是很多方案评审会上最容易被混在一起讨论的两个概念。很多团队在启动第一个 Agent 项目时会问:到底应该用 RAG,还是应该给 Agent 加 Memory?这个二选一本身就会误导设计。RAG 负责把外部知识送到模型面前,Memory 负责让 Agent 在多次交互中保持上下文和状态。两者服务的不是同一层问题,不能互相替代,却可以在同一条 Agent 工作流里协作。下面会把两者的职责、配合方式、选型判断、落地细节、验证方法和排错路径展开讲清楚。
1. 先厘清:RAG 解决“模型不知道”,Memory 解决“状态记不住”
1.1 RAG 的本质是把“外部知识”接到模型外面
RAG 的全称是 Retrieval-Augmented Generation,中文通常叫检索增强生成。它在模型生成回答之前,先从外部知识库召回与用户问题相关的内容,把召回结果作为上下文片段和用户问题一起交给模型。模型不需要在参数中记住这些知识,也不需要为每次制度更新重新训练,只要知识库里的文档更新,检索结果就会变化。
一个典型的 RAG 链路包括文档加载、文档清洗、切块、向量化、写入向量库、用户提问时查询改写、向量召回、重排、拼接 Prompt。这个链路里最容易被低估的是文档切块和重排。并不是任意切一块文本再算向量相似度就能得到好答案,切块粒度、重叠长度、元数据完整度都会直接影响最终回答准确率。
理解 RAG 的关键点在于:它的数据来源是外部资料,读取时机在用户问题到来之后,核心目标是让模型看到它原本不知道的内容。如果用户问的问题在模型参数里已经存在,RAG 的价值会下降;如果问题依赖的是企业私有文档、最新政策或实时数据,RAG 几乎是必选项。
1.2 Memory 的本质是让 Agent 管理“状态和上下文”
Agent Memory 指的是 Agent 对交互过程的记录和状态管理能力。它至少包含三层:当前会话的短期记忆,比如最近几轮对话;跨会话的长期记忆,比如用户偏好、历史结论、任务上下文;以及工作记忆,比如当前执行到第几步、哪个工具已经返回结果、临时变量是什么。
需要注意,这个 Memory 是 Agent 的逻辑概念,不是 Java 进程里的内存。如果 Agent 进程报出OutOfMemoryError,通常不是“记忆模块太多”导致,而是程序设计上没有限制上下文体积或没有释放对象。把 Agent Memory 和服务进程内存混为一谈,是排查问题时最常见的误区之一。
Memory 的写入动作发生在 Agent 执行过程中,读取动作发生在下一步决策之前。它解决的是“模型看不到刚才发生了什么”的问题。没有 Memory 的 Agent 即使有很强的工具调用能力,也会在第三步忘掉第一步的结果。多步任务、工具调用、用户偏好维护,都依赖 Memory。
1.3 两者的差异不能靠“是不是向量检索”来区分
RAG 和 Agent Memory 都可能会用向量库,这是它们容易被混淆的另一个原因。但真正区分它们的是数据来源和读写时机:RAG 的语料来自文档知识库,通常在后台批量索引;Memory 的语料来自用户与 Agent 的实时交互,在一轮对话结束后增量写入。
| 维度 | RAG | Agent Memory |
|---|---|---|
| 核心问题 | 模型不知道的知识 | Agent 记不住的上下文与状态 |
| 数据来源 | 文档、网页、数据库、知识库 | 对话历史、工具结果、任务中间状态 |
| 写入时机 | 文档入库或更新时离线写入 | 每轮对话或每个 Agent 步骤后在线写入 |
| 读取时机 | 用户提问后按相关性召回 | 每轮开始加载,或决策节点按需读取 |
| 典型存储 | 向量库、倒排索引、混合检索 | KV 存储、数据库、向量库、内存对象 |
| 核心风险 | 检索不准、引用无依据 | 上下文膨胀、敏感信息长期驻留 |
RAG 更像 Agent 的“外部图书馆”,Memory 更像 Agent 的“工作台和笔记”。查资料和记状态是两件不同的事。
2. 把 RAG 和 Memory 放进同一条 Agent 工作流
2.1 一次代答的完整数据流
在实际 Agent 系统中,一次用户请求很少只做一次模型调用。更常见的流程是:先加载当前会话和历史状态,再判断需不需要外部知识,然后调用工具或检索服务,最后生成回复并更新状态。
一个典型的数据流可以拆成以下步骤:
- Agent runtime 从请求中拿到
user_id和session_id。 - Memory 管理器读取历史消息、用户画像和当前任务状态。
- 根据历史和当前输入,对用户问题做查询改写。
- 如果判断需要外部知识,调用 RAG 检索服务获取文档片段。
- 如果涉及工具调用,执行工具并把结果放入上下文。
- 把历史、上下文、检索片段、工具结果一起拼成 Prompt。
- 模型生成回复,或生成下一步动作。
- 对话内容、关键事实、任务进度写回 Memory。
在这个流程里,RAG 和 Memory 的位置不同:Memory 在回合开始前读取,RAG 在需要知识时才读取,工具结果又可能写回 Memory。这样不会出现“把所有知识都塞进记忆”或者“把所有历史都塞进知识库”的设计混乱。
2.2 为什么不能只靠“把所有历史都塞进 Prompt”
有人会问:既然模型上下文窗口已经很大,为什么还要单独设计 Memory?直接把历史消息和检索结果全部拼进 Prompt 不行吗?
可行,但不适合生产环境。上下文窗口再大,也有 token 成本上限。历史越长,无关信息越多,模型越容易忽略关键内容。企业场景下如果每个请求都带上全部对话历史加全部检索片段,接口延迟和费用都会快速上升。更严重的是,Agent 在执行多步任务时,真正需要的往往不是“完整聊天记录”,而是“这一步的状态”。
Memory 的职责是对上下文做压缩、抽取、筛选和持久化,而不是无脑拼接。比如上一轮已经确认了“预算 2 万”,下一轮就不必再把“预算 2 万”相关的所有原始聊天记录都塞进去,而是把它转成一条结构化事实字段。
2.3 最小代码结构:用 Python 演示两者如何协作
下面是一段用于说明数据流的伪工程代码,不涉及具体框架。它的重点不是语法,而是展示 Memory 和 RAG 在同一个 Agent 方法里的协作位置。
class Agent: def __init__(self, retriever, memory, llm): self.retriever = retriever self.memory = memory self.llm = llm def handle(self, user_id, session_id, user_input): history = self.memory.load_session(session_id) facts = self.memory.load_facts(user_id) rewritten = self.memory.rewrite_query(user_input, history) docs = [] if self.memory.need_external_knowledge(rewritten): docs = self.retriever.search(rewritten, top_k=5) prompt = self._build_prompt( query=user_input, history=history, facts=facts, documents=docs, ) answer = self.llm.chat(prompt) self.memory.append(session_id, user_input, answer) self.memory.update_facts(user_id, answer) return answer关键点有三个:
rewrite_query会把用户问题结合历史上下文改写,避免直接拿“它”或“这个方案”去检索。need_external_knowledge决定了是否需要调用 RAG,不是每个请求都必须走知识库检索。append和update_facts必须在模型调用之后执行,确保写入的是这一轮的结果,而不是模型推理前的旧状态。
这就是 RAG 和 Memory 的协作位置:Memory 先提供上下文,RAG 再叠加外部资料,最后结果又写回 Memory。
3. 选型判断:什么场景先上 RAG,什么场景先上 Memory
3.1 先上 RAG 的场景
如果用户问题彼此独立,答案主要来自文档,并且不需要记住上一轮说了什么,那么优先考虑 RAG。典型场景包括企业制度问答、产品说明书检索、法务条款查询、政策文件问答。
这类场景的特点是:用户问完一个问题,得到答案后大概率会问另一个独立问题;即使他连续问五个问题,五个问题之间也没有强依赖。此时用 Memory 只会增加复杂度。只要做好文档切块、向量召回和引用溯源,一个无状态的 RAG 问答服务就能满足需求。
RAG 还适合需要引用来源的场景。用户在客服场景里会追问“这个依据在哪里”,如果检索接口能返回doc_id、chunk_id、source等字段,Agent 就可以在回答里带出出处,降低模型编造概率。
3.2 先上 Memory 的场景
如果下一步动作依赖上一步结果,那么 Memory 必须优先。典型场景是:用户说“在我刚才说的预算内推荐一款”“继续处理上一个报销单”“把刚才生成的页面改成深色主题”。这些问题如果去掉历史上下文,模型根本无法理解。
Agent 的多步工具调用也强依赖 Memory。比如一个 Agent 需要先查订单编号,再查物流状态,最后生成汇总表;如果它不保存“订单编号”这个中间值,每一步都要让用户重新输入。此时即使外部知识很完备,Agent 也会因为记不住状态而无法完成任务。
3.3 两者都要上时,先画数据流再写代码
真实项目里,RAG 和 Memory 经常需要一起用。比如客服 Agent:Memory 负责记住用户是谁、之前沟通到哪一步、有没有确认过订单号;RAG 负责检索赔偿政策、售后流程、产品说明。二者缺一不可。
建议不要一上来就选 Agent 框架,而是先列数据来源:哪些信息来自用户输入,哪些来自历史状态,哪些来自外部文档,哪些来自工具返回结果。然后决定这些信息分别进入哪个模块。
| 请求类型 | 判断依据 | 推荐方案 |
|---|---|---|
| 单个独立知识问答 | 答案完全来自文档 | RAG |
| 多轮上下文问答 | 需要上一轮信息才能理解 | Memory |
| 按制度核对当前单据 | 既有规则也有当前对象 | RAG + Memory |
| 继续上次任务并查最新数据 | 有任务状态也有实时数据 | Memory + RAG |
在 Agent 项目里,Memory 通常要先于 RAG 落地。因为 Agent 的核心是执行任务,任务必须有状态。如果没有状态,只能算问答机器人,不能算 Agent。
4. Agent Memory 落地:先设计记忆模型,再谈长期记忆
4.1 短期会话记忆:用有限窗口管理上下文
短期记忆通常指当前会话内的上下文。实现时最简单的方式是维护一个消息列表,但这并不等于“把列表原样塞进 Prompt”。应该设置窗口上限,比如保留最近 20 轮,超出部分用摘要压缩。
一个可参考的策略:
- 保留最近 5 轮完整原始消息。
- 更早的消息滚动生成会话摘要。
- 每一轮执行时,把摘要加最近消息一起送入 Prompt。
- 摘要也要限制 token 长度,防止无限膨胀。
这样做的好处是既保留近期细节,又不会让模型失去长距离上下文。它的成本来自摘要更新,每次摘要更新都需要一次模型调用,因此不需要每一轮都重新摘要,可以按轮数或 token 阈值触发。
4.2 长期记忆:事实表、事件日志和向量记忆
长期记忆不是简单把历史消息永久保存,而是应该分类型管理。比较实用的分层方式如下。
| 记忆类型 | 内容示例 | 存储方式 | 更新策略 |
|---|---|---|---|
| 工作记忆 | 当前任务步骤、临时变量 | 会话内对象 | 每轮覆盖 |
| 情景记忆 | 用户上次提过“预算只剩两万” | 向量库或数据库 | 追加并去重 |
| 语义记忆 | 用户偏好、公司部门 | 结构化事实表 | 字段覆盖或版本化 |
| 程序记忆 | 常用工具调用流程 | 配置、脚本、Agent 技能库 | 版本管理 |
一条长期记忆记录可以设计成下面的 JSON 结构。它说明 Memory 不能只存字符串,还要存字段名、来源、时间戳和状态。
{ "