我们在建设内部 Agent 能力平台的过程中,几乎每隔一段时间就会有人提出同一个问题:"要不要上一个 AI 工作流 / DAG 编排引擎?"

Dify、Coze、n8n 这类产品已经把"可视化拖拽编排 Agent"做成了主流叙事。在这种背景下,明确说"不引入 DAG 引擎"需要一个足够扎实的理由。这篇文章就是我们内部的立场论证:它不是"暂缓",而是从架构原则推出的必然结论。

结论先行

  • 我们不引入 DAG / 可视化工作流引擎。全代码库没有 workflow / DAG / step-graph 引擎。
  • 默认且唯一的运行时编排是动态 Agent 编排(由 LLM 决定下一步):单 Agent 原生 tool-calling,或主/子 Agent 的消息总线协作。
  • 确定性编排是少数情况,仅在"无人值守、需可复现"时出现;届时用命令式编排代码(几行应用层代码,把 Agent 当步骤调)表达——不用 DAG,也不用声明式 step-DSL。

一句话记忆:

编排的决策权在谁手里,决定用哪种形态——LLM 决定 → 动态 Agent;人点 UI 决定 → UI 即编排器;固定脚本决定 → 命令式代码。三者都不是"图引擎"。


一、现状:编排 100% 动态

先陈述事实。我们的平台目前:

  • 单 Agent 一轮 = 一次流式 LLM 调用 + 框架原生 tool-calling。多步工具循环(调工具 → 喂结果 → 再推理)由 Spring AI 的 ChatClient 内部完成;应用层没有 ReAct 循环、没有工作流状态机。
  • 多 Agent = 消息传递,不是流水线。主 Agent 调用 send_message → Redis 消息总线 → 子 Agent 懒加载、独立推理、发消息回报。协作顺序由 LLM 涌现式决定,没有预先画好的连线。
  • 知识库、工具、技能、子 Agent 都是独立能力单元,推理引擎在运行时动态组装,代码里禁止"if 场景==X then 调用工具 Y"的业务硬编码。

注意最后一条,它是整个论证的支点:

一个 DAG 引擎本质上是把"if 场景==X then 调用 Y"这类硬编码从代码挪进一张图——换了形式,违反同一条原则。

如果你在代码里写分支逻辑是反模式,那么把同样的分支逻辑画成节点连线,并不会让它变成好模式。

二、设计依据

2.1 确定性编排 ≠ DAG 引擎

最常见的误解是:"你需要确定性控制流,所以你需要工作流引擎。"这个推论不成立。

需要确定性控制流,不等于需要图引擎。业界成熟的纯 Agent 系统(如 Claude Code)没有 DAG 引擎:其核心是 agent loop,而当控制流确需确定(循环、条件、并行)时,用的是命令式脚本pipeline / parallel / 循环),不是节点连线的图。确定性来自代码,不来自图。

所以:确定性场景用代码即可覆盖,无须为此引入专门框架。

2.2 编排决策权决定形态

一个多步任务由谁决定"下一步做什么",决定了它该用哪种编排:

决策方 形态 后端是否需要编排引擎
LLM 动态 Agent(单 Agent / 主子 Agent 协作) 否,交给 LLM + 消息总线
人(点 UI stepper) 前端分步交互 否,UI 本身就是编排器
固定脚本(批量/定时) 命令式编排代码 否,就是一个具名函数

三种里没有一种需要"运行时解释一张流程图"。

第二行值得展开。我们有一条真实业务线:把一条多步的业务漏斗(策略生成 → 内容生成 → 单步重做)拆成几个单职责的 JSON 契约 Agent,每个输出稳定的 JSON 结构,前端用分步 stepper 渲染,步骤之间的推进由人点击驱动。这套设计里,后端没有任何流程引擎——人在 stepper 上点"下一步"这个动作,本身就是编排。把编排权交给人时,UI 就是编排器,后端再藏一个引擎是多余的复杂度。

2.3 step-DSL 是 DAG 引擎的前一站

有人会说:那我不上图引擎,就写一份"轻量"的声明式 steps: 配置,总可以吧?

不可以。一份看似轻量的 steps 配置,一旦需要为它写解释器,就已经在造引擎——即便它当下是线性的。它与 DAG 之间是滑坡:先线性 steps,再加条件分支,再加并行,最终得到一个自制的半成品工作流引擎——维护成本最高、能力最差的那种。

判据很简单:

"你是否需要为它写一个解释器?" 若是,就是在造引擎,停手——改用命令式代码。

2.4 单一职责经由独立 Agent / Skill,而非巨型 prompt

