Agent :记忆与 RAG的区别与联系
适用读者:正在学习 / 开发 LLM Agent、RAG 系统的工程师。本文从概念、对比、存储底座、代码实现、优化手段五个层面,把 Agent 记忆(Memory)与 RAG 的关系讲透。
目录
为什么 Agent 需要 “记忆” 和 “检索”
概念基础:RAG 与 Agent 记忆
核心区别:一张表 + 六个维度
共同联系:数据库是两者的底座
记忆的实现方式(含完整代码)
记忆与 RAG 的优化手段
落地架构:记忆 + RAG + Agent 如何配合
总结与常见误区
一、为什么 Agent 需要 “记忆” 和 “检索”
大语言模型本身有三个天然缺陷:
上下文有限:GPT 类模型的上下文窗口再大也有上限,长对话必然溢出;
知识静态:模型训练完成后知识就冻结了,无法知道训练截止日之后的新事实;
无状态:模型是 “一次性” 的 —— 每次调用都是独立函数,不记得上一轮说过什么。
RAG 和 Agent 记忆分别补上了两块短板:
| 缺陷 | 对应解法 | 作用 |
|---|---|---|
| 知识静态、可能过时 | RAG(检索增强生成) | 把外部知识库实时检索出来,注入 Prompt |
| 无状态、上下文有限 | Agent 记忆(Memory) | 把对话历史、用户偏好、状态持久化,跨轮 / 跨会话复用 |
一句话概括:
RAG 解决 “模型不知道” 的问题,记忆解决 “模型不记得” 的问题。
二、概念基础:RAG 与 Agent 记忆
2.1 RAG:检索增强生成
RAG(Retrieval-Augmented Generation)在生成前先检索外部知识,把检索结果拼进 Prompt,再让模型基于这些材料回答。
\[文档库] ──▶ \[切分 Chunk] ──▶ \[Embedding 向量化] ──▶ (向量数据库)   ▲ \[用户问题] ──▶ \[Query Embedding] ────────────────────────────┘   ▼   \[相似度检索 Top-K] ──▶ \[注入 Prompt] ──▶ \[LLM 生成回答]典型流程:
离线索引:文档 → 切分(chunk)→ 向量化(embedding)→ 写入向量数据库;
在线检索:用户问题 → 向量化 → 在向量库中找最相似的 Top-K 片段;
生成:检索结果 + 问题 + 指令拼成 Prompt → LLM 输出。
特点:知识来源是外部静态文档,可实时更新(重新索引)、可溯源(引用原文)、无需训练模型。
2.2 Agent 记忆:状态的持久化
Agent 记忆把 “这个用户是谁、之前聊了什么、任务做到哪一步” 等运行时状态存下来,让 Agent 表现出连续性和个性化。
学术界把记忆分为四类(参考认知科学对记忆的划分):
| 记忆类型 | 英文 | 含义 | 存储形态 |
|---|---|---|---|
| 短期记忆 | Short-term / Working Memory | 当前会话内上下文、任务中间状态 | 上下文列表、缓存 |
| 情景记忆 | Episodic Memory | 发生过的事件(“上周帮用户改过合同”) | 事件日志、向量库 |
| 语义记忆 | Semantic Memory | 沉淀出的事实与偏好(“用户是律师”) | 键值表、向量库 |
| 程序性记忆 | Procedural Memory | 技能 / 工具使用流程 | 代码、Prompt 模板、工具描述 |
特点:数据来源是交互过程本身,动态写入、持续演化、高度个性化。
三、核心区别:一张表 + 六个维度
3.1 对比总表
| 维度 | RAG(检索增强生成) | Agent 记忆(Memory) |
|---|---|---|
| 要解决的问题 | 模型知识不足 / 过时 | 模型无状态 / 不连续 |
| 数据来源 | 外部知识库:文档、网页、数据库记录 | 交互历史:对话、行为、反馈、状态 |
| 写入方式 | 批量索引(离线 / 定时),静态 | 实时增量写入,随对话演化 |
| 读取方式 | 每次问答都检索外部知识 | 按需召回,常与短期上下文拼接 |
| 数据粒度 | 文档块(chunk),面向 “知识” | 事件 / 事实 / 偏好 / 状态,面向 “个体” |
| 时效性 | 知识更新依赖重新索引 | 天然反映最新交互 |
| 个性化程度 | 低(同一知识库对所有用户一致) | 高(记忆是用户专属的) |
| 可溯源 | 强(可引用原文出处) | 弱(记忆是加工后的摘要 / 事实) |
| 生命周期 | 知识库常驻,随文档更新 | 随用户关系长期演化,可遗忘可沉淀 |
| 典型存储 | 向量数据库为主 | 向量库 + 关系库 + KV + 图库组合 |
3.2 六个关键差异点详解
差异一:回答的是不同问题
RAG:给模型喂 “知识”(What)。
记忆:给模型喂 “经历”(Who / When / How)。
差异二:数据的写路径不同
RAG 是 “一次索引、多次读取”,写操作低频;
记忆是 “每轮对话都在写”,写操作高频且需要去重、冲突解决。
差异三:召回策略不同
RAG 是 “必检”:每轮都检索知识库(除非命中缓存);
记忆是 “按需”:需要时才从长期记忆召回,日常对话主要用短期上下文。
差异四:个性化程度
同一个 RAG 知识库,服务所有用户,答案一致;
记忆严格按用户隔离,是 “千人千面” 的来源。
差异五:遗忘机制
RAG 靠删除文档 / 重新索引实现 “遗忘”;
记忆需要专门的遗忘策略(过期、重要性评分、用户主动删除)。
差异六:失败模式
RAG 失败:检索不到相关内容 → 幻觉风险;
记忆失败:召回错误记忆 → 答非所问或泄露历史信息。
3.3 两者不能互相替代
RAG 替代不了记忆:RAG 不知道 “用户上次聊到哪了”,无法跨轮保持上下文。
记忆替代不了 RAG:记忆是交互产物,装不下企业知识库;把公司全部文档写进记忆既贵又不可维护。
正确姿势是组合:短期记忆承载当前对话,长期记忆承载个性化,RAG 承载外部知识。
\[用户] ──▶ \[Agent 主循环] ──┬──▶ \[短期记忆:当前会话]   ├──▶ \[长期记忆:用户画像/历史]   └──▶ \[RAG:外部知识检索]   │   ▼   \[Prompt 组装] ──▶ \[LLM] ──▶ \[输出(并回写记忆)]四、共同联系:数据库是两者的底座
RAG 和记忆的 “殊途同归” 点在于:它们都是 “读数据库 → 拼 Prompt → 生成” 的模式,区别只是存了什么、怎么索引。
4.1 共同的架构位置
┌─────────────── 数据层:各类数据库 ───────────────┐ │ │ │ 向量数据库 关系型数据库 键值/缓存 图数据库 │ │ Chroma/FAISS MySQL/PG Redis Neo4j │ │ Milvus/pgvector │ │ │ │ 对象存储:原始文档 / 历史归档(S3、MinIO) │ │ │ └──────────────────────┬───────────────────────────┘   │   ┌────────────┴────────────┐   ▼ ▼   \[RAG 管道] \[Agent 记忆层]4.2 数据库选型对照表
| 数据库类型 | 代表产品 | RAG 中承担的角色 | 记忆中承担的角色 |
|---|---|---|---|
| 向量数据库 | Chroma、FAISS、Milvus、Qdrant、Pinecone、pgvector | 存文档 chunk 的 embedding,做相似度检索 | 存历史对话 / 事件 / 事实的 embedding,做语义召回 |
| 关系型数据库 | PostgreSQL、MySQL、SQLite | 存文档元数据、chunk 归属、权限 | 存用户表、偏好表、状态表、结构化事件 |
| 键值 / 缓存 | Redis | 检索结果缓存、embedding 缓存 | 短期记忆、会话状态、TTL 自动过期 |
| 图数据库 | Neo4j、NebulaGraph | 知识图谱增强检索(实体关系) | 实体关系记忆(“用户 A 与项目 B 的关系”) |
| 对象存储 | S3、MinIO | 原始文档归档 | 长音频 / 图片等非结构化历史 |
4.3 关键点:为什么 “向量数据库” 是交集
两者都需要语义检索,所以向量数据库成为最大交集:
RAG:
query_embeddingvsdocument_chunk_embedding记忆:
query_embeddingvsmemory_item_embedding
两者共用同一套 “Embedding → 近似最近邻(ANN)检索” 技术栈。这也是为什么很多框架(LangChain、LlamaIndex)里 RAG 检索器和记忆检索器的 API 几乎一样。
五、记忆的实现方式(含完整代码)
下面从简到繁给出 5 种记忆实现,代码基于 Python,依赖openai(或任何兼容接口)、chromadb、sqlite3。所有代码是可直接运行的骨架,换掉 embedding 和 LLM 客户端即可上生产。
5.1 短期记忆:上下文窗口管理(最基础)
短期记忆的核心是 “放得下”—— 用 token 预算管理上下文,超出就丢最老的。
"""短期记忆:带 Token 预算的滑动窗口上下文管理""" class ShortTermMemory:   def \_\_init\_\_(self, max\_tokens: int = 4000, encoder=None):   self.messages = \[] # \[{"role": ..., "content": ...}]   self.max\_tokens = max\_tokens   # encoder: 传入分词器(如 tiktoken / transformers 的 tokenizer)   self.\_encoder = encoder   def \_count\_tokens(self, text: str) -> int:   if self.\_encoder is None:   # 兜底估算:中英文混排约 1 字符 ≈ 0.6\~1 token   return int(len(text) \* 0.8)   return len(self.\_encoder.encode(text))   def add(self, role: str, content: str):   self.messages.append({"role": role, "content": content})   self.\_trim()   def \_trim(self):   """超出预算时丢弃最老的中间消息,始终保留 system 指令"""   keep = \[m for m in self.messages if m\["role"] == "system"]   rest = \[m for m in self.messages if m\["role"] != "system"]   total = sum(self.\_count\_tokens(m\["content"]) for m in keep)   trimmed = \[]   for m in reversed(rest): # 从最新往旧加   total += self.\_count\_tokens(m\["content"])   if total > self.max\_tokens:   break   trimmed.append(m)   self.messages = keep + list(reversed(trimmed))   def get(self):   return self.messages \# 使用示例 mem = ShortTermMemory(max\_tokens=2000) mem.add("system", "你是一个智能助手") mem.add("user", "我叫小明") mem.add("assistant", "你好小明") print(mem.get())局限:只解决 “当前会话不溢出”,跨会话就丢了。要跨会话,需要下面几种长期记忆。
5.2 长期记忆:向量化存储 + 语义召回
把历史对话切成条目,embedding 后存进向量库;新会话按问题语义召回相关记忆注入 Prompt。
"""长期记忆:向量库 + 语义召回(使用 ChromaDB)""" import chromadb from chromadb.utils import embedding\_functions class LongTermMemory:   def \_\_init\_\_(self, collection\_name: str = "agent\_memory",   embed\_model="text-embedding-3-small"):   self.client = chromadb.PersistentClient(path="./memory\_db")   self.ef = embedding\_functions.OpenAIEmbeddingFunction(   api\_key="sk-xxx", model\_name=embed\_model   )   \# 若已有同名 collection 则复用   try:   self.col = self.client.get\_collection(   collection\_name, embedding\_function=self.ef)   except Exception:   self.col = self.client.create\_collection(   collection\_name, embedding\_function=self.ef)   def write(self, memory\_id: str, text: str, metadata: dict = None):   """写入一条记忆:文本 + 元数据(时间、用户、类型)"""   self.col.upsert(   ids=\[memory\_id],   documents=\[text],   metadatas=\[metadata or {"time": "", "type": "fact"}],   )   def recall(self, query: str, top\_k: int = 5, user\_id: str = None) -> list:   """按语义召回记忆;可加 where 条件过滤用户"""   where = {"user\_id": user\_id} if user\_id else None   res = self.col.query(   query\_texts=\[query], n\_results=top\_k, where=where   )   return res\["documents"]\[0]   def forget(self, memory\_id: str):   self.col.delete(ids=\[memory\_id]) \# 使用示例 mem = LongTermMemory() mem.write("u1-pref-1", "用户是前端工程师,主用 TypeScript",   {"user\_id": "u1", "type": "preference"}) mem.write("u1-hist-1", "上周用户问过 React 服务端渲染的优化方案",   {"user\_id": "u1", "type": "episode"}) hits = mem.recall("用户做什么开发", user\_id="u1") print(hits) # \['用户是前端工程师,主用 TypeScript', ...]关键设计点:
每条记忆要有
user_id,严格按用户隔离(记忆是隐私数据);元数据至少包含:类型(偏好 / 事件 / 事实)、时间戳、来源;
upsert保证重复写入以 id 去重。
5.3 摘要记忆:滚动压缩(长对话不丢信息)
对话太长时,与其丢掉老内容,不如让模型把老内容压缩成摘要再存下来。
"""摘要记忆:滚动对话摘要(LangChain 风格,手写版)""" class SummaryMemory:   def \_\_init\_\_(self, llm\_client, summarize\_prompt: str = None):   self.llm = llm\_client # 任意 LLM 客户端   self.summary = "" # 已积累的摘要   self.buffer = \[] # 待处理的近期对话   self.buffer\_limit = 6 # 每 6 轮压缩一次   self.summarize\_prompt = summarize\_prompt or (   "把下面对话压缩为不超过 3 句话的摘要,保留关键事实、决定和用户偏好。\n"   "已有摘要:{summary}\n新增对话:\n{buffer}"   )   def add(self, role: str, content: str):   self.buffer.append({"role": role, "content": content})   if len(self.buffer) >= self.buffer\_limit:   self.\_compress()   def \_compress(self):   prompt = self.summarize\_prompt.format(   summary=self.summary,   buffer="\n".join(f"{m\['role']}: {m\['content']}" for m in self.buffer),   )   self.summary = self.llm.chat(prompt) # 伪代码:换真实客户端   self.buffer = \[]   def build\_prompt(self) -> str:   """组装给 Agent 的上下文:摘要 + 近期缓冲"""   recent = "\n".join(f"{m\['role']}: {m\['content']}" for m in self.buffer)   return f"\[历史摘要]\n{self.summary}\n\[近期对话]\n{recent}"特点:上下文占用从 O (对话轮数) 降到 O (摘要长度),但摘要过程有信息损耗,适合与向量记忆配合使用。
5.4 结构化记忆:SQL 存事实与状态
偏好、任务状态这类 “结构化事实” 适合用关系型数据库,便于精确查询和统计。
"""结构化记忆:SQLite 存用户偏好与任务状态""" import sqlite3 import json class StructuredMemory:   def \_\_init\_\_(self, db\_path: str = "agent\_state.db"):   self.conn = sqlite3.connect(db\_path)   self.conn.execute("""   CREATE TABLE IF NOT EXISTS memories (   user\_id TEXT NOT NULL,   key TEXT NOT NULL,   value TEXT NOT NULL, -- JSON 序列化   updated\_at TEXT DEFAULT (datetime('now')),   PRIMARY KEY (user\_id, key)   )   """)   self.conn.commit()   def set(self, user\_id: str, key: str, value):   self.conn.execute(   "INSERT OR REPLACE INTO memories (user\_id, key, value) VALUES (?,?,?)",   (user\_id, key, json.dumps(value, ensure\_ascii=False)),   )   self.conn.commit()   def get(self, user\_id: str, key: str):   row = self.conn.execute(   "SELECT value FROM memories WHERE user\_id=? AND key=?",   (user\_id, key),   ).fetchone()   return json.loads(row\[0]) if row else None   def query\_by\_type(self, user\_id: str, key\_prefix: str):   """按前缀查一类记忆,例如 key 用 'pref.\*' / 'task.\*' 命名空间"""   rows = self.conn.execute(   "SELECT key, value FROM memories WHERE user\_id=? AND key LIKE ?",   (user\_id, key\_prefix + "%"),   ).fetchall()   return {k: json.loads(v) for k, v in rows} \# 使用示例 sm = StructuredMemory() sm.set("u1", "pref.language", "中文") sm.set("u1", "task.current", {"id": 42, "step": "review"}) print(sm.get("u1", "pref.language")) # 中文 print(sm.query\_by\_type("u1", "task.")) # {'task.current': {...}}要点:用key做命名空间(pref.*、task.*、fact.*),写入用INSERT OR REPLACE天然去重。
5.5 完整的最小 Agent 记忆系统
把上面几种组合成一个可运行的最小 Agent:短期窗口 + 长期向量记忆 + 结构化偏好。
"""最小完整 Agent:短期 + 长期 + 结构化记忆组合""" from dataclasses import dataclass, field @dataclass class MinimalAgent:   short: ShortTermMemory = field(default\_factory=ShortTermMemory)   long: LongTermMemory = field(default\_factory=LongTermMemory)   structured: StructuredMemory = field(default\_factory=StructuredMemory)   user\_id: str = "default\_user"   def run(self, user\_input: str) -> str:   \# 1) 从长期记忆召回相关历史(语义)   recalled = self.long.recall(user\_input, top\_k=3, user\_id=self.user\_id)   \# 2) 读取结构化偏好   prefs = self.structured.query\_by\_type(self.user\_id, "pref.")   \# 3) 组装上下文   context = {   "recalled\_memories": recalled,   "preferences": prefs,   }   \# 4) 此处调用 LLM:prompt = 系统指令 + context + 短期窗口   \# response = llm.chat(prompt) # 伪代码   response = f"\[模拟回复] 召回 {len(recalled)} 条记忆"   \# 5) 写入短期记忆,并把重要信息沉淀到长期记忆   self.short.add("user", user\_input)   self.short.add("assistant", response)   self.\_consolidate(user\_input, response)   return response   def \_consolidate(self, user\_input: str, response: str):   """把本轮对话沉淀为长期记忆条目(生产环境应由 LLM 抽取要点)"""   memory\_id = f"{self.user\_id}-{hash(user\_input) % 100000}"   self.long.write(   memory\_id,   f"用户说:{user\_input};助手答:{response}",   {"user\_id": self.user\_id, "type": "episode"},   )运行闭环:每轮对话 = 召回 → 组装 → 生成 → 写入 → 沉淀。
六、记忆与 RAG 的优化手段
6.1 检索优化(RAG 和记忆共用)
(1)混合检索:向量 + 关键词(BM25)
纯向量检索对专有名词、代码、ID 类查询不敏感,混合检索可显著提升召回率。
"""混合检索:向量召回 + BM25 关键词召回 + 融合""" from rank\_bm25 import BM25Okapi def hybrid\_search(query: str, chunks: list, embed\_fn, top\_k: int = 5):   """chunks: 候选片段列表(或直接从向量库取更大召回集再融合)"""   \# 向量召回(此处简化为两两相似度,生产用向量库 ANN)   import numpy as np   q\_vec = embed\_fn(\[query])\[0]   c\_vecs = embed\_fn(chunks)   vec\_scores = (c\_vecs @ q\_vec).tolist() # 余弦相似度   \# 关键词召回   tokenized = \[c.split() for c in chunks]   bm25 = BM25Okapi(tokenized)   bm25\_scores = bm25.get\_scores(query.split())   \# 融合:归一化后加权相加(RRF 更稳健,见下)   v\_norm = np.array(vec\_scores) / max(np.max(vec\_scores), 1e-9)   b\_norm = np.array(bm25\_scores) / max(np.max(bm25\_scores), 1e-9)   fused = 0.7 \* v\_norm + 0.3 \* b\_norm   idx = np.argsort(fused)\[::-1]\[:top\_k]   return \[chunks\[i] for i in idx](2)Rerank 重排序:先粗召回 Top-100,再用重排模型(如 Cohere Rerank /bge-reranker)精排 Top-5,精度提升明显。
\# 伪代码:粗召回 → 精排 def retrieve\_with\_rerank(query, candidate\_ids, rerank\_fn, top\_k=5):   candidates = \[load\_chunk(i) for i in candidate\_ids] # 粗召回 Top-100   scores = rerank\_fn(query, candidates) # 重排模型打分   ordered = \[c for \_, c in sorted(zip(scores, candidates), reverse=True)]   return ordered\[:top\_k](3)查询改写(Query Rewrite):对口语化 / 指代不明的问题,先让 LLM 改写 / 生成多个子查询(HyDE 生成假设文档)再检索。
6.2 写入优化(记忆特有)
(1)记忆整合(Consolidation):高频相似记忆 → 定期合并成更高层的摘要,避免记忆碎片化。
"""记忆整合:定期把同类记忆合并为一条""" def consolidate\_memories(memories: list, llm\_client) -> str:   """memories: 同一主题的多条原始记忆 -> 返回合并摘要"""   joined = "\n".join(f"- {m}" for m in memories)   prompt = ("以下是与同一主题相关的记忆,请合并去重,提炼成不超过 100 字的"   f"总结,保留所有不重复的事实:\n{joined}")   return llm\_client.chat(prompt)(2)记忆评分与遗忘:给每条记忆打 “重要性 + 最近使用” 分,低分 / 过期记忆定期清理或降级归档。参考 Ebbinghaus 遗忘曲线思路:长期未使用的记忆降权。
"""记忆评分:重要性 × 新鲜度,低于阈值则遗忘""" import time def memory\_score(importance: float, last\_accessed: float, now: float,   half\_life: float = 30 \* 86400) -> float:   """half\_life: 30 天;越久没访问,新鲜度衰减越快"""   freshness = 0.5 \*\* ((now - last\_accessed) / half\_life)   return importance \* freshness \# 示例:重要性 0.9,10 天未访问 score = memory\_score(0.9, time.time() - 10 \* 86400, time.time()) \# 若 score < 0.2 -> 删除或移动到冷存储(3)冲突解决:同一事实的新旧记忆矛盾时,默认 “新值覆盖旧值”,但保留旧值到审计表。
6.3 成本优化
| 手段 | 做法 | 收益 |
|---|---|---|
| 上下文压缩 | 摘要记忆 / 关键信息抽取后再入 Prompt | 大幅降低 token 成本 |
| 缓存 | Redis 缓存相同问题的检索结果 / LLM 回复 | 命中率提升后延迟和费用双降 |
| 记忆分级 | 热记忆(常用)常驻,冷记忆(低频)存廉价存储 | 控制向量库规模 |
| 增量索引 | RAG 只重索引变更文档,而非全量重建 | 减少索引成本 |
6.4 架构优化:分层记忆 + 反思
\[每轮对话] ──▶ 是否重要?   │是 │否   ▼ ▼   \[工作记忆·短期] \[丢弃/极简记录]   │   ▼   \[定期整合 Consolidation]   │   ▼   \[语义记忆·长期向量库] ──▶ \[按需召回]   │   ▼   \[反思:LLM 提炼用户画像] ──▶ \[用户画像·结构化存储] ──▶ \[按需召回]反思机制(Reflection):不是直接存原始对话,而是定期让 LLM 从多轮对话中提炼用户画像(“用户偏好深度优先的答案”" 用户反感空话 "),画像比原始对话更精炼、更好用。这也是 MemGPT /mem0 等框架的核心思想之一。
七、落地架构:记忆 + RAG + Agent 如何配合
7.1 分层上下文组装(生产级参考)
\[用户输入] ──▶ \[意图/路由] ──┬──▶ 需要外部知识?   │ │是 │否   │ ▼ ▼   │ \[RAG 检索] \[仅用记忆]   │ 文档向量库   ▼   \[记忆召回:短期窗口 + 长期语义 + 画像]   ▼   \[Prompt 组装:系统指令 + 记忆 + 检索片段 + 当前问题]   ▼   \[LLM] ──▶ \[输出] ──▶ \[写入闭环:更新短期/长期记忆]7.2 代码:RAG + 记忆同框的最小 Agent
"""Agent = 短期记忆 + 长期记忆 + RAG 检索,组合示例""" class RagMemoryAgent:   def \_\_init\_\_(self, llm, vector\_db, doc\_store, memory\_long):   self.llm = llm   self.vector\_db = vector\_db # 文档向量库(RAG 用)   self.doc\_store = doc\_store # 文档原始内容   self.memory\_long = memory\_long # 记忆向量库   self.history = \[] # 短期记忆   def answer(self, question: str, user\_id: str) -> str:   \# 1) 记忆召回   memories = self.memory\_long.recall(question, top\_k=3, user\_id=user\_id)   \# 2) RAG 检索   doc\_ids = self.vector\_db.query(question, top\_k=3)   passages = \[self.doc\_store.get(i) for i in doc\_ids]   \# 3) 组装(分层:记忆在前,知识在后,问题在最后)   prompt = "\n".join(\[   "\[用户相关记忆]",   \*memories,   "",   "\[参考资料]",   \*passages,   "",   "\[当前问题] " + question,   ])   answer = self.llm.chat(prompt)   \# 4) 写回记忆   self.history.append((question, answer))   self.memory\_long.write(f"{user\_id}-{len(self.history)}",   f"Q:{question}\nA:{answer}",   {"user\_id": user\_id, "type": "episode"})   return answer组装顺序经验:系统指令 → 用户记忆(个性化) → 检索知识(事实依据) → 短期上下文 → 当前问题。记忆与知识之间用分隔符区分,防止模型混淆来源。
八、总结与常见误区
8.1 一句话总结
RAG 和 Agent 记忆是 “同一套检索 - 生成架构的两个实例”:RAG 检索外部知识,记忆检索内部经历;它们的共同底座是数据库(尤其向量数据库),差别在于数据来源、写入方式、个性化程度和生命周期。
8.2 常见误区
“给 Agent 配了 RAG 就不需要记忆”—— 错,RAG 不解决连续性和个性化;
“记忆就是把所有对话塞进 Prompt”—— 错,上下文会溢出,必须压缩 / 检索 / 分级;
“记忆只能存向量库”—— 错,偏好和状态适合 SQL,热点会话适合 Redis,实体关系适合图库;
“RAG 和记忆是同义词”—— 错,前者解决知识,后者解决状态,二者互补而非互斥;
“记忆越多越好”—— 错,脏数据、过期数据、隐私数据会拉低效果甚至造成泄露,必须有写入治理和遗忘机制。
8.3 选型速查
| 你的场景 | 优先方案 |
|---|---|
| 回答基于公司知识库的问题 | RAG + 向量数据库 |
| 跨会话记住用户偏好 | 结构化记忆(SQL)+ 用户画像 |
| 长对话不丢上下文 | 短期窗口 + 滚动摘要 |
| 让 Agent 记住 “做过的事” | 情景记忆(向量库 + 元数据) |
| 全部都要 | 分层记忆 + RAG 组合架构 |
参考与延伸阅读
LangChain 文档:Memory 模块(ConversationBufferMemory / SummaryMemory 等)
MemGPT(Letta):分层记忆 + 自我编辑记忆的开源框架
mem0:面向 Agent 的长期记忆层,支持向量库 / 图库后端
《Retrieval-Augmented Generation for Large Language Models: A Survey》(Gao et al.)
LlamaIndex:RAG 与 Agent 记忆统一抽象(Memory / ChatMemoryBuffer)
注:文中代码为教学骨架,生产使用请补充鉴权、限流、隐私脱敏、数据备份与合规(如 GDPR 对用户数据删除权的支持)。