1. 结论与阅读建议
如果要先用一句话概括 Dify Workflow 的本质,可以这样记:
Dify Workflow 不是简单的顺序执行器,而是一套队列驱动、事件驱动、支持外部命令控制的图执行引擎。
如果再进一步压缩成最核心的执行闭环,则是:
- 上层业务创建
WorkflowEntry WorkflowEntry装配GraphEngineGraphEngine持有Graph与GraphRuntimeState- 可执行节点进入
ready queue Worker从ready queue取节点并执行- 节点执行过程持续产出
node events - 事件进入
event queue Dispatcher分发事件给EventHandlerEventHandler更新图状态、变量池和边状态- 后继节点被重新放入
ready queue - 重复直到成功、失败、暂停或被中止
这意味着:
Graph负责"结构"GraphRuntimeState负责"共享运行态"Node负责"统一节点执行协议"GraphEngine负责"总调度"Worker负责"实际跑节点"Dispatcher + EventHandler负责"根据事件推进图"CommandProcessor负责"把外部控制命令接入运行时"
如果只是第一次读这块源码,建议先建立这个分工图,再下钻具体文件。
2. 当前模块在整个 Dify 中的位置
api/core/workflow/ 是 Dify 后端里最核心的执行引擎之一。
如果说应用层、服务层解决的是:
什么时候触发 workflow
那么 api/core/workflow/ 解决的是:
workflow 一旦开始执行,内部到底怎么流转、调度、暂停、恢复和收尾
这块模块承担的不是简单的数据编排,而是 Dify 的平台级执行能力,包括:
- 图结构执行
- 并行分支
- 条件分支
- 迭代与循环
- 节点暂停
- 外部中止
- 恢复执行
- 事件流输出
- 变量池共享
因此这块代码的阅读方式,不能按"找一个主函数一路跟到底"的思路来做,而要先按分层理解。
3. 目录结构应该怎么理解
当前 api/core/workflow/ 下最值得先建立认知的目录如下:
workflow_entry.pygraph/runtime/nodes/node_events/graph_events/graph_engine/entities/context/repositories/
如果按职责再压缩一层,可以拆成 6 个阅读块。
3.1 入口装配层
workflow_entry.py
这一层回答的问题是:
- 上层业务是如何把一个 workflow 运行起来的
- 引擎启动前需要装配哪些依赖
3.2 结构定义层
graph/entities/
这一层回答的问题是:
- 工作流图在内存中如何表示
- 节点、边、初始化参数分别如何建模
3.3 共享运行态层
runtime/
这一层回答的问题是:
- 执行期间哪些状态是跨节点共享的
- 变量池、ready queue、暂停态、输出、用量统计放在哪里
3.4 节点协议层
nodes/node_events/
这一层回答的问题是:
- 一个节点怎样被定义、注册、执行和发事件
- 具体节点类型如何挂入统一协议
3.5 执行调度层
graph_engine/graph_events/
这一层回答的问题是:
- 调度器如何推进图执行
- 事件如何消费
- 边如何推进
- 图状态如何收敛
3.6 外部控制与扩展层
graph_engine/command_channels/graph_engine/layers/repositories/
这一层回答的问题是:
- workflow 运行中如何被外部 stop / pause / resume
- 引擎怎样挂载调试、限制、观测等横切能力
4. 先看官方 README,先建立正确的抽象
api/core/workflow/README.md 已经给了很重要的抽象框架。
其中最关键的两个信息是:
第一,当前引擎是:
queue-based distributed workflow execution system
第二,它在结构上遵循严格分层:
graph_enginegraph_eventsgraphnodesnode_eventsentities
这个分层顺序很重要,因为它直接反映了代码的依赖方向。
也就是说,在 Dify Workflow 里,作者并不希望你把"结构定义""节点实现""调度逻辑""事件系统"混在一起写。
阅读源码时,如果先建立这个分层意识,后面很多目录命名会变得很清楚。
5. 第一个入口:WorkflowEntry
最值得先读的文件是:
api/core/workflow/workflow_entry.py
5.1 它的角色不是执行器,而是装配器
WorkflowEntry 本身并不负责跑图。
它解决的是更上游的问题:
- 为一次 workflow 执行构造正确的运行环境
- 注入 command channel
- 创建
GraphEngine - 挂上必要的 layers
- 对外暴露统一
run()接口
因此它更接近:
- 引擎入口
- 执行上下文装配器
- 运行时依赖组织者
5.2 这层为什么重要
很多人读 workflow 引擎,会一上来就扎进 graph_engine.py。
但如果不先理解 WorkflowEntry,就会漏掉很多"引擎外部是怎么把依赖喂进去的"这一层上下文。
5.3 它最值得看的内容
从阅读角度,WorkflowEntry 有三类价值:
- 看引擎初始化依赖
- 看 layer 如何挂载
- 看单节点运行模式
其中 single_step_run() 尤其值得看,因为它暴露了一个"最小执行闭环":
- 构造
GraphInitParams - 创建
GraphRuntimeState - 通过工厂拿到节点实例
- 处理变量映射
- 将输入灌入变量池
- 最后执行
node.run()
如果你想快速理解"节点单独运行时最少依赖什么",这个入口比直接读大引擎更高效。
6. Graph:工作流图的内存表示
关键目录与文件:
api/core/workflow/graph/api/core/workflow/graph/graph.py
6.1 Graph 的角色
Graph 不是调度器,也不是运行态容器。
它是:
- 工作流图的内存表示
- 节点与边的组织者
- root node 和边关系的索引持有者
6.2 它关心的是"结构",不是"执行过程"
Graph 主要持有的内容包括:
nodesedgesin_edgesout_edgesroot_node
这说明 Graph 的任务重心是:
- 节点配置解析
- 边关系建立
- root node 识别
- 节点与边的索引组织
而不是:
- 节点什么时候可运行
- 哪个节点已经完成
- 当前 workflow 是否暂停
这些都不属于 Graph 的职责范围。
6.3 这一层最重要的理解
阅读 Graph 时要特别避免一个误区:
不要把"工作流图"和"工作流执行过程"混为一谈。
在 Dify 里,两者是明确拆开的:
Graph是静态或半静态结构GraphRuntimeState是动态运行态
这也是后面理解整个引擎的关键前提。
7. GraphRuntimeState:共享运行态容器
关键目录与文件:
api/core/workflow/runtime/api/core/workflow/runtime/graph_runtime_state.py
7.1 它是什么
如果用一句话概括:
GraphInitParams 是启动输入,GraphRuntimeState 是执行期间持续变化的共享状态。`
7.2 它承载什么
从源码结构和已有阅读结果看,这里集中承载了 workflow 运行期间的核心共享态,例如:
variable_poolready_queuegraph_executionresponse_coordinatoroutputsllm_usagenode_run_stepspaused_nodesdeferred_nodes- 图级和节点级状态快照
stop_event
7.3 为什么它是核心
如果没有 GraphRuntimeState,引擎里很多角色就只能彼此强耦合:
- Worker 执行节点需要访问变量
- EventHandler 推进边时需要更新图状态
- 外部命令处理需要看到暂停和停止状态
- Layer 观察运行过程也需要只读视图
GraphRuntimeState 的存在让这些角色围绕同一份运行态协作,而不是彼此直接调用和共享零散字段。
7.4 对阅读者最重要的判断
如果你想知道:
- 某个状态到底存在哪里
- 某个节点为什么能读到前置节点输出
- 暂停信息从哪儿取
第一反应应该先查 GraphRuntimeState,而不是先查 GraphEngine。
8. Node 基类:统一节点协议
关键目录与文件:
api/core/workflow/nodes/base/api/core/workflow/nodes/base/node.py
8.1 为什么先看基类,而不是先看具体节点
nodes/ 下面具体节点很多:
llmagentcodehttp_requestiterationlooptoolif_elsestartend
如果一开始就选某个节点实现往里扎,很容易只理解局部业务逻辑,却看不到 Dify 如何统一抽象"节点执行协议"。
8.2 Node 基类解决了哪些共性问题
从阅读角度,最值得关注的共性问题至少有三类。
第一,节点类型注册。
通过 __init_subclass__ 一类机制,节点子类会被自动纳入注册体系,并关联自己的 NodeData 类型。
第二,节点配置的强类型化。
节点不会长期直接操作原始 config["data"],而是会被 hydrate 成明确的 BaseNodeData 子类。
第三,统一执行包装。
外界不是直接调用 _run(),而是调用 run()。
而 run() 负责统一做这些事情:
- 生成执行 id
- 发出开始事件
- 调用真正的
_run() - 将 node event 映射为 graph 侧可消费事件
- 捕获异常并统一发失败事件
8.3 这一层的意义
这说明 Dify 的节点体系不是"每个节点自己随意跑"。
相反,它强调:
- 节点有统一生命周期
- 节点有统一事件协议
- 节点有统一的输入输出与异常包装方式
这对后面的调度器和事件系统至关重要。
9. GraphEngine:执行引擎总调度器
关键目录与文件:
api/core/workflow/graph_engine/api/core/workflow/graph_engine/graph_engine.py
9.1 它的角色
GraphEngine 是整个 workflow 运行期的总 orchestrator。
如果只看职责,不看细节,它解决的是:
- 怎么把结构、运行态、事件、Worker、命令处理、Layer 组装起来
- 怎么把一次 workflow 执行稳定推进到终态
9.2 它装配了哪些子系统
结合当前目录与源码结构,GraphEngine 附近最关键的子系统包括:
ready_queueevent_managementgraph_state_managerresponse_coordinatorerror_handlergraph_traversalcommand_processingworker_managementorchestrationlayers
更具体地说,阅读时最值得建立的对象图是:
GraphStateManagerEventManagerEventHandlerDispatcherWorkerPoolExecutionCoordinatorEdgeProcessorCommandProcessorResponseCoordinator
9.3 它为什么不是单纯的"主循环"
很多工作流引擎会有一个清晰的 while loop 主循环,里面顺序做:
- 取节点
- 跑节点
- 判断后继
- 继续
但 Dify 这里更像一个协作式系统:
- Worker 执行节点
- Dispatcher 消费事件
- EventHandler 推进状态
- EdgeProcessor 处理边
- CommandProcessor 处理中途命令
所以 GraphEngine 更像"装配和协调中心",而不是"所有逻辑都写在一个大循环里"。
10. Worker:真正执行节点的运行者
关键文件:
api/core/workflow/graph_engine/worker.pyapi/core/workflow/graph_engine/worker_management/worker_pool.py
10.1 Worker 的职责很纯粹
Worker 的核心职责可以压缩成四步:
- 从
ready_queue取出 node id - 从
Graph中定位节点 - 执行
node.run() - 将执行产生的事件写入
event queue
10.2 为什么这个角色必须独立存在
如果调度器一边决定执行顺序,一边自己执行节点,耦合会非常重。
拆出 Worker 之后,整个系统就变成:
- 调度负责推进
- Worker 负责执行
- 事件系统负责传递结果
这让并发、暂停、失败收敛、Layer 观测都更容易实现。
11. Dispatcher 与 EventHandler:图推进的真正核心
关键文件:
api/core/workflow/graph_engine/orchestration/dispatcher.pyapi/core/workflow/graph_engine/event_management/event_handlers.py
11.1 为什么说"真正决定图怎么前进"的不是 Worker
Worker 只负责把节点跑完。
但一个节点跑完之后:
- 是成功还是失败
- 是否进入 fail-branch
- 是否要暂停
- 是否可以激活后继边
- 是否应该把下一个节点放进 ready queue
这些都不是 Worker 自己决定的。
真正决定图怎么推进的是:
DispatcherEventHandler
11.2 Dispatcher 做什么
Dispatcher 负责:
- 从
event queue取事件 - 将事件交给
EventHandler - 在运行过程中检查命令和终止条件
- 判断图是否 complete / paused / aborted
11.3 EventHandler 做什么
EventHandler 负责根据不同事件类型推进运行态。
从阅读角度,最值得先抓的四类事件是:
NodeRunStartedEventNodeRunSucceededEventNodeRunFailedEventNodeRunPauseRequestedEvent
这四类事件分别对应:
- 节点进入运行
- 节点成功完成
- 节点异常失败
- 节点主动触发暂停
真正的图推进逻辑,大部分都藏在"这些事件被如何消费"里,而不是藏在节点本身。
11.4 这一层揭示的设计思想
Dify Workflow 不是通过"节点直接调用节点"来推进图。
它采用的是:
节点执行 -> 产出事件 -> 事件被消费 -> 状态推进 -> 后继节点入队
这使得:
- 节点实现可以保持相对纯粹
- 调度策略可以集中管理
- Layer 和观测系统更容易挂接
12. GraphStateManager 与 EdgeProcessor:状态推进和路径选择
关键文件:
api/core/workflow/graph_engine/graph_state_manager.pyapi/core/workflow/graph_engine/graph_traversal/edge_processor.pyapi/core/workflow/graph_engine/graph_traversal/skip_propagator.py
12.1 GraphStateManager 的角色
它统一管理图执行期间的关键状态,例如:
- node state
- edge state
- 当前执行中的节点
- ready queue 入队相关状态
它解决的是:
- 哪些节点已开始
- 哪些节点已结束
- 哪些节点可进入 ready 状态
- 图当前收敛到什么程度
12.2 EdgeProcessor 的角色
节点成功后,后续该怎么走,不是节点自身决定,而是由 EdgeProcessor 基于图结构和边规则来决定。
也就是说,路径推进的主语不是:
- 节点自己调用下游节点
而是:
- 调度侧根据边规则推进图
12.3 这一层为什么重要
如果不把"节点执行"和"边推进"拆开,那么:
- 分支逻辑会散落到各个节点内部
- fail-branch 和 skip 规则难以统一
- 调试和可视化都会变乱
当前这种拆法说明 Dify 对 workflow 的理解是"图执行系统",而不是"节点函数链"。
13. 外部控制是如何进入引擎的
关键文件:
api/core/workflow/graph_engine/manager.pyapi/core/workflow/graph_engine/command_processing/command_processor.pyapi/core/workflow/graph_engine/command_processing/command_handlers.pyapi/core/workflow/graph_engine/entities/commands.pyapi/core/workflow/graph_engine/command_channels/in_memory_channel.pyapi/core/workflow/graph_engine/command_channels/redis_channel.py
13.1 这块解决什么问题
workflow 在运行中经常需要被外部系统控制,例如:
- stop
- pause
- resume
- update variable
这些命令显然不能通过"直接拿到某个 Python 对象然后调方法"来完成,因为引擎可能正在异步运行,甚至可能跨进程。
13.2 当前的基本思路
从 api/core/workflow/README.md 和当前目录结构来看,这套机制本质上是:
外部系统 -> command channel -> CommandProcessor -> RuntimeState / GraphExecution
其中 command channel 可以是:
InMemoryChannelRedisChannel
这意味着 Dify 在设计上已经把"外部控制入口"和"引擎内部处理逻辑"分离开了。
13.3 这一层的重要性
这不是附属功能,而是 workflow 平台化能力的关键部分。
如果没有 command channel 和 command processor:
- workflow 就难以被外部可靠中止
- pause / resume 会很难设计
- 分布式控制能力会明显受限
14. Layer:引擎级中间件机制
关键文件:
api/core/workflow/graph_engine/layers/base.pyapi/core/workflow/graph_engine/layers/debug_logging.pyapi/core/workflow/graph_engine/layers/execution_limits.py
14.1 Layer 解决的是什么问题
Workflow 引擎运行过程中,经常会有一些横切需求:
- 调试日志
- 执行限制
- 可观测性
- 运行监控
如果把这些逻辑硬写进 GraphEngine 主体,会迅速让引擎膨胀。
因此当前设计提供了 Layer 机制。
14.2 Layer 能看见什么
从 README 和已有阅读结果来看,Layer 可以观察:
- graph start
- graph end
- event 流
- node run start / end
- 只读 runtime state
这本质上就是引擎级中间件。
14.3 这层的价值
它说明 Dify Workflow 不是只追求"能跑起来",而是在设计上为:
- 调试
- 观测
- 约束
- 扩展
预留了清晰挂点。
15. import 分层规则说明了什么
api/core/workflow/README.md 里还有一部分很容易被忽略,但非常重要:
graph_engine -> graph_events -> graph -> nodes -> node_events -> entities
以及 graph engine 内部的更细分层:
orchestration -> command_processing -> event_management -> graph_traversal -> domain
这说明两件事。
第一,作者非常在意分层边界。
第二,这不是"看起来像分层",而是明确尝试用 import 规则约束的分层。
从阅读角度,这能帮助你快速判断:
- 某个模块如果开始反向依赖下层,通常意味着设计边界被破坏
- 某个对象该不该直接 import 另一个对象,可以先看它们所在层级
16. 我对当前 Workflow 核心设计的总体理解
16.1 它本质上是一个队列驱动的图执行器
它不是:
- 递归 DFS 执行器
- 简单的拓扑排序顺推器
- 单线程的串行节点解释器
它的执行核心是:
ready queueWorkerevent queueDispatcher
这几者协作推进。
16.2 它本质上是事件驱动,而不是节点直接互调
节点执行结束后,并不会直接调用下游节点。
它采用的是:
- 节点产出事件
- 调度器消费事件
- 事件处理器更新状态
- 边处理器计算后继
- 后继节点重新入队
这使执行流更可观测,也更利于扩展。
16.3 它把"结构""运行态""调度""控制"拆得比较清楚
大致可以记成四块:
Graph: 结构GraphRuntimeState: 运行态GraphEngine: 调度装配CommandChannel / Layer: 控制和扩展
这是一种平台型工作流引擎常见但很重要的拆法。
16.4 它从设计上就不是只支持 happy path
从目录和对象划分就能看出,这套引擎原生考虑了:
- fail
- partial success
- fail-branch
- skip
- pause
- abort
- resume
- external command
所以阅读时不要只用"成功路径"去理解它。
17. 推荐的源码精读顺序
如果后续真要深读,我建议按下面顺序推进。
17.1 第一轮:先建立整体结构感
api/core/workflow/README.mdapi/core/workflow/workflow_entry.pyapi/core/workflow/graph/graph.pyapi/core/workflow/runtime/graph_runtime_state.pyapi/core/workflow/nodes/base/node.py
这一轮的目标不是看完所有细节,而是建立:
- workflow 是怎么装配起来的
- Graph 和 RuntimeState 如何分工
- 节点执行协议长什么样
17.2 第二轮:再读执行引擎
api/core/workflow/graph_engine/graph_engine.pyapi/core/workflow/graph_engine/worker.pyapi/core/workflow/graph_engine/worker_management/worker_pool.pyapi/core/workflow/graph_engine/orchestration/dispatcher.pyapi/core/workflow/graph_engine/event_management/event_handlers.pyapi/core/workflow/graph_engine/graph_state_manager.pyapi/core/workflow/graph_engine/graph_traversal/edge_processor.py
这一轮重点看:
- ready queue 如何推进
- event queue 如何被消费
- 节点完成后如何推进后继
17.3 第三轮:再读外部控制和扩展机制
api/core/workflow/graph_engine/manager.pyapi/core/workflow/graph_engine/command_processing/command_processor.pyapi/core/workflow/graph_engine/command_channels/redis_channel.pyapi/core/workflow/graph_engine/layers/base.pyapi/core/workflow/graph_engine/layers/debug_logging.pyapi/core/workflow/graph_engine/layers/execution_limits.py
这一轮重点看:
- stop / pause / resume 命令是如何接入的
- Layer 如何做观测与约束
17.4 第四轮:最后回到具体节点实现
等前面三轮建立结构感以后,再去读:
nodes/llm/nodes/agent/nodes/http_request/nodes/iteration/nodes/loop/nodes/tool/
这时你读到的就不只是"节点业务逻辑",而是"节点如何被纳入整个引擎协议"。
18. 后续最值得继续补的专题
如果这篇导读后续继续扩展,最值得单独写的几篇是:
Dify Workflow 节点系统梳理Dify Workflow 执行时序图与事件流笔记Dify Workflow 暂停、恢复与命令通道机制笔记Dify Workflow 变量池与上下文传播笔记
19. 总结
理解 Dify Workflow 的关键,不是记住某个类里写了什么,而是先建立一个判断:
它是一套分层明确、队列驱动、事件驱动、支持外部命令控制的图执行系统。
因此,最合理的阅读方式不是直接陷进某个节点实现,而是先按下面这条主线建立认知:
WorkflowEntry看装配入口Graph看静态结构GraphRuntimeState看共享运行态Node基类看统一执行协议GraphEngine看总调度Worker + Dispatcher + EventHandler看执行推进CommandProcessor + Channel + Layer看控制与扩展
只要这个主线建立起来,后面再看具体节点、暂停恢复、边规则、变量传播,都会容易很多。