记忆管理 - Agent 的大脑 / Memory Management as the Brain of an Agent
📅 创建时间:2026-05-08 🏷️ 标签:#Memory #ContextWindow #会话管理 #记忆类型 📚 前置知识:[[03-rag-basics]]
📋 本章目标
- 理解为什么 Agent 需要记忆
- 掌握短时记忆与长时记忆的区别
- 了解三种记忆类型:语义、情景、程序
- 掌握上下文窗口的管理策略
- 能够实现基础的记忆管理方案
- 理解记忆压缩与摘要技术
第0部分:"记忆"到底存在哪里?
Function Calling 那篇已经讲了:LLM 每次 API 调用是无状态的——你把 messages 数组 POST 过去,它返回结果,然后就"忘了"。那"记忆"是什么?就是你的程序在 LLM 之外维护的数据,在每次 API 调用前拼进 messages 里。
0.1 短时记忆——就是 messages 数组本身
每次 API 调用时,你的 messages 大概长这样:
messages = [
{"role": "system", "content": "你是助手..."},
{"role": "user", "content": "我叫张三"},
{"role": "assistant", "content": "你好张三!"},
{"role": "user", "content": "帮我查下北京天气"},
{"role": "assistant", "content": None, "tool_calls": [...]},
{"role": "tool", "content": '{"temp": 25}'},
{"role": "assistant", "content": "北京今天25°C"},
{"role": "user", "content": "我刚才说我叫什么?"}, ← 新问题
]LLM 能看到整个数组,所以它"记得"你叫张三——因为 messages 的前几行里就有。所谓"短时记忆",就是你的程序把对话历史维护在一个数组里,每次 API 调用时原样发回去。 LLM 自己没存任何东西。
这个数组存在哪?存在你的程序内存里——就是一个 Python list 或 JS array。服务重启就没了,除非你把它持久化到数据库。
0.2 长时记忆——存在外部数据库,用时检索
短时记忆的问题是:messages 数组会越来越长,最终超出模型的 context window 限制(比如 GPT-4o 的 128K tokens,Claude 的 200K tokens)。而且把所有历史对话都塞进每次请求,token 费用也会爆炸。
长时记忆的思路是:把历史信息存到外部数据库,每次请求时只检索相关的部分拼进 prompt。
┌─────────────────────────────────────────────────────────────┐
│ 短时记忆 vs 长时记忆的数据流 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 短时记忆(Session Memory): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 你的程序维护一个 messages 数组 │ │
│ │ │ │
│ │ POST /v1/chat/completions │ │
│ │ {messages: [所有历史对话 + 新问题]} │ │
│ │ │ │
│ │ 存储位置:程序内存(Python list / Redis / session DB)│ │
│ │ 生命周期:当前对话期间 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 长时记忆(Long-term Memory): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 存储:向量数据库 / PostgreSQL / Redis │ │
│ │ │ │
│ │ 每次请求前: │ │
│ │ 1. 用户问题 → Embedding API → 查询向量 │ │
│ │ 2. 向量数据库.search(查询向量) → 找到相关记忆 │ │
│ │ 3. messages = [system_prompt, 相关记忆, 最近对话, 新问题]│ │
│ │ 4. POST /v1/chat/completions │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘关键认知:长时记忆和 RAG 本质上是同一套技术——把信息存到外部数据库,用 Embedding 做语义检索,在需要时注入 prompt。区别只在于存的内容不同:RAG 存的是外部文档,长时记忆存的是 Agent 自己和用户的互动历史。
0.3 一句话总结
| 记忆类型 | 存在哪里 | 怎么用 |
|---|---|---|
| 短时记忆 | 程序的 messages 数组(内存/Redis) | 每次 API 调用原样发回去 |
| 长时记忆 | 向量数据库/普通数据库 | 每次请求前检索相关内容,拼进 prompt |
带着这个数据流模型,下面的细节就好理解了。
第1部分:为什么 Agent 需要记忆?
1.1 LLM 的"失忆症"
┌─────────────────────────────────────────────────────────────┐
│ LLM 的记忆特性 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 每次 API 调用是独立的 │
│ │
│ 请求1: │
│ messages = [{"role": "user", "content": "我叫张三"}] │
│ → LLM 回复:"你好张三!" │
│ │
│ 请求2(完全独立,无记忆): │
│ messages = [{"role": "user", "content": "你叫什么?"}] │
│ → LLM 回复:"我没有名字,我是 AI 助手"(忘了张三) │
│ │
│ 结论:LLM 本身没有持久记忆 │
│ 记忆需要我们手动管理 │
│ │
└─────────────────────────────────────────────────────────────┘1.2 Agent 记忆的重要性
┌─────────────────────────────────────────────────────────────┐
│ Agent 记忆的价值 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 没有记忆的 Agent: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 用户:帮我订下周一去上海的机票 │ │
│ │ Agent:好的,请提供您的航班偏好... │ │
│ │ 用户:我之前说过要坐国航的... │ │
│ │ Agent:抱歉,我没有之前的对话记录 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 有记忆的 Agent: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 用户:帮我订下周一去上海的机票 │ │
│ │ Agent(查记忆):查到你之前偏好国航+靠窗座位 │ │
│ │ Agent:好的,为你搜索下周一国航上海航班,需要靠窗吗? │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘1.3 记忆的两个层级
┌─────────────────────────────────────────────────────────────┐
│ 记忆的两个层级 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 短时记忆(Short-term Memory) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 当前会话的上下文 │ │
│ │ • 消息历史 │ │
│ │ • 生命周期:当前会话 │ │
│ │ • 容量限制:上下文窗口(Context Window) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 长时记忆(Long-term Memory) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 跨会话的持久化信息 │ │
│ │ • 用户偏好、历史交互、积累知识 │ │
│ │ • 生命周期:永久(或很长时间) │ │
│ │ • 容量:理论上无限 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第2部分:短时记忆 — 上下文管理
2.1 上下文窗口的限制
┌─────────────────────────────────────────────────────────────┐
│ 主流模型的上下文窗口 │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 模型 │ 上下文窗口 │ 大约能放多少文字? │
│ ├───────────────┼────────────────┼────────────────────────┤
│ │ GPT-3.5-turbo│ 16,385 tokens │ 约 8000 字(4页) │
│ │ GPT-4o │ 128,000 tokens │ 约 6.4 万字(32页) │
│ │ Claude 3.5 │ 200,000 tokens │ 约 10 万字(50页) │
│ │ Gemini 1.5 │ 100万 tokens │ 约 50 万字(250页) │
│ │
│ 超出上下文窗口 = 被截断 / 报错 │
│ │
└─────────────────────────────────────────────────────────────┘2.2 上下文窗口的权衡
┌─────────────────────────────────────────────────────────────┐
│ 上下文窗口权衡 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 塞太少 ← 缺少关键信息 ← Agent 表现差 │
│ │
│ 塞太多 ← Token 成本高 ← 响应慢 ← 模型效果下降(丢失重点) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 最佳策略:只塞"最相关"的信息 │ │
│ │ │ │
│ │ 用户:"帮我写一封感谢邮件给李经理" │ │
│ │ │ │
│ │ ❌ 塞整个对话历史(100 条消息) │ │
│ │ ✅ 只塞:用户偏好(正式邮件)+ 李经理的联系方式 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2.3 上下文管理策略
策略1:滑动窗口(Sliding Window)
class SlidingWindowMemory:
"""只保留最近 N 条消息"""
def __init__(self, max_messages=10):
self.max_messages = max_messages
self.messages = []
def add(self, role, content):
self.messages.append({"role": role, "content": content})
# 超过限制时,丢弃最老的
if len(self.messages) > self.max_messages:
self.messages.pop(0)
def get_context(self):
return self.messages.copy()策略2:摘要策略(Summarization)
class SummarizedMemory:
"""定期摘要旧消息,保留关键信息"""
def __init__(self, llm, max_messages=20, summary_threshold=15):
self.llm = llm
self.max_messages = max_messages
self.summary_threshold = summary_threshold
self.messages = []
self.summary = "" # 历史摘要
def add(self, role, content):
self.messages.append({"role": role, "content": content})
# 当消息太多时,摘要早期内容
if len(self.messages) > self.summary_threshold:
# 摘要最近 10 条之前的所有内容
old_messages = self.messages[:-10]
new_summary = self._summarize(old_messages)
self.summary = f"{self.summary}\n{new_summary}".strip()
self.messages = self.messages[-10:]
def _summarize(self, messages):
prompt = f"请摘要以下对话的关键信息(保留重要事实和偏好):\n{messages}"
return self.llm.invoke(prompt).content策略3:选择性记忆(Selective Memory)
class SelectiveMemory:
"""只保留与当前任务相关的信息"""
def __init__(self, llm):
self.llm = llm
self.all_messages = [] # 全部历史
self.important_facts = [] # 重要事实
def add(self, role, content):
self.all_messages.append({"role": role, "content": content})
# 自动提取重要事实
if "喜欢" in content or "偏好" in content or "我叫" in content:
self.important_facts.append(content)
def get_relevant_context(self, query, max_tokens=4000):
"""只返回与查询相关的信息"""
# 简单策略:包含关键词的历史消息
keywords = self._extract_keywords(query)
relevant = []
for msg in self.all_messages[-20:]: # 只看最近 20 条
if any(kw in msg["content"] for kw in keywords):
relevant.append(msg)
# 加上重要事实
return self.important_facts + relevant第3部分:长时记忆 — 持久化存储
3.1 长时记忆的用途
┌─────────────────────────────────────────────────────────────┐
│ 长时记忆的用途 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 用户偏好记忆 │
│ • "用户喜欢正式语气" │
│ • "用户总是要靠窗座位" │
│ • "用户不喜欢浪费时间寒暄" │
│ │
│ 2. 历史交互摘要 │
│ • "用户之前问过 Python 问题" │
│ • "用户曾在 3 月份问过税务问题" │
│ │
│ 3. 知识积累 │
│ • "这是用户第三次问这个问题了" │
│ • "用户的工作是律师" │
│ │
│ 4. 跨会话连贯性 │
│ • "用户上周让我帮他设置的自动化还在跑吗?" │
│ │
└─────────────────────────────────────────────────────────────┘3.2 长时记忆的存储方案
方案1:向量数据库存储
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
class VectorMemory:
"""用向量数据库存储记忆"""
def __init__(self, collection_name="user_memory"):
self.embeddings = OpenAIEmbeddings()
self.vectorstore = Chroma(
collection_name=collection_name,
embedding_function=self.embeddings,
persist_directory="./memory_db"
)
def add(self, content, metadata=None):
"""添加记忆"""
self.vectorstore.add_texts(
texts=[content],
metadatas=[metadata or {}]
)
def recall(self, query, top_k=5):
"""根据查询找回相关记忆"""
results = self.vectorstore.similarity_search(query, k=top_k)
return [doc.page_content for doc in results]
def delete(self, memory_id):
"""删除记忆"""
self.vectorstore.delete(ids=[memory_id])方案2:键值对存储(适合结构化偏好)
import json
from pathlib import Path
class KVUserMemory:
"""键值对存储用户偏好"""
def __init__(self, user_id):
self.user_id = user_id
self.file_path = Path(f"./user_memory/{user_id}.json")
self.memory = self._load()
def _load(self):
if self.file_path.exists():
return json.loads(self.file_path.read_text())
return {}
def set(self, key, value):
self.memory[key] = value
self.file_path.parent.mkdir(parents=True, exist_ok=True)
self.file_path.write_text(json.dumps(self.memory, ensure_ascii=False))
def get(self, key, default=None):
return self.memory.get(key, default)
def get_all(self):
return self.memory.copy()
# 使用示例
memory = KVUserMemory("zhang_san")
memory.set("communication_style", "简洁专业")
memory.set("default_language", "中文")
memory.set("job_title", "律师")
preferences = memory.get_all()
# {"communication_style": "简洁专业", "default_language": "中文", "job_title": "律师"}3.3 组合方案
class HybridMemory:
"""组合短时 + 长时记忆"""
def __init__(self, user_id, llm):
self.short_term = SlidingWindowMemory(max_messages=20)
self.long_term_vector = VectorMemory(collection_name=f"user_{user_id}")
self.long_term_kv = KVUserMemory(user_id)
self.llm = llm
def add(self, role, content):
self.short_term.add(role, content)
# 自动提取重要信息到长时记忆
if self._is_important(content):
self.long_term_vector.add(content, {"role": role})
# 提取键值信息
self._extract_kv(content)
def get_context(self, query=None):
"""获取完整上下文"""
# 1. 短时记忆(最近对话)
short = self.short_term.get_context()
# 2. 相关长时记忆(向量检索)
if query:
relevant_long = self.long_term_vector.recall(query)
else:
relevant_long = []
# 3. 用户偏好
preferences = self.long_term_kv.get_all()
return {
"recent_messages": short,
"relevant_memories": relevant_long,
"user_preferences": preferences
}
def _is_important(self, content):
"""判断内容是否重要到需要存入长时记忆"""
important_keywords = ["喜欢", "不喜欢", "我叫", "我是", "以后", "总是", "从不"]
return any(kw in content for kw in important_keywords)
def _extract_kv(self, content):
"""从文本中提取键值对"""
# 简单实现,实际可以用 LLM 提取
pass第4部分:三种记忆类型
4.1 概念解释
┌─────────────────────────────────────────────────────────────┐
│ 三种记忆类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 语义记忆(Semantic Memory) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 事实和概念性的知识 │ │
│ │ "我叫张三"、"我喜欢喝美式咖啡" │ │
│ │ → 通常存储为键值对或向量 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 情景记忆(Episodic Memory) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 过去发生的事件序列 │ │
│ │ "用户上周五问过我关于年假的问题" │ │
│ │ → 通常存储为带时间戳的日志或摘要 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 程序记忆(Procedural Memory) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Agent 自己学到的行为模式 │ │
│ │ "用户每次问代码问题,我应该先问用什么语言" │ │
│ │ → 通常存储为 Agent 的 System Prompt 或规则 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘4.2 在 Agent 中的应用
# 在构建 Agent 时整合三种记忆
class IntelligentAgent:
def __init__(self, user_id, llm):
self.memory = HybridMemory(user_id, llm)
def chat(self, user_input):
# 获取完整上下文
context = self.memory.get_context(user_input)
# 构建 System Prompt(程序记忆)
preferences = context["user_preferences"]
system_prompt = f"""你是一个助手。
用户偏好:{preferences}
沟通风格:{preferences.get('communication_style', '普通')}
语言:{preferences.get('default_language', '中文')}
"""
# 构建消息
messages = [{"role": "system", "content": system_prompt}]
# 添加相关记忆(语义记忆)
for mem in context["relevant_memories"][:3]:
messages.append({"role": "user", "content": f"【记忆】{mem}"})
# 添加最近对话(情景记忆)
for msg in context["recent_messages"][-10:]:
messages.append(msg)
messages.append({"role": "user", "content": user_input})
# 调用 LLM
response = self.llm.invoke(messages)
# 保存对话到记忆
self.memory.add("user", user_input)
self.memory.add("assistant", response.content)
return response.content第5部分:实战技巧
5.1 记忆何时写入
┌─────────────────────────────────────────────────────────────┐
│ 记忆写入时机 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✅ 应该写入: │
│ • 用户明确表达偏好:"我想要..." / "我喜欢..." │
│ • 关键事实:"我叫..." / "我是..." / "我在...工作" │
│ • 任务关键信息:"要发给王总的邮件" │
│ • Agent 发现反复出现同一问题 │
│ │
│ ❌ 不需要写入: │
│ • 闲聊:"今天天气不错" │
│ • 一次性问题:"今天星期几" │
│ • 用户明确说过"不用记住"的信息 │
│ │
└─────────────────────────────────────────────────────────────┘5.2 记忆何时读取
┌─────────────────────────────────────────────────────────────┐
│ 记忆读取策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 方式1:每次都读取(保守) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 每次查询时,把所有相关记忆都塞进 context │ │
│ │ 优点:不遗漏 │ │
│ │ 缺点:Token 消耗大,可能稀释关键信息 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 方式2:按需读取(推荐) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 用户输入 → 提取关键词 → 只检索相关记忆 │ │
│ │ 优点:精准、Token 高效 │ │
│ │ 缺点:可能遗漏间接相关的信息 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 方式3:定期预取(主动) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 根据对话主题,主动预取相关记忆 │ │
│ │ 例如:检测到"代码"主题 → 预取用户的编程偏好 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘5.3 记忆的遗忘机制
class MemoryWithForget:
"""带遗忘机制的记忆"""
def __init__(self, max_memories=1000, decay_days=30):
self.max_memories = max_memories
self.decay_days = decay_days
self.memories = [] # (content, timestamp, importance)
def add(self, content, importance=1.0):
import time
self.memories.append((content, time.time(), importance))
self._prune()
def _prune(self):
"""删除低重要度或过旧的记忆"""
import time
now = time.time()
# 删除策略:保留 top N
if len(self.memories) > self.max_memories:
# 按重要度排序,保留最高的
self.memories.sort(key=lambda x: x[2], reverse=True)
self.memories = self.memories[:self.max_memories]
# 也可按时间衰减删除
self.memories = [
(c, t, i) for c, t, i in self.memories
if (now - t) < self.decay_days * 86400 or i > 0.8
]核心总结
总结1:记忆的两个层级
短时记忆(上下文窗口内)
• 当前会话的消息历史
• 需要管理容量,避免超限
长时记忆(持久化)
• 跨会话的用户偏好、积累知识
• 用向量数据库或键值存储总结2:三种记忆类型
| 类型 | 内容 | 存储方式 |
|---|---|---|
| 语义记忆 | 事实、偏好 | 键值对、向量 |
| 情景记忆 | 历史事件、对话 | 带时间戳的日志/摘要 |
| 程序记忆 | 行为模式 | System Prompt、规则 |
总结3:上下文管理策略
| 策略 | 适用场景 |
|---|---|
| 滑动窗口 | 对话模式简单,不需要长期上下文 |
| 摘要 | 对话很长,需要保留关键信息 |
| 选择性记忆 | 对话复杂,需要精准检索 |
章节测试
测试1:短时 vs 长时记忆
短时记忆和长时记忆的主要区别是什么?各自的容量限制是什么?
测试2:记忆类型
三种记忆类型(语义、情景、程序)分别存储什么内容?
测试3:上下文窗口
当上下文窗口快满时,有哪三种主要处理策略?
测试4:长时记忆存储
向量数据库和键值对存储分别适合存储什么类型的长时记忆?
测试5:记忆读写
什么信息应该被写入长时记忆?什么信息不需要?
参考答案
测试1答案
答案:
- 短时记忆:当前会话的上下文,容量受限于模型的上下文窗口(如 GPT-4o 是 128K tokens)
- 长时记忆:跨会话的持久化信息,容量理论上无限,受限于存储成本
测试2答案
答案:
- 语义记忆:事实和概念性知识(如用户偏好"喜欢美式咖啡")
- 情景记忆:过去发生的事件序列(如"用户上周问过年假问题")
- 程序记忆:Agent 自己学到的行为模式(如"用户问代码问题时应先问语言")
测试3答案
答案:
- 滑动窗口:丢弃最老的消息,只保留最近 N 条
- 摘要:将旧消息压缩成摘要,保留关键信息
- 选择性记忆:只保留与当前任务相关的信息
测试4答案
答案:
- 向量数据库:适合存储非结构化的、语义相关的内容(如对话摘要、提取的事实)
- 键值对存储:适合存储结构化的用户偏好(如"communication_style": "简洁")
测试5答案
答案:
- 应该写入:用户明确表达的偏好("我喜欢...")、关键事实(姓名、职业)、任务关键信息
- 不需要写入:闲聊内容、一次性问题("今天星期几")、用户明确说不用记住的信息
相关笔记
- [[03-rag-basics]] - RAG 也是一种"外部记忆"的使用方式
- [[05-agent-workflow]] - 记忆在工作流中的具体应用
- [[06-multi-agent]] - 多 Agent 系统中每个 Agent 的记忆管理
下一步学习
- [ ] 阅读 05 - Agent 工作流
学习状态:🟡 开始学习