"拆步骤"这个诉求本身是对的,问题是用什么手段拆。把一条业务漏斗拆成多个单职责单元,正确手段是独立 Agent / Skill(技能即命名的提示词片段,注入 system prompt 指导方法论),而不是在单个 Agent 的 systemPrompt 里堆 mode flag("当 mode=strategy 时……当 mode=email 时……")。

前者各自小而聚焦、指令与 schema 独立、可独立调优与回归;后者会让 prompt 臃肿、工具选择准确率下降、无法独立演进。反面对照很明确:把多步塞进一个巨型 prompt + mode flag,才是真正的坏味道。

三、判定树与约束

一个多步任务来了
  │
  ├─ 每步需 LLM 判断,顺序由「人点 UI」或「LLM 自己」决定?
  │     →  动态 Agent 编排(默认,占绝大多数)
  │        · 单 Agent + 框架原生 tool-calling
  │        · 或 主/子 Agent + 消息总线
  │        · 步骤拆分 = 单职责子 Agent / Skill
  │        · 顺序若由人驱动 → 前端 stepper 即编排器,后端无引擎
  │
  ├─ 顺序固定、可复现、无人值守(批量 / 定时 / 无 UI)?
  │     →  命令式编排代码(应用层几行代码,把 Agent 当步骤调)
  │        · 一个有名字的函数(如 runXxxFunnel),不是 DAG、不是 step-DSL
  │
  └─ 想要「可视化拖拽连线的图引擎」?
        →  不做。永不引入封闭图格式。

由此形成四条约束:

约束 1:推理引擎核心禁止出现 workflow / DAG / step-graph 解释器。动态编排交给 LLM 与消息总线,别无第三种运行时。

约束 2:确定性编排一律用命令式代码表达,禁止引入声明式 step-DSL 或图格式。判据:"是否需要为它写解释器?"

约束 3:步骤的单一职责通过独立 Agent / Skill 实现,禁止用单个巨型 prompt + mode flag 模拟多步。

约束 4:可视化编辑器(若做)只能作为生成命令式代码 / Agent 配置的前端外壳,产物须为 AI 编程助手可读可改的文本,不得以封闭图形工程文件作为唯一事实源。

约束 4 对应我们"文本优先"的原则:配置即代码,所有 Agent / 工具 / 技能配置都是 JSON 文本,AI 编程助手可以直接读写。一张存在专有格式里的流程图,对 AI 助手是不可见的——这在 AI 辅助开发成为主要工作方式的今天,是一个实质性的生产力损失。

四、回应反方观点

"但确定性场景怎么办?" 用命令式代码。触发信号很明确:出现"无人值守批量跑"(如给几百个对象跑一晚上,无人点 stepper)或"定时触发的多步流程"时,把该主干落成一个具名命令式编排函数,Agent 当步骤调。若这类函数多到需要复用和统一观测,抽一层极薄的编排工具库(重试、步骤日志、结果透传)——仍是代码,仍非图引擎。

"但非技术人员需要可视化编排。" 这是"workflow"一词最大的语义陷阱。这个词指代两个完全不同的东西:

含义 A:可视化 DAG 工作流产品 含义 B:命令式编排代码
形态 节点 + 连线 + 图引擎 代码里的 pipeline / parallel / 循环 / 条件
事实源 封闭图格式 源代码(AI 可读可改)
谁决定控制流 代码
代表 Dify / Coze / n8n Claude Code 的 Workflow 原语
我们的立场 不采用 采用,但仅限确定性场景

面对"要不要支持工作流",先确认对方指 A 还是 B。我们对 A 说不;对 B 说"在少数确定性场景里用代码即可,无需专门框架"。

"DAG 至少让流程一目了然,可观测性好。" 恰恰相反。动态编排的可观测性来自结构化日志与消息事件流(每次工具调用、每条 Agent 间消息都有事件和 trace),而不是来自一张静态的图。图告诉你"可能会发生什么",日志告诉你"实际发生了什么"。LLM 决策的运行系统里,后者重要得多。

"立场是不是太教条?" 立场不是教条,有明确的重审条件。当真实场景(非预判)出现——无人值守批量、定时多步流程、命令式编排函数多到需要收敛——我们重审的方向是"要不要写命令式编排代码",不是"要不要上 DAG 引擎"。可视化 DAG builder、封闭图格式、把业务分支编码进图/DSL 的运行时,无论场景如何都不做。

五、结语

DAG 工作流引擎解决的是一个真实问题——多步任务的编排——但它给出的答案在我们看来是错误的抽象层级:它假设"下一步做什么"应该由一个静态结构决定,而 LLM 时代的核心能力恰恰是让模型在运行时做这个决定。

我们的答案分三层:能交给 LLM 的交给 LLM(动态编排),需要人把关的交给人(UI 即编排器),必须确定性的写成代码(命令式函数)。三个答案都不需要一张图。