1. 自动化跑完最后一公里,人反而更忙了
你有没有遇到过这种场景:凌晨两点,你盯着屏幕上那个跑了三个小时的自动化工作流,进度条卡在87%不动了。日志里最后一行写着“等待元素超时”,然后就是一片死寂。你心里清楚,明天早上九点之前如果这个流程跑不完,下游的报表、通知、数据同步全部要延迟。于是你只能手动打开那个界面,一步一步把剩下的13%点完。
这不是段子,这是很多做AI自动化和Agent工作流的人每天都在经历的事。我们花了大量时间搭建流程、调试节点、优化提示词,结果真正让人崩溃的,往往不是流程本身跑不通,而是跑了一半失败之后,工作该怎么继续。
“麦睿菱AI实践”这个项目标题里提到的“自动化失败后,工作怎样继续”,恰恰戳中了当前AI Agent和工作流落地中最真实的痛点。大家聊AI自动化的时候,喜欢聊“一键完成”“全自动闭环”“无人值守”,但真正在一线干活的人都知道,自动化系统的健壮性,不取决于它顺利时跑得多漂亮,而取决于它失败时能不能优雅地交棒给人。
这篇文章想聊的就是这件事:当你的AI工作流、Agent任务、自动化脚本在中途挂掉之后,怎么设计一套“失败续跑”的机制,让工作不至于从头再来,也不至于让人陷入比手动做还累的窘境。不管你是用Coze搭工作流、用Playwright做自动化测试、还是自己写Agent调度逻辑,这套思路都能直接拿去用。
2. 为什么自动化失败后的“续跑”比“重跑”难十倍
2.1 重跑的成本被严重低估了
很多人对自动化失败的第一反应是:重新跑一遍不就行了?这个想法在流程很短、状态很轻的场景下没问题。比如你写个脚本批量重命名文件,失败了重来一遍,几秒钟的事。但放到AI工作流和Agent场景里,重跑的代价完全不是一个量级。
我拿一个典型的简历筛选工作流举例。假设你的流程是这样的:从邮箱拉取简历附件,调用OCR提取文本,用大模型做信息抽取和评分,最后把结果写入表格并发送通知。这个流程跑到“大模型评分”这一步失败了,如果你选择重跑,意味着前面拉取邮件、OCR识别、文本清洗全部要再来一遍。如果简历有几百份,光是OCR那一步就可能花掉十几分钟。更关键的是,有些操作是有副作用的——比如你已经给候选人发了确认邮件,重跑会导致重复发送;比如你已经写入了部分数据,重跑会产生脏数据。
这就是重跑和续跑的本质区别:重跑假设系统是无状态的,续跑要求系统能记住自己走到哪了。而绝大多数AI工作流工具,默认并不帮你做这个状态记录。
2.2 Agent场景下的失败更隐蔽
传统自动化脚本的失败通常很明确:元素找不到、接口返回错误、文件不存在。但AI Agent的失败往往更隐蔽。比如你让Agent去完成一个“调研竞品并生成报告”的任务,它可能不会报错,而是自信地给你一份基于错误信息的报告。这种“静默失败”比直接崩溃更可怕,因为你不知道它从哪一步开始跑偏的。
还有一种情况是Agent陷入了循环。它反复调用同一个工具,每次都得到相似的结果,但就是无法推进到下一步。你在日志里看到的是正常的工具调用记录,但整个任务实际上已经卡死了。这种失败没有异常抛出,没有错误码,只有时间在一分一秒地流逝。
所以讨论“失败后工作怎样继续”,首先要区分失败的几种类型:
| 失败类型 | 典型表现 | 是否可续跑 | 处理策略 |
|---|---|---|---|
| 硬失败 | 接口超时、元素找不到、进程崩溃 | 通常可以 | 从检查点恢复 |
| 软失败 | 结果质量差、逻辑跑偏 | 需要人工判断 | 人工介入修正后继续 |
| 静默失败 | 无报错但任务未完成 | 需要检测机制 | 增加校验节点 |
| 循环失败 | Agent反复执行同一操作 | 需要熔断 | 设置最大重试次数 |
2.3 状态管理的三个层次
要让工作流在失败后能继续,核心是做好状态管理。我把状态管理分成三个层次,你可以对照自己的系统看看在哪一层。
第一层:无状态。每次执行都是全新的,失败了就从头来。这是最简单的模式,适合短流程、无副作用的场景。大部分入门级的自动化脚本都在这一层。
第二层:检查点状态。在流程的关键节点记录执行进度,失败后可以从最近的检查点恢复。比如每处理完10条数据就写一次进度文件,重启时读取这个文件决定从哪继续。这是大多数生产级工作流应该达到的层次。
第三层:事务状态。不仅记录进度,还记录每个操作的输入输出和副作用,支持回滚和补偿。比如某个步骤失败了,系统能自动撤销之前步骤产生的副作用,然后从干净的状态重新执行。这是金融级系统才需要的层次,对大多数AI工作流来说过于重了。
对于“麦睿菱AI实践”这类场景,我建议至少做到第二层。具体怎么落地,下一节展开讲。
3. 给工作流装上“存档点”:检查点机制的设计与落地
3.1 检查点应该记什么
设计检查点机制,第一个要回答的问题是:我需要记住什么,才能在失败后继续?
很多人第一反应是记住“执行到第几步了”。但这个信息远远不够。假设你的流程是处理一个列表,执行到第37条失败了。你知道要从第37条继续,但第37条之前的处理结果在哪?如果结果只存在内存里,进程一挂就全没了。所以检查点至少要包含三类信息:
- 进度信息:当前处理到哪个元素、哪个步骤。这是最基本的。
- 中间结果:已经处理完的数据、已经生成的中间产物。这些需要持久化到磁盘或数据库。
- 副作用记录:已经执行了哪些不可逆的操作,比如发送了哪些通知、写入了哪些外部系统。这是为了避免重复执行。
我见过很多团队只做了第一项,结果续跑的时候发现中间结果丢了,只能把前面的步骤重跑一遍来重建上下文。这就失去了续跑的意义。
3.2 检查点的写入时机
检查点写得太频繁,会影响性能;写得太稀疏,失败时会丢失大量进度。怎么找平衡点?
我的经验法则是:按“重跑成本”来决定写入频率。如果一个步骤重跑要花30秒,那每完成这个步骤就写一次检查点。如果一个步骤只要0.1秒,那可以每100个步骤写一次。核心思路是让“最坏情况下需要重跑的工作量”控制在一个可接受的范围内。
具体到代码层面,一个简单的检查点写入逻辑大概长这样:
import json import os CHECKPOINT_FILE = "checkpoint.json" def save_checkpoint(step, processed_items, side_effects): checkpoint = { "step": step, "processed_items": processed_items, "side_effects": side_effects, "timestamp": time.time() } # 先写临时文件再重命名,避免写入过程中崩溃导致文件损坏 tmp_file = CHECKPOINT_FILE + ".tmp" with open(tmp_file, "w") as f: json.dump(checkpoint, f) os.replace(tmp_file, CHECKPOINT_FILE) def load_checkpoint(): if os.path.exists(CHECKPOINT_FILE): with open(CHECKPOINT_FILE) as f: return json.load(f) return None这里有个细节值得注意:写检查点要用“临时文件+原子重命名”的方式。如果你直接往目标文件写,写到一半进程崩了,检查点文件就损坏了,下次启动读不出来,等于白做。这个坑我在早期项目里踩过,排查了半天才发现是文件写入的原子性问题。
3.3 续跑时的状态恢复逻辑
有了检查点,续跑的逻辑就清晰了:启动时先读检查点,如果存在,就从检查点记录的位置继续;如果不存在,就从头开始。
但这里有个容易忽略的问题:检查点可能过期。比如你的工作流是每天处理当天的数据,昨天的检查点今天就不适用了。所以检查点里最好带上一个“批次标识”或“时间戳”,启动时校验一下这个检查点是否属于当前批次,不属于就忽略。
另一个问题是检查点与外部状态的一致性。假设你的检查点记录“已处理到第37条”,但外部数据库里第35条的数据被人工修改了。这时候从第37条继续,可能会导致数据不一致。对于这种情况,我的建议是:检查点只记录进度,不记录业务数据的快照。业务数据以外部系统为准,续跑时重新读取。检查点的作用是告诉你“从哪继续”,而不是“当时的数据长什么样”。
4. 人工接管的接口设计:让“交棒”不添乱
4.1 失败通知要带够上下文
自动化失败后,如果系统决定交给人来处理,第一件事是通知人。但很多系统的通知做得极其敷衍,比如只发一句“任务失败,请检查”。这种通知等于没发,因为人还得自己去翻日志、找原因、定位进度。
一个好的失败通知应该包含这些信息:
- 失败位置:在哪个步骤、处理到哪个元素时失败的
- 失败原因:错误类型、错误信息、相关日志片段
- 当前进度:整体完成了多少,还剩多少
- 已产生的副作用:已经做了哪些不可逆的操作
- 建议操作:是重试、跳过、还是人工处理
把这些信息组织成一条消息,推送到人能看到的地方。如果是即时通讯工具,最好带上一个“一键继续”的链接或按钮,让人不用登录服务器就能触发续跑。
4.2 人工修正后的“回填”机制
人工介入处理完失败点之后,工作流需要从人工处理的结果继续往下走。这里的关键是人工处理的结果要能被工作流识别和消费。
举个具体例子。假设你的工作流在“调用大模型评分”这一步失败了,因为某个简历的文本格式异常导致模型返回了错误。人工介入后,你手动给这份简历打了个分。现在工作流要继续处理剩下的简历,它需要知道:这份简历已经有人工评分了,不要再调模型了。
实现方式可以是在数据里加一个字段,比如manual_override: true和manual_score: 85。工作流在处理每条数据时先检查这个字段,有值就直接用,没有才走自动流程。这样人工修正的结果就无缝地融入了后续流程。
4.3 部分成功怎么算
还有一种棘手的情况:一个步骤处理了10个元素,成功了7个,失败了3个。这时候工作流应该算成功还是失败?
我的处理原则是:能继续就继续,不能继续的单独标记。也就是说,不要因为3个失败就阻断整个流程。把成功的7个正常推进到下一步,失败的3个记录下来,等整个流程跑完之后再统一处理。这样至少保证了大部分工作能往前走,而不是卡在一个点上。
当然,这要求你的流程设计是“元素级独立”的。如果元素之间有依赖关系,比如第5个元素的处理需要第3个元素的结果,那就不能这么简单处理了。所以在设计工作流的时候,尽量让每个元素的处理是独立的,这会大大降低失败处理的复杂度。
5. 从工具层面看:不同平台的续跑能力对比
5.1 Coze、Dify这类工作流平台
Coze和Dify这类平台的优势是可视化搭建,节点之间的数据流转很直观。但它们在失败续跑方面的能力参差不齐。
Coze的工作流目前对失败处理的支持比较基础,主要是节点级的重试配置。你可以设置某个节点失败后重试几次、重试间隔多久。但它没有内置的检查点机制,如果整个工作流在中途失败,重新执行是从头开始的。对于短流程这没问题,对于长流程就比较痛苦。
Dify在这方面稍好一些,它的工作流支持变量和会话状态,可以在一定程度上实现断点续传。但需要你自己在节点里写逻辑来存取状态,平台本身不自动帮你做这件事。
如果你用这类平台搭建长流程,我的建议是:把长流程拆成多个短流程。比如把“拉取数据”做成一个工作流,“处理数据”做成另一个,“写入结果”再做成一个。每个短流程独立执行、独立重试。这样即使中间某个环节失败,也不需要从头再来。
5.2 Playwright、Appium这类自动化测试框架
自动化测试框架的失败续跑是另一个典型场景。Playwright和Appium本身不提供检查点机制,但它们的测试运行器通常支持“失败重试”和“只跑失败的用例”。
以Playwright为例,你可以在配置里设置retries: 2,这样失败的用例会自动重试两次。但这是用例级的重试,不是步骤级的。如果一个用例有20个步骤,在第15步失败了,重试是从第1步开始的。
要做到步骤级续跑,需要自己封装。一个常见的做法是把每个步骤的完成状态写到一个上下文对象里,用例开始时先检查这个对象,已经完成的步骤就跳过。这样虽然不能完全避免重跑,但至少能跳过那些耗时的前置步骤。
5.3 自建Agent调度系统
如果你是自己写Agent调度逻辑,那续跑的灵活性最高,但工作量也最大。你需要自己实现检查点存储、状态恢复、失败通知、人工接管这一整套机制。
我的建议是不要一开始就追求大而全。先从最简单的做起:在流程的关键节点写一个进度文件,失败后能从这个文件恢复。这个最简单的机制就能解决80%的问题。等真的有更复杂的需求了,再逐步加上副作用记录、人工接管接口这些高级功能。
6. 几个真实踩过的坑和对应的解法
6.1 检查点文件被并发写坏了
早期我做的一个数据同步工作流,支持多个实例同时跑。结果发现检查点文件经常损坏,续跑时读不出来。排查后发现是多个实例同时往同一个文件写检查点,互相覆盖导致的。
解法很简单:每个实例用独立的检查点文件,文件名里带上实例ID或进程ID。续跑时根据实例ID找到对应的检查点。如果实例是动态创建的,那就用一个中心化的存储(比如数据库)来管理检查点,用行级锁保证并发安全。
6.2 续跑后重复发送了通知
这个坑更隐蔽。工作流在处理完一条数据后会发送通知邮件,检查点是在发送邮件之后写的。结果有一次在写检查点之前进程崩了,续跑时从上一个检查点恢复,导致最后一条数据的通知被重复发送。
解法是把副作用操作和检查点写入放在一个事务里。要么都成功,要么都失败。如果做不到事务,那就把检查点写在副作用操作之前,续跑时先检查副作用是否已经执行过。比如发邮件前先查一下发送记录,已经发过的就跳过。
6.3 Agent续跑后“失忆”了
Agent场景下的续跑有个特殊问题:Agent的上下文丢失了。比如一个Agent在前面几步通过工具调用获取了一些信息,这些信息存在对话历史里。失败重启后,对话历史没了,Agent不知道之前发生了什么,可能会重复调用工具或者做出错误的决策。
解法是把Agent的关键上下文也纳入检查点。不只是记录“执行到第几步”,还要记录“Agent当前知道什么”。具体来说,就是把对话历史、工具调用结果这些信息序列化存下来,续跑时恢复。当然这会让检查点变大,需要权衡存储成本和恢复价值。
6.4 人工接管后忘了“交还控制权”
这个坑是流程设计的问题。人工介入处理完失败点后,工作流应该自动继续。但有时候人工处理完就忘了触发续跑,工作流一直停在那里。或者更糟的是,人工处理的同时工作流也在重试,两边同时操作导致数据冲突。
解法是明确控制权的转移。工作流失败后进入“等待人工”状态,这个状态下工作流不再自动重试。人工处理完后,通过一个明确的动作(比如点击按钮、调用接口)把控制权交还给工作流。在“等待人工”状态下,工作流的所有自动重试都要暂停。
7. 一套可复用的失败续跑设计清单
把上面的经验整理成一份清单,你在设计任何AI工作流或Agent任务时都可以对照检查:
状态记录方面:
- 是否在关键节点写了检查点?
- 检查点是否包含进度、中间结果、副作用记录?
- 检查点写入是否原子化,避免损坏?
- 检查点是否有批次标识,避免跨批次误用?
失败检测方面:
- 是否有超时机制,避免无限等待?
- 是否有循环检测,避免Agent卡死?
- 是否有结果校验,避免静默失败?
人工接管方面:
- 失败通知是否包含足够的上下文?
- 人工处理的结果是否能被工作流消费?
- 控制权转移是否有明确的机制?
续跑执行方面:
- 续跑时是否能正确恢复状态?
- 副作用操作是否有幂等性保证?
- 部分成功的情况下是否能继续推进?
这份清单不是要求每一条都做到,而是帮你识别当前系统的薄弱环节。根据实际场景选择最重要的几条先落地,比追求完美更重要。
8. 写在最后的一点个人体会
做AI自动化和Agent工作流这几年,我最大的体会是:自动化的价值不在于替代人,而在于让人把精力花在真正需要人的地方。一个设计良好的自动化系统,不是永远不出错,而是出错之后能快速恢复,让人只需要处理那20%的异常情况,而不是被80%的重复劳动困住。
“自动化失败后工作怎样继续”这个问题,本质上是在问:我们怎么和自动化系统协作?是把所有控制权都交给它,还是保留人在关键节点的介入能力?我的答案是后者。至少在目前的技术水平下,人机协作的混合模式,比追求全自动无人值守更现实、更可靠。
所以下次你的工作流又卡在87%的时候,别急着骂它不争气。想想怎么给它加个存档点,怎么让它在失败时优雅地喊你帮忙,怎么让你帮完忙之后它能自己接着跑。这些看起来不起眼的机制,才是自动化系统真正能落地的关键。