假设你让一个 AI 完成这样一件事:分析一家公司的竞争格局,写出报告,并根据结论生成一份演示文稿。
AI 很快给出一个 Plan:搜集资料、整理竞争对手、分析市场、撰写报告、制作 PPT。看起来条理清楚,但真正执行时问题才出现:哪些步骤必须等前一步完成?哪些可以同时进行?如果两个任务互相等待怎么办?AI 生成的执行图,又怎么判断它真的能跑?
Plan 解决的是"要做什么",DAG 解决的则是"这些事情应该以什么顺序执行"。
两者之间,差的正是一套关于依赖关系的判断方法。
判断 Dependency,别看任务顺序,要看输入从哪里来
很多人判断两个任务有没有 Dependency,会直接看 Plan 里的排列顺序。
比如:
A. 搜索行业资料
B. 搜索竞争对手资料
C. 分析市场趋势
D. 撰写报告
因为 B 写在 A 后面,就默认 B 依赖 A。
这通常是不对的。
真正应该问的问题是:
任务 B 能不能在完全不知道任务 A 输出结果的情况下开始,并正确完成?

如果可以,两者就不存在执行依赖。
例如"搜索行业资料"和"搜索竞争对手资料",虽然在计划中前后排列,但它们各自只需要用户提供的研究主题,因此完全可以同时执行。
真正的 Dependency 通常来自三种情况。
第一种是数据依赖。
任务 B 需要任务 A 的输出作为输入。
例如:
搜索竞争对手 → 比较竞争对手优劣
没有竞争对手资料,就没法进行比较。
第二种是决策依赖。
后面的任务虽然不直接读取前一个任务的数据,但执行方式取决于前面的判断。
例如:
判断用户属于哪种类型 → 选择对应营销策略
第三种是状态依赖。
任务必须等某个动作完成之后才能继续。
例如:
创建数据库表 → 写入数据
写入动作需要目标表已经存在。
因此,判断 Dependency 最实用的方法,不是问"谁在谁前面",而是检查每个任务需要哪些输入,以及这些输入由谁产生。
Plan 转成 DAG,关键是先找节点,再画边

自然语言 Plan 往往是线性的。
但真实任务通常不是一条流水线,而更像一张交通网络。
假设任务是:
调研一个行业,并生成投资分析报告。
Plan 可能写成:
-
搜索市场规模
-
搜索主要公司
-
搜索行业风险
-
分析市场机会
-
分析竞争格局
-
撰写报告
-
检查报告
如果照着编号直接连接:
1 → 2 → 3 → 4 → 5 → 6 → 7
虽然能运行,却损失了大量并行能力。
更合理的方法,是先把每个动作看成一个 Node,然后给节点标注 Input 和 Output。
例如:
-
市场搜索 → 输出市场资料
-
公司搜索 → 输出公司资料
-
风险搜索 → 输出风险资料
-
市场分析 → 输入市场资料
-
竞争分析 → 输入公司资料
-
报告撰写 → 输入市场分析、竞争分析、风险资料
-
报告检查 → 输入最终报告
这时依赖关系会自然出现:
市场搜索 ─→ 市场分析 ─┐
│
公司搜索 ─→ 竞争分析 ─┼→ 报告撰写 → 报告检查
│
风险搜索 ────────────┘
DAG 的核心并不是"画一张图",而是把一个模糊计划变成明确的数据流。
一个很好用的转换流程是:
拆任务 → 定义输入输出 → 建立依赖边 → 检查环 → 找出可并行节点。
只要输入输出定义清楚,后面很多问题都会简单很多。
DAG 出现环,通常说明任务定义出了问题

DAG 的全称是 Directed Acyclic Graph,也就是"有向无环图"。
所以一旦出现:
A → B → C → A
它就已经不是 DAG 了。
更麻烦的是,这在 LLM 自动规划时并不少见。
假设 AI 生成:
A:确定报告结构
B:根据报告结构完成研究
C:根据研究结果调整报告结构
于是形成:
A → B → C → A
看起来每一步都合理,但执行器会陷入问题:A 等 C,C 等 B,B 又等 A。
这种环往往来自一个常见原因:把迭代关系误写成执行依赖。
现实工作当然允许反复修改,但单次 DAG 执行需要有明确方向。
一种处理方法,是把循环展开。
原本:
结构 → 研究 → 调整结构 → 结构
可以改成:
初版结构 → 研究 → 修订结构 → 最终写作
虽然"初版结构"和"修订结构"处理的是同一个对象,但它们已经是两个不同阶段的任务。
如果确实需要反复迭代,也应该让 DAG 表达"一轮执行",再由外层控制逻辑决定是否启动下一轮,而不是把循环直接塞进 DAG。
检查 LLM 生成的 DAG,至少过四道关

让 LLM 生成 DAG 并不难。难的是不能因为它输出了一段结构漂亮的 JSON,就认为计划一定正确。
最基础的一层是结构检查。
例如每个 Node 是否有唯一 ID,所有 Dependency 指向的节点是否真的存在,有没有节点依赖自己。
第二层是无环检查。
经典方法是 Topological Sort,也就是拓扑排序。
如果一个包含 N 个节点的图最终能成功排序出 N 个节点,说明不存在环;如果还有节点始终无法进入结果,就说明某处存在循环依赖。
第三层是语义检查。
这是纯图算法发现不了的。
比如:
Node B:总结搜索结果
depends_on:\[\]
图结构完全合法,但如果 B 明明需要 Node A 的搜索结果,却没有声明依赖,那么执行时依然会失败。
因此需要检查:
每个节点声明的输入,是否都能从用户输入或它的祖先节点中获得。
第四层是冗余依赖检查。
假设:
A → B → C
同时又写了一条:
A → C
如果 C 只需要 B 的输出,那么 A → C 很可能没有必要。
过多的 Dependency 不一定让 DAG 更安全,反而可能减少并行机会,让整个任务跑得更慢。
所以一个合法 DAG 至少应该满足:
引用合法、没有环、数据可达、依赖尽量最小。
哪些节点可以 Parallel?看它们是否已经"Ready"

并行并不意味着"没有 Dependency 的任务才能一起跑"。
更准确的判断是:在当前时刻,哪些节点的所有前置依赖都已经完成。
假设:
A ─→ C ─┐
├→ E
B ─→ D ─┘
一开始 A 和 B 都没有前置依赖,可以并行。
A 完成后 C 可以开始;B 完成后 D 可以开始。
如果此时 A 已完成、B 还在执行,那么 C 可以和 B 同时运行。
所以 Parallel 是一个动态概念。
执行器通常维护一个 Ready Set:
所有尚未执行,并且全部依赖已经完成的节点。
每完成一个 Node,就重新计算一次 Ready Set,然后把新的 Ready Nodes 放进执行队列。
这里还有一个容易忽视的问题:图上可以并行,不代表现实中一定适合并行。
两个节点可能没有数据依赖,却同时操作同一个文件;也可能同时调用一个限流严重的 API;甚至同时修改同一个数据库对象。
因此实际系统还需要考虑资源冲突、并发限制和副作用。
可以把并行判断理解成两层:
Dependency 决定"能不能一起执行",资源约束决定"应不应该一起执行"。
好的 DAG,追求的不是复杂,而是最小必要依赖

从 Plan 到 DAG,最容易犯的错误,是试图把计划中的每一个先后关系都变成一条边。
结果通常是一张看起来非常严谨、执行起来却极其串行的图。
更好的问题是逐个检查:
如果删除这条 Dependency,任务还能够得到正确结果吗?
如果答案是可以,这条边很可能就不应该存在。
一个优秀的 DAG,不是依赖越多越可靠,而是在保证正确性的前提下,只保留最小必要依赖。
这样做的直接收益不仅是速度。
任务失败时,你更容易知道影响范围;某个节点需要重试时,不必重新执行整条链路;想增加并发时,也能快速发现哪些任务真正独立。
所以以后看到一个 Plan,不妨先别急着问"下一步是什么"。
先给每个任务写下两个东西:它需要什么输入,它会产生什么输出。
当这两个问题足够清楚时,Dependency、DAG、环检测和 Parallel,往往都会从同一张图里自然地长出来。
