03. Workflow 模型:节点、连接、数据和表达式
3.1 工作流 JSON 的核心形状
一个可执行工作流至少包含:
Workflow
├── nodes: INode[]
│ ├── name / type / typeVersion
│ ├── position
│ ├── parameters
│ └── credentials?
├── connections: IConnections
├── active / settings
└── pinData / meta / versionId
INode 是编辑器和执行器共享的最小单元;节点的 parameters 是动态 JSON,实际解释方式由节点类型的 description 决定。工作流本身不把每个节点的实现硬编码进去,而是通过 type 找到 INodeType。
入口文件:packages/workflow/src/Interfaces.ts、packages/workflow/src/workflow.ts。
3.2 连接不是简单的边
连接由目标节点、连接类型和输入/输出索引组成。最常见的是 main,但 AI 工作流还会出现 ai_languageModel、ai_tool、ai_memory、ai_outputParser 等类型。连接类型表达的是“数据语义”,不是 UI 颜色。
main 连接形成普通数据/控制流;AI 辅助连接为根节点提供能力。执行器在寻找从 trigger 到 destination 的子图时,不能把所有连接类型都当成父子数据流,否则一个模型节点会被错误地当成工作流主路径上的触发器。
这条规则在 packages/core/src/execution-engine/partial-execution-utils/find-subgraph.ts 中有明确注释:寻找主执行子图时跳过非 main 父连接,但保留 AI 辅助连接的语义。
3.3 数据 item 和配对关系
n8n 的节点通常不是接收一个对象、返回一个对象,而是接收和输出 item 数组:
INodeExecutionData[]
= [{ json: {...}, binary?: {...}, pairedItem?: ... }, ...]
一个执行中的 IRunData 会按节点名保存 ITaskData[]。每个 task 记录输入、输出、开始/结束时间、错误、执行状态和可选的 source/paired item 信息。配对关系让下游表达式可以回答“这个 item 来自上游哪个 item”,也是 Merge、循环、分支和部分执行能保持可解释性的关键。
3.4 表达式是运行时语言
编辑器中写的 ={{ $json.customer.email }} 不是在保存时求值,而是在某个节点、某个 item、某个 run index 的上下文中解释。表达式需要同时看到:
- 当前节点和当前参数。
- 当前输入 item 与 item index。
- 其他节点的已执行数据。
- workflow、execution、环境变量和凭证代理。
因此表达式解析不能简单地 eval 一个字符串。WorkflowDataProxy 和 expression runtime 负责构造受限的数据视图,NodeExecutionContext 把它挂到节点执行环境。阅读表达式相关代码时,应沿 IWorkflowDataProxyAdditionalKeys、WorkflowDataProxy、expression-runtime 三条线看权限和数据来源。
3.5 图算法处理的难点
分支
If/Switch 节点可以让不同输出走不同路径。执行器需要记录 output index,不能只记录“节点已执行”。
循环
循环不是把图展开成无限列表,而是通过 node execution stack 和 run data 反复推进。循环边界、已访问节点和 source data 共同决定下一次入栈。
部分执行
编辑器点击某个节点的“执行”时,系统通常只需运行它的祖先子图。findSubgraph 从 destination 反向查找 trigger,并在 WorkflowExecute.run() 中生成 runNodeFilter。AI 工具节点又要求把辅助图接回正确的根节点,这是部分执行最容易被误判的地方。
3.6 设计取舍
动态参数 + 静态 description:节点可以复用一个执行类描述多级参数和版本,但代价是类型不能完全在 TypeScript 层表达,运行时校验必须足够强。
按节点名保存 run data:调试和编辑器展示很直观;代价是大工作流和大 item 会产生庞大的 execution 数据,需要 pruning、binary storage 和可配置的保存策略。
连接带类型:让 AI 等非主数据流可以自然叠加;代价是所有图算法、编辑器 handle、校验和序列化都不能假设只有 main。