Rust Agent + DAG:为什么真正复杂的任务,必须用“任务图”

  • King
  • 发布于 1天前
  • 阅读 79

如果你已经实现了前面几篇里的Agent:有Executor有并发有长期记忆你很快会撞上一堵结构性的墙。一个必然会出现的问题:Plan→Act循环开始失控先看一个真实但常见的Agent行为:“我先查A,再查B,如果B失败就重试A,然后根据A+B决定是否

如果你已经实现了前面几篇里的 Agent:

  • 有 Executor
  • 有并发
  • 有长期记忆

你很快会撞上一堵结构性的墙


一个必然会出现的问题:Plan → Act 循环开始失控

先看一个真实但常见的 Agent 行为:

“我先查 A,再查 B,如果 B 失败就重试 A,然后根据 A+B 决定是否查 C, 如果 C 成功就汇总,否则回到 B 换参数再试一次。”

while loop + if else 写出来会变成什么?

  • 状态散落在各处
  • 很难判断“现在在第几步”
  • Debug 基本靠猜
  • 想并发?几乎不可能

根因是:你在用“线性模型”,解决“图结构问题”。


Agent 的真实执行模型,其实是 DAG

几乎所有“稍微复杂”的 Agent 任务,本质都是:

      ┌── Task A ──┐
Goal ─┤             ├── Task D ── Final
      └── Task B ──┘
            │
         Task C

这就是一个 DAG(Directed Acyclic Graph,有向无环图)


为什么 DAG 对 Agent 是质变

你可以在文章里直接点名这 5 个能力:

能力 Loop 模型 DAG 模型
并发 困难 天然支持
失败处理 混乱 节点级
重试 全局 局部
可解释性 很差 非常清晰
扩展性 快速腐化 结构稳定

一句话总结:

Loop 是 Demo,DAG 才是系统。


用 Rust 建模 Task Graph(核心抽象)

Step 1:定义 Task Node

pub struct TaskNode {
    pub id: TaskId,
    pub tool: String,
    pub input: Value,
    pub depends_on: Vec<TaskId>,
}

关键点只有一个: 👉 显式依赖关系


Step 2:Task Graph 本身

pub struct TaskGraph {
    pub nodes: HashMap<TaskId, TaskNode>,
}

你现在可以清楚回答:

  • 哪些任务可以并发?
  • 哪些任务必须等?

Agent 的新职责:不再“直接行动”,而是“生成任务图”

这是一个非常重要的思维转变

LLM 不负责“怎么执行” LLM 只负责“任务结构”

LLM 输出示例(JSON)

{
  "type": "TaskGraph",
  "nodes": [
    {"id":"A","tool":"read_file","input":{"path":"log.txt"}},
    {"id":"B","tool":"http_get","input":{"url":"..."}},
    {"id":"C","tool":"analyze","depends_on":["A","B"]},
    {"id":"D","tool":"summarize","depends_on":["C"]}
  ]
}

Executor 升级:从“执行任务”到“调度任务图”

核心算法:拓扑执行(Topological Execution)

while let Some(ready_tasks) = graph.ready_nodes() {
    run_in_parallel(ready_tasks).await;
    graph.mark_done(ready_tasks);
}

ready_nodes 的定义

一个节点:
- 所有 depends_on 都已完成
- 自己还没执行

DAG + Tokio:并发调度的自然结合

use futures::stream::FuturesUnordered;

let mut running = FuturesUnordered::new();

for task in ready_tasks {
    running.push(executor.run_task(task));
}

while let Some(result) = running.next().await {
    graph.record_result(result);
}

📌 Tokio 在这里不是“加速器”,而是调度引擎的一部分


失败不是异常,而是图的一部分

这是 DAG 模型最强的一点。

三种失败策略(非常适合写成小节)

① Fail Fast

  • 任一关键节点失败 → 整体失败

② Retry Subgraph

  • 失败节点 + 下游节点全部重跑

③ Fallback Branch

Task B
  ├─ success → Task C
  └─ failure → Task C'

这些在 loop 模型中几乎无法优雅实现


Debug 体验的质变:任务图 = 执行记录

当你把每次执行都记录成:

{
  "node":"C",
  "start":"...",
  "end":"...",
  "status":"failed",
  "error":"timeout"
}

你会第一次感觉到:

“Agent 是一个可观测系统,而不是黑盒。”


DAG + Memory:让 Agent 真正“学会做复杂事”

结合上一篇的 Semantic Memory,你可以做到:

  • 记住哪些子图经常失败
  • 记住某类任务的最优结构
  • 下次让 LLM 复用成熟 DAG 模板

这一步,就是从:

Agent = 推理机器 升级为 Agent = 经验驱动的执行系统


到这里,Agent 已经变成什么?

如果你把前四篇能力叠加起来:

  • 并发 Executor
  • 成本控制
  • 长期记忆
  • DAG 调度

你现在拥有的,其实是一个:

“LLM 驱动的分布式任务编排系统(单机版)”

这也是为什么:

大厂真正落地的 Agent,最后都会长得像 Workflow / DAG / Scheduler。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论