上周帮一个技术团队评审智能客服方案时,负责人问了我一个很典型的问题:“我们已经用 Agent 搭了对话框架,是不是就不用再单独做 RAG 和 Memory 了?”这个问题不是个例。在 AI 应用开发里,RAG、Memory、Agent 经常被当成三种可以互相替代的“记忆方案”来比较,但事实上,它们解决的是完全不同层面的问题。
RAG 解决的是“模型不知道外部知识”的问题,Memory 解决的是“模型记不住对话状态”的问题,Agent 则是一个执行框架,负责判断什么时候该检索、什么时候该回忆、什么时候该调用工具。三者并不是同一个赛道上的竞品。如果硬要放在一起二选一,架构设计从一开始就会走偏。
这篇文章会把三个概念拆开讲清楚,给出一个基于 Python 的最小可运行示例,最后再补充工程落地时最常见的坑和排查思路。读完你能判断:自己的场景到底需要 RAG、Memory、还是三者组合使用。
1. 这篇文章真正要解决的问题
很多项目在起步阶段都会遇到这几个典型问题。
第一,团队把 RAG 当成“万能记忆”,把所有用户历史、偏好、聊天记录都塞进向量库,结果检索结果混杂、上下文爆炸,回答质量反而下降。第二,团队只做了 Memory 不做 RAG,模型对私有文档、最新数据一无所知,回答全靠“幻觉”。第三,团队引入了 Agent,但既没有给 Agent 接记忆,也没有把知识库检索能力暴露成工具,导致一个号称“智能体”的系统只是套了一层循环调用。
这些问题的根源,不是某一个技术选错了,而是没有先分清“知识”“状态”和“执行”这三个维度。RAG 负责管理静态知识,Memory 负责管理动态状态,Agent 负责管理执行流程。真正的生产级 AI 应用,往往需要把三者组合起来,而不是在它们之间做非此即彼的选择。
这篇文章的价值在于:先给你一套判断标准,再用一个最小示例把组合方式跑通,最后告诉你哪些地方最容易翻车。
2. RAG、Memory、Agent 的核心概念与边界
2.1 RAG:检索增强生成,解决知识来源问题
RAG 的全称是 Retrieval-Augmented Generation,中文通常叫“检索增强生成”。它的核心思路是:在模型生成回答之前,先从外部知识库中检索出与问题相关的文本片段,再把片段拼进提示词,让模型基于这些片段生成答案。
可以把它理解成给模型准备了一个“随时可查的参考书”。模型不需要背下全书内容,只需要在回答问题时翻开对应章节,引用其中的说法。这个机制天然适合私有知识库问答、企业文档检索、产品说明书答疑等场景。
RAG 的关键流程包括:文档切块、向量化、存入向量库、查询时召回、拼装上下文、生成回答。它的工程质量主要取决于切块策略、Embedding 模型、召回算法和引用溯源能力。
2.2 Memory:上下文存储,解决状态记忆问题
Memory 在 LLM 应用里指的是“记忆系统”,核心功能是保存和恢复对话过程中的状态信息。它至少分成两层。
短期记忆负责保存当前会话中的对话轮次、用户意图、最近几轮问答,通常以消息列表的形式放在提示词中。长期记忆负责保存跨会话的用户偏好、历史事实、业务约束,一般落地在数据库或向量库中,在需要时按用户或实体召回。
可以这样理解:Memory 是“便签本”,短期便签记住刚才说了什么,长期档案记住这个用户关心什么。没有 Memory,无论你的模型有多强,聊到第五轮就会忘记第一轮的内容。
2.3 Agent:执行框架,解决编排与决策问题
Agent 通常指“以大模型为决策内核、能调用外部工具完成复杂任务的执行系统”。Agent 不只是“聊天机器人”,它要自主判断下一步动作:是直接回答,还是去检索知识库,还是调用计算器,还是请求外部 API。
一个完整的 Agent 循环通常是:理解用户目标、拆解任务、选择工具、执行动作、观察结果、再决策,直到任务完成或达到最大迭代次数。这个循环需要内部状态来记录“当前执行到哪一步”,所以 Agent 对 Memory 的依赖其实很高。
同时,Agent 如果要做知识类任务,也必须把“检索器”注册成一个可用工具。这就解释了为什么生产环境里的 Agent 往往同时依赖 RAG 和 Memory。
2.4 三者的边界对比
| 维度 | RAG | Memory | Agent |
|---|---|---|---|
| 解决的核心问题 | 外部知识来源 | 对话状态与历史 | 任务编排与工具调用 |
| 数据特征 | 静态知识,更新频率低 | 动态状态,随交互实时变化 | 任务目标、中间步骤、执行结果 |
| 典型载体 | 向量库、文档索引、重排序服务 | 内存队列、Redis、数据库、向量库 | 大模型 + 工具注册表 + 执行循环 |
| 典型使用方式 | 检索片段并注入提示词 | 保存上下文、摘要、用户画像 | 规划、调用 RAG/Memory/API 等能力 |
| 失败表现 | 回答不了私有知识、事实错误 | 多轮对话失忆、状态错乱 | 任务执行中断、循环空转 |
2.5 重点结论
从架构分层看,RAG 和 Memory 不是竞争关系,Agent 也不是它们的替代品。更准确的关系是:Agent 在最上层负责决策,RAG 和 Memory 都是它可调用的“能力底座”。问题不是“该用哪个”,而是“这个场景里,知识、状态、执行哪个是瓶颈”。
3. 如何决策:该用哪个还是组合用
在实际项目中,如果你还在纠结“RAG 和 Memory,Agent 到底该用哪个”,可以先按下面的步骤过一遍。
第一步,看用户问题是否需要“模型不知道的外部知识”。如果答案依赖企业私有文档、实时行情、产品说明书,那么 RAG 是必须的,因为模型训练数据里没有这些内容;如果问题只依赖通用常识,RAG 可以是可选项。
第二步,看系统是否需要“记住用户说过什么”。如果是多轮客服、咨询、销售助手,需要记住上一轮答案,需要 Memory;如果是单轮问答、一次性文档解析,Memory 可以很轻。
第三步,看任务是否需要“拆解和执行多个步骤”。如果系统需要用户说一句话,然后自动查询数据库、计算指标、生成报告,那必须用 Agent 编排;如果只是单轮问答,Agent 反而是过度设计。
下面这张表可以帮你快速判断。
| 业务诉求 | 优先方案 | 原因 |
|---|---|---|
| 回答私有文档、最新数据 | RAG | 注入外部知识,降低幻觉 |
| 多轮对话中保持一致 | Memory | 保存对话上下文和状态 |
| 能自主选择工具、执行任务 | Agent | 负责规划和调度 |
| 需要检索资料并引用出处 | RAG + Agent | Agent 调用检索器并校验结果 |
| 需要按用户画像长期定制回答 | Memory + RAG | 长期记忆 + 外部知识共同驱动 |
| 需要复杂工作流多步执行 | Agent + RAG + Memory | 三者在不同阶段协同工作 |
比较稳妥的判断是:先做最小闭环,判断主要瓶颈在“知识”“状态”还是“执行”。大多数生产项目最后都会走向三者共存,只是不同模块的复杂度和优先级不一样。
4. 环境准备与底层设计
4.1 运行环境
这篇文章的示例代码使用 Python 3.9+,只需要安装两个常见依赖:scikit-learn负责文本向量化,numpy负责数值计算。实际生产环境可以替换为sentence-transformers、chromadb、qdrant-client,或者 Java 生态里的 Spring AI + Qdrant 组合。
建议先创建一个干净的虚拟环境:
mkdir rag-memory-agent-demo cd rag-memory-agent-demo python -m venv .venv source .venv/bin/activate pip install scikit-learn numpy版本以实际安装为准,本文重点演示通用设计思路,不绑定具体版本号。
4.2 最小系统设计
我们要构建的系统包含四个部分。
第一,文档检索器Retriever。它负责把知识文档加入索引,并针对用户问题召回最相关的文本片段。为了便于运行演示,代码中使用 TF-IDF 向量化;生产环境中应换成语义向量模型。
第二,对话记忆ConversationMemory。它负责保存用户和助手的较新消息,并在超过长度限制时淘汰最老的记录。
第三,Agent 主循环Agent。它负责把用户输入、对话历史、检索结果组装成提示词,调用 LLM,再把新的问答写入记忆。
第四,一个模拟 LLM 的入口。真实项目可以替换为外部模型 API、本地部署模型或 Spring AI 统一接口。
这样设计的好处是:RAG、Memory、Agent 各自独立,替换任一部分都不会影响其他模块。
5. 完整示例与代码实现
5.1 项目结构
rag-memory-agent-demo/ ├── retriever.py ├── memory.py ├── agent.py ├── main.py └── config.yaml下面逐个文件给出代码。
5.2 检索器实现
# 文件路径:retriever.py import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity class Retriever: """一个教学用检索器:用 TF-IDF 向量化,按余弦相似度召回文本片段。""" def __init__(self): self.documents = [] self.vectorizer = TfidfVectorizer() self.doc_vectors = None def add_documents(self, docs): """添加知识文档并重新建立索引。""" self.documents.extend(docs) self.doc_vectors = self.vectorizer.fit_transform(self.documents) def retrieve(self, query, top_k=2): """召回与 query 最相关的 top_k 个文档片段。""" if not self.documents: return [] query_vec = self.vectorizer.transform([query]) scores = cosine_similarity(query_vec, self.doc_vectors).flatten() top_indices = scores.argsort()[-top_k:][::-1] return [self.documents[i] for i in top_indices if scores[i] > 0]这段代码把几个知识片段加入索引,然后用查询文本计算相似度。TF-IDF 只能匹配词面重合,不能理解同义改述,所以只适合教学演示。真实项目建议用sentence-transformers这类语义向量模型,并配合qdrant、milvus、elasticsearch等向量存储。
5.3 记忆模块实现
# 文件路径:memory.py from dataclasses import dataclass, field @dataclass class ConversationMemory: """一个简单的滑动窗口记忆:只保留最近最多 max_characters 个字符的对话内容。""" max_characters: int = 2000 messages: list = field(default_factory=list) def add(self, speaker: str, text: str): self.messages.append({"speaker": speaker, "text": text}) self._trim() def _trim(self): total = sum(len(item["text"]) for item in self.messages) while total > self.max_characters and len(self.messages) > 1: self.messages.pop(0) total = sum(len(item["text"]) for item in self.messages) def history(self): return "\n".join(f"{item['speaker']}: {item['text']}" for item in self.messages)这个 Memory 结构非常简单,适合作为起点。它记录了说完一段话后的消息列表,并在超限时丢弃最早的消息。生产环境可以做两件事:一是引入“摘要记忆”,把老消息压缩成摘要再保留;二是把长期记忆落库,按用户 ID 持久化到数据库或向量库。
5.4 Agent 编排实现
# 文件路径:agent.py class Agent: """把记忆、检索和 LLM 组合起来的 Agent 主循环。""" def __init__(self, retriever, memory, llm, rag_enabled=True, debug=False): self.retriever = retriever self.memory = memory self.llm = llm self.rag_enabled = rag_enabled self.debug = debug def run(self, user_input): # 1. 从 RAG 中召回相关知识 knowledge = "" if self.rag_enabled: docs = self.retriever.retrieve(user_input, top_k=2) knowledge = "\n".join(f"[知识片段] {doc}" for doc in docs) # 2. 组装提示词:系统指令 + 对话历史 + 知识片段 + 当前问题 prompt = ( "你是智能客服助手。回答用户问题时,优先使用知识片段,并保持对话上下文连贯。\n" f"对话历史:\n{self.memory.history() or '无'}\n" f"知识片段:\n{knowledge or '无'}\n" f"用户:{user_input}\n" "助手:" ) if self.debug: print("[DEBUG] 组装后的提示词如下:\n" + prompt) # 3. 调用 LLM 生成回答 response = self.llm(prompt) # 4. 把当前问答写入 Memory self.memory.add("用户", user_input) self.memory.add("助手", response) return response这段代码展示了 Agent 与 RAG、Memory 的组合方式。Agent 是“大脑”,RAG 是“外部资料”,Memory 是“工作台记录”。Agent 的决策在这一版本里体现为一个简单规则:只要开启 RAG,就统一执行检索。更复杂的场景中,Agent 应该自己决定是否检索,这属于 Agentic RAG 的范畴。
5.5 主程序与配置示例
# 文件路径:main.py from retriever import Retriever from memory import ConversationMemory from agent import Agent def mock_llm(prompt): """模拟 LLM 返回结果,只用于验证流程。实际项目替换成真实模型接口即可。""" if "[知识片段]" in prompt and "RAG" in prompt: return "根据知识片段,RAG 通过检索外部资料增强生成,能提高事实准确性。" if "刚才" in prompt: return "你刚才问的问题是:什么是 RAG?" return "我已经结合历史上下文做出了回答。" if __name__ == "__main__": # 初始化检索器并加入知识文档 retriever = Retriever() retriever.add_documents([ "RAG 的全称是 Retrieval-Augmented Generation,用于让模型基于外部知识生成答案。", "Memory 在 LLM 应用中负责保存对话状态,分为短期记忆和长期记忆。", "Agent 是执行框架,通过规划、工具调用和记忆协作完成复杂任务。", ]) # 初始化记忆和 Agent memory = ConversationMemory(max_characters=2000) agent = Agent(retriever=retriever, memory=memory, llm=mock_llm, debug=True) print(agent.run("什么是 RAG?")) print("---") print(agent.run("我刚才问了什么?")) print("---") print(agent.run("根据知识片段,Agent 是什么?"))对应的config.yaml可以作为生产配置结构参考:
rag: chunk_size: 256 # 文档切块大小,单位是 token 或字符,按实际模型调整 chunk_overlap: 50 # 切块重叠区间 top_k: 3 # 召回片段数量 embedding_model: "local_or_api_embedding" memory: short_term_max_messages: 20 long_term_store: "qdrant_or_mysql" agent: max_iterations: 5 max_tool_calls: 10 tools: ["retriever", "calculator", "http_api"]真实项目中chunk_size、chunk_overlap和top_k都需要经过评测调整。这里给出的 256 和 50 是常见的起始经验值,不代表最优值。
6. 运行结果与效果验证
在项目目录下运行:
python main.py预期会看到类似下面的输出。因为开启了debug=True,每次调用 Agent 时都会先打印组装后的提示词。
[DEBUG] 组装后的提示词如下: 你是智能客服助手。回答用户问题时,优先使用知识片段,并保持对话上下文连贯。 对话历史: 无 知识片段: [知识片段] RAG 的全称是 Retrieval-Augmented Generation,用于让模型基于外部知识生成答案。 [知识片段] Agent 是执行框架,通过规划、工具调用和记忆协作完成复杂任务。 用户:什么是 RAG? 助手: 根据知识片段,RAG 通过检索外部资料增强生成,能提高事实准确性。 --- [DEBUG] 组装后的提示词如下: 你是智能客服助手。回答用户问题时,优先使用知识片段,并保持对话上下文连贯。 对话历史: 用户:什么是 RAG? 助手:根据知识片段,RAG 通过检索外部资料增强生成,能提高事实准确性。 知识片段: 用户:我刚才问了什么? 助手: 你刚才问的问题是:什么是 RAG? --- [DEBUG] 组装后的提示词如下: 你是智能客服助手。回答用户问题时,优先使用知识片段,并保持对话上下文连贯。 对话历史: 用户:什么是 RAG? 助手:根据知识片段,RAG 通过检索外部资料增强生成,能提高事实准确性。 用户:我刚才问了什么? 助手:你刚才问的问题是:什么是 RAG? 知识片段: [知识片段] Agent 是执行框架,通过规划、工具调用和记忆协作完成复杂任务。 [知识片段] RAG 的全称是 Retrieval-Augmented Generation,用于让模型基于外部知识生成答案。 用户:根据知识片段,Agent 是什么? 助手: 我已经结合历史上下文做出了回答。要判断示例是否跑通,有几个观察点。
第一,Debug 输出里能看到“对话历史”区域从“无”变成包含多轮消息,说明 Memory 生效了。第二,第一轮和第三轮提示词里有“知识片段”,说明 RAG 被成功调用。第三,第二轮命令能够引用之前的问题,说明记忆状态被保留到了下一轮。
如果您追求更真实的语义检索,可以在retriever.py中把 TF-IDF 替换为sentence-transformers,并接入真正的 LLM 接口。替换后,输出质量会明显提升。
7. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
运行python main.py时提示ModuleNotFoundError: sklearn | 当前环境未安装 scikit-learn | 检查pip list,确认是否激活了虚拟环境 | 执行pip install scikit-learn numpy |
| 检索结果总是不相关 | 切块太大或太小,词面匹配方式不适用于该文档 | 打印召回片段,人工检查语义相关性 | 调整chunk_size和chunk_overlap;换语义向量模型;增加重排序 |
| 多轮对话后忘掉之前内容 | Memory 窗口太小,或没有做持久化 | 查看对话历史是否在 Debug 提示词中被截断 | 调大max_characters;引入摘要记忆;实现长期记忆存储 |
| Agent 从不调用 RAG | 检索能力没有被暴露为工具,或系统提示词没有引导 | 检查 Agent 的工具列表和提示词模板 | 把检索器注册为工具,并在提示词中说明适合调用检索的场景 |
| 知识库更新后模型回答没变化 | 索引没有增量更新,或缓存未失效 | 查看向量库中文档版本 | 建立数据更新、索引重建、缓存清理流程 |
| 回答中出现了知识库没有的信息 | RAG 召回为空时模型自由发挥 | 检查召回结果是否为空,输出是否包含引用标记 | 设置“无命中时拒答”规则,并要求模型给出引用来源 |
除了这些基础排查项,生产环境还需要特别关注两个问题。
一是“引用溯源与 groundedness”。RAG 的回答必须能追溯到原文片段,不能只是让模型“看着像有依据”。实现时可以在知识片段中附带source_id,要求 LLM 在输出时标注引用的来源编号,并用程序校验最终答案是否确实基于这些片段。
二是“Agent 安全”。Agent 如果接入了工具调用,必须设置工具白名单、调用超时、最大迭代次数、日志审计和最小权限。不要让模型直接执行高权限命令,也不要让模型在没有边界的情况下反复调用 API。
8. 最佳实践与工程建议
8.1 RAG 侧的工程建议
文档切块是 RAG 最容易踩坑的地方。纯按固定 token 数切,容易把一句话从中间切断,导致语义不完整。更好的做法是优先按 Markdown 标题、章节、段落、表格结构切块;结构不明显的文档再使用固定长度加重叠。
切块之后,建议对每个片段生成摘要或关键词,一并存入向量库,这样能在检索时补充上下文。真正的企业级 RAG 还要设计引用溯源机制:每个片段对应文档 ID、页码、章节路径,生成回答后能反向校验是否覆盖所有关键证据。
8.2 Memory 侧的工程建议
Memory 不是“越多越好”。把全部历史都塞进提示词,只会让模型注意力分散,还会增加成本。建议区分短期记忆和长期记忆:短期记忆保留最近几轮,长期记忆按用户维度存储重要事实,在需要时通过向量召回或规则匹配取回。
Memory 数据往往包含用户隐私。长期记忆落库前必须做脱敏、权限控制和有效期管理。用户主动要求删除历史数据时,系统要能完整清除对应记录。
8.3 Agent 侧的工程建议
Agent 的价值在于“决策”,而不是“堆工具”。建议把 RAG 检索器、计算器、数据库查询等都封装成标准工具,并给每个工具写清楚适合调用的场景和返回示例。Agent 内部要设置最大步数和失败回退逻辑,避免陷入无限循环。
在设计组合方案时,可以采用分层策略。第一层是 Memory,负责维护对话状态。第二层是 RAG,负责按需检索知识。第三层是 Agent,负责判断当前任务是否需要调用第一层和第二层。这样即使某一块能力暂时失效,系统也能给出降级响应,而不是整体崩溃。
8.4 评测优先级
不要只靠直觉判断系统变好了。建议建立一套最小评测集,包含三类用例:需要知识库回答的用例、需要记忆上下文的用例、需要多步工具调用的用例。每次调整切块、Embedding、提示词或 Memory 策略后,都跑一遍评测集,对比召回命中率和最终回答准确率。
9. 总结与后续学习方向
RAG、Memory、Agent 并不是互相取代的关系。RAG 管知识来源,Memory 管状态记忆,Agent 管执行编排。在真实项目中,三者通常是一个组合系统:Agent 作为调度层,把 RAG 当作外部工具调用,把 Memory 当作工作状态的记录载体。
如果你正打算从零搭建一个智能问答应用,可以先按本文的最小示例跑通流程,再逐步替换成真实模型、语义向量库和生产级记忆存储。接下来值得深入的方向包括 Agentic RAG、长期记忆的向量化建模,以及引用溯源和评测机制。每个方向单独拿出来都足够做一个完整项目,但这篇文章给出的分层框架,可以帮你避免在最开始就走错路。