到上一篇为止,我们的图有一个共同的弱点:它是"一次性"的。
invoke一返回,执行过程中的一切就消失了——用户第二次提问,Agent 不记得上一轮;某个节点中途失败,只能从头再跑一遍;想知道"第三步的时候 State 长什么样",也无从查起。这一篇讲 LangGraph 的持久化机制 Checkpointer。它只做一件简单的事:每走一步,存一次档。而会话记忆、故障恢复、时间旅行,乃至下一篇的人机协作,都建立在这个"存档"之上。
一、存档这件事,应该在什么时候做#
先想一个问题:如果让你给图加上"存档"功能,什么时候存最合适?
每个节点执行完就存?听起来很自然,但第一篇讲过,同一个超步里的节点是并行执行的,它们的写入要到超步结束时才统一合并。在超步中间存档,存下的是一个"合并了一半"的不完整状态。
所以 LangGraph 选择在每个超步结束时存一次档,这份存档叫 checkpoint(检查点)。用第一篇的并行问答图实测一下,一次完整运行留下了这样四份存档:
| 存档 | 超步 | 当时 State 里已有的字段 | 下一步要执行 |
|---|---|---|---|
| 1 | -1(接收输入) | 无 | __start__ |
| 2 | 0 | question | search_kb、search_web |
| 3 | 1 | question、kb、web | answer |
| 4 | 2 | 全部字段 | 无(执行结束) |
两个并行的检索节点共享第 1 步的那一份存档,它们的结果是一起出现的。每份存档不仅记下了"当时的数据",还记下了"接下来该执行谁"——这一点是后面所有能力的关键:只要有一份存档,就能从那个位置接着跑。
二、开启持久化:Checkpointer 与线程#
开启持久化只需要两处改动:
from langgraph.checkpoint.memory import InMemorySaver
graph = builder.compile(checkpointer=InMemorySaver()) # 1. 编译时装上存档器
config = {"configurable": {"thread_id": "alice"}}
graph.invoke({"messages": [("user", "我叫小睦")]}, config) # 2. 调用时指定线程
Checkpointer(存档器) 决定存档放在哪里,thread_id(线程) 决定存档归到哪一组。可以把线程理解成一个独立的存档槽:同一个 thread_id 的多次调用,共享同一串存档;不同的 thread_id 互不干扰。


还没有公开留言,成为第一个写下回声的人。