For AI agents: the complete documentation index is available at https://a3s-lab.github.io/Flow/llms.txt, the full documentation bundle is available at https://a3s-lab.github.io/Flow/llms-full.txt, and this page is available as Markdown at https://a3s-lab.github.io/Flow/concepts/workflow-dag.md.
  • 简体中文
  • v1.1.0
  • 工作流图

    WorkflowDslWorkflowDag 负责可移植工作流文档的结构边界。它们解析节点与边、保留不认识的扩展字段、校验图结构,并生成确定性计划。具体节点怎样执行仍由宿主绑定。

    Flow 不根据 data.type 自动获得模型、数据库或工具权限。宿主先验证并授权节点,再把节点语义接到持久步骤上。

    文档与裸图

    完整文档包含版本、应用信息和 workflow.graph。只需要处理画布图时,也可以直接解析 WorkflowDag

    use a3s_flow::{WorkflowDag, WorkflowDsl};
    
    let document = WorkflowDsl::from_yaml(&yaml_source)?;
    let graph = document.graph();
    
    let bare_graph = WorkflowDag::from_json(&json_source)?;

    单份 UTF-8 文档上限为 10 MiB。当前经过测试的 DSL 版本是 0.7.0。旧小版本可以带警告导入,新版本或不同主版本需要宿主明确决定是否接受。

    最小图结构

    {
      "nodes": [
        {
          "id": "start",
          "position": { "x": 0, "y": 0 },
          "data": { "type": "start" }
        },
        {
          "id": "validate-order",
          "position": { "x": 280, "y": 0 },
          "data": {
            "type": "tool",
            "operation": "orders.validate"
          }
        },
        {
          "id": "end",
          "position": { "x": 560, "y": 0 },
          "data": { "type": "end" }
        }
      ],
      "edges": [
        { "id": "start-validate", "source": "start", "target": "validate-order" },
        { "id": "validate-end", "source": "validate-order", "target": "end" }
      ],
      "viewport": { "x": 0, "y": 0, "zoom": 1 }
    }

    节点必须有非空 id 和字符串 data.type。边必须有唯一 ID,并且引用已经存在的起点与终点。

    Playground 中的连线名称

    Playground 默认把源节点输出端口的名称显示在连线中间。例如批处理节点的 done 端口会显示为“全部完成”。这段文字用于帮助阅读图结构,不是引擎用来路由的端口 ID。

    选中连线后,点击名称旁的铅笔按钮,或双击名称即可编辑。也可以先选中连线,再按 Enter 或 F2。输入完成后按 Enter 保存,按 Escape 放弃,失去焦点也会保存。把输入框清空会恢复端口默认名称。自定义名称会保存在本地草稿和导出的工作流文件中。

    编辑名称不会改变 sourceHandletargetHandle、执行计划或运行语义。条件节点的成立与其他分支名称仍应在节点配置中的 matched_labelotherwise_label 字段修改,因为这两个字段属于条件节点的业务文案。普通连线名称只是画布展示字段,发布前仍应按端口 ID 校验整张图。

    生成执行计划

    let plan = graph.execution_plan()?;
    println!("top_level={:?}", plan.top_level());
    
    for (scope, order) in plan.scopes() {
        println!("scope={scope:?} order={order:?}");
    }

    同一作用域中有多个可执行节点时,计划按稳定 ID 排序消除输入数组顺序的影响。计划只表达结构拓扑,不执行节点,也不决定失败、重试或授权策略。

    空画布可以被解析并再次保存,便于编辑器保留草稿。execution_plan() 会拒绝空画布,因为没有可执行节点。

    结构校验

    编译计划前会拒绝以下情况。

    • 重复或空的节点 ID、边 ID
    • 缺少字符串 data.type
    • 边引用不存在的节点
    • 节点连接自己
    • 任一作用域内存在环
    • 边跨越顶层与容器作用域
    • parentId 指向不存在或不支持的容器
    • 迭代与循环容器缺少对应的起始子节点
    • 超过 10,000 个节点或 100,000 条边

    这些检查保证拓扑可确定,但不会判断节点参数是否满足某个业务执行器。宿主需要对每个已授权节点类型继续做模式校验。

    Playground 性能预算

    Playground 保持 React Flow 的可见元素渲染开关。布局输入会传给独立 Worker, 再由 WebAssembly 图布局内核计算坐标。配置校验、DAG 编译、序列化和元素对账 仍然保持确定性,并分别记录耗时。

    website/ 目录运行下面的命令即可复现基准。

    npm run bench:playground

    命令会测量 100、500 和 1,000 个节点的扇出图,并输出每项操作的 p50、p95、p99 耗时,单位是毫秒。Node.js 没有浏览器 Worker 和浏览器 WebAssembly 加载器, 所以布局一行测的是 JavaScript 回退实现。浏览器中的 Worker 布局需要在目标浏览器 单独测量。

    下面这组数据是 2026-08-26 的本地参考运行,环境为 arm64 Mac14,9、macOS 26.3、 Node.js 26.7.0 和 Vitest 3.2.7。它用于比较本地改动,不代表所有设备或部署环境 都会得到相同结果。

    节点数操作p50p95p99
    100JavaScript 布局回退0.094 毫秒0.136 毫秒0.224 毫秒
    100配置校验0.010 毫秒0.013 毫秒0.028 毫秒
    100DAG 编译0.458 毫秒0.708 毫秒0.916 毫秒
    100DSL 序列化0.437 毫秒0.571 毫秒0.708 毫秒
    100无变化元素对账0.015 毫秒0.023 毫秒0.045 毫秒
    100单节点运行状态更新0.016 毫秒0.021 毫秒0.041 毫秒
    500JavaScript 布局回退0.472 毫秒0.625 毫秒0.759 毫秒
    500配置校验0.052 毫秒0.068 毫秒0.134 毫秒
    500DAG 编译2.617 毫秒3.020 毫秒3.344 毫秒
    500DSL 序列化2.306 毫秒2.616 毫秒2.935 毫秒
    500无变化元素对账0.088 毫秒0.131 毫秒0.191 毫秒
    500单节点运行状态更新0.093 毫秒0.134 毫秒0.227 毫秒
    1,000JavaScript 布局回退0.972 毫秒1.299 毫秒1.559 毫秒
    1,000配置校验0.103 毫秒0.159 毫秒0.262 毫秒
    1,000DAG 编译5.377 毫秒5.997 毫秒7.340 毫秒
    1,000DSL 序列化4.642 毫秒5.134 毫秒15.599 毫秒
    1,000无变化元素对账0.196 毫秒0.330 毫秒0.508 毫秒
    1,000单节点运行状态更新0.202 毫秒0.284 毫秒0.432 毫秒

    评审时可以把 1,000 个节点的 p95 预算设为布局回退 2 毫秒、配置校验 1 毫秒、 DAG 编译和序列化各 12 毫秒、元素对账 2 毫秒。浏览器版本还要记录首次 Worker 布局、滚动响应和缩略图开关的影响。大图只挂载 React Flow 当前可见的节点,长列表 则使用 CSS content-visibility 和 containment,让屏幕外项目跳过布局计算。 节点数超过 800 或边数超过 4,000 时,缩略图会自动暂停并显示状态提示,避免它 与画布滚动争用绘制资源。

    迭代与循环作用域

    iterationloop 节点可以成为容器。容器内节点通过 parentId 归属作用域,内部边只能连接同一容器里的节点。

    {
      "id": "for-each-item",
      "data": {
        "type": "iteration",
        "start_node_id": "iteration-start"
      }
    }

    对应的起始节点必须满足三个条件。

    1. parentId 等于容器 ID。
    2. 迭代使用 iteration-start,循环使用 loop-start
    3. 容器内至少还有一个可执行子节点。

    plan.scope("for-each-item") 返回容器内部的确定性顺序。容器自身留在顶层计划中。

    语义摘要

    execution_digest() 为可执行语义生成稳定 SHA-256 身份。

    let digest = graph.execution_digest()?;
    println!("execution_digest={digest}");

    节点位置、选中状态、视口和输入数组顺序不会改变摘要。节点 data、边的语义数据、连接端点和作用域会改变摘要。宿主可以用它判断两次发布是否只调整了画布布局。

    摘要只证明输入语义相同,不证明宿主绑定的节点实现相同。节点执行器版本仍要进入宿主发布身份或 runtime_build_id

    Flow 的 digest v2 由 Rust 引擎和 Flow UI 共用。它采用 JavaScript 数字 语义、UTF-16 对象键排序,把边标签视为展示字段,拒绝超出 JavaScript 安全 整数范围的整数,并把规范化 JSON 深度限制为 256 层。digest 格式升级时, 必须显式迁移已持久化的旧值。

    无损往返与扩展字段

    解析器会保留未解释的顶层字段、应用字段、工作流字段、节点展示字段和边展示字段。to_yaml()to_json() 可把这些字段写回。

    保留字段不代表接受其语义。安全边界仍然要采用允许列表。

    • 先解析并限制文档大小。
    • 再运行结构计划校验。
    • 对允许的 data.type 做宿主模式校验。
    • 解析凭据引用,禁止把明文密钥放入图文档。
    • 发布时记录文档摘要和节点执行器版本。

    与持久运行的衔接

    图编译器输出节点顺序,Flow 引擎持久化运行决定。宿主通常会把一个节点映射为稳定步骤 ID,把分支选择或容器游标写进步骤输出或续段输入。

    如果图在运行期间发生语义变化,应创建新的工作流定义版本或补丁路径。不要用新图解释已经开始的旧历史。

    完整导入示例位于 examples/workflow_dsl_import.rs