夜雨聆风学习资料网

ARTICLE · 1351492

【LangGraph源码精读】第12期|一个Checkpoint存了什么?6个字段全拆解

【LangGraph源码精读】第12期|一个Checkpoint存了什么?6个字段全拆解

【LangGraph源码精读】第12期|一个Checkpoint存了什么?6个字段全拆解

30篇源码精读系列·第12篇(持久化层第一篇)。你每次调用invoke,LangGraph都在保存Checkpoint。但一个Checkpoint到底存了什么?为什么不能只存"状态值"?为什么还要版本号、节点看到的版本、待执行Send?这篇把Checkpoint的6个核心字段、CheckpointMetadata、CheckpointTuple全部拆解,讲清楚每个字段的作用、为什么不能少、以及它们怎么一起支撑恢复、中断和时间旅行。


一、开头:为什么不能只存"状态"?

很多人对Checkpoint的理解是:把当前的State(比如messages列表、几个字段)存下来。这还不够吗?

不够。因为恢复时需要回答的问题,远比"状态是什么"多。

恢复时,Pregel引擎要知道:

  • ● 这一步之后,下一轮该执行哪些节点?
  • ● 每个节点上次看到的是哪个版本,要不要重新触发?
  • ● 有没有节点执行到一半、writes已经保存但还没合并?
  • ● 有没有运行时动态创建的Send任务还没完成?
  • ● 这个Checkpoint是怎么来的?用户输入?循环?手动更新?
  • ● 它在整个执行历史中的位置是什么?

只存状态值,这些问题一个都回答不了。 一个Checkpoint真正存储的是"恢复一个Pregel执行所需的完整信息"。这篇把6个核心字段逐个拆开。


二、Checkpoint的6个核心字段总览

字段
类型
作用
id
字符串
这个Checkpoint的唯一标识
ts
字符串
创建时间戳
channel_values
字典
所有Channel当前的值(序列化后)
channel_versions
字典
每个Channel的当前版本号
versions_seen
字典
每个节点最近看到的各Channel版本
pending_sends
列表
待执行的Send任务(动态并行分支)

另外还有一个schema版本字段v,用于标记Checkpoint结构本身的版本,兼容框架升级。


三、字段一:id:唯一标识

特性
说明
格式
UUID或类似的唯一字符串
作用
唯一标识一个Checkpoint
生成时机
每次创建新Checkpoint时
用途
时间旅行定位、配置引用、去重

为什么需要id? 因为同一个thread会有很多个Checkpoint(每个Super-step一个)。时间旅行时,你要指定"回到哪一个",靠的就是id。config中可以传入checkpoint_id,让引擎从特定点恢复。


四、字段二:ts:时间戳

特性
说明
格式
ISO时间字符串
作用
标记Checkpoint创建时间
用途
排序、按时间查询、调试、历史展示

ts让Checkpoint形成一条时间线。 list_checkpoints按时间返回历史,前端时间旅行界面按时间展示,排查问题时可以按时间定位。


五、字段三:channel_values:状态的真正载体

这是Checkpoint中最核心、体积最大的字段。

5.1 存了什么?

内容
说明
字典
channel名 → 该channel当前值
覆盖所有channel
每个State字段对应的channel都有一份
序列化存储
默认MsgPack,可选JSON,可选加密
不是Python对象
是序列化后的字节,可跨机器、跨时间恢复

5.2 为什么序列化?

设计要求
说明
可在任何机器恢复
不依赖特定机器的内存
可在任意时间后恢复
不依赖进程还活着
可持久化到数据库
必须是可存储格式
可加密
敏感数据可以加密存储

官方原话:checkpoints that can be resumed on any machine, an arbitrary amount of time after they were saved。 这就是为什么channel_values必须序列化,不能直接存Python对象引用。

5.3 恢复时

channel_values被反序列化,重新填入所有Channel,状态就回到了这个Checkpoint的时刻。


六、字段四:channel_versions:版本号

6.1 存了什么?

内容
说明
字典
channel名 → 当前版本号
单调递增
channel每次更新,版本号+1
字符串形式
版本号是字符串而非整数
初始版本
未更新的channel有初始版本标记

6.2 为什么需要版本号?

场景
只靠值做不到
判断channel是否被更新
同一个值写两次,值相同但版本应该变化
触发节点
节点订阅channel,channel版本变化才触发
避免重复执行
节点已经看到最新版本,不重复触发
支持并行合并
多个节点写同一channel,版本号记录更新顺序

关键:节点是否执行,看的不是"值变没变",而是"版本号变没变"。 即使新值和旧值相同,只要channel被写入,版本就递增,订阅节点就会触发。

6.3 版本号的递增

每次after_tick应用writes时:

  • ● 被写入的channel,版本号递增
  • ● 没被写入的channel,版本号保持不变
  • ● 新创建的channel,获得初始版本

七、字段五:versions_seen:节点看到的版本

这是最容易被忽略、但最关键的字段。

7.1 存了什么?

内容
说明
字典
节点名 → 该节点看到的各channel版本
记录"最后看到"
每个节点最近一次执行时,订阅channel的版本
触发判断依据
当前版本 > 节点看到的版本 → 触发

7.2 为什么需要versions_seen?

这是Plan阶段判断节点是否执行的核心:

比较
结果
channel当前版本 > 节点看到的版本
节点有新数据没处理,触发
channel当前版本 == 节点看到的版本
节点已经处理过,不触发
所有节点都看到了最新版本
没有节点可执行,终止

没有versions_seen,引擎无法知道"哪些节点还有新数据要处理"。 这就是为什么不能只存channel_values——还需要记录每个节点"看到了哪一步"。

7.3 一个具体例子

时刻
messages版本
agent看到的
tools看到的
初始输入
1
0(未看到)
0
agent执行后
2
2
0
tools执行后
3
2
3
agent再执行后
4
4
3

Plan阶段比较:agent看到messages版本4(最新),tools看到版本3(比当前4旧),但tools订阅的是agent节点输出,不是messages...具体触发取决于订阅关系。核心逻辑就是"当前版本 vs 看到版本"。


八、字段六:pending_sends:待执行的动态分支

8.1 存了什么?

内容
说明
列表
已创建但还没执行的Send对象
Send
动态指定目标节点和输入(第22期详细讲)
用于动态并行
运行时才创建的分支

8.2 为什么需要pending_sends?

场景
说明
节点内部创建Send
比如为每个tool call创建一个Send
中断恢复时
已创建的Send不能丢失
跨Super-step
Send可能在一个step创建,下个step执行

没有pending_sends,恢复后动态创建的并行分支会丢失。


九、CheckpointMetadata:Checkpoint的"元信息"

Checkpoint本体之外,还有一组metadata。

9.1 source:这个Checkpoint怎么来的

source值
含义
input
用户调用invoke/stream,最初输入创建
loop
Pregel循环中,一轮Super-step后创建
update
手动更新状态(update_state)
fork
从某个点分叉(时间旅行重跑)

9.2 step:在执行中的位置

step值
含义
-1
输入Checkpoint(还没执行任何节点)
0, 1, 2...
第1、2、3轮Super-step后

9.3 其他字段

字段
作用
run_id
本次运行唯一标识
parents
父Checkpoint IDs(嵌套图/子图)
checkpoint_ns
命名空间(区分子图)

十、CheckpointTuple:存储层返回的完整对象

从Checkpointer读取时,返回的不是裸Checkpoint,而是CheckpointTuple。

字段
作用
config
定位这个Checkpoint的配置(thread_id、checkpoint_id)
checkpoint
Checkpoint本体(上面6个字段)
metadata
CheckpointMetadata
parent_config
父Checkpoint的配置(回溯链)
pending_writes
节点已执行但未合并的writes

CheckpointTuple = Checkpoint + 元信息 + 父链 + 半成品writes。 这是恢复时需要的所有信息。

10.1 pending_writes的作用

回顾第06期:节点执行完后,writes先通过put_writes保存(task级),在after_tick才合并。如果中间失败了,pending_writes记录了这些"已经完成但没合并"的结果,恢复时不重跑这些节点。


十一、为什么这些字段一个都不能少?

字段
缺失后果
id
无法定位特定Checkpoint,时间旅行无法实现
ts
无法排序、无法按时间查询
channel_values
状态丢失,恢复后没有数据
channel_versions
无法判断channel是否更新
versions_seen
无法判断节点要不要执行
pending_sends
动态并行分支丢失
metadata.source
无法区分Checkpoint来源
metadata.step
无法判断执行位置
parent_config
无法建立回溯链
pending_writes
半成品工作丢失,恢复时重复执行

十二、常见坑:Checkpoint的5个问题

坑
现象
根因
解法
以为只存状态值
不理解恢复为什么需要这么多信息
忽略了版本和触发逻辑
理解6个字段的作用
存了无法序列化的对象
保存报错
channel里有不能序列化的对象
用可序列化类型,或自定义序列化
版本号不递增
节点不触发
自定义channel没正确更新版本
用标准channel
忘记pending_sends
恢复后动态分支丢失
手动创建checkpoint遗漏
用标准create_checkpoint
不理解step=-1
调试困惑
输入checkpoint还没执行节点
-1是初始状态

十三、面试关联:Checkpoint的5道高频题

面试题
答题要点
考察深度
一个Checkpoint存了什么?
id、ts、channel_values、channel_versions、versions_seen、pending_sends,外加metadata
数据结构
为什么不能只存状态值?
恢复时要判断节点触发,需要版本号和versions_seen
设计理解
versions_seen是什么?
每个节点最近看到的channel版本,当前版本更新就触发节点
触发机制
channel_values为什么要序列化?
跨机器、跨时间恢复,不依赖进程和内存
持久化原理
CheckpointTuple包含什么?
config、checkpoint、metadata、parent_config、pending_writes
完整结构

十四、本期小结

Checkpoint结构,核心就6句话:

  1. 1. Checkpoint存储的是"恢复一个Pregel执行所需的完整信息",不是只存状态值。 6个核心字段:id、ts、channel_values、channel_versions、versions_seen、pending_sends。
  2. 2. channel_values是状态载体,所有channel当前值,序列化存储(默认MsgPack,可加密)。 序列化是为了跨机器、跨时间恢复,不依赖运行进程。
  3. 3. channel_versions记录每个channel的单调递增版本。 节点触发看的是版本变化,不是值变化。即使值相同,写入后版本递增,节点就会触发。
  4. 4. versions_seen记录每个节点最近看到的版本,是Plan阶段触发判断的核心。 当前版本 > 节点看到版本 → 触发;所有节点看到最新版本 → 终止。
  5. 5. pending_sends保存动态创建的并行分支,恢复时不丢失。 metadata记录source(input/loop/update/fork)、step(-1初始,0+循环)、父链。
  6. 6. CheckpointTuple是存储层返回的完整对象:config、checkpoint、metadata、parent_config、pending_writes。 pending_writes记录"已完成但未合并"的节点结果,支持部分恢复。

十五、下一期预告

下一期(源码13),持久化层第二篇:

【LangGraph源码精读】第13期|为什么持久化接口要这样设计?BaseCheckpointSaver精读

持久化的抽象接口:get_tuple、list、put、put_writes、delete_thread,同步和异步两套方法。搞懂为什么是这5个方法,以及如何实现自己的Checkpointer。

你之前理解Checkpoint存了什么吗?有没有漏掉版本相关的字段?评论区聊聊。


本文为《LangGraph源码精读30讲》系列第12篇。内容基于LangGraph v1.2.11源码结构梳理,核心文件位于libs/checkpoint/langgraph/checkpoint/base/init.py(Checkpoint、CheckpointMetadata、CheckpointTuple、create_checkpoint),以及官方文档checkpointers。版本更新后部分细节可能变化。如有错漏欢迎指正。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料