LangGraph 恢复后重复
LangGraph exit 恢复后重复值,核对 DeltaChannel pending writes 与 1.2.15 修复,比较实时状态和 checkpoint 重放顺序。
LangGraph 并行节点中,一个节点已经写完列表,另一个节点停在 interrupt()。用 Command(resume=...) 恢复后,实时结果看着正常,重新 get_state() 却多了一份已完成节点的值。如果该字段使用 DeltaChannel,运行又设置了 durability="exit",应核对恢复写入是否被持久化了两次。
这类问题有 1.2.12 开发代码的实际反馈。已核对的正式版 1.2.15 包含相应修复。它与恢复时重新执行被中断节点的前置代码不同:这里要比较的是 DeltaChannel 从 checkpoint 重建出来的状态。
比较运行结果与重新读取的状态
在受影响路径中,列表原本应包含输入和已完成节点的输出,例如 ['in-1', 'p'];恢复后重新读取,却可能变成 ['in-1', 'p', 'p']。同一图里使用普通列表 reducer、接收相同更新的字段,可以仍然保持正确。
先确认实际 channel 类型、持久化模式和恢复入口。记录 thread、namespace 与明确的 checkpoint 标识,核对是否确实恢复同一次中断,而不是重新提交了输入或读取了另一条会话。日志只保留判断所需的标识,不输出完整用户状态。
如果只是被中断节点开头的日志又打印了一次,应按interrupt 前置代码重执行检查。这一框架契约仍然存在,不能仅凭重复日志就认定是持久化缺陷。
重复来自已经保存的 pending writes
exit 模式在运行退出时集中保存相应变更。恢复一个带 pending writes 的 checkpoint 时,先前已经完成的节点写入会被加载,供本次恢复使用。
旧实现又把这些已保存的写入收进 exit 累积器,以另一份任务标识保存到锚点 checkpoint。DeltaChannel 重放祖先写入时便看见两份,最终列表重复。
只简单删掉重复的一份,也可能把重放顺序改错。修复还需要让恢复步的新写入与原来加载的写入按实际步骤和任务路径交错,再让后续步骤排在它们之后。普通列表的事后去重不能替代这个顺序与持久化修复:两个相同值可能本来就代表两次合法操作。
选择包含完整恢复修复的版本
旧部署应沿项目兼容范围选择包含修复的正式版本;已核对的 1.2.15 会跳过恢复步中已经保存过的写入,并保留新步骤的排序。核对实际 LangGraph 和 checkpointer 组合,自定义 saver 尤其不能仅按主包版本号推断重放顺序。
无法立即升级时,可以在获准的测试环境中评估更换持久化模式。sync 在下一步开始前保存变更,async 在下一步运行时异步保存,exit 在图退出时保存;它们的写入频率和失败恢复边界不同,不能只换一个字符串就宣称恢复了完全相同的语义。
不要为消除重复清空 checkpoint 数据,也不要直接修改私有 pending writes。版本修复针对新的持久化行为,已有异常状态需要结合原始记录核查,不能靠删除相同字符串决定哪一份合法。
验收恢复与重放顺序
在获准的测试环境中保留原并行中断场景,覆盖已完成节点的写入、恢复节点和恢复后的后续节点。比较实时值与通过相同配置重新读取的值,确认每份合法更新出现一次、顺序符合 channel 的业务契约。
还要区分不指定 checkpoint 的正常恢复与明确指定 checkpoint 的重放路径,检查实际 saver 的行为。若最终 checkpoint 保存失败再重试,exit 模式还可能暴露其他保存边界,不能把这一修复扩大成所有崩溃下“恰好一次”的保证。


