🎯 课程主题
对比 LangGraph 中两种短期记忆实现方式:InMemorySaver(内存存储)与 PostgresSaver(外部持久化存储)的核心区别。
📝 核心知识点
- InMemorySaver:将 state 持久化到内存,进程结束或重建 Saver 后历史状态丢失
- PostgresSaver:将 state 持久化到外部存储介质(PostgreSQL),状态不随进程终止而丢失
- 关键差异:同一 thread_id 下,重新运行程序时:
- InMemorySaver:每次运行重新创建 Saver 实例,历史状态无法恢复
- PostgresSaver:数据已持久化在数据库表中,重建对象只是重新引用已有数据,历史状态完整保留
- 数据累积特性:PostgresSaver 在不变更 thread_id 的情况下,每次对话都在历史数据基础上累加,消息数量持续增长
🏗️ 架构与工作流
【InMemorySaver — 内存存储】
第一次 run:
thread_id="1" → Saver 实例 A → state 在内存
run 结束 → 进程终止 → Saver 销毁 → state 丢失
第二次 run (同一 thread_id):
thread_id="1" → 新建 Saver 实例 B → state 为空
→ 无法恢复上次的对话历史
→ 重新从零开始
【PostgresSaver — 持久化存储】
第一次 run:
thread_id="3" → Saver 连接到数据库 → state 写入 PostgreSQL
run 结束 → 进程终止 → 数据库中的数据仍在
第二次 run (同一 thread_id):
thread_id="3" → Saver 重新连接数据库 → 从 PostgreSQL 加载 6 条历史消息
→ 新消息在 6 条基础上累加 → 变为 8 条
→ 再对话 → 10 条 → 12 条...
💻 代码实战
对比实验:InMemorySaver
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt import create_react_agent
# 每次 run 都创建全新的 Saver
checkpointer = InMemorySaver()
agent = create_react_agent(model, tools, checkpointer=checkpointer)
config = {"configurable": {"thread_id": "1"}}
# 第一次调用
agent.invoke(
{"messages": [HumanMessage(content="你好,我是谁?")]},
config=config
)
# → AI: 我不知道你是谁
# 第二次调用
agent.invoke(
{"messages": [HumanMessage(content="我是老王")]},
config=config
)
# → AI: 很高兴认识你,老王
# 第三次调用
agent.invoke(
{"messages": [HumanMessage(content="你好,我是谁?")]},
config=config
)
# → AI: 你是老王 ✅
# ─── 重新 run,thread_id 不变 ───
# 再次执行整个脚本,thread_id 仍为 "1"
# 第一次调用
agent.invoke(
{"messages": [HumanMessage(content="你好,我是谁?")]},
config=config
)
# → AI: 我不知道你是谁 ❌ (历史已丢失!)
对比实验:PostgresSaver
from langgraph.checkpoint.postgres import PostgresSaver
DB_URL = "postgresql://langchain:password@<IP>:5432/langgraph_db"
checkpointer = PostgresSaver.from_connection_string(DB_URL)
checkpointer.setup()
agent = create_react_agent(model, tools, checkpointer=checkpointer)
config = {"configurable": {"thread_id": "3"}}
# 第一次 run:3 次对话 → 共 6 条消息存入数据库
# ─── 重新 run,thread_id 不变 ───
# 再次执行整个脚本,thread_id 仍为 "3"
# 第一次调用
agent.invoke(
{"messages": [HumanMessage(content="你好,我是谁?")]},
config=config
)
# → AI: 你是老王 ✅ (从上轮 6 条历史中恢复)
# 此时共 8 条消息 (6 条历史 + 本轮 2 条)
⚠️ 常见问题与避坑指南
| 对比维度 | InMemorySaver | PostgresSaver |
|---|---|---|
| 存储位置 | 进程内存 | PostgreSQL 数据库 |
| 进程结束后 | state 丢失 | state 保留 |
| 重建 Saver | 新实例,历史丢失 | 重新关联已有数据 |
| thread_id 不变 | 无法恢复历史 | 在历史基础上累加 |
| 适用场景 | 开发测试、临时会话 | 生产环境、需持久化会话 |
| 部署依赖 | 无 | 需 PostgreSQL 服务 |
- 不是 thread_id 的问题:即使 thread_id 不变,InMemorySaver 每次 run 都是全新对象,无法恢复历史
- PostgresSaver 重建对象≠重建数据:
PostgresSaver.from_connection_string()只是构建一个指向已有数据库的连接对象,底层数据不丢失 - 消息会无限累加:PostgresSaver 同一 thread_id 下消息持续增长,需要配合治理策略(裁剪/删除/摘要)
- thread_id 管理:新 thread_id 从空开始,旧 thread_id 恢复历史 — 这就是会话隔离机制
💡 个人总结与延伸
- 两种 Saver 实现同一接口,替换只需改两行代码,体现了 LangGraph 良好的抽象设计
- InMemorySaver 适合原型验证和单元测试,PostgresSaver 适合生产部署
- 可进一步扩展:除 PostgreSQL 外,LangGraph 还支持 SQLite、Redis 等后端
- 实际项目中,应结合治理策略(裁剪、删除、摘要)控制消息数量,避免数据库无限膨胀