从一次点击开始
你在 Dify 里点下"运行",背后发生了什么?
Dify 是什么?用一句话说清楚
想象你是一个厨师,但你不需要从零建造厨房——你只需要说"我要做这道菜",Dify 会把所有厨具、食材通道、出菜窗口都帮你准备好。
Dify 是一个 LLM 应用开发平台:你不用写底层代码,就能把 AI 能力组合成真正能用的产品——聊天机器人、文档问答、自动化工作流。
工作流编排
像拼乐高一样把 AI 步骤串联成流程,无需写代码
RAG 知识库
上传你的文档,AI 就能基于它们回答问题
Agent 能力
AI 能主动调用工具、搜索网络、执行多步骤任务
模型无关
GPT、Claude、Llama、国产模型——随时切换,一行不改
你点下"发送"之后的 7 秒
想象你在 Dify 搭建的客服机器人里发了一条消息:"我的订单什么时候到?"
这 7 秒里,至少有 6 个系统协同工作。让我们逐步追踪这段旅程:
为什么 Dify 要这样设计?
你可能会问:为什么需要这么多层?直接调 OpenAI 的 API 不就好了?
直接调 API 能做出 Demo,但无法处理:并发 1000 个用户、API 限流重试、对话历史存储、多模型切换、用量计费……这就是 Dify 的价值所在。
API 收到请求立即返回,Worker 在后台慢慢处理——用户不等待,系统不崩溃
SSE 让 AI 的回答像打字一样逐字出现,而不是等 30 秒后突然弹出一大段
每一条消息、每一次工作流运行都存档——可回溯、可分析、可审计
代码翻译:Flask 应用工厂
Dify 后端的入口文件 api/app_factory.py 里有一段很有代表性的代码,它展示了生产级应用如何处理每一个请求:
def create_flask_app_with_configs() -> DifyApp:
dify_app = DifyApp(__name__)
dify_app.config.from_mapping(
dify_config.model_dump()
)
@dify_app.before_request
def before_request():
init_request_context()
RecyclableContextVar
.increment_thread_recycles()
定义一个"工厂函数"——它每次被调用都会创建并返回一个全新的 Web 应用实例
造一个新的 Dify 应用对象,__name__ 告诉它自己在哪个文件里
从配置文件加载所有设置——数据库地址、密钥、API 限制等
model_dump() 把 Python 对象转成普通字典,方便 Flask 读取
注册一个"钩子"——每一个请求到达之前,都先执行 before_request 里的代码
初始化这次请求的日志上下文,这样后续所有日志都会带上请求 ID
清理线程变量,防止上一个请求的数据污染这一个请求
理解检验
你在 Dify 里发了一条消息,等了 15 秒才收到回答。这个 15 秒主要消耗在哪里?
用户为什么能看到 AI 回答"一个字一个字蹦出来"的效果,而不是等待后一次性显示全部内容?
架构全景:五大角色
认识 Dify 的演员阵容——每个服务扮演什么角色
一场管弦乐演出
Dify 的架构像一场管弦乐:指挥(Nginx)站在最前面分配任务,不同乐手(各个服务)各司其职,乐谱(共享配置)确保大家演奏同一首曲子。
没有人"全部都做"——每个服务只做好自己那一件事。这是软件工程里最重要的原则之一:单一职责。
所有流量入口。把 /console/api 转给 API 服务,把 / 转给 Web 服务
Python Flask 应用。处理所有业务逻辑:验证、路由、协调其他服务
Celery 异步工作进程。专门执行耗时任务:调用 LLM、处理文档、发邮件
Next.js 前端。用户看到的所有界面——工作流画布、聊天界面、配置面板
PostgreSQL 存持久数据(消息、工作流、用户);Redis 存临时状态和任务队列
服务之间怎么"说话"?
当你在 Dify 里创建一个新的聊天应用时,这几个服务是这样沟通的:
代码住在哪里?
了解文件结构,你就知道该让 AI 把新功能写在哪里:
知道文件结构后,你就能说"在 api/controllers/console/app/ 下新增一个处理 XXX 的路由"——而不是模糊地说"加一个 API"。AI 听懂精确指令,代码质量提升 10 倍。
代码翻译:Docker Compose 服务定义
Dify 的 docker/docker-compose.yaml 定义了所有服务。这是 API 服务的配置片段:
x-shared-env: &shared-api-worker-env
CELERY_WORKER_AMOUNT:
${CELERY_WORKER_AMOUNT:-4}
APP_MAX_EXECUTION_TIME:
${APP_MAX_EXECUTION_TIME:-1200}
DB_HOST:
${DB_HOST:-db_postgres}
定义一个"共享配置块"——用 & 标记,API 和 Worker 两个服务都会引用它
后台 Worker 进程数量:从环境变量读取
如果没有设置环境变量,默认启动 4 个 Worker 进程
单个应用最大运行时间(秒)
默认 1200 秒(20 分钟)——防止一个任务跑太久把系统卡死
数据库主机地址:
默认连接名叫 db_postgres 的容器(Docker 内部网络会自动解析这个名字)
理解检验
用户上传了一个 500 页的 PDF 让 Dify 建立知识库。这个处理工作主要由哪个服务执行?
你想让工作流画布上的 LLM 节点显示当前使用的模型名称。你应该让 AI 修改哪里的代码?
工作流:可视化流程引擎
那些节点和连线背后,藏着什么样的执行引擎?
工作流是一张乐谱
你在画布上拖出来的工作流,本质上是一张"乐谱"——它描述了"应该发生什么、以什么顺序、在什么条件下"。但它本身不执行任何事情。
真正的演奏者,是 api/core/workflow/ 里的执行引擎——它读取乐谱(DSL),然后驱动每一个节点按顺序"演奏"。
拖入节点(LLM / 代码 / 工具 / 知识库),用箭头连接,设定条件分支
保存时,整个画布被转成一个 JSON 对象存进数据库——节点列表 + 边的列表
每次触发时,引擎从数据库加载 JSON,构建有向图,按 拓扑顺序逐节点执行
每个节点完成都触发一个事件,流式推送给前端——你看到节点一个个变绿
节点类型:工作流的词汇表
工作流里的每种节点类型对应 api/core/workflow/nodes/ 下的一个目录。了解它们,你才能知道该组合哪些"词汇"来描述你的业务流程:
LLM 节点
调用大语言模型生成文本,支持 Prompt 模板和上下文变量注入
知识检索节点
从知识库里搜索相关段落,把结果注入到后续 LLM 节点的上下文
代码节点
在沙箱里执行 Python 或 JavaScript 代码——处理数据、格式化输出
条件分支节点
If/Else 逻辑——根据前一步的结果走不同的路径
工具节点
调用内置或自定义工具:Google Search、DALL-E、HTTP 请求等
循环节点
对列表中的每个元素重复执行一段子流程(类似 for 循环)
代码翻译:工作流触发入口
当你点击"运行",请求到达 api/core/app/apps/workflow/app_generator.py 的 WorkflowAppGenerator。这里是真正"发动引擎"的地方:
class WorkflowAppGenerator(BaseAppGenerator):
def generate(
self,
app_model: App,
workflow: Workflow,
invoke_from: InvokeFrom,
inputs: Mapping[str, Any],
stream: bool,
) -> ...:
定义一个"工作流应用生成器"类,继承自基础生成器
核心方法:generate,负责触发一次工作流执行
(self 表示这是类的实例方法)
要运行哪个应用(数据库里的 App 记录)
要运行哪个工作流版本(草稿 or 已发布版本)
谁触发的:用户通过界面、API 调用还是自动化触发
用户传入的变量(比如 {article: "我要总结的文章内容"})
是否流式返回——true 时用 SSE 推送,false 时等全部完成后返回
变量是怎么在节点之间传递的?
工作流最神奇的地方是:前一个节点的输出可以作为后一个节点的输入。Dify 用"变量池"来实现这一点:
每次工作流运行,Dify 维护一个"共享变量池"。每个节点执行完毕后,把自己的输出写入池子。下一个节点从池子里读取它需要的变量,用 {{节点名.输出字段}} 语法引用。
用户输入:topic = "AI 发展"
LLM 节点读取 {{sys.query}},输出 outline
第二个 LLM 节点读取 {{llm1.outline}},生成正文
代码节点格式化 {{llm2.text}} 输出 Markdown
理解检验
你设计了一个有 8 个节点的复杂工作流并保存了。下次再打开时,Dify 是怎么恢复这个工作流的?
你想搭一个工作流:如果 LLM 判断用户意图是"投诉",就走投诉处理流程;否则走普通客服流程。你需要用哪种节点类型?
RAG 管道:让 AI 读你的文档
知识库是怎么建立的?AI 是怎么在你的文档里"找到"答案的?
为什么 AI 需要"读"你的文档?
LLM 的知识在训练时就固定了——它不认识你公司的产品手册,不知道你昨天发布的公告,也不了解你独特的业务规则。
RAG 解决这个问题的方式非常聪明:不是"训练 AI 学你的知识"(很贵),而是"回答时临时把相关文档页给 AI 看"(很便宜)。
微调(fine-tuning)让 AI 记住知识 = 闭卷考试。RAG 给 AI 相关页面 = 开卷考试。对于私有知识库,开卷考试便宜 100 倍,效果还往往更好。
从上传到回答:RAG 的完整管道
Dify 的 RAG 分两个阶段:建索引(你上传文档时)和检索(用户提问时)。
从 PDF/Word/网页中提取纯文本,支持 20+ 种格式。代码在 api/core/rag/extractor/
把长文档切成小段落(默认 500 token/段)。代码在 api/core/rag/splitter/
每个段落被转成一个数字向量(向量),存入向量数据库
用户的问题也被向量化,在数据库里找"最相似的段落"——余弦相似度计算
把检索到的段落塞进 Prompt:"基于以下资料回答:[检索结果]",LLM 生成答案
代码翻译:索引构建器
api/core/indexing_runner.py 里的 IndexingRunner 负责整个建索引流程。看看它的错误处理有多严谨:
def _handle_indexing_error(
self,
document_id: str,
error: Exception
) -> None:
document = db.session.get(
DatasetDocument, document_id)
if document:
document.indexing_status =
IndexingStatus.ERROR
document.error = str(error_message)
db.session.commit()
定义一个"处理建索引失败"的方法
(self 是类实例)
哪个文档出错了(用 ID 标识)
错误是什么(Python 里所有异常的基类)
返回类型是 None——这个方法只做副作用(更新数据库),不返回值
从数据库里查出这个文档的记录
如果文档还在(没有被删除)
把状态改为 ERROR——前端会显示"处理失败"
记录错误原因——方便用户和管理员排查
提交到数据库——这个修改正式生效
注意 IndexingStatus.ERROR 这种模式——文档处理有明确的状态流转:pending → indexing → completed / error。这叫"状态机"。生产代码总是把状态显式化,而不是靠猜测。
切块策略:影响检索质量的隐藏参数
切块大小是 RAG 质量最重要的调参点,也是最容易被忽略的。理解它,你才能在 Dify 里做出正确的配置选择:
段落缺乏上下文,检索出来的片段 AI 看不懂——答案支离破碎
Dify 默认值。一般覆盖一个完整段落,语义连贯,检索精度和上下文长度平衡
每个段落包含多个主题,检索出来的内容不够精准,且占用更多 Prompt 空间
理解检验
公司今天更新了一份产品手册,你想让知识库里的 AI 立刻用上新版本。你应该怎么做?
你用 Dify 搭建了一个基于公司文档的 QA 机器人。一个用户问了一个问题,但相关信息根本没有在任何已上传的文档里。AI 最可能会怎么回答?
模型层:厂商无关的统一接口
为什么换一个 AI 模型只需要点几下,不需要改代码?
模型层 = USB 标准接口
USB 发明之前,每个设备都有自己的接口——打印机插头、键盘插头、鼠标插头各不相同,换设备就要换接口。USB 统一了标准,任何设备插进去都能用。
Dify 的模型层做的是完全一样的事:不管是 OpenAI、Anthropic、百度千帆还是本地 Ollama,Dify 的工作流代码都用同一套接口调用——底层细节被屏蔽掉了。
OpenAI / Azure
GPT-4o, GPT-4, o1,最广泛使用的商业模型
Anthropic
Claude 3.5 Sonnet,长上下文、代码和分析能力强
国产模型
通义千问、GLM、文心一言——数据合规、国内延迟低
本地部署
Ollama / LM Studio——数据不出内网,适合高合规要求
代码翻译:ModelInstance 统一模型实例
api/core/model_manager.py 里的 ModelInstance 就是那个"USB 接口"——工作流调用它,不需要知道底层是什么模型:
class ModelInstance:
def __init__(
self,
provider_model_bundle,
model: str,
credentials: dict | None = None
) -> None:
self.provider =
provider_model_bundle
.configuration.provider.provider
if credentials is None:
credentials =
self._fetch_credentials_from_bundle(
provider_model_bundle, model
)
定义"模型实例"类——代表一个准备好被调用的 AI 模型
构造方法:创建实例时调用
(self 是实例自身)
模型的"套餐"——包含厂商配置、认证信息、负载均衡设置
具体的模型名称,比如 "gpt-4o" 或 "claude-3-5-sonnet"
API 密钥,如果不传就自动从数据库读取
记录这个实例用的是哪家厂商(openai / anthropic / xxx)
如果没有传入密钥
就从数据库(provider_model_bundle)里取出
该厂商为该模型配置的 API 密钥
生产级特性:负载均衡 & 限流保护
只配一个 API Key 在生产中会很快触发 限流。Dify 内置了两个生产级保护机制:
配置多个 API Key,Dify 自动在它们之间轮转请求——把总配额叠加起来
遇到 429/503 等错误,自动等待后重试;如果主模型持续失败,切换到备用模型
Demo 阶段一个 Key 够用。但产品上线后如果有 100 个并发用户,单个 OpenAI Key 的每分钟限额(TPM)会瞬间打满。在 Dify 里提前配置多 Key 负载均衡,避免上线即崩。
不止 LLM:Dify 管理的 5 种模型
很多人以为 Dify 只管理聊天模型,其实它统一管理了 5 种不同用途的模型——每种对应不同的业务场景:
LLM
大语言模型——对话、生成、推理(GPT-4o 等)
Embedding
文本向量化——用于 RAG 知识库的索引和检索(text-embedding-3 等)
Reranker
重排序模型——对检索结果二次打分,提升 RAG 精准度
TTS / ASR
语音合成 / 语音识别——文字转语音、语音转文字
Moderation
内容审核——过滤有害内容,保护产品安全
理解检验
你用 GPT-4o 搭建了一套工作流,现在想切换到 Claude 3.5 试试效果。需要改多少代码?
你在 Dify 里建了一个知识库,上传了 100 份文档。文档内容被转成向量存储时,用的是哪种模型?
调试生产:当事情出错时
学会读懂错误信号,从 AI 的 bug 循环中脱身
Dify 应用的三类故障
大多数 Dify 应用故障都可以归入三个大类。知道类型,就知道去哪里查:
回答跑题、不遵循格式、产生幻觉——根源是 Prompt 设计或上下文不足
超时、503、429 限流——根源是配置、并发或 API 配额问题
节点卡住、变量为空、循环不终止——根源是流程设计或数据格式不匹配
Dify 在 api/logs/ 下记录了所有请求。Docker 部署时用 docker logs dify-api 实时查看。日志里有 stack trace,能精确定位出错位置。
排查对话:一个真实的调试流程
你的工作流突然开始返回 500 错误。跟着这个排查流程走:
代码翻译:错误处理架构
api/core/app/task_pipeline/based_generate_task_pipeline.py 里的错误处理展示了 Dify 如何把底层错误转成对用户友好的信息:
def handle_error(self, *, event, ...):
e = event.error
if isinstance(
e, InvokeAuthorizationError
):
err = InvokeAuthorizationError(
"Incorrect API key provided"
)
elif isinstance(e, InvokeError):
err = e
else:
description = getattr(
e, "description", None)
err = Exception(description)
定义处理错误的方法,* 表示后面的参数必须用名字传入
从事件对象里取出原始错误
判断错误类型——isinstance 检查"这个错误是不是某种类型"
如果是 API 密钥认证失败
包装成统一的认证错误,附上用户能看懂的提示语
如果是其他 LLM 调用错误(超时、限流等)
直接把原始错误往上传——保留完整信息给日志
如果是未知类型的错误
尝试读取 description 字段(如果有的话)
用 description 包装成通用 Exception 传给上层
找 Bug 挑战
下面这段工作流配置有一个常见问题——导致 LLM 节点总是返回空内容。你能找出来吗?
找出问题:这个工作流为什么总输出空字符串?
nodes:
- type: llm
model: gpt-4o
prompt: "请总结以下内容:{{user_input}}"
output_var: result
- type: answer
content: "{{result}}"
三条调试黄金法则
这三条法则能帮你系统性地解决 90% 的 Dify 应用问题:
工作流复杂时,用 Dify 的"单步调试"模式——每个节点执行后暂停,查看输入输出是否符合预期,再继续
LLM 输出为空?先检查输入变量有没有值。输入变量为空?再往上查是哪个节点没有正确输出——沿着数据流回溯
AI 乱答?90% 的情况是 System Prompt 不够明确。先在 Prompt 里加上"你是…你只能回答…你必须按以下格式输出"