🎯 课程主题
理解 LangChain/LangGraph 中 Memory(记忆) 的概念、必要性,以及记忆的分类体系——短期记忆(Short-term Memory)与长期记忆(Long-term Memory),并建立与 LangGraph API(State、Store、Context)的映射关系。
📝 核心知识点
1. 为什么需要记忆?
- 大模型天然无状态:每次 invoke 都是一次全新的开始,模型不保留之前的交互信息。
- 复杂任务需要多轮交互:一次交互往往无法满足用户需求,后续交互需要能够访问之前的信息。
- 用户体验:具备记忆能力的 Agent 更像一个"有温度的朋友",而非冷冰冰的机器。
2. 豆包示例说明
| 场景 | 说明 |
|---|---|
| 同一会话内追问"我是谁" | 豆包能记住,说明具备短期记忆 |
| 关闭应用进程后重新打开,追问"我是谁" | 豆包仍能记住,说明信息持久化到了数据库(而不仅是内存) |
豆包 = 大模型应用(非裸模型),LangChain 本章目标就是开发具有类似豆包记忆能力的应用。
3. 上下文工程(Context Engineering)
- 记忆(Memory):存储历史交互信息(类似"笔记本",信息可能杂乱)。
- 上下文工程:管理和组织记忆信息,使其更规范、精准,方便后续交互。
- 典型操作:压缩、删除、摘要处理、裁剪无关信息。
- 好处:提高回答精准度 + 降低 token 消耗。
记忆 = 数据;上下文工程 = 对数据的管理。
4. 上下文 / 记忆的分类
| 类型 | LangGraph API | 对应记忆类别 | 特点 |
|---|---|---|---|
| 动态运行时上下文 | State 对象 | 短期记忆 | 单次 invoke 运行中可变的数据,以单次会话线程为单位 |
| 跨会话上下文 | Store 对象 | 长期记忆 | 多个线程/会话间共享的数据(用户偏好、历史洞察等) |
| 静态运行时上下文 | Context 对象 | (本章不重点) | 短期内不会修改的数据(用户元数据、工具列表、数据库连接信息等) |
5. 短期记忆 vs 长期记忆(核心区分)
常见误区:不能简单把"短期记忆 = 内存存储、长期记忆 = 数据库存储"。
| 维度 | 短期记忆(Short-term Memory) | 长期记忆(Long-term Memory) |
|---|---|---|
| 官方定义 | 会话级记忆(Thread-scope Memory) | 跨会话记忆(Cross-session Memory) |
| 作用范围 | 仅限当前线程/会话内有效 | 多个线程/会话间共享 |
| 持久化方式 | 可以是内存(InMemorySaver)或数据库(PostgreSQL 等) | 同样可以是内存或数据库 |
| LangGraph API | State + Checkpointer + thread_id | Store |
| 生命周期 | 线程结束即失效 | 跨线程/跨会话持久存在 |
🏗️ 架构与工作流
无记忆 vs 有记忆(直观对比)
【无记忆】 【有记忆】
用户: 我叫小明 用户: 我叫小明
Agent: 你好小明 Agent: 你好小明
--- Memory 存储 ---
用户: 之前我叫什么? 用户: 之前我叫什么?
Agent: 我不知道... Agent: 你叫小明!
StateGraph 中的记忆架构
┌─────────────────────────────────────┐
│ StateGraph Agent │
│ │
│ State (短期记忆) ←→ Store (长期记忆) │
│ │ │ │
│ ▼ ▼ │
│ Checkpointer 持久化后端 │
│ ├─ InMemorySaver ├─ PostgreSQL │
│ └─ SqliteSaver └─ ... │
└─────────────────────────────────────┘
💻 代码实战
演示:无记忆的 Agent
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="deepseek-chat", api_key="your-key")
# 创建 Agent(省略具体构建代码)
# 第一轮对话
response = agent.invoke({"messages": [HumanMessage(content="我叫小明")]})
print(response["messages"][-1].content)
# 输出: 你好小明!
# 第二轮对话 — 全新 invoke,与上一轮隔离
response2 = agent.invoke({"messages": [HumanMessage(content="我叫什么名字?")]})
print(response2["messages"][-1].content)
# 输出: 我还不知道你叫什么名字呢... ← 没有记忆!
演示:单次 invoke 合并消息(变通方案)
# 把所有消息放在一次 invoke 中
response = agent.invoke({
"messages": [
HumanMessage(content="我叫小明"),
HumanMessage(content="我叫什么名字?")
]
})
# 回答能知道叫小明 — 因为所有信息都在同一次请求的上下文中
⚠️ 常见问题与避坑指南
| 问题 | 说明 |
|---|---|
| 误解记忆分类 | 不能简单认为短期记忆=内存级、长期记忆=数据库级。短期/长期是按会话范围区分,而非存储介质。 |
| 忽略上下文工程的价值 | 记忆信息日益膨胀时,不做压缩/裁剪/摘要,会显著增加 token 消耗,影响准确性。 |
| 混淆 State 和 Store | State 按线程(thread)组织数据 → 短期记忆;Store 跨线程共享数据 → 长期记忆。 |
💡 个人总结与延伸
- 记忆是 Agent 智能化的基石:没有记忆的 Agent 无法处理多轮复杂任务,也无法提供个性化体验。
- 区分记忆的本质按"作用域"而非"存储介质"——这是官方文档反复强调的核心概念。
- LangChain
0.3版本使用xxxMemory系列 API 管理记忆,而 LangChain 1.0+ 则完全基于 LangGraph 的State/Store/Checkpointer体系来实现,API 风格发生了显著变化。 - 后续章节将分别深入短期记忆(
InMemorySaver→SqliteSaver/PostgresSaver)和长期记忆(Store)的具体实现。