Agent 工作流的异常处理:失败可恢复的执行设计
Agent 工作流的异常处理失败可恢复的执行设计一、多步执行的脏状态陷阱Agent 一次任务往往分多步查数据、调工具、写结果。走到第三步失败了前两步的副作用已落地。数据库写了一半文件改了一半外部 API 调了。重跑从头来副作用重复执行。不跑脏状态留在系统里下次接着错。进退两难是 Agent 落地最痛的坑。和单次推理不同多步执行是有状态的。状态没管好失败就不可恢复。本文探讨让 Agent 失败可回滚的执行设计。二、事务式执行与检查点机制可恢复的核心是事务思维。每步执行前先存检查点checkpoint。检查点记录当前状态、已完成的步骤、待补偿动作。失败时按检查点回放补偿动作。补偿是正向操作的反函数写了就删加了就减。不是所有操作都可补偿不可补偿的必须前置校验。下面是事务式执行的链路flowchart TD A[Agent 任务] -- B[存检查点] B -- C[执行步骤] C -- D{成功?} D --|是| E[更新检查点] E -- F{还有步骤?} F --|是| B F --|否| G[任务完成] D --|否| H[读检查点] H -- I[执行补偿动作] I -- J[回滚到安全态] J -- K[断点续跑或告警] style G fill:#e8f5e9 style K fill:#ffebee关键在补偿的完备性。不可补偿的操作如发短信、转账不能靠事后撤销。必须前置要么改设计为可补偿要么用两阶段提交。补偿设计不到位回滚就是空谈。补偿与回滚不是一回事。回滚是数据库事务的原生能力依赖日志恢复到旧版本。补偿是应用层的正向操作用“反向动作”抵消已产生的副作用。数据库写可以回滚发出去的短信只能补偿再发一条撤销通知。补偿必须逆序执行。先执行的步骤副作用在底层后执行的在上层。逆序撤销才不会破坏中间状态。比如先建文件再写内容补偿要先删内容再删文件顺序反了会报错。检查点要存外部。进程内存里的检查点进程一崩就没了。存数据库或对象存储进程重启后能读回来续跑。检查点要带版本号格式升级后旧检查点还能解析。三、生产级带检查点的执行器下面用 Python 实现一个带检查点与补偿的 Agent 执行器。from dataclasses import dataclass, field from typing import Callable dataclass class Step: 一个执行步骤含正向动作与补偿动作 name: str do: Callable[[], None] # 补偿是正向的反函数失败时按完成步骤逆序调用 undo: Callable[[], None] lambda: None dataclass class Checkpoint: 检查点记录已完成步骤供失败时回滚与续跑 completed: list[str] field(default_factorylist) def save(self, name: str) - None: if name not in self.completed: self.completed.append(name) def run(steps: list[Step], cp: Checkpoint) - bool: 事务式执行成功推进失败按已完成步骤逆序补偿 for step in steps: # 跳过已完成步骤支持断点续跑 if step.name in cp.completed: continue try: step.do() cp.save(step.name) except Exception as exc: # 失败时逆序补偿已完成的步骤回滚到安全态 for done in reversed(cp.completed): for s in steps: if s.name done: try: s.undo() except Exception: # 补偿失败要告警不能静默吞掉 print(f补偿失败: {s.name}) print(f任务失败于 {step.name}: {exc}) return False return True if __name__ __main__: cp Checkpoint() def write_db(): print(写入数据库) def rollback_db(): print(回滚数据库) def call_api(): raise RuntimeError(API 不可用) steps [ Step(write_db, write_db, rollback_db), Step(call_api, call_api), ] # 第二步失败第一步的补偿会被自动触发 ok run(steps, cp) print(成功 if ok else 已回滚)真实系统会把检查点持久化到外部存储。进程崩了重启后读检查点续跑而非从头来。补偿动作要做幂等重试不产生重复副作用。幂等靠业务 ID 兜底。每个副作用操作带唯一键执行前先查是否已做过。已做过直接跳过未做过才执行。补偿动作同理带键查重避免重复撤销把正确的状态也撤掉。检查点写入要原子。检查点与副作用操作不能跨网络分两次写否则中间崩了会出现“副作用做了但检查点没记”的脏状态。应先写检查点标记“待执行”再执行副作用最后更新检查点为“已完成”。这套写前日志模式借鉴自数据库的 WAL。四、Agent 工作流的异常处理的代价与边界事务式执行稳妥但不是银弹。补偿的不可行性。发短信、发邮件、真实扣款。这些操作无法撤销补偿无从设计。必须前置校验或用 saga 模式拆成可补偿的子事务。检查点的存储成本。每步存检查点有 I/O 开销。高频小步任务检查点开销可能抵消 Agent 的效率。应在关键节点存而非每步都存。补偿失败的雪崩。补偿本身也可能失败。补偿失败不告警脏状态更深。必须对补偿失败做告警并留人工介入入口。幂等性的要求。续跑时已执行步骤可能重复执行。正向与补偿动作都必须幂等。否则重试一次副作用翻倍。事务式执行的补偿完备性审计要做在前面。上线前逐步骤检查这个操作能否撤销撤销失败怎么办答不上来的步骤就是定时炸弹。另一个被忽视的点是检查点的可见性检查点存在哪、什么格式、怎么读要文档化。否则故障时想人工续跑连状态都看不懂。最后对外部系统的调用要设超时与重试上限避免一步卡死整个事务补偿动作也跟着永远等不到触发脏状态无限期残留。五、总结Agent 可恢复执行本质是用检查点 补偿换失败可回滚。机制上每步存检查点失败时逆序执行补偿动作。工程上补偿需幂等不可补偿操作前置校验。落地路线先识别每步的可补偿性关键节点存检查点失败逆序补偿并告警持久化检查点支持断点续跑。Agent 可以失败但失败后系统必须能回到安全态。
