从 Dify 到 Rill-Flow:大模型应用平台的进化之路
来源:互联网
时间:2026-07-20 13:05:38
# 1. 基于 dify 的大模型应用平台构建
最近几年,大语言模型领域的发展速度确实惊人,涌现出了一大批优秀的产品,实实在在地解决了不少业务场景下的痛点,显著提升了工作效率。很多公司也顺势而为,搭建起了自己的大模型应用平台,统一管理各类大语言模型,构建更复杂的应用,以满足各部门的多样化需求。

一开始,我们选择了基于 dify 项目提供的工作流引擎来构建大模型应用平台,目标是把多个大语言模型串联起来,形成更复杂的流程和应用。
dify 是 2023 年开源的一款聚焦于工作流引擎、大模型对话应用、知识库管理等功能的 AI 大模型应用平台。截至 2024 年 11 月,它在 GitHub 上已经积累了 5.3 万 star,算是大模型应用基础服务里最热门的项目之一了。
dify 的界面做得相当漂亮,尤其是工作流引擎这块,通过简单的拖拽就能编排工作流,上手非常轻松。
# 2. 我们遇到的问题
但在实际使用过程中,逐渐暴露出了一些局限,无法完全满足我们对工作流引擎的诉求。
## 2.1 流程开始后无法人工干预
在实际业务中,一个非常核心的诉求就是能支持任务的等待和触发。比如,经常遇到这样的情况:AI 大模型生成完文本后,需要进入人工审核环节,只有审核通过才能继续下一步。
很多业务流程中,还得等待某个外部事件的发生,比如订单支付成功、外卖接单成功,或者等到某个特定时间点,才能触发下一步执行。
## 2.2 错误处理能力有限
另一个问题在于,dify 对工作流中错误的处理能力比较有限。只要工作流中任何一个节点执行失败,整个工作流就会直接挂掉,没办法进行错误处理和恢复。
事实上,有些节点并非关键路径,它们执行失败后,我们更希望能跳过这些节点,继续执行后面的步骤,而不是让整个流程直接崩塌。此外,任务执行失败后,理想的情况是能自动发起几次重试,尝试自动恢复,而不是非得人工介入,手动重启整个工作流。
## 2.3 多任务并发的性能问题
最初使用 dify 搭建平台时,它还不支持多任务分支的并行执行,工作流只能一个任务接一个任务地顺序处理。
虽然后来在 0.8.0 版本中,dify 引入了多任务分支并行执行的能力,但问题仍然存在:每次执行都被限制在单机环境下,不同任务节点无法分布到不同机器上并行处理。如果工作流中的任务节点过多,很容易导致分布式集群资源使用不均衡,某台单机负载过重。
另外,循环任务中的每次迭代都是在整个循环中串行执行的,迭代之间不能并发,这成了工作流执行性能的瓶颈。比方说,需要把一篇文章分段后,让大模型对每段进行总结。如果用 dify 实现,每一段都得挨个执行;但理想情况显然是同时发起多次大模型调用,并行处理每个分段。
# 3. dify 代码分析
针对上述不足,我们尝试对 dify 项目进行改造,来适配实际业务场景。
## 3.1 dify 的代码结构
dify 的前端项目基于 Typescript 的 Next.js 框架开发,代码在 `web` 目录下。Next.js 是构建全栈 Web 应用的 React 框架,用于快速搭建交互式、动态的 React 应用。
```
[web/]
├── app // 布局、页面和组件
│ ├── (commonLayout) // 整个应用通用的布局
│ ├── (shareLayout) // 在特定会话中共享的布局
│ ├── activate // 激活页面
│ ├── components // 页面和布局共享的组件
│ ├── install // 安装页面
│ ├── signin // 登录页面
│ └── styles // 全局共享的样式
├── assets // 静态资源
├── bin // 构建步骤运行的脚本
├── config // 可调整的设置和选项
├── context // 应用中不同部分使用的共享上下文
├── dictionaries // 语言特定的翻译文件
├── docker // 容器配置
├── hooks // 可重用的钩子
├── i18n // 国际化配置
├── models // 描述数据模型和 API 响应的形状
├── public // 如 fa vicon 等元资源
├── service // 定义 API 操作的形状
├── test
├── types // 函数参数和返回值的描述
└── utils // 共享的实用函数
```
dify 的服务端基于 Python 语言和 Flask 框架开发,代码被拆分为 controller、service、model、core 等部分,放在 `api` 目录下相应的子目录中。
```
[api/]
├── constants // 用于整个代码库的常量设置。
├── controllers // API 路由定义和请求处理逻辑。
├── core // 核心应用编排、模型集成和工具。
├── docker // Docker 和容器化相关配置。
├── events // 事件处理和处理。
├── extensions // 与第三方框架/平台的扩展。
├── fields // 用于序列化/封装的字段定义。
├── libs // 可重用的库和助手。
├── migrations // 数据库迁移脚本。
├── models // 数据库模型和架构定义。
├── services // 指定业务逻辑。
├── storage // 私钥存储。
├── tasks // 异步任务和后台作业的处理。
└── tests
```
服务端对外提供的 API 接口外层实现在 `api/controllers` 下,而核心代码在 `api/core` 下。运行过程中还会不断触发各类事件,这些事件的定义与管理放在 `api/events` 下;对第三方组件(如登录、邮件发送、存储、任务队列)的依赖和封装,则位于 `api/extensions` 下。
按功能划分,`api/core` 目录中的组件进一步拆分到子目录中:
```
[api/core/]
├── agent
├── app
├── callback_handler
├── embedding
├── entities
├── errors
├── extension
├── external_data_tool
├── file
├── helper
├── llm_generator
├── memory
├── model_runtime
├── moderation
├── ops
├── prompt
├── rag
├── tools
└── workflow
```
## 3.2 dify 工作流的实现
工作流引擎模块的代码在 `api/core/workflow` 下,调用入口在 `api/controllers/web/workflow.py` 中。整个工作流执行的发起,就是通过调用此文件中定义的 `/workflows/run` 接口触发的。
### (1) dify 服务端的流程处理
当 `/workflows/run` 接口被调用后,服务端执行以下步骤:
1. **请求接收**:通过 `WorkflowRunApi.post` 方法,使用 `reqparse.RequestParser` 完成请求体与参数的解析。
2. **根据模式选择应用生成器**:调用 `api/services/app_generate_service.py` 中的 `generate` 方法,匹配传入的 `app_model` 参数,根据模式选择具体的应用实现(如工作流、对话),进而调用具体应用的 `generate` 方法。
3. **生成应用**:对于工作流应用,会调用 `api/core/app/workflow/app_generator.py` 中定义的 `generate` 方法,生成工作流应用的执行对象并执行相应逻辑。
### (2) 工作流应用的生成
在工作流应用的 `generate` 方法中,又执行了以下流程:
1. **获取参数与应用配置**:`WorkflowAppGenerator.generate` 方法中,先解析参数,通过 `WorkflowAppConfigManager` 获取应用配置,并拼装成工作流执行所需的 `WorkflowAppGenerateEntity` 对象。
2. **创建工作线程**:完成参数与应用配置后,调用 `WorkflowAppGenerator._generate` 方法,创建并初始化用于通信的 `WorkflowAppQueueManager` 对象,然后创建工作线程,执行 `WorkflowAppGenerator._generate_worker` 方法。
3. **工作线程的执行**:具体逻辑就在 `_generate_worker` 中,这里创建了 `WorkflowAppRunner` 实例来执行工作流。
### (3) 工作流的执行
在 `WorkflowAppRunner` 的 `run` 方法中,实现了工作流的执行:
1. **获取用户信息、应用和工作流**:通过查询数据库,获取用户信息、应用记录以及工作流实例。
2. **初始化工作流**:创建并初始化变量池 `VariablePool`,调用 `_init_graph` 方法初始化工作流 DAG 图,然后创建 `WorkflowEntry` 实例,调用其 `run` 方法开始运行工作流。
3. **运行工作流**:调用 `GraphEngine` 的 `run` 方法,将 DAG 图切分为若干 item 迭代处理,流式生成器 `AnswerStreamProcessor` 不断生成 item 并执行,触发不同事件。
4. **事件处理**:对于每个事件,通过 `WorkflowBasedAppRunner` 的 `_handle_event` 方法,将事件发布到队列中,实现响应。事件发布后,监听事件的 `AppQueueManager` 通过 yield message 生成消息,最外层的 `WorkflowRunApi` 通过流式通信将事件消息发送给客户端。
## 3.3 dify 的通信模式
通过以上代码流程分析可以看出,dify 采用多线程模式处理并发请求。每次工作流执行,都是一次基于 Server-Sent Events 协议的 HTTP 请求。客户端通过调用 `/workflows/run` 接口与服务端建立长连接后,服务端会持续不断地将工作流执行过程中的状态变化和数据消息作为一个个事件,通过流式通信返回给客户端。
这种流式通信机制,是 dify 服务端与客户端通信的核心机制。在这样的设计下,dify 构建的应用与 AI 大语言模型的交互方式完全一致,用户可以将工作流或对话流程的执行都封装成 AI 应用的执行,无需关心背后的具体实现。
但这样的设计也带来了不足。由于每次工作流执行都需要与服务端建立长连接,整个工作流的每一次完整执行都必须在同一台服务器上完成,无法让不同任务节点分布在不同的服务器上并行执行。如果想在这套机制下实现任务的等待和触发,客户端与服务端的长连接必须一直保持打开状态,直到任务被触发并执行完成。这种长时间的长连接在生产环境中既不可靠,也会浪费资源。并发量大的情况下,服务端压力会非常大。
从本质上说,dify 的设计目的是将其实现的各种功能都封装成与 AI 大语言模型相同的交互模式。而我们在大模型应用平台的开发中,对工作流引擎的诉求是**高吞吐和高可用**。因此,要实现工作流中任务的等待和触发、让不同任务在分布式环境中并发执行等功能,就需要对 dify 工作流引擎的基础设计机制和通信模式进行改造,工作量巨大,相当于整体重构。考虑到 dify 目前正处于快速迭代期,版本迭代频繁,代码变更庞大,改造工作会面临越来越大的合并难度,后续维护成本只会越来越高。最终摆在面前的是两条路:要么放弃同步 dify 开源版本的升级,要么放弃改造 dify 项目。
# 4. 云原生的高性能工作流引擎 rill-flow
放弃改造 dify 项目后,我们转而选择了将 dify 的执行部分对接到开源工作流引擎 rill-flow 的方案。
## 4.1 代码分析
rill-flow 是新浪微博开源的高性能、可扩展的分布式流程编排服务。它的核心设计目标是成为易用、高并发、低延时的工作流引擎,是云原生分布式场景下解决复杂流程编排、大流量任务执行性能、AIGC 应用快速集成的优秀方案。它已经在微博得到大规模应用,日处理任务量达千万级,支撑了微博多个业务的核心流程。
rill-flow 的服务端用 Ja va 语言和 SpringBoot 框架实现,代码简洁清晰,划分为若干 Ja va 模块相互依赖。
```
[/]
├── rill-flow-common // 项目通用类型、异常、常量等
├── rill-flow-dag // 流程图调度器的实现
│ ├── olympicene-core // 调度器所依赖的基础类型及工具类
│ ├── olympicene-ddl // DAG 图的解析与转换
│ ├── olympicene-spring-boot-starter // SpringBoot的bean启动类
│ ├── olympicene-storage // DAG 图及相关存储
│ └── olympicene-tra versal // 调度器与派发器的代码实现
├── rill-flow-service // web api 所需调用的接口定义
├── rill-flow-impl // web api 所需调用的接口实现
├── rill-flow-interfaces // 插件需要实现的统一接口定义
├── rill-flow-plugins // 派发器插件的具体实现
│ ├── aliyun-ai-plugin
│ └── chatgpt-plugin
├── rill-flow-trigger // 触发器实现
├── rill-flow-web // web api 入口
├── rill-flow-ui // web 前端代码
└── flow-graph // 核心功能微应用
```
rill-flow 还支持通过开源的 PF4J 项目协议扩展自定义的派发器作为插件,集成到执行环境中,从而让用户通过自定义实现完成各类协议任务的派发。
## 4.2 项目架构
从架构上看,rill-flow 分为触发器、调度器、派发器、执行器等模块:
1. **触发器**(Trigger):当工作流需要执行时,触发器负责处理事件触发,发起整个工作流调用。
2. **调度器**(Tra versal):调度器负责遍历流程图中的每个任务节点,将待执行的任务传递给派发器。
3. **派发器**(Dispatcher):派发器接到待执行任务后,根据任务类型和其他属性,找到应该负责执行的执行器,并将任务派发给具体执行器。比如 HTTP 协议的任务下发给 HTTP 执行器,AI 大模型任务派发给大模型执行器。
4. **执行器**(Executor):负责任务的具体执行,如 HTTP 接口调用、AI 大模型任务执行,并将执行结果状态和数据反馈给派发器。
在这整个流程中,模块与模块之间均采用异步方式下发任务。每次任务下发时,负责发布的模块只完成下发动作并更新任务信息,直到任务完成后再被调用唤起。
相比之下,dify 项目采用的是流式阻塞式通信模式,而 rill-flow 的异步非阻塞架构设计能更高效地利用资源。客户端不需要和服务端保持长连接,而是通过异步查询来获取工作流执行结果。而且,rill-flow 本身就提供了任务的阻塞与唤醒、并发调度、错误处理等功能,很好地满足了我们的实际业务需要。因此,最终决定以 rill-flow 作为大模型应用平台的工作流引擎核心组件。
另外,rill-flow 作为云原生应用,可以直接部署在分布式集群环境中,每个任务节点会被自动调度到集群中的空闲节点上执行,最大限度地利用分布式集群带来的并发性能提升。
# 5. DSL 转换器:从 dify 到 rill-flow 的平滑过渡
基于上述考量,我们最终决定将工作流引擎从 dify 更换为 rill-flow。但由于大模型应用平台已经基于 dify 运行了一段时间,构建了不少实际的业务流程,操作人员也已充分熟悉 dify 的界面和操作方式。如果直接切换到 rill-flow,学习成本会大大增加,而且所有现有的工作流都需要重新构建,工作量不容忽视。
于是我们开始思考:有没有一种方案,能让大模型应用平台平滑过渡到 rill-flow,同时最大限度地减少对现有平台用户的影响?
最终,我们开发了一个 DSL 转换器,能够将 dify 的 DSL 描述文件转换为 rill-flow 的 DSL 描述。这样,用户在大模型应用平台上仍然可以通过 dify 的界面拖拽配置工作流,但在后台实际由 rill-flow 来调度执行。这样一来,就实现了无缝切换到 rill-flow 的目标。通过 dify 界面上的“导出 DSL”选项,或者 dify 提供的 DSL 查询接口,都可以获取到某个工作流的描述文件。
## 5.1 整体架构
DSL 转换器将 dify 导出的 DSL 描述转换为 rill-flow 支持的 DSL 描述,然后导入到 rill-flow 服务中,由 rill-flow 调度派发,实现了业界先进的交互体验与高性能工作流引擎的结合。
同时,dify 还提供了知识库、大模型配置和使用等丰富功能。在转换器改造过程中,我们仍然希望充分利用 dify 本身的执行能力。因此,设计了新的架构,主要包括两个流程改造:
- **工作流的创建与编辑流程改造**
在用户完成工作流编辑后,大模型应用平台自动触发调用 DSL 转换服务,通过调用 dify 服务端的 DSL 导出接口,获取 dify 工作流的 DSL 描述,并执行格式转换,完成从 dify 的 DSL 描述到 rill-flow 所需 DSL 的转换。
- **工作流的执行流程改造**
当用户触发任务执行时,大模型应用平台网关调用 rill-flow 引擎的 API 接口,触发工作流调度。基于 DSL 描述,任务最终会调用 dify 后端服务的任务执行接口,完成任务的执行。
## 5.2 dify 与 rill-flow 的 DSL 对比
dify 和 rill-flow 都使用 YAML 格式的 DSL 描述,两者都非常易于理解。与 rill-flow 不同,dify 的 DSL 除了描述节点之间的依赖关系和参数外,还包含节点在界面上展示所需的位置信息。而 rill-flow 的 DSL 描述则较为纯粹,只包含节点依赖关系、输入输出信息,以及可选的异常处理等信息。因此,将 dify 的 DSL 描述转换成 rill-flow 的 DSL 是相对比较简单的。
例如,下面是一个 dify 工作流的 DSL 描述:
```
app:
description:''
icon:?
icon_background:'#FFEAD5'
mode:workflow
name:hello
use_icon_as_answer_icon:false
kind:app
version:0.1.2
workflow:
conversation_variables:[]
environment_variables:[]
features:
file_upload:
allowed_file_extensions:
-.JPG
-.JPEG
-.PNG
-.GIF
-.WEBP
-.SVG
allowed_file_types:
-image
allowed_file_upload_methods:
-local_file
-remote_url
enabled:false
fileUploadConfig:
audio_file_size_limit:50
batch_count_limit:5
file_size_limit:15
image_file_size_limit:10
video_file_size_limit:100
image:
enabled:false
number_limits:3
transfer_methods:
-local_file
-remote_url
number_limits:3
opening_statement:''
retriever_resource:
enabled:true
sensitive_word_a voidance:
enabled:false
speech_to_text:
enabled:false
suggested_questions:[]
suggested_questions_after_answer:
enabled:false
text_to_speech:
enabled:false
language:''
voice:''
graph:
edges:
-data:
isInIteration:false
sourceType:start
targetType:http-request
id:1728717788734-source-1728717794809-target
source:'1728717788734'
sourceHandle:source
target:'1728717794809'
targetHandle:target
type:custom
zIndex:0
-data:
isInIteration:false
sourceType:http-request
targetType:end
id:1728717794809-source-1732851756085-target
source:'1728717794809'
sourceHandle:source
target:'1732851756085'
targetHandle:target
type:custom
zIndex:0
nodes:
-data:
desc:''
selected:false
title:开始
type:start
variables:[]
height:54
id:'1728717788734'
position:
x:80
y:282
positionAbsolute:
x:80
y:282
sourcePosition:right
targetPosition:left
type:custom
width:244
-data:
authorization:
config:null
type:no-auth
body:
data:
-type:text
value:''
type:none
desc:''
headers:''
method:get
params:wd:当前时间
selected:false
timeout:
max_connect_timeout:0
max_read_timeout:0
max_write_timeout:0
title:HTTP请求
type:http-request
url:https://www.baidu.com/s
variables:[]
height:94
id:'1728717794809'
position:
x:384
y:282
positionAbsolute:
x:384
y:282
selected:false
sourcePosition:right
targetPosition:left
type:custom
width:244
-data:
desc:''
outputs:
-value_selector:
-'1728717794809'
-body
variable:result
selected:false
title:结束
type:end
height:90
id:'1732851756085'
position:
x:688
y:282
positionAbsolute:
x:688
y:282
selected:true
sourcePosition:right
targetPosition:left
type:custom
width:244
viewport:
x:-20
y:1
zoom:1
```
它定义了一个包含三个节点的工作流。
转换为 rill-flow 的 DSL 则是:
```
workspace: "default"
dagName:"httpRequestDemo"
type:"flow"
tasks:
-name:"httpRequest"
title:"HTTP 请求"
category:"function"
resourceName:"http://www.baidu.com/s"
resourceProtocol:"http"
pattern:"task_sync"
input:
query.wd:"当前时间"
output:
end:$.httpRequest.result
```
rill-flow 的这个 DSL 描述了一个只有一个节点的工作流,实现对 HTTP 接口的请求,并输出返回 JSON 中 result key 对应的数据。
可以看出,rill-flow 的 DSL 配置非常简洁。结合官方文档中提到的其他配置字段,能够实现包括错误处理、失败重试、流式处理、分布式并发执行等强大功能,完全可以支撑大模型应用平台的各种场景。比如,想让上述工作流中的 HTTP 请求失败后间隔 2 秒重试 3 次,只需在 DSL 中添加 retry 字段:
```
workspace: "default"
dagName:"httpRequestDemo"
type:"flow"
tasks:
-name:"httpRequest"
title:"HTTP 请求"
category:"function"
resourceName:"http://www.baidu.com/s"
resourceProtocol:"http"
pattern:"task_sync"
input:
query.wd:"当前时间"
retry:
maxRetryTimes:3
intervalInSeconds:2
output:
end:$.httpRequest.result
```
同样的,可以将某个任务节点的 tolerance 属性设置为 true,就能将其设为执行失败就跳过的非核心节点。
## 5.3 对 dify 的改造
由于 dify 服务端不支持对整个流程图中的单个节点独立调用和执行,在上述 DSL 转换服务架构下,需要对 dify 进行一定的改造,暴露出 dify 任务执行器的执行接口。
首先,在 `controllers/workflow/workflow.py` 中增加工作流单个节点的执行接口。在这个接口中,直接调用 `api/core/workflow/workflow_entry.py` 中的 `WorkflowEntry.single_step_run` 方法,并传递单个任务执行所需的全部参数,实现单个任务节点的执行与事件生成。然后通过 `WorkflowEntry.handle_special_values` 方法接收相应事件结果并返回。
在 DSL 转换服务执行转换时,每个任务的调用目标地址都指向 dify 服务端这个新增的单个节点执行接口。这样,在工作流执行时,rill-flow 可以通过不断调度和派发,完成整个工作流的执行。
通过上述改造,我们用最低的改造成本,将 dify 的前端交互体验和大模型任务执行能力,与 rill-flow 功能强大、高性能的调度能力结合在了一起。由于采用新增接口的方式对 dify 进行改造,对 dify 代码几乎没有任何侵入性,因此随着 dify 项目的后续迭代,也不会出现维护成本上升的问题。
## 5.4 改造效果
下面这个工作流是一个典型例子:首先传入一段长文本,然后代码执行节点通过 Python 代码将文本切分为以 100 字符为单位的字符串列表,接着对这个列表进行迭代,让大模型对每个分段进行总结。
在改造前的 dify 项目中,上述流程单次运行时间为 47.4 秒,而改造后的运行结果缩短到了 19 秒,运行时间减少了 60%,效果非常显著。除此之外,改造后的工作流还增加了任务等待、触发,以及丰富的错误处理、重试、跳过等能力。
# 6. 总结
经过不断发展和实践,我们的大模型应用平台从最初整合多个大模型 API 接口,提供简单的问答功能,并通过 dify 项目提供复杂工作流的流程编排能力,到后来将工作流引擎的执行切换到 rill-flow 项目,在保证前端操作体验不变的同时,优化了工作流的性能和稳定性。
最终,在 dify 美观、易用的界面基础上,通过实现 DSL 转换器,实现了用户在 dify 界面上通过拖拽配置工作流,但后台实际通过 rill-flow 来调度执行。这样既最大限度地保留了用户的使用习惯,又享受到了 rill-flow 带来的高性能、高可靠性工作流引擎服务。充分发挥了 dify 与 rill-flow 两个开源项目各自的优势,最大程度上满足了大模型应用平台的实际业务场景需要。