跳转到内容

Flowise 的图驱动执行引擎:BFS 遍历、依赖解析与动态节点加载

学习目标

  • 理解 constructGraphs 如何将 React Flow 的 nodes/edges 转换为有向图
  • 掌握起始/终止节点的查找算法
  • 了解 BFS 遍历中节点的动态加载与执行

前置知识

本章为项目特定的工程实践。建议在阅读本章前了解:


项目实践

从 React Flow 到有向图

React Flow 画布的数据结构:

{
nodes: [
{ id: 'node1', data: { name: 'chatOpenAI', inputs: {...} } },
{ id: 'node2', data: { name: 'promptTemplate', inputs: {...} } },
],
edges: [
{ source: 'node1', target: 'node2', sourceHandle: 'output', targetHandle: 'model' }
]
}

constructGraphs 函数将其转换为邻接表有向图:

graph = {
'node1': ['node2'], // node1 的输出指向 node2
'node2': []
}
nodeDependencies = {
'node1': 0, // 入度
'node2': 1
}

起始节点与终止节点

// 起始节点:入度为 0(没有上游依赖的节点)
getStartingNodes(graph) → ['node1']
// 终止节点:出度为 0(没有下游消费者的节点)
getEndingNodes(nodeDependencies, graph, nodes) → ['node2']

反向图查找起始节点

Flowise 构建反向图来从终止节点回溯到起始节点:

const reversedGraph = constructGraphs(nodes, edges, { isReversed: true })
for (const endingNodeId of endingNodeIds) {
const result = getStartingNodes(reversedGraph, endingNodeId)
startingNodeIds.push(...result.startingNodeIds)
}

BFS 执行流程

buildFlow 函数实现 BFS 遍历:

关键实现要点

  1. 深度队列(depthQueue):记录每个节点所在的 BFS 层级。同层级的节点可以并行执行
  2. 依赖检查:节点执行前检查其所有上游节点是否已完成
  3. 变量解析resolveVariables 在节点执行前解析所有 {{variable}} 引用
  4. 动态加载import(nodeInstanceFilePath) 按需加载节点实例

API Override 机制

Flowise 支持通过 API 请求覆盖节点的运行时配置:

{
"question": "你好",
"overrideConfig": {
"model": "gpt-4o",
"temperature": 0.5
}
}

replaceInputsWithConfig 函数将 overrideConfig 中的值替换到对应节点的 inputs 中:

function replaceInputsWithConfig(nodeData, overrideConfig, nodeOverrides) {
for (const [key, value] of Object.entries(overrideConfig)) {
if (nodeData.inputs.hasOwnProperty(key)) {
// 检查该字段是否允许被覆盖
if (isOverrideAllowed(nodeOverrides, nodeData.name, key)) {
nodeData.inputs[key] = value
}
}
}
}

问题与规避

循环依赖导致死循环

问题:如果图中存在环,BFS 遍历会无限循环。

对策:Flowise 假设图为 DAG,没有内置环检测。前端 React Flow 应该禁止用户创建环(通过验证边连接)。如果需要在流程中实现循环逻辑,应使用 AgentFlow V2 的 Loop 节点(内置 loopCounts 计数淘汰)。

大 Flow 的性能问题

问题:一个包含数百个节点的 Flow,BFS 遍历 + 动态 require 可能导致较高的响应延迟。

对策

  • 使用队列模式(BullMQ + Redis)异步执行大 Flow
  • 将大 Flow 拆分为多个子 Flow,通过 ExecuteFlow 节点嵌套调用
  • 节点池在启动时预加载,避免运行时 require 延迟

变量引用链断裂

问题:当节点 A 引用节点 B 的输出,但节点 B 执行失败时,节点 A 收到 undefined。

对策

  • resolveVariables 中检测引用链断裂,提前报错
  • 使用 setVariable 节点显式设置默认值
  • 在 CustomFunction 中添加空值检查

设计取舍

动态 require vs 静态导入

维度动态 require静态导入
灵活性运行时按需加载启动时全量加载
启动速度慢(运行时 require)快(编译时确定)
热插拔支持不支持
Tree-shaking不支持支持

Flowise 选择动态 require,因为节点库需要支持社区贡献和热插拔。每个节点是独立的 .js 文件,放入指定目录即可自动加载。

BFS vs 拓扑排序

维度BFS拓扑排序
实现复杂度
并行性天然支持(同层节点)需额外分析
环检测需要额外实现天然支持
执行顺序确定性依赖队列顺序完全确定

Flowise 选择 BFS 是因为其简单性和天然并行性。拓扑排序虽然更严谨,但对于 Flowise 的 DAG 场景(用户通过 UI 创建、天然无环)已经足够。


参考来源