数据截至 (上游 commit 97b9856e49f2)
工作流数据模型:DAG 是怎么被描述的
30 秒导读: PySpur 里,"一个 agent / 一条工作流"不是一段代码,而是一份数据——一张节点表加一张连线表,构成有向无环图(DAG,Directed Acyclic Graph)。这份数据由几个 Pydantic 模型定义,并被一组校验规则守着底线(必须恰好一个入口、节点 id 不能重复……)。读懂这份数据结构,就读懂了整个系统的地基:后面的执行引擎(02)、节点体系(03)全都围着它转。
本章只讲数据模型与它的校验,不讲怎么执行(留给 02-executor.md),也不讲单个节点内部干了什么(留给 03-node-system.md)。全部内容锚定在一个文件:backend/pyspur/schemas/workflow_schemas.py。
1. 这是什么(零基础也能懂)
一句话定义: 一条 PySpur 工作流 = 一个 DAG,用 JSON / Pydantic 对象描述:有哪些节点(每步做什么)、节点之间怎么连线(数据从谁流到谁)。
打个比方: 把工作流想成一张地铁线路图。
- 节点(node) = 一个个站点(输入站、某个处理站、输出站)。
- 连线(link) = 站与站之间的轨道,规定了"车"(数据)从哪站开往哪站。
- 校验(validator) = 通车前的安检:必须有且只有一个始发站、站名不能重名、终点站最多一个……不合规就不让这张图跑起来。
关键心智模型:这是纯声明式的数据,不是命令式的代码。 你不写"先调 A 再调 B",你只描述这张图长什么样;真正"按图施工、决定先跑谁"的是执行引擎。所以这一层没有任何执行逻辑,只有"结构 + 约束"。
用起来什么样: 一份最小工作流,拆开看就是 nodes(节点数组)+ links(连线数组)两块。下面这段是从源码 __main__ 里搬来的真实例子(backend/pyspur/execution/workflow_executor.py:__main__),做的是"输入一个问题 → BestOfN 节点生成回答 → 输出":
{
"nodes": [
{ "id": "input_node", "node_type": "InputNode", "config": { "output_schema": { "question": "string" } } },
{ "id": "bon_node", "node_type": "BestOfNNode", "config": { "samples": 1, "llm_info": { "model": "gpt-4o", ... }, ... } },
{ "id": "output_node", "node_type": "OutputNode", "config": { "output_map": { "response": "bon_node.response" }, ... } }
],
"links": [
{ "source_id": "input_node", "target_id": "bon_node" },
{ "source_id": "bon_node", "target_id": "output_node" }
],
"test_inputs": [ { "id": 1733466671014, "question": "<p>Is altruism inherently selfish?</p>" } ]
}
这张图对应的结构一眼就能读出来:
input_node ──▶ bon_node ──▶ output_node
(InputNode) (BestOfNNode) (OutputNode)
三个节点、两条线,一条直链。这就是 PySpur 眼中"一个工作流"最朴素的样子。
2. 顶层全景(几个模型怎么拼)
整份数据模型由 4 个核心 Pydantic 类拼成,外加一个枚举 SpurType。它们的包含关系是:
WorkflowDefinitionSchema "整张图" schemas/workflow_schemas.py:98
├── spur_type: SpurType 工作流品类(workflow / chatbot / agent)
├── nodes: List[WorkflowNodeSchema] 一张节点表 :40
│ └── coordinates: WorkflowNodeCoordinatesSchema :26 (画布 x/y)
│ └── dimensions: WorkflowNodeDimensionsSchema :33 (画布宽高)
│ └── subworkflow: WorkflowDefinitionSchema (可选,递归嵌套子图)
├── links: List[WorkflowLinkSchema] 一张连线表 :86
└── test_inputs: List[Dict] 画布上"跑一下"用的样例输入
各部件一句话职责:
| 部件 | 干什么 | 位置(schemas/workflow_schemas.py) |
|---|---|---|
SpurType | 枚举:这张图是普通工作流、聊天机器人、还是自治 agent | :12 |
WorkflowDefinitionSchema | 整张 DAG——持有 nodes / links / test_inputs / spur_type,并挂着全图级校验 | :98 |
WorkflowNodeSchema | 一个节点——id / 标题 / 类型 / config / 画布坐标,可选子工作流 | :40 |
WorkflowLinkSchema | 一条连线——source→target,加可选的 handle(具体连哪个输出/输入口) | :86 |
WorkflowNodeCoordinatesSchema / ...Dimensions... | 节点在可视化画布上的位置与大小(纯 UI 用) | :26 / :33 |
术语:PySpur 把"工作流"这个东西统称 spur。
SpurType(:12)区分三种 spur:WORKFLOW(标准工作流)、CHATBOT(带聊天式 IO + 会话管理的工作流)、AGENT(会自己调工具的自治 agent 节点,也有聊天式 IO)。品类不同,校验规则会不同(见 §5)。
主线走一遍(高层): 前端画布 / API 递上来一份 JSON → WorkflowDefinitionSchema.model_validate(...) 把它解析成对象,同时跑完所有校验 → 得到一个结构合法的 DAG 对象 → 交给执行引擎去跑。本章负责到"结构合法的 DAG 对象"为止。
3. 三个核心模型逐个看
3.1 WorkflowNodeSchema —— 一个节点
它描述什么: 图里的一步。字段见 schemas/workflow_schemas.py:40-61:
| 字段 | 含义 | 备注 |
|---|---|---|
id | 节点在图里的唯一标识 | 全图必须唯一(§4.1) |
title | 显示名 | 空则回落到 id(见下) |
parent_id | 父节点 id | 用于嵌套/分组;顶层节点为 None |
node_type | 节点类型名 |