- 培歌行学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(14):启用可恢复执行(Checkpoint)
- 培歌行学LangGraph(15):持久化模式——何时保存检查点?
一、为什么需要查看历史检查点?
想象一下你在玩游戏:
- 你存了好几个档
- 有时候你想看看某个存档点的人物属性
- 有时候你想读档回到某个位置
检查点就是这个"存档"。启用检查点后,LangGraph 会把同一个 thread_id下的每一次执行都记录下来。你可以:
- 回头看:某一步执行完后,状态长什么样?
- 读档:回到某个历史时刻,看看当时的情况
- 恢复:程序崩了,从最近的存档点继续
LangGraph 给了你两个方法来看这些"存档":
| 方法 | 作用 |
|---|---|
get_state_history(config) | 查看所有历史存档(从新到旧) |
get_state(config) | 查看最新存档,或者查看指定 ID 的存档 |
二、每个存档长什么样?(StateSnapshot)
每个检查点都是一个 StateSnapshot对象,里面装着这些信息:
| 字段 | 意思 | 举个例子 |
|---|---|---|
values | 这一刻的状态值 | {"topic": "猫咪", "poem": "床前明月光..."} |
next | 下一步要执行哪个节点 | ("node_poem", "node_joke") |
config | 这个存档的身份证,里面有 thread_id和 checkpoint_id | 一串乱码一样的 ID |
metadata | 元数据,比如这是第几步 | {"step": 1} |
created_at | 什么时候创建的 | 时间戳 |
parent_config | 上一个存档的身份证 | 串成一串链条 |
tasks | 下一步要做哪些任务 | 比如"写诗"、"讲笑话" |
interrupts | 有没有被人为打断 | 一般是空的 |
三、查看所有历史存档(get_state_history)
3.1 怎么用
history = list(graph.get_state_history(config=config))
就这么一行,就能拿到这个 thread_id下的所有存档。
注意:返回的顺序是从新到旧,最新的排第一个。
3.2 举个例子
假设我们有一个图,功能是:给定一个主题,同时生成一首诗和一个笑话,然后把它们拼在一起输出。
# 省略了图和节点的定义...
checkpointer = InMemorySaver()
config = {"configurable": {"thread_id": "123"}}
graph = builder.compile(checkpointer=checkpointer)
# 跑一次
result = graph.invoke({"topic": "猫咪"}, config=config)
# 查看所有存档
history = list(graph.get_state_history(config=config))
3.3 你会看到什么?
你会看到 4 个存档,按顺序是:
[第2步, 第1步, 第0步, 第-1步]
每个存档的内容如下:
| 位置 | 第几步 | 状态里有什么 | 下一步要去哪 |
|---|---|---|---|
| 第1个 | 第2步 | 主题、诗、笑话、最终输出都有了 | 结束了,哪儿也不去 |
| 第2个 | 第1步 | 主题、诗、笑话都有了 | 去汇总节点 |
| 第3个 | 第0步 | 只有主题 | 去写诗和讲笑话两个节点 |
| 第4个 | 第-1步 | 空的 | 去启动节点 |
3.4 每个存档到底什么意思?
第-1步(输入存档) :
- 刚刚收到你的输入,还没开始处理
- 状态是空的
{} - 下一步:启动内部流程
第0步(第一个工作存档) :
- 你的输入"猫咪"已经被写进状态了
- 状态:
{"topic": "猫咪"} - 下一步:同时去写诗和讲笑话
第1步(第二个工作存档) :
- 诗和笑话都写完了
- 状态:
{"topic": "猫咪", "poem": "...", "joke": "..."} - 下一步:去汇总节点,把诗和笑话拼起来
第2步(第三个工作存档) :
- 全都完成了
- 状态里连最终输出都有了
- 下一步:空,结束了
3.5 一个小坑:tasks 字段
你可能会在 Step 0 的存档里看到这样的情况:
tasks=(
任务(name="写诗", result={"poem": "床前明月光..."}),
任务(name="讲笑话", result={"joke": "一只猫走进酒吧..."}),
)
哎?这不明明是 Step 0 的存档吗?怎么连 Step 1 的结果都有了?
解释:这是 LangGraph 的一个优化。它会在 Step 0 执行完后,先把 Step 0 的"主存档"保存好,然后把 Step 1 要执行的任务结果也绑到这个存档上。这样,如果 Step 1 某个节点失败了,已经成功的节点就不用重新跑了。
四、只看最新存档(get_state)
4.1 怎么用
latest = graph.get_state(config=config)
只返回最新的那个存档。
4.2 什么时候用
- 图跑完了,想看看最终状态
- 图跑到一半停了,想看看卡在哪一步
4.3 怎么看
latest = graph.get_state(config=config)
print(latest.values) # 最新的状态
print(latest.next) # 下一步要去哪(空=结束了)
如果图已经跑完了,next会是空的 ()。
五、查看指定存档(get_state + checkpoint_id)
5.1 怎么用
# 先拿到某个存档的 checkpoint_id
checkpoint_id = history[2].config["configurable"]["checkpoint_id"]
# 构造目标配置
target_config = {
"configurable": {
"thread_id": "123",
"checkpoint_id": checkpoint_id
}
}
# 查看这个存档
snapshot = graph.get_state(config=target_config)
5.2 什么时候用
- 你想回到第 0 步看看当时的状态
- 你想从某个历史时刻"分叉",开一条新的执行路线
- 你想审计某个时间点的状态
5.3 完整例子
# 1. 拿到所有存档
history = list(graph.get_state_history(config=config))
# 2. 挑一个存档(比如第 0 步)
checkpoint_id = history[2].config["configurable"]["checkpoint_id"]
# 3. 查看这个存档
target_config = {
"configurable": {
"thread_id": "123",
"checkpoint_id": checkpoint_id
}
}
snapshot = graph.get_state(config=target_config)
# 4. 看看当时的状态
print(snapshot.values) # 只有 {"topic": "猫咪"}
print(snapshot.next) # 下一步去写诗和讲笑话
六、三种方式总结
| 方式 | 怎么写 | 拿到什么 | 什么时候用 |
|---|---|---|---|
| 看所有存档 | get_state_history(config) | 全部存档,从新到旧 | 调试、审计、选一个存档来回退 |
| 看最新存档 | get_state(config) | 最新的那个 | 看看现在跑到哪了 |
| 看指定存档 | get_state(config + checkpoint_id) | 指定的那一个 | 回溯、分叉、恢复 |
七、面试题
面试题1:get_state_history返回的存档是按什么顺序排列的?
答:从最新到最旧。第一个是最新的,最后一个是第 -1 步的输入存档。
面试题2:第 -1 步的存档是什么意思?为什么它的值是空的?
答:第 -1 步是"输入存档",刚收到你的输入但还没开始处理。所以状态是空的 {},下一步是去启动内部流程。
面试题3:为什么 Step 0 的存档里,会有 Step 1 的任务结果?
答:这是 LangGraph 的优化。它会先把"主存档"保存好,然后把下一步的任务结果也绑到这个存档上。这样如果某个节点失败了,已经成功的节点就不用重新跑。
面试题4:get_state(config)和 get_state(config + checkpoint_id)有什么区别?
答:
- 只传
thread_id:看最新的存档 - 传
thread_id+checkpoint_id:看指定的存档
一个看"现在",一个看"当时"。
面试题5:查看历史存档在实际项目中有啥用?
答:
- 查 bug:看看某一步的状态对不对
- 审计:记录每一步的变化
- 分叉:从某个历史时刻开一条新路
- 恢复:从故障点继续跑
- 回滚:发现错了,回到之前的状态重新来
检查点就像你图的"朋友圈"。 每走一步就发一条动态,记录着那一刻的状态。学会翻看这些动态,你就能清楚地知道你的图每一步都干了什么。