调度内核:WorkflowQueue 怎么把一张图跑起来
30 秒导读: 用户在画布上连出的工作流是一张有向图(节点 + 边)。本章讲这张图怎么被跑起来——不是某个 LLM/检索节点内部怎么算,而是那台"发牌员":它决定谁先跑、谁后跑、哪条分支不走就把后面一串跳过、带环的循环怎么不死循环。核心就是一个类:
WorkflowQueue(packages/service/core/workflow/dispatch/index.ts:343)。
本章定位:这是 FastGPT 工程含量最高的一章,是纯粹的执行引擎。图长什么样、节点里有哪些字段,看 01-workflow-data-model;一次对话怎么从 API 进来、SSE 怎么流式返回,看 02-chat-pipeline;单个 LLM 节点里的工具循环、检索算法,看 04-ai-nodes 和 05-knowledge-base。这里只讲"把图跑起来"这一件事。
1. 这是什么(零基础也能懂)
一句话定义: WorkflowQueue 是一台图执行器——给它一堆节点和边,它按依赖顺序、带并发上限地把每个节点执行一遍,中途该跳过的分支跳过,该等的节点等齐上游再跑。
它要解决的问题,用大白话说:
你在 FastGPT 画布上画了这么一张流程:
开始 ──▶ 判断问题类型 ──(是"退货")──▶ 查订单 ──▶ 回复
└──(是"闲聊")──▶ 直接回复
现在用户问了句"我要退货"。引擎必须做到三件看似简单、其实很容易做错的事:
| 要做到 | 说人话 |
|---|---|
| 顺序对 | "查订单"必须等"判断问题类型"跑完、且判定结果确实流向它,才能跑 |
| 分支干净 | 既然走了"退货","闲聊 → 直接回复"这条整条都不能执行 |
| 不重复 | 同一个节点,哪怕有好几条边指向它,也只跑一次 |
为什么不能直接"递归下去就完了"? 因为真实工作流有汇聚(一个节点等多个上游都到齐)、有分支(走了 A 就得干净地废掉 B)、有环(loop 循环体会往回连边)、还有并发(几条独立支路想同时跑但又要限流)。天真的深度递归会栈 爆、会重复执行、会在环里转不出来。
一句话直觉: 把它想成发牌桌——activeRunQueue 是"轮到谁出牌"的待办牌堆,发牌员一次只发几张(并发上限),每张牌打完会翻出下一批牌放回牌堆;打不成的支路进另一个"作废牌堆"(skipNodeQueue)单独清理。整局不靠一层套一层地喊话(递归),而是牌堆空了才散场。
本节不碰代码细节。你只要记住:它是把一张图安全、有序、不重不漏地跑完的东西。
2. 顶层全景(它大概怎么转)
2.1 一张图看懂主循环
先给一句"怎么读这张图":中间那个 startProcessing 死循环是心脏,它反复从两个队列取节点交给 checkNodeCanRun 判定,判定结果又会往队列里塞新节点,直到两个队列都空 → 散场(resolve)。
入口节点 (isEntry)
│ addActiveNode()
▼
┌───────────────────────────┐
│ activeRunQueue │ 待检查的节点(可能能跑)
│ (Set<id>) │
└───────────────────────────┘
│ startProcessing 循环取节点
│ (并发 ≤ maxConcurrency)
▼
┌───────────────────────────┐
│ checkNodeCanRun │ 看入边状态 → run / skip / wait
└───────────────────────────┘
run │ skip │ wait │
▼ ▼ ▼
nodeRunWithActive nodeRunWithSkip (什么都不做,
跑真节点逻辑 只标记边为 等别的边到齐
callbackMap[type] skipped) 再被唤醒)
│ │
▼ ▼
nodeOutput:把出边标成 active / skipped,
收集下一批节点 → active 的塞回 activeRunQueue
skip 的塞进 skipNodeQueue
│
▼
两个队列都空 & 无并发在跑 ──▶ resolve(this) 散场