READ · UNDERSTAND · TRANSFER
阅读理解,按需巩固
先沿着原理、问答和迁移案例阅读。需要检查理解时,再切换巩固练习或展开个人记录。
核心知识 · Checkpoint、重放与外部副作用的边界
先理解核心原理
先备概念:本地事务与远端调用、幂等操作键、运行状态机
Checkpoint 持久化计算状态,不能把远端动作和本地提交变成一个事务。结果未知时必须沿稳定操作身份核对,重放不能冒充只读历史。
恢复的是计算,不是世界
程序宕机后可以找回下一节点和已保存结果,但工单、邮件或支付发生在另一个系统。外部提交成功而本地未记录,是两个系统之间必然需要考虑的窗口。Checkpoint 写得再频繁也只能缩小重算范围,不能确认丢失的远端响应。
稳定身份区分操作与尝试
先持久化逻辑意图与 operation_id,再调用工具;每次重试沿用它。一个循环合法创建三张工单要有三次逻辑操作身份,单次工单的多次网络尝试不换身份。结果状态区分确认成功、确认失败和未知;未知先查询目标或凭业务唯一键对账。没有幂等、没有可查询回执的目标系统,无法凭本地状态承诺端到端只执行一次。
回放也是重新执行
LangGraph 从历史 checkpoint 回放会重新执行其后的节点,包含模型、API 与中断;它不是把旧录像播放出来。回放用来探索新分支时,写工具应进入隔离环境或明确绑定新的合法意图,否则可能重复外部动作。恢复原运行与创建新业务操作需要接口语义区分。
持久化模式影响本地保证
同步写入在进入下一步前提交,异步写入有退出前尚未落盘的风险,exit 模式不保存中途全部进度。选择要符合可重算范围与成本,但同步也不解决远端事务。按调用前、远端成功后本地提交前、提交后三个点注入故障;验收看业务记录数与操作身份,而不只看最后生成一句“完成”。这些新增测试场景是教学设计。
回到问题:怎样回答?
Checkpoint 保存的是运行状态及继续执行的位置,不等于外部操作已经且仅已经执行一次。恢复时,未完成节点或显式回放的后续节点可能重新执行。发送邮件、提交工单等副作用必须有稳定 operation_id,并由目标系统幂等处理或查询执行账本。把意图、调用结果、状态提交分别记录;不确定结果先对账,再决定重试。验收时在调用前、远端成功后本地提交前、提交后分别杀进程,检查恢复结果和业务副作用次数。
实现与取舍
状态恢复和历史回放解决不同问题
Checkpoint 至少要记录 run_id、状态版本、已提交节点结果、下一步位置和引用的业务资源。进程重启后从持久化状态继续,避免重做已经提交的计算。LangGraph 当前文档明确指出,指定历史 checkpoint 回放时,之前的节点跳过,之后的节点会重新执行;其中包含模型调用、API 请求和中断。不能把回放理解成播放一段只读录像。恢复原任务、创建新分支和重新执行业务操作,应在产品和接口层明确区分。
真正难点是两个系统之间的失败窗口
假设工具提交工单成功,网络断开导致调用方拿不到响应,本地 checkpoint 仍显示未完成。直接重试会有重复工单风险;直接跳过则无法确定是否真的成功。工程方案是为业务操作生成稳定键,例如 run_id 加逻辑节点名和输入版本;重试沿用同一键,真正的新操作才换键。目标系统支持幂等键时,重复提交返回同一业务结果;否则用业务唯一键查询对账。执行账本要区分 prepared、confirmed、unknown、failed,unknown 不自动当成失败。若目标既不提供幂等也不可查询,只能暂停等待核实,不能承诺端到端 exactly-once。
持久化模式和部署版本也是恢复契约
同步 checkpoint 会在进入下一步前等待写入,异步模式吞吐更好但有进程退出前尚未落盘的窗口,exit 模式不能保证中途崩溃后恢复。选择要结合任务价值、允许重算范围和存储开销;同步落盘仍无法把远端 API 变成同一个事务。状态中保存 schema_version、工具契约版本和资源引用,恢复前做迁移或拒绝不兼容版本。旧状态不要直接交给已更换语义的新节点。大文件用对象引用与校验值,避免每一步复制整个二进制内容。
验收要观察业务结果而不是只看最终回答
设置三个故障点:工具调用之前、远端成功但本地结果未写入、结果写入之后。每个点重启并恢复,核对业务操作键、目标记录数量、checkpoint 和最终任务状态。还要覆盖重复恢复请求、同一 run 并发恢复以及恢复期间撤销权限。下面的 SQLite 示例只演示本地事务内的业务唯一键与状态提交;远端系统必须另行提供幂等或对账能力。
代码示例
用唯一操作键模拟重复恢复
Python 标准库可运行。业务副作用在同一个本地数据库事务中;不代表邮件、支付或远端 API 能与 checkpoint 原子提交。
import sqlite3, tempfile
from pathlib import Path
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / 'run.db'
db = sqlite3.connect(path)
db.executescript('CREATE TABLE effects(op TEXT PRIMARY KEY, result TEXT); CREATE TABLE checkpoints(run TEXT PRIMARY KEY, state TEXT);')
for attempt in range(2):
with db:
db.execute('INSERT OR IGNORE INTO effects VALUES (?, ?)', ('run-1:create-ticket:v1', 'ticket-42'))
db.execute('INSERT OR REPLACE INTO checkpoints VALUES (?, ?)', ('run-1', 'done'))
db.close()
db = sqlite3.connect(path)
print(db.execute('SELECT COUNT(*) FROM effects').fetchone()[0])
print(db.execute('SELECT state FROM checkpoints').fetchone()[0])
db.close()
预期输出
1
done工程推演
- 场景
- 假设工程场景:研究 Agent 创建任务工单后宕机。
- 设计决策
- 以 run 与逻辑操作组合为工单幂等键,未知响应先查目标系统,再恢复节点。
- 验证目标
- 验收目标是重复恢复仍定位到同一工单,并继续生成报告;这是设计验收要求,未宣称真实公司效果。
- 适用边界
- 目标系统没有幂等或查询接口时,需要人工核实;checkpoint 本身不能补足这个能力。
连续追问与解答
沿着问题的前提和约束继续向下读。先理解参考解答,再尝试收起答案,用自己的话解释因果和取舍。
第 1 层同一个节点循环三次时,如何区分三次合法操作和一次操作的重试?
节点恢复遇到循环后,幂等身份必须表达合法多次与同次重试的区别。
参考解答
把逻辑循环代次持久化为 intent_generation,例如用 run、节点、业务项 ID、输入 revision 与该代次共同标识 intent_id;同一业务项和相同输入版本的三次合法操作仍要有三个代次。一次合法操作对应一个稳定 operation_id,attempt_id 另记重试次数:合法下一轮递增代次,网络重试及恢复沿用原代次。不能只用节点名,也不能每次进程恢复生成随机键。代次分配与意图提交原子完成,防止两个 Worker 各自发明下一次操作。
沿着这个回答继续深入
第 2 层循环计数在内存,宕机后归零,会有什么后果?
父问建立操作身份后,身份来源的持久化成为新故障点。
参考解答
计数重用可能把第二次合法操作当成第一次重试,或重新生成键造成重复。循环代次和业务项身份应在工具调用前持久化,恢复只读取已提交意图;不存在已提交意图时按条件写建立一次。不能由恢复次数推导业务操作次数。
沿着这个回答继续深入
第 3 层两个 Worker 都读到下一代次为 2,怎样避免创建两个意图?
持久化代次还面临并发分配,需要本地唯一性与远端幂等共同保护。
参考解答
在数据库用唯一约束及条件更新,原子分配逻辑代次并写意图;只有成功领取者执行。第二个 Worker 读现有意图并对账,不新增键。外部工具仍需按同一 operation_id 幂等,因为租约过期的旧 Worker 可能晚到执行。
第 1 层工具已成功而本地数据库不可写,如何进入 unknown 并对账?
远端成功后的本地故障要求承认记录本身也可能失败。
参考解答
本地库不可写时不能声称已把 unknown 持久化到它。调用前已提交的 prepared 意图成为恢复线索:任何没有 confirmed 回执的意图都按可能未知处理,停止新写动作并等待存储恢复再对账。可用独立可靠日志补充,但不能用内存日志替代。远端结果已知也保留其操作键,禁止盲重试。
第 1 层升级后旧状态无法反序列化,回滚与迁移怎么选择?
恢复契约不仅依赖落盘,还依赖代码继续理解原状态。
参考解答
先暂停受影响恢复,按 state_schema 与 workflow 版本判断能否安全迁移。确定性迁移保留原快照、操作账本和审批约束;语义不明确的任务由旧执行器收尾或人工处理。回滚前确认新状态可被旧代码读取,代码回滚不能逆转已发生的副作用。
举一反三:条件变了,怎样推导?
先找出改变的条件,再判断原方案中哪些前提仍成立。下面的案例是教学推演,便于将原理迁移到新问题。
只读研究任务
改变的条件:工具不产生业务写入,但数据可能变化
延伸问题:回放是不是完全没有风险?
推导与参考解答
没有重复写效果的风险,但会重查变化后的资料、花费 Token 或触发限流,结果未必与当时一致。需要复现时保存来源版本或快照和模型配置;需要当前答案则明确采用新资料。计算可重复不代表输入世界保持不变。
保持不变的原理:Checkpoint 保存计算状态,外部世界与成本仍有独立生命周期。
远端不支持幂等
改变的条件:写入 API 没有幂等键,但可按业务编号查询
延伸问题:还能设计可恢复流程吗?
推导与参考解答
先持久化稳定业务编号,超时后查询是否存在,确认后记录回执;查询未找到也要考虑可见性延迟,按目标一致性契约等待。若查询不能唯一识别或结果长期未知,暂停人工核实,不能由本地重放保证不重复。
保持不变的原理:恢复必须证明外部结果,未知状态不能自动等同于失败。
易错点
- 把 checkpoint 当成外部副作用事务
- 每次重试生成新的随机幂等键
- 用内存保存器声称进程重启后可以恢复
- 回放旧状态时忽略当前权限与代码版本
参考资料
依据公开技术资料设计;参考资料支持技术机制,场景与评分标准为本站设计,不代表某公司面试原题。 新增问答与迁移案例用于原理讲解,来源核查与案例运行验证分别记录。
检查自己理解到哪一步
读完后可以对照这些标准解释原理、边界和取舍。掌握程度由你自评;需要进一步验证时,再完成下方小任务。
- 基础达标
- 区分保存状态与外部副作用保证。
- 中高级信号
- 明确恢复起点、完成记录与幂等操作身份。
- 资深信号
- 通过提交前后宕机证明重放边界与未知结果处理。
巩固练习 按需完成 · 建议 15 分钟
在工具提交后、Checkpoint 保存前宕机,推演恢复。
展开验收要求与检查点
- 外部事实不丢失
- 重复请求与重复效果分开
- 恢复不重复消耗已结算预算
重点检查
- 能区分图状态、执行日志与外部业务状态
- 能描述远端成功但本地未记录的失败窗口
- 能解释稳定幂等键、查询对账与无法幂等时的人工介入
- 能设计恢复兼容的状态版本及故障注入验收