006|Agent Memory 设计:让 Agent 有记性
Memory 让 Agent 不只是单次推理,而是能记住上下文、用户偏好和历史事实。短期记忆管一次对话,长期记忆管跨会话。
核心结论:Memory(读作:麦·莫·瑞)让 Agent 有"记性"。短期记忆管一次对话,长期记忆管跨会话。Buffer、Window、Summary、Vector、Entity 五种方式各有适用场景。Memory 和 RAG 不一样:一个记"聊过什么",一个查"事实是什么"。
一、为什么 Agent 需要 Memory
LLM 本质上是无状态的。每次调用它,都是一次独立的推理。它不会自动记住你上一轮说了什么。
但 Agent 是要连续做任务的:
用户:帮我查一下上个月广东的销售额。
Agent:好的,上个月广东销售额是 1200 万。
用户:那环比呢?
Agent:???(不知道你说的"那"指什么)
没有 Memory,Agent 就接不住上下文。
二、Memory 解决什么问题
| 问题 | Memory 的作用 |
|---|---|
| 多轮对话 | 记住历史对话 |
| 用户偏好 | 记住用户习惯和设定 |
| 任务状态 | 记住任务执行到哪一步 |
| 知识积累 | 记住从交互中学到的东西 |
| 个性化 | 根据不同用户给出不同回答 |
三、短期记忆 vs 长期记忆
短期记忆(Short-term Memory / 肖特·特姆 麦·莫·瑞)
定义: 当前会话内的上下文,用完就可能丢。
特点:
- 存在内存里
- 会话结束就清空
- 容量有限,受 Context Window(康·泰克斯特·温·道)限制
- 访问速度快
例子:
用户:帮我查一下上个月广东的销售额。
Agent:1200 万。
用户:那环比呢?
Agent:根据刚才的结果,环比下降 15%。
这里的"刚才的结果"就是短期记忆。
长期记忆(Long-term Memory / 朗·特姆 麦·莫·瑞)
定义: 跨会话、跨任务保留的信息。
特点:
- 存在数据库、向量库、文件里
- 可以持久化
- 容量大
- 需要检索
- 访问速度慢一些
例子:
用户:(昨天)我是做跨境电商的,以后分析都用跨境视角。
用户:(今天)帮我看看美国站的销售额。
Agent:好的,从美国站跨境视角分析,本月销售额...
这里的"做跨境电商"就是长期记忆。
对比表
| 维度 | 短期记忆 | 长期记忆 |
|---|---|---|
| 存储位置 | 内存 | 数据库 / 向量库 |
| 生命周期 | 一次会话 | 长期 |
| 容量 | 小 | 大 |
| 速度 | 快 | 慢 |
| 检索方式 | 直接读取 | 需要检索 |
| 典型实现 | Context Window | Redis、Chroma、Postgres |
| 面试考点 | 上下文管理 | 个性化、持久化 |
四、常见的 Memory 类型
1. Buffer Memory(巴·弗·麦·莫·瑞)
最简单粗暴:把所有历史对话原封不动塞进 prompt。
memory = [
{"role": "user", "content": "查广东销售额"},
{"role": "assistant", "content": "1200万"},
{"role": "user", "content": "那环比呢"}
]
prompt = f"历史对话:{memory}
请回答:{最新问题}"
优点: 简单、信息完整
缺点: 上下文长了 token 爆炸,容易超过 Context Window,成本高
适合: 短对话、测试 demo
2. Window Memory(温·道·麦·莫·瑞)
只保留最近 N 轮对话。
memory = 最近5轮对话
prompt = f"历史对话(最近5轮):{memory}
请回答:{最新问题}"
优点: 控制上下文长度,实现简单
缺点: 会丢失更早信息,N 选不好关键信息会丢
适合: 多轮对话,对历史依赖不强的任务
3. Summary Memory(萨·莫·瑞·麦·莫·瑞)
让 LLM 把历史对话压缩成摘要。
# 原始对话
history = [
{"role": "user", "content": "我叫张三,做跨境电商"},
{"role": "assistant", "content": "好的,我记住了"},
{"role": "user", "content": "查一下美国站销售额"},
{"role": "assistant", "content": "100万"},
...
]
# 压缩成摘要
summary = LLM总结(history)
# 结果:"用户叫张三,做跨境电商,主要关注美国站销售额。"
prompt = f"历史摘要:{summary}
请回答:{最新问题}"
优点: 极大节省 token,保留关键信息
缺点: 摘要会丢失细节,多一步 LLM 调用增加延迟,摘要质量影响后续效果
适合: 长对话,需要记住核心事实的场景
4. Vector Memory(维克·托·麦·莫·瑞)
把历史对话转向量,存储到向量数据库,按需检索。
# 把每轮对话转成向量
for message in history:
vector = embedding_model.encode(message)
vector_db.add(vector, message)
# 提问时检索相关历史
relevant_messages = vector_db.query(最新问题, top_k=5)
prompt = f"相关历史:{relevant_messages}
请回答:{最新问题}"
优点: 只返回相关内容,可以处理大量历史,支持语义匹配
缺点: 检索质量决定效果,延迟更高,需要维护向量库
适合: 超长历史,只需要部分相关信息
5. Entity Memory(恩·梯·梯·麦·莫·瑞)
专门提取和记忆"实体"信息:用户名、身份、偏好、业务关键对象。
entities = {
"用户名": "张三",
"行业": "跨境电商",
"关注站点": "美国站",
"常用指标": "销售额、转化率"
}
prompt = f"已知用户信息:{entities}
请回答:{最新问题}"
优点: 结构化,检索快,不会丢失关键事实,适合做个性化
缺点: 需要抽取实体,实体关系复杂时难维护
适合: 用户画像、客户关系管理、长期个性化服务
五、Memory 类型对比表
| Memory 类型 | 读法 | 核心思想 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|---|---|
| Buffer Memory | 巴·弗·麦·莫·瑞 | 保留全部对话 | 完整 | token 多 | 短对话 |
| Window Memory | 温·道·麦·莫·瑞 | 保留最近 N 轮 | 简单 | 丢失历史 | 一般多轮对话 |
| Summary Memory | 萨·莫·瑞·麦·莫·瑞 | 摘要压缩 | 省 token | 丢细节 | 长对话 |
| Vector Memory | 维克·托·麦·莫·瑞 | 向量检索相关历史 | 可处理大量历史 | 检索质量决定效果 | 超长历史 |
| Entity Memory | 恩·梯·梯·麦·莫·瑞 | 提取结构化实体 | 快、稳定 | 抽取逻辑复杂 | 用户画像 |
六、Memory 在代码里怎么实现
简单版:Buffer + Window
class Memory:
def __init__(self, max_turns=5):
self.history = []
self.max_turns = max_turns
def add(self, role, content):
self.history.append({"role": role, "content": content})
# 只保留最近 N 轮
if len(self.history) > self.max_turns * 2:
self.history = self.history[-self.max_turns * 2:]
def get(self):
return self.history
高级版:短期 + 长期分离
class AgentMemory:
def __init__(self):
# 短期:当前会话上下文
self.short_term = []
# 长期:向量数据库
self.long_term = VectorDB()
def add(self, role, content):
self.short_term.append({"role": role, "content": content})
# 重要信息存入长期记忆
if self.is_important(content):
self.long_term.add(content)
def get_context(self, question):
# 1. 短期记忆直接拿
short = self.short_term[-5:]
# 2. 长期记忆检索相关
long = self.long_term.query(question, top_k=3)
return {
"short_term": short,
"long_term": long
}
七、Memory 和 RAG 的区别
这是面试非常爱问的点。
| 维度 | Memory | RAG |
|---|---|---|
| 数据来源 | 用户历史交互 | 知识库文档 |
| 目的 | 让 Agent 记住"谁、聊过什么" | 让 Agent 有"事实依据" |
| 存储内容 | 对话、偏好、实体 | 文档片段、schema |
| 更新频率 | 每次交互都可能更新 | 相对固定,定期更新 |
| 检索方式 | 直接读取 / 向量检索 | 向量检索 |
| 典型问题 | "我昨天说过什么" | "这个指标怎么算" |
一句话区别
Memory 是记住"你和我说过什么",RAG 是知道"这件事本身是什么"。
八、Memory 设计的常见坑
坑 1:上下文太长,token 爆炸
表现: 对话越跑越慢,成本越来越高,超过 Context Window 报错。
解决: Window Memory 限制轮数、Summary Memory 压缩、只保留必要字段。
坑 2:重要信息被窗口截断
表现: 用户说"我是 VIP",过几轮 Agent 忘了,服务降级。
解决: 把关键信息抽成 Entity Memory,单独存到长期记忆,每轮 prompt 都带上。
坑 3:长期记忆检索不准确
表现: 用户问"上次说的那个项目",检索出来的不是那次对话。
解决: 历史记录加 metadata(时间、主题),混合检索(向量 + 关键词),给记忆打分排序。
坑 4:记忆和隐私冲突
表现: 记住用户敏感信息,不同用户互相看到,合规问题。
解决: 按用户 ID 隔离记忆,敏感信息脱敏,设置记忆过期时间,用户可删除自己的记忆。
坑 5:记忆更新冲突
表现: 用户昨天说"我喜欢红色",今天说"我喜欢蓝色",Agent 不知道以哪个为准。
解决: 给记忆加时间戳,标记最新偏好,冲突时主动询问。
九、面试高频问题
Q1:Agent 的 Memory 怎么设计?
答: 我一般会分两层。短期记忆存在内存里,保留当前会话最近 N 轮,控制 Context Window。长期记忆用向量数据库或结构化数据库,存储用户画像、关键实体、历史摘要。提问时,短期直接读,长期按需检索。
Q2:Memory 和 RAG 有什么区别?
答: Memory 是 Agent 记住用户交互历史,RAG 是 Agent 从知识库检索事实。Memory 是"我们聊过什么",RAG 是"这件事本身是什么"。两者可以互补:RAG 提供知识,Memory 提供上下文。
Q3:多轮对话中怎么避免上下文爆炸?
答: 三种方法:1. 用 Window Memory 限制轮数;2. 用 Summary Memory 压缩摘要;3. 用 Vector Memory 只检索相关历史。实际生产中常组合使用。
Q4:长期记忆怎么更新?
答: 可以在每次交互后,用 LLM 提取关键信息,更新 Entity Memory 或 Summary。也可以定期批量总结。更新时要处理冲突,比如新偏好覆盖旧偏好,或者保留多个偏好。
Q5:Memory 怎么和 LangGraph 结合?
答: 在 LangGraph 里,State 的一部分就是 Memory。可以设计一个 memory 字段,每次节点执行前读取相关记忆,执行后把新信息写回记忆。记忆本身可以存在 State 里,也可以外接到 Redis、向量库。
十、Memory 在 LangGraph 里的位置
class AgentState(TypedDict):
question: str
# 短期记忆
short_term_memory: list
# 长期记忆检索结果
long_term_memory: list
# 最终输出
final_output: str
def 记忆加载节点(state):
question = state["question"]
short = state["short_term_memory"][-5:]
long = vector_db.query(question)
return {
"short_term_memory": short,
"long_term_memory": long
}
def 记忆保存节点(state):
new_memory = state["final_output"]
vector_db.add(new_memory)
return {}
十一、一句话总结
Memory 让 Agent 有"记性"。短期记忆管一次对话,长期记忆管跨会话。Buffer、Window、Summary、Vector、Entity 五种方式各有适用场景。Memory 和 RAG 不一样:一个记"聊过什么",一个查"事实是什么"。
下一步可以读
- 007|Agent 面试高频问题串讲(已更新)
- 008|Agent 项目工程化:代码组织、测试、部署(待更新)