# Plan-and-Execute怎么落地成DAG执行器
上一篇ReAct、Reflection、规划执行三种思路讲了Plan-and-Execute的设计理念:先拆步骤,再按计划推进,适合复杂任务。
但到了真实项目里,还有一个问题绕不开:
模型吐出来的计划是一段文字,怎么变成能跑、能恢复、能改的系统?
# 面试官会怎么问
"你们的Plan-and-Execute是怎么实现的?"
很多录友的回答是:
"模型输出了一个步骤列表,我们用for循环依次执行,每步调一个工具,执行完返回结果。"
这个回答有三个问题:
第一,它默认了所有步骤都必须串行。 实际上很多步骤互相不依赖,完全可以并行,for循环把能30秒跑完的任务拉成2分钟。
第二,它没说失败怎么办。 第3步挂了,前两步的结果丢不丢?整个任务重来还是跳过继续?
第三,它没考虑计划本身可能有问题。 执行到一半发现某步的结果证明后续步骤没意义了,只能整个推翻重来,还是能局部调整?
面试官真正想听的是:你们怎么把文本计划变成可调度的DAG,怎么处理并行、失败、恢复和重规划。
# 简要回答:四层解决三个问题
for循环执行的问题不是浅,是它回避了依赖、失败和纠错三件事。
要做到并行、可恢复、可纠错,Plan-and-Execute落地要过四层:
第一层,结构化计划。 不要纯文本列表,让模型输出JSON格式的nodes和edges,每个节点是一步,每条边是依赖关系,变成有向无环图DAG。
第二层,校验和排序。 执行前做环检测,确保没有死循环;用拓扑排序算出哪些节点可以同时跑,哪些必须等前置完成。
第三层,状态追踪和持久化。 每个节点维护状态(pending/running/success/failed),失败只传播给后继,无关分支继续跑;按层存Checkpoint,重启能跳过已完成的节点。
第四层,局部重规划。 节点失败或结果证伪了后续步骤时,锁定已完成部分,只对受影响的子图做Replan,上限3次,不全盘推翻。
计划不是一次性的步骤清单,是可变的执行图:并行靠依赖,恢复靠状态,纠错靠局部重规划。

这张图回答的是:文本计划要经过哪几层处理,才能变成可调度、可恢复的执行图。上层是规划校验(环检测→拓扑排序),下层是调度执行(并行调度→状态机→Checkpoint),两条虚线反馈分别处理成环和节点失败。
# 为什么不能按列表串行执行
先看一个真实场景。
用户说:"分析昨天支付接口变慢的原因,写个报告。"
模型给出的计划:
1. 查支付接口延迟监控
2. 查依赖服务健康度
3. 查昨天的发布记录
4. 查错误日志
5. 分析延迟和错误的时间相关性
6. 汇总生成报告
2
3
4
5
6
看着是6个步骤,实际上是3层:
前4步互相不依赖,可以同时发起。第5步要等前4步的数据都到齐。第6步等第5步。
for循环把这3层压成了6步串行。本来30秒能跑完的事,拖成2分钟。
再看失败。第3步查发布记录时API超时了,for循环只有两条路:整个任务抛异常,前两步的结果丢掉;或者跳过继续,第5步拿着不完整的数据硬分析。
正确的行为是第三种:只暂停依赖第3步的节点,其他分支照跑,第3步单独重试。
要做到这一点,前提是系统知道谁依赖谁。所以第一步是把计划转成DAG——节点是步骤,箭头是依赖。
# 结构化计划:让模型输出可解析的DAG
关键在输出格式。不要纯文本列表,要能直接解析的JSON:
{
"nodes": [
{"id": "1", "action": "查延迟监控", "tool": "query_metrics", "params": {...}},
{"id": "2", "action": "查依赖健康度", "tool": "query_service_health", "params": {...}},
{"id": "5", "action": "分析相关性", "tool": "analyze_correlation", "params": {...}}
],
"edges": [
{"from": "1", "to": "5"},
{"from": "2", "to": "5"}
]
}
2
3
4
5
6
7
8
9
10
11
节点带上要调的工具和参数,边表示"from完成后to才能执行"。这样模型交付的就不是文字,而是一张执行图。
# 环检测不能省
DAG的硬要求是无环。但模型在任务复杂时,确实会输出"1依赖2、2依赖3、3依赖1"这种死循环。
标准做法是DFS配三色标记:白色未访问,灰色在当前递归路径上,黑色已访问完。遍历中撞到灰色节点,就说明成环。
检测到环,优先让模型重新规划,而不是自动打断某条边。自动断边看着能跑,但破坏了原始意图,事后排查也不知道断的是哪条。
# 拓扑排序找出并行的层
无环之后要算执行顺序,用Kahn算法:
先统计每个节点的入度(多少条边指向它)。入度为0的节点没有前置依赖,进队列。从队列取节点执行,删掉它的出边,邻居入度减1,减到0就入队。
顺带一个好处:如果最后排出来的节点数少于总数,说明有环。 环检测和排序可以合成一步。
Kahn算法一次从队列里取出的一整批节点,就是可以并行的一层。前面的例子第一层是1、2、3、4,第二层是5,第三层是6。
执行器用异步任务池,对同一层的节点一起发起,全部返回后再进下一层。
但并行度必须有上限。 一层里有20个节点就同时打20个工具调用,很容易撞上API限流,或者一口气吃掉Token预算。用信号量把并发压到固定数量(比如5个),ready队列再长,同时在跑的也只有5个。
# 状态机让失败只影响该影响的节点
每个节点维护自己的状态:pending(前置未完成)、ready(可执行)、running、success、failed、skipped(前置失败被跳过)。
有了状态,三件事才成立:实时知道任务进展到哪;某节点失败时,只把它的后继标成skipped,无关分支继续跑;重启后知道哪些节点已经成功,可以跳过。
默认的失败传播是"前置失败,后继全部跳过"。但这条规则不该一刀切。
比如第4步查错误日志挂了(日志服务故障),第5步用1、2、3的数据仍然能分析,只是结论不够全面。这种情况把边标成软依赖:{"from": "4", "to": "5", "optional": true},第4步失败不阻塞第5步,但要在第5步的输入里明确标注"缺少日志数据"。
# Checkpoint解决长任务重启
20个步骤跑到第15步,服务重启,没有Checkpoint就得从头来。
要存的是四样:DAG结构、每个节点的状态、已完成节点的输出、当前的ready队列。恢复时跳过success的节点,重试running的(它可能只做了一半),然后接着跑。
频率是个权衡:每个节点存一次开销大,全跑完再存等于没存。通常按层存一次,或者固定30秒存一次,具体看单个节点的重跑成本有多高。
# 局部Replan:改后半段,不推翻前半段
Plan-and-Execute的优势是有全局规划,但模型不是神,计划照样会错。
两种情况需要重规划:节点失败且重试无用(权限不足、接口不存在);或者节点的返回结果说明后面的计划没意义了——比如查发布记录返回空,昨天根本没发布,那"分析发布影响"这一步就是空转。
这时候不该放弃整个计划,也不该硬跑完交一份不完整的报告。正确做法是只重规划受影响的那部分:
已成功的节点锁定,不许改。把失败节点连同它的所有后继摘出来,带上原始目标、已完成节点的结果、失败原因,让模型重新规划这一段,然后把新的子图合并回原DAG继续执行。
前面例子里,1、2、4已成功保留,第5步从"分析发布与延迟的相关性"改成"分析依赖服务与延迟的相关性",第6步不动。
Replan必须有次数上限,一般3次。模型每次都规划错就会陷入死循环,超限就终止,返回已完成的部分结果和失败原因。
# 面试怎么讲
别停在"让模型输出计划再执行"。可以这样讲:
我们把它落地成了DAG执行器。
模型输出的是结构化的nodes和edges,不是文本列表。执行前做环检测,用Kahn算法拓扑排序,同一层的节点并行,信号量控制并发数。
每个节点有状态机,失败只传播给后继,无关分支照跑。长任务按层存Checkpoint,重启能跳过已完成的节点。
节点失败或结果证伪了后续计划时,锁定已完成部分,只对受影响的子图做Replan,上限3次。
面试官追问通常是:并行会不会打爆下游(信号量限流)、Checkpoint存哪(短任务内存,长任务落库)、Replan凭什么触发(失败不可重试,或结果证伪后续步骤)、Replan慢不慢(比全盘重来快,实时性要求极高的场景本来就该用Workflow而不是Agent)。
# 知识拓展
# DAG和Workflow是一回事吗?
调度机制几乎一样,区别在图谁定的。Workflow的图是开发者提前画好的,运行时不变;Agent的DAG是模型每次现场生成的。所以Airflow、Argo这类Workflow引擎的调度内核可以直接复用。
# 结构化输出保证了格式,参数错了怎么办?
JSON Schema管得住形状,管不住语义。模型把时间范围写成"yesterday"而不是具体日期,格式完全合法。要么在工具调用前加一层参数校验,要么让工具自己容错解析。前者边界清楚,后者对模型更宽容,选一个在团队内统一。
# 节点粒度怎么定?
默认一个节点对应一次工具调用,或一个逻辑完整的子任务。太粗则失败后无法细粒度恢复,重跑代价高;太细则依赖关系爆炸,Checkpoint和Replan的开销反过来盖过收益。
# 有的节点要跑几小时怎么办?
同步等待会把执行器堵死。让这类节点启动异步任务后立即返回task_id,状态机加一个polling状态定期查进度,完成后再切success触发后继。
# Replan时怎么把已完成的结果给模型?
全塞进Prompt会撑爆上下文。存到外部,Prompt里只放摘要或引用ID,模型需要细节时自己用工具去读。多Agent协作里的Artifact引用也是同一个思路。
# 写在最后
DAG、状态机、Checkpoint、Replan这四层,每一层都对着一个具体的坑:DAG解决依赖和并行,状态机解决失败隔离,Checkpoint解决重启,Replan解决计划本身出错。
少了这些,Plan-and-Execute就只是"先规划再执行"六个字。
面试时能把这四层的因果讲出来,面试官才知道你真跑过这套东西。
评论
验证登录状态...