前面我们已经学习了 Checkpoint、
get_state_history()、Replay 和 Fork。这一篇继续看一个更实用的问题:
如果同一个 SuperStep 中,一个节点执行成功,另一个节点执行失败,修复后恢复运行时,成功节点还会不会重新执行?
LangGraph 中:已经成功的任务结果可以被检查点保存,恢复时直接复用。
1. 一个节点失败后,LangGraph 保存了什么?
假设图结构是:
START | node_change_topic | +------> node_poem | +------> node_joke | v node_output | END其中node_poem和node_joke属于同一个 SuperStep。
为了模拟失败,可以故意让:
defnode_joke(state):raiseException("人为中断")第一次执行时:
node_change_topic 执行成功 ↓ node_poem 和 node_joke 并行执行 ↓ node_poem:成功 node_joke:失败 ↓ 图中断这时查看历史检查点:
history=list(graph.get_state_history(config))可以看到对应的tasks中:
PregelTask(name="node_poem",error=None,result={"poem":"..."})而失败节点:
PregelTask(name="node_joke",error="Exception('人为中断')",result=None)所以这里可以理解为:
result = 节点已经成功产生的结果 error = 节点执行失败时记录的异常2. 为什么 node_poem 不需要重新执行?
虽然node_poem和node_joke在同一个 SuperStep 中运行,但其中一个失败,并不会让另一个已经成功的结果全部丢失。
检查点已经知道:
node_poem 成功 result 已保存 node_joke 失败 error 已保存因此恢复时,LangGraph 可以:
复用 node_poem.result + 重新执行 node_joke而不是:
node_poem 再执行一次 node_joke 再执行一次这对包含大模型调用、API 请求、数据库查询等昂贵操作的工作流尤其重要。
3. 修复 BUG 后如何恢复?
先把node_joke修好:
defnode_joke(state):topic=state["topic"]joke=model.invoke([HumanMessage(f"写一个关于{topic}的笑话")]).contentreturn{"joke":joke}然后重新编译图:
new_graph=builder.compile(checkpointer=checkpointer)这里最重要的是:
必须继续使用之前那个 Checkpointer。
因为历史 Checkpoint 就保存在它里面。
如果重新创建:
checkpointer=InMemorySaver()那就是一个新的空存储器,原来的历史也就找不到了。
恢复时:
res=new_graph.invoke(None,config={"configurable":{"thread_id":"123"}})这里:
input=None表示:
不再提供新的输入 而是从当前 thread_id 已保存的状态继续实际恢复结果中:
node_joke 重新执行 node_output 继续执行 node_poem 没有重新执行最终输出中的诗仍然是第一次node_poem成功时生成的内容,说明它的结果被复用了。
4. 失败恢复和 Replay 有什么区别?
这两个概念看起来很像,但目的不同。
Replay:
我主动找到一个历史 Checkpoint 然后从那里重新执行例如:
graph.invoke(None,old_checkpoint.config)而失败恢复是:
上一次执行因为异常中断 ↓ 修复 BUG ↓ 继续使用原来的 Checkpointer ↓ 通过相同 thread_id 恢复 ↓ 接着未完成的任务继续执行可以简单记:
Replay 重新走一次 失败恢复 上次没走完,现在接着走它还在解决:
任务执行到了哪里,以及哪些计算已经完成。