- 发布日期
第 16 讲 · 序列化机制(Serde)
学习目标
- 理解
SerializerProtocol的dumps_typed/loads_typed契约。 - 看懂
JsonPlusSerializer如何用 msgpack + 扩展类型序列化复杂对象。 - 了解安全开关与
EncryptedSerializer加密。
为什么需要专门的 serde
第 15 讲的 channel_values 要落到 DB/磁盘,里面可能是 BaseMessage、Pydantic 模型、 numpy 数组、datetime 等非原生 JSON 类型。普通 json.dumps 处理不了。 LangGraph 用一套自己的序列化层(serde)来"类型保真"地存取。
协议层:SerializerProtocol
libs/checkpoint/langgraph/checkpoint/serde/base.py,SerializerProtocol(15–26 行):
dumps_typed(obj) -> (type_tag, bytes):序列化,带类型标签。loads_typed((type_tag, bytes)) -> obj:按标签反序列化回正确类型。
"带类型标签"是关键——存的时候记下"这是什么类型",读的时候才能精确还原, 而不是退化成一堆 dict。
JsonPlusSerializer:默认序列化器
libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py,JsonPlusSerializer(82 行起)。 dumps_typed(258–271 行)按对象类型选编码:
| type_tag | 适用 | 说明 |
|---|---|---|
"null" | None | |
"bytes" / "bytearray" | 原始字节 | 直接存 |
"msgpack" | 默认路径 | ormsgpack + 自定义扩展 hook |
"pickle" | 仅开启 fallback 时 | 兜底,存在安全风险 |
"json" | 旧 checkpoint 兼容 | loads_typed 反向读 |
msgpack 扩展类型
msgpack 本身只懂基础类型,复杂对象靠扩展类型(ext code):序列化时把 Pydantic v1/v2 模型、numpy array、_DeltaSnapshot(第 6 讲,ext code 7)等 编码成自定义 ext,反序列化时按 ext code 还原。
这套机制让你在 state 里放 LangChain 消息对象、Pydantic 模型都能正确持久化与恢复。
安全:限制反序列化类型
反序列化任意类型有风险(尤其 pickle)。LangGraph 提供收紧开关:
- 环境变量
LANGGRAPH_STRICT_MSGPACK=true或配置allowed_msgpack_modules(97–114 行) 限制能被反序列化的模块/类型。 - 第 8 讲
compile()里见过STRICT_MSGPACK_ENABLED时会根据 schema/channels 自动构建 serde 白名单(build_serde_allowlist),把允许的类型范围缩到图实际用到的。
生产建议:处理不可信输入的图,开启严格模式,避免反序列化攻击面。
EncryptedSerializer:加密
libs/checkpoint/langgraph/checkpoint/serde/encrypted.py(8–36 行)。 它包装任意 SerializerProtocol:先用底层序列化器产出 (typ, bytes),再把 bytes 加密, type tag 变成 {typ}+{ciphername}。读时先解密再交给底层反序列化。
适用:合规要求 checkpoint 静态加密(对话内容、PII 等敏感数据落库前加密)。
checkpoint_id 的生成:时间有序 UUID
libs/checkpoint/langgraph/checkpoint/base/id.py,uuid6(79–109 行)。 checkpoint id 用 UUID6——时间有序,所以可以直接按 id 排序得到时间顺序, DB 索引友好。第 15 讲说的"单调递增 id"就是它。 Pregel 侧 create_checkpoint 还用 clock_seq=step 把 step 绑进 id。
机制全景
flowchart LR
OBJ[channel_values<br/>含复杂对象] --> DT[dumps_typed]
DT --> MP{类型?}
MP -->|复杂| EXT[msgpack + ext code]
MP -->|bytes| RAW[原样]
EXT --> ENC[可选: 加密 EncryptedSerializer]
RAW --> ENC
ENC --> DB[(存储: type_tag + bytes)]
DB --> LT[loads_typed 按 tag 还原]
LT --> OBJ
使用场景
- 在 state 里放自定义对象:只要它能被 msgpack ext 处理(或注册自定义编码),就能持久化。 实在不行才退 pickle(注意安全)。
- 合规加密:用
EncryptedSerializer包裹默认序列化器,传给 checkpointer。 - 加固安全:面向公网/多租户的服务开启
LANGGRAPH_STRICT_MSGPACK,限制反序列化类型。 - 排查"恢复后对象变成 dict":通常是该类型没有 typed 序列化支持,退化成了普通 JSON。
动手实验
- 在 state 里放一个 Pydantic 模型字段,存档后
get_state取回,确认类型保真(不是 dict)。 - 用
EncryptedSerializer包裹JsonPlusSerializer,对比 DB 里 blob 是否变成密文。 - 开启
LANGGRAPH_STRICT_MSGPACK=true,放一个不在白名单的类型,观察报错。
阅读作业
- 精读
serde/jsonplus.py的dumps_typed/loads_typed(258–282 行)与 ext 注册(295–302 行)。 - 浏览
serde/encrypted.py与serde/base.py的协议定义。
小结
- serde 用"类型标签 + bytes"实现类型保真的存取,默认走 msgpack + 扩展类型。
- 复杂对象(消息、Pydantic、numpy、DeltaSnapshot)靠 ext code 编解码。
- 安全:严格模式限制反序列化类型;
EncryptedSerializer提供静态加密。
下一讲:三种检查点实现(InMemory / SQLite / Postgres)的存储差异。