- 培歌行学LangGraph(1):从 LangChain 到 LangGraph 的思维跃迁]
- 培歌行学LangGraph(2):Reducer(归约器)彻底搞懂
- 培歌行学LangGraph(3):编译、执行与可视化
- 培歌行学LangGraph(4):一文搞懂图的状态(State)管理
- 培歌行学LangGraph(5):状态管理与
graph.invoke入参深度解析 - 培歌行学LangGraph(6):Multi Schema多状态管理详解
- 培歌行学LangGraph(7):预定义状态MessagesState与AgentState
- 培歌行学LangGraph(8):控制流详解
- 培歌行学LangGraph(9):控制流详解:defer延迟节点——让收尾工作自动排到最后
- 培歌行学LangGraph(10):多分支汇聚Fan-in——多个分支如何汇聚到一起?
- 培歌行学LangGraph(11):用循环结构实现 ReAct Agent
- 培歌行学LangGraph(12):递归限制——别让你的图无限跑下去
- 培歌行学LangGraph(13):节点执行与容错机制
一、什么是可恢复执行?
可恢复执行指的是:图在执行过程中,每一步的状态都被保存下来。即使程序中断、服务器重启,也能从上次中断的地方继续运行。
这在以下场景中非常重要:
- 多轮对话:用户说完话离开,回来还能接着聊
- 长时间运行的任务:数据处理到一半宕机,恢复后继续
- 人工审批流程:在某个节点等待审批,审批通过后继续执行
LangGraph 通过 检查点(Checkpoint) 机制实现可恢复执行。
二、检查点(Checkpoint)是什么?
检查点是 图在某个时刻的完整状态快照,包括:
- 当前 State 的所有字段值
- 当前执行到哪个节点
- 已执行过的节点历史
可以把检查点想象成游戏的存档:
- 你可以在任何时候存档
- 读档后,游戏从存档点继续,而不是从头开始
三、启用可恢复执行的两步
3.1 第一步:编译时传入 Checkpointer
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)
3.2 第二步:调用时传入 thread_id
config = {"configurable": {"thread_id": "session_001"}}
graph.invoke(input_data, config=config)
**thread_id** 是会话的唯一标识。同一个 thread_id的多轮调用,会共享同一份状态历史。
四、检查点后端的选择
LangGraph 提供了多种检查点后端:
| 后端 | 包名 | 特点 |
|---|---|---|
| InMemorySaver | langgraph-checkpoint | 内存存储,进程结束即丢失 |
| SqliteSaver | langgraph-checkpoint-sqlite | 文件存储,轻量级持久化 |
| PostgresSaver | langgraph-checkpoint-postgres | 数据库存储,生产环境首选 |
| MongoDBSaver | langgraph-checkpoint-mongodb | NoSQL 存储 |
| RedisSaver | langgraph-checkpoint-redis | 高速缓存存储 |
五、基于内存的检查点(InMemorySaver)
5.1 特点
- 数据保存在当前 Python 进程的内存中
- 进程结束 → 数据丢失
- Jupyter 内核不重启 → 数据保留
5.2 完整示例
from langgraph.checkpoint.memory import InMemorySaver
# 1. 创建检查点存储器
checkpointer = InMemorySaver()
# 2. 编译时传入
graph = builder.compile(checkpointer=checkpointer)
# 3. 定义 thread_id
config = {"configurable": {"thread_id": "session_001"}}
# 4. 多轮对话,共享状态
graph.invoke({"messages": [HumanMessage("你好,我是老王")]}, config=config)
graph.invoke({"messages": [HumanMessage("从现在开始,你是小王")]}, config=config)
res = graph.invoke({"messages": [HumanMessage("我是谁?你是谁?")]}, config=config)
print(res["output"])
# 输出:老王,我是小王啊!
5.3 注意事项
如果重新执行完整代码,InMemorySaver()实例被重新创建,历史检查点会被清空。
# 重新执行这行,之前的记忆就没了
checkpointer = InMemorySaver()
六、基于 PostgreSQL 的检查点(PostgresSaver)
6.1 特点
- 数据保存在 PostgreSQL 数据库中
- 程序重启 → 数据不丢
- 适合生产环境
6.2 完整示例
from langgraph.checkpoint.postgres import PostgresSaver
# 1. 数据库连接 URL
DB_URL = "postgresql://langgraph_user:123456@localhost:5432/langgraph_db?sslmode=disable"
# 2. 创建检查点存储器
with PostgresSaver.from_conn_string(DB_URL) as checkpointer:
# 初始化数据库表(实际项目中应作为独立步骤)
checkpointer.setup()
# 3. 编译时传入
graph = builder.compile(checkpointer=checkpointer)
# 4. 定义 thread_id
config = {"configurable": {"thread_id": "session_002"}}
# 5. 多轮对话
graph.invoke({"messages": [HumanMessage("你好,我是老王")]}, config=config)
graph.invoke({"messages": [HumanMessage("从现在开始,你是小王")]}, config=config)
res = graph.invoke({"messages": [HumanMessage("我是谁?你是谁?")]}, config=config)
print(res["output"])
6.3 程序重启后仍然有效
# 即使重新运行整个脚本,只要 thread_id 相同,历史还在
with PostgresSaver.from_conn_string(DB_URL) as checkpointer:
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "session_002"}}
# 模型还记得之前的对话
res = graph.invoke({"messages": [HumanMessage("我刚才说了什么?")]}, config=config)
# 输出:你说你是老王,让我叫你小王
七、InMemorySaver vs PostgresSaver
| 对比项 | InMemorySaver | PostgresSaver |
|---|---|---|
| 存储位置 | 内存 | PostgreSQL 数据库 |
| 程序重启 | 数据丢失 | 数据保留 |
| 跨进程共享 | 不支持 | 支持 |
| 性能 | 极快 | 较快 |
| 适用场景 | 开发调试 | 生产环境 |
| 配置复杂度 | 简单 | 需要数据库 |
八、检查点的工作原理
调用 1(thread_id=001)
→ 执行节点 A → 保存检查点 1
→ 执行节点 B → 保存检查点 2
→ 执行节点 C → 保存检查点 3
调用 2(thread_id=001)
→ 从检查点 3 恢复状态
→ 执行节点 D → 保存检查点 4
调用 3(thread_id=002)
→ 新的会话,从头开始
每个检查点包含:
- 当前 State
- 父检查点 ID(形成链条)
- 执行时间戳
九、面试题
面试题1:什么是检查点(Checkpoint)?它的作用是什么?
答:检查点是图在某个时刻的完整状态快照,包含 State 的所有字段、当前执行位置和历史记录。它的作用是:
- 可恢复执行:程序中断后可以从上次中断处继续
- 多轮对话:同一个 thread_id 的多轮调用共享状态
- 状态回溯:可以回退到任意历史检查点
面试题2:InMemorySaver和 PostgresSaver的根本区别是什么?
答:根本区别在于存储介质:
InMemorySaver存储在内存中,进程结束即丢失PostgresSaver存储在数据库中,程序重启后数据仍在
这也决定了它们的适用场景:前者适合开发调试,后者适合生产环境。
面试题3:thread_id的作用是什么?不传会怎样?
答:thread_id是会话的唯一标识,用于区分不同会话的状态。如果不传 thread_id,每次调用都会创建一个新的匿名会话,无法共享状态,也无法从历史检查点恢复。
面试题4:检查点机制如何实现多轮对话的记忆功能?
答:流程如下:
- 第一轮调用:传入
thread_id=001,图执行完毕后保存检查点 - 第二轮调用:传入相同的
thread_id=001,LangGraph 从检查点恢复状态 - 新的消息追加到已有的消息列表中
- 模型看到完整的对话历史,从而实现"记忆"
面试题5:如果我想在生产环境中使用检查点,应该选择哪个后端?为什么?
答:推荐使用 PostgresSaver或 RedisSaver。原因:
- 持久化:数据不会因为程序重启而丢失
- 可扩展:支持多进程、多服务共享状态
- 可靠性:数据库有完善的备份和恢复机制
InMemorySaver只适合开发和调试,不适合生产环境。
检查点就像是给你的图装上了"时光机"。 有了它,你的 Agent 不再是"一次性"的,而是可以记住过去、延续对话、甚至在故障后原地复活。