Plan-and-Execute 里的 DAG 是怎么工作的

文章目录

    • [一、什么是 DAG?](#一、什么是 DAG?)
    • [二、Plan-and-Execute 架构中的 DAG 从何而来?](#二、Plan-and-Execute 架构中的 DAG 从何而来?)
    • [三、DAG 如何驱动任务执行?](#三、DAG 如何驱动任务执行?)
      • 1、任务节点:Task是最小执行单元
      • [2、计划容器:ExecutionPlan 维护整张图](#2、计划容器:ExecutionPlan 维护整张图)
      • [3、Planner:让 LLM 把自然语言变成 DAG](#3、Planner:让 LLM 把自然语言变成 DAG)
    • [四、为什么 Plan-and-Execute 选了 DAG 而不是一条直线?](#四、为什么 Plan-and-Execute 选了 DAG 而不是一条直线?)
    • 总结

在 ReAct 模式里,Agent 是"走一步看一步"的------每轮 LLM 调用决定下一个动作,没有全局蓝图。当任务变复杂(例如"分析项目结构,找出性能瓶颈,给出优化方案并验证"),这种局部决策容易陷入反复试探。

而在 Plan-and-Execute模式里,先让 LLM 把目标拆解成一张任务依赖图(DAG),再按拓扑顺序调度执行,能并行的地方绝不串行。这篇文章就拆解这套 DAG 从生成到落地的完整生命周期。

一、什么是 DAG?

DAG,全称 Directed Acyclic Graph(有向无环图),本质上是一种特殊的数据结构。它由两部分组成:

  • 顶点(vertex):代表一个个的实体,比如任务、交易、数据节点;
  • 有向边(directed edge):代表实体之间的单向关系,比如依赖、验证、执行顺序。

之所以叫"无环",是因为沿着这些有向边永远无法走回起点,也就是说图里不存在闭环。这个性质让 DAG 天然适合用来表达具有依赖关系的流程------你只能沿着一个方向推进,不会出现循环等待。

在区块链和分布式系统中,DAG 常用于表示交易的验证关系,比如每一笔新交易需要验证前两笔未被验证的交易。而在 Plan-and-Execute 智能体架构中,DAG 扮演的角色更加"日常":它负责把一个大任务拆解成小步骤,并且清晰地管理步骤之间的依赖顺序

二、Plan-and-Execute 架构中的 DAG 从何而来?

Plan-and-Execute,顾名思义,是一种"先规划,后执行"的智能体工作模式。它有三个核心角色:

  • 规划器(Planner):把用户的复杂任务拆解成一个有序的子任务列表,并指定每个子任务依赖哪些前面的结果;
  • 执行器(Executor):按照依赖关系,逐步(或者并行)执行这些子任务;
  • 重规划器(Replanner):如果某个子任务失败或者遇到意外情况,动态调整后续计划。

注意,规划器给出的子任务列表并不是一个简单的线性清单,而是一个带有依赖声明的结构化计划。举个例子,假设我们要分析市场并制作一份竞争报告,规划可能会是这样:

复制代码
步骤1: 收集竞争对手列表 [web_search]
步骤2: 分析产品特点 [web_scrape] (依赖步骤1)
步骤3: 收集市场份额 [data_query] (依赖步骤1)
步骤4: 生成报告 [document_generate] (依赖步骤2, 3)

这里每一步都明确写了自己需要前面哪些步骤的输出。把这些步骤看作顶点,依赖关系看作有向边,我们立刻就能画出一个 DAG:

复制代码
步骤1 → 步骤2
步骤1 → 步骤3
步骤2 → 步骤4
步骤3 → 步骤4

这个图没有环,方向清晰,Plan-and-Execute 中的 DAG 精确地指出每条执行的分支和汇合点。

三、DAG 如何驱动任务执行?

有了DAG,执行器就知道该如何行动了。它的核心逻辑就像是一个聪明的调度器,不断扫描当前所有可以执行的任务,一个任务能开始的前提是:它的所有前置依赖都已经成功完成

1、任务节点:Task是最小执行单元

java 复制代码
private final List<String> dependencies;  // 我依赖谁
private final List<String> dependents;    // 谁依赖我

dependencies 是前置条件。只有所有依赖都进入 COMPLETED 状态,该任务才变成可执行:

java 复制代码
public boolean isExecutable(Map<String, Task> allTasks) {
    if (status != TaskStatus.PENDING) return false;
    for (String depId : dependencies) {
        Task dep = allTasks.get(depId);
        if (dep == null || dep.getStatus() != TaskStatus.COMPLETED) {
            return false;
        }
    }
    return true;
}

只有当所有依赖都标记为 COMPLETED,这个任务的状态才会从 PENDING 变为 RUNNING。这正是 DAG 拓扑排序思想的具体实现:在任何一个时刻,系统中都会有一批"就绪任务",它们要么没有依赖,要么依赖已经满足。

这种机制带来了两个关键好处:

  1. 并行执行:所有就绪任务可以同时启动。上面的例子中,步骤2和步骤3在步骤1完成后可以并发执行,而不需要一个等另一个。这就是现代智能体高效处理多步骤任务的基础。
  2. 有序推进:复杂的任务永远不会出现"先跑了下游任务却发现上游还没完成"的混乱情况。DAG 的无环性质保证了执行流程的确定性。

2、计划容器:ExecutionPlan 维护整张图

ExecutionPlan 是 DAG 的载体。它用 LinkedHashMap<String, Task> 保存所有节点,并在添加任务时双向建立依赖

java 复制代码
public void addTask(Task task) {
    tasks.put(task.getId(), task);
    for (String depId : task.getDependencies()) {
        Task dep = tasks.get(depId);
        if (dep != null) {
            dep.addDependent(task.getId());  // 反向索引
        }
    }
}

拓扑排序:DFS 检测环 + 定序

执行前必须确定合法顺序。用经典 DFS 拓扑排序:

java 复制代码
private boolean topologicalSort(Task task, Set<String> visited, Set<String> visiting) {
    if (visiting.contains(id)) return false;  // 发现回边,有环
    if (visited.contains(id)) return true;
    visiting.add(id);
    for (String depId : task.getDependencies()) {
        if (!topologicalSort(dep, visited, visiting)) return false;
    }
    visiting.remove(id);
    visited.add(id);
    executionOrder.add(id);  // 后序加入,自然形成拓扑序
    return true;
}

如果 LLM 生成的计划中不小心形成了循环依赖(A 依赖 B,B 依赖 A),这里会直接失败,Planner 会抛出异常并要求重新生成。

3、Planner:让 LLM 把自然语言变成 DAG

Planner负责"画图"。它向 LLM 发送专用的 PLANNER Prompt,要求返回 JSON:

json 复制代码
{
  "summary": "...",
  "tasks": [
    { "id": "1", "description": "读取 pom.xml", "type": "FILE_READ", "dependencies": [] },
    { "id": "2", "description": "分析依赖版本", "type": "ANALYSIS", "dependencies": ["1"] }
  ]
}

两遍解析处理前向引用

JSON 中的 dependencies 可能引用后面才出现的任务 ID,所以解析分两步走:

  1. 第一遍 :遍历 tasks 数组,创建所有 Task 节点,同时做 ID 映射(防止 LLM 用奇怪字符串当 ID,内部统一转成 task_1task_2)。
  2. 第二遍 :再次遍历,根据 dependencies 建立真正的依赖和反向依赖关系。
java 复制代码
// 第一遍:创建节点
for (JsonNode taskNode : tasksNode) {
    String newId = "task_" + taskIndex++;
    plan.addTask(new Task(newId, description, type));
}

// 第二遍:连线
for (JsonNode taskNode : tasksNode) {
    Task task = plan.getTask(newId);
    for (JsonNode depNode : depsNode) {
        task.addDependency(newDepId);
        dep.addDependent(task.getId());
    }
}

四、为什么 Plan-and-Execute 选了 DAG 而不是一条直线?

直白的线性列表(Step 1 → Step 2 → Step 3...)当然也能描述执行顺序,但它无法表达"多个任务可以同时开始"的并行可能性,也无法清晰地管理复杂的依赖关系。一旦任务多了,线性列表很快会变得臃肿且低效。

DAG 的精髓就在于用图的结构显式地暴露并行度。执行器只要遵守"依赖满足即可执行"的原则,就能自动实现最大程度的并发,这正是 Plan-and-Execute 架构高效、可扩展的关键所在。

总结

Plan-and-Execute 里的 DAG,不是高高在上的理论概念,而是一个实实在在的"任务路线图"。它用顶点表示子任务,用有向边表示依赖,形成一张无环的网,驱动着系统自动、并行、有序地完成复杂工作。而且得益于 DAG 易于局部修改的特性,它还让失败重试和动态重规划变得异常清晰。

相关推荐
爬楼的猪7 小时前
WSL 的 GUI 为什么总是卡卡的
后端
辞忧九千七7 小时前
MCP 协议完全指南:从原理到 LangGraph 集成,打造即插即用的 AI Agent 工具生态
agent·langgraph·mcp
猿的天空7 小时前
机器人双手迎来全栈训练系统:灵初智能EgoSteer让灵巧手无所不能
网络·人工智能·计算机·ai·程序员·机器人·编程
weixin_727535628 小时前
MinIO大文件上传深度拆解:从原理到生产落地的完整指南
java·中间件
imbackneverdie8 小时前
知网官宣:禁止 AI 列为论文署名作者,标注 Gemini、DeepSeek 等 AI 为作者的稿件统一下架
大数据·人工智能·ai·职场和发展·aigc·科研·高校
摇滚侠8 小时前
Java 全栈开发实战教程 课程笔记 34-36
java·笔记
芝士熊爱编程9 小时前
创建型模式-单例模式
java·单例模式·设计模式
程序猿DD9 小时前
OPC必备!在 Cloudflare Worker 上免费部署 AI 网关:集中管理与供应 Token
后端
大模型码小白9 小时前
Spring AI 框架实战:Java 后端集成大模型的架构设计与工程落地
java·人工智能·python·spring
HIT_Weston9 小时前
153、【Agent】【OpenCode】启动分析(completion)
人工智能·agent·opencode