凌晨两点,一个跑跨系统数据迁移的 agent 在第七步调第三方 SDK 时超时,进程被杀。告警弹出来,日志最后一行停在一个不完整的 JSON 上。摆在面前的只有两个选项:从头重跑,还是从 checkpoint 恢复?我最早觉得,这有什么好纠结的,能恢复当然恢复,省时省钱。直到一次线上故障教会我:checkpoint 恢复看起来在省成本,实际上它的可靠性账,远比表面复杂。

Checkpoint 里存的到底是什么?
你可以把 agent 想象成一个带上下文的执行体。checkpoint 不是一张照片,它至少包含三层:
- 模型的生成态:Transformer 的 KV cache,也就是上下文已经被前向计算过的中间激活值。它让你免去把长 prompt 重新算一遍。
- 执行轨迹:已完成的工具调用、对应的返回结果、当前处于流程图中的第几个节点。这一层通常由 LangGraph、Temporal 这类工作流框架帮你持久化。
- 外部世界状态:数据库事务是否已提交、API 调用是否已生效、邮件是否已发出。这一层框架管不了,只能靠业务代码去核实。
前两层决定你能否在逻辑上恢复,第三层决定恢复后的世界是否可信。大多数框架做好的只是前两层,而真正的故障往往发生在第三层。
| 维度 | 从头重来 | Checkpoint 重启 |
|---|---|---|
| Token 与调用成本 | 已发生步骤全部重做,成本随失败点前移而放大 | 跳过已完成步骤,但保存、读取和校验恢复态本身有成本 |
| 时间 | 依赖上游服务响应,慢但可预期 | 恢复快,但状态一致性校验可能拖时间 |
| 副作用 | 重跑可能产生重复写入,需要幂等兜底 | 崩溃前已生效的副作用不会自动回滚,继续执行可能二次触发 |
| 确定性 | 从头跑更容易定位问题,行为可复现 | 从断点继续生成的后续动作可能跑偏,实际出现平行分支 |
| 实现复杂度 | 外层加个 retry 就行 | 需要序列化、验证状态、回放与回滚策略 |
Checkpoint 重启不是继续推理,而是重放
我踩过最深的坑,是把 checkpoint 当成 LLM 的暂停键。后来翻了不少框架实现才想通:所谓恢复,实际是把之前执行过的决策按日志重放一遍,遇到新的决策点才让模型重新生成。如果模型推理带随机性,从同一状态续跑,后续动作不一定和原本相同。
为什么这件事重要?因为 agent 的后续决策依赖整个历史上下文。恢复后第一步如果不同,后面就全变了。严格说,你并没有恢复原来那个 agent,你只是启动了一个平行版本,碰巧拥有同一个过去。这个平行版本可能做出完全不同的行为。
所以可靠的恢复方式不是保留一堆 KV cache,而是保存一份决策记录:某一步调了什么 API、返回了什么,模型在哪个节点做了什么样的选择。恢复时优先重放这些记录,而不是让模型重新猜一遍。框架里常见的 checkpoint 保存消息历史和节点状态,本质就是在为重放准备素材。
再加上工具结果缓存后,重放还可以避掉一部分真实 API 调用。此时 checkpoint 变成了一个重放缓存,而不是神秘断点。
把 checkpoint 理解成重放缓存,你对恢复机制的理解就到位了一半。
我因为自动重试付了两次费
有段时间我为了省事,给 agent 整体套重试。某次支付调用超时,agent 自动重跑,第二笔订单真的创出去了。不是 retry 的错,也不是 checkpoint 的错,是我忘了把写操作设计成幂等。后来我在所有写操作里加幂等键:同一个 request_id 后端只生效一次。从那以后,不管重跑还是恢复,至少副作用不会加倍。
如果你没有处理幂等,checkpoint 恢复和从头重来会踩同一个坑。甚至更隐蔽:因为恢复给人的安全感更强,你更容易忽略那些已经发生但还没有确认的副作用。
更稳的模式其实是事件溯源,而不是简单快照。把每一步决策和工具结果以追加日志的形式记录下来,恢复时从日志重建状态,只执行未完成的部分。它比 checkpoint 多一个优点:可审计。任何一个外部副作用都有上下文可查,LLM 中间输出也不会凭空消失。
不该为了省 token 放弃简单性
从成本看,checkpoint 确实能省下前面十几轮的 token 和服务调用。假设任务有 20 步,每步上下文几千 token,从头重跑几乎就是双倍预算。但要让 checkpoint 恢复可靠运行,你得额外处理状态版本、脏数据、工具结果缓存一致性。这些复杂度本身可能引发新故障,于是故障恢复机制变成了新的故障来源。
我现在做选择时会按这样的顺序:
- 任务步骤少于十个:直接从头重跑。失败点之前的成本本来就小,简单策略最不容易出错。
- 任务长、成本高且工具调用都带幂等键:考虑 checkpoint。前提是外部状态快照和执行轨迹对齐,否则只能当缓存看。
- 拿不准时:先保证每个工具调用可重放。这一条同时改善重跑和恢复两种策略。
另一个很容易被忽略的因素是上游数据会变。如果 checkpoint 保存的是两小时前的上下文,而数据库里相关记录已经更新,按旧状态恢复就是一个 Bug。事件日志能帮你判断哪些状态过期了,纯快照做不到。
恢复的边界在哪?
我认为 agent 的 checkpoint 本质上是一种缓存,而不是游戏存档。它避免不必要的工作,但不会自动保证外部世界一致。从头重来最可靠的前提,是你容忍重复计算和失败成本。而 checkpoint 的合理切入点是任务长到无法简单重做,并且你已经用幂等把副作用问题处理干净之后。
回到那个凌晨的问题。现在我大概率会先看日志:如果没有产生不可逆副作用,直接从头重跑;如果已经有写入、扣款或通知,我会先找到对应的幂等键和事件记录,再决定从哪个边界开始重放。没有这条重放边界,checkpoint 不过是用更复杂的方式从头再跑罢了。
原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/696.html