尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Dify 第5课:Dify 架构设计深挖

Dify 第5课:Dify 架构设计深挖
📅 发布时间:2026/6/21 6:23:08

这节课不讲怎么点按钮,讲底层怎么跑的。面试被问"如果让你设计一个企业级 LLM 应用平台",这部分就是你的答案。


一、工作流引擎设计

DAG vs 顺序链路

大多数人的认知:工作流就是"上一步做完下一步"。但 Dify 不一样。

Dify 的工作流是 DAG(有向无环图)引擎,不是顺序执行器。

顺序链路(n8n 那种): A → B → C → D 每次只走一条线,串行。 DAG 引擎(Dify 的): ┌─ B ─┐ A ─┤ ├─ D └─ C ┘ A 跑完,B 和 C 可以同时跑,都跑完了才进 D。

面试考点:Dify 怎么判断一个节点能跑了?

答:每个节点有一个dependencies列表(前置节点 ID 列表)。只有所有 dependencies 的状态都是succeeded,这个节点才会被调度器放进执行队列。

并行执行策略

Dify 的并行不是真的多线程,而是异步并发:

  1. 工作流引擎扫描 DAG,找出所有"就绪节点"
  2. 把它们同时提交给 LLM / 工具执行
  3. 一个节点完成后,重新扫描 DAG,找新的就绪节点
  4. 重复直到所有节点完成或出错

所以它的吞吐瓶颈在哪?

  • LLM 调用(最慢,几百毫秒到几秒)
  • 外部 API 调用(网络延迟)
  • 代码执行节点(如果写在 Python 里的计算很重)

节点级重试/超时

每个节点可以独立配置:

  • retry_times:失败重试次数
  • retry_interval:重试间隔(秒)
  • timeout:超时时间(秒)

面试考点:一个节点超时了,整个工作流怎么处理?

默认策略:节点超时 → 标记为 failed → 如果有 error_handle 分支就走分支 → 没有就整个工作流失败

所以好的工作流设计一定是:关键节点配重试 + 容错分支兜底。


二、变量系统实现

Dify 的变量系统是面试高频坑,很多人搞混。

三种变量类型

类型作用域生命周期
环境变量(env)全局,所有应用共享永久,写在配置里
会话变量(conversation_vars)单次会话内从对话开始到结束
节点变量(node_vars)当前节点内节点执行完可能就释放了

变量注入机制

Dify 是怎么把变量塞进 prompt 的?

本质是个模板引擎:

用户的问题是:{{sys.query}} 当前上下文:{{#context#}} 请根据以上信息回答。

运行时的实际过程:

  1. 工作流执行到 LLM 节点
  2. 解析 prompt 模板,提取所有的{{...}}占位符
  3. 从变量作用域链查找对应的值
  4. 替换占位符,生成最终 prompt
  5. 发给 LLM

作用域链查找顺序(重要):

节点变量 → 会话变量 → 环境变量

相同名称时,节点变量优先。

变量传递的坑

面试题:A 节点的输出怎么传给 C 节点?

Dify 的做法:节点输出自动写入一个全局的nodes_outputs字典。其他节点通过{{nodes.A.output}}引用。

但注意:这不是真正的数据流传递,是共享状态。如果 B 和 C 都读了 A 的输出,它们读到的是同一个值。


三、RAG Pipeline 完整链路

这节和第 3 课有重叠,但第 5 课关注的是每一层的设计考虑。

文档上传 │ ├─ 预处理:格式解析 → 清洗 → 分段 │ - PDF/Word/HTML 各自有解析器 │ - 分段策略:段落、N 元、滑动窗口 │ - 分段大小:默认 500 tokens,可配 │ ├─ Embedding(异步任务) │ - Celery worker 异步执行 │ - 每批 16/32 条,减少 API 调用次数 │ - 失败重试:3 次,递增退避 │ ├─ 写入向量数据库 │ - 支持的引擎:Weaviate / Qdrant / Milvus / PostgreSQL(pgvector) │ - 写入完成后标记索引状态为 completed │ └─ 检索时 - 用户 query 也做 embedding - 向量检索 + 全文检索(BM25) - 混合检索后重排序(Rerank) - Top-K 截断(默认 3-5 条)

面试考点:为什么 Embedding 要用 Celery 异步执行?

因为 Embedding 是 I/O 密集型(调 API)且可能很慢(大批量文档)。同步执行会阻塞 Web 进程。Dify 用 Celery + Redis 做任务队列,API 只负责提交任务,Worker 后台慢慢处理。


四、Agent Function Calling 执行流程

Dify 的 Agent 执行过程,从源码角度看是这样的:

用户输入 │ ├─ 1. 构建 system prompt │ - 工具描述列表(name + description + parameters) │ - Agent 策略指令(ReAct / FC) │ - 历史对话摘要 │ ├─ 2. 调用 LLM │ - 如果是 FC 模式,模型决定是否调工具 │ - 如果是 ReAct 模式,模型输出 Thought + Action │ ├─ 3. 解析 LLM 输出 │ - FC:解析 tool_calls 字段 │ - ReAct:正则匹配 Action: xxx 和 Action Input: xxx │ ├─ 4. 执行工具 │ - 验证参数(JSON Schema) │ - 执行工具代码 │ - 获取结果 │ ├─ 5. 结果反馈给 LLM │ - FC:以 tool_result 消息返回 │ - ReAct:以 Observation 文本返回 │ ├─ 6. 再次调用 LLM(如果有必要) │ - 检查迭代次数是否超限 │ - 没超 → 回到第 2 步 │ - 超了 → 生成最终回复 │ └─ 7. 返回最终结果给用户

关键词:工具的 JSON Schema 验证发生在哪一步?

第 4 步。不是 LLM 决定调你就调,Dify 会校验参数格式。参数不符合 Schema 会返回校验错误,LLM 收到错误后修正参数重新调用。


五、多租户与权限设计

Dify 是 SaaS 产品,多租户是必考题。

隔离层级

层级隔离策略数据范围
工作空间(Workspace)逻辑隔离,数据库用 tenant_id 字段区分所有数据
应用(App)工作空间内隔离,可以被分享应用配置 + 数据
知识库(Dataset)应用级隔离文档 + 向量数据
API Key应用级,前端/后端 API 区分调用次数 + 限额

数据隔离实现

最简单但也最有效:所有表加一个 tenant_id 列,查询时 where tenant_id = xxx。

SELECT*FROMdocumentsWHEREtenant_id='workspace_123'ANDdataset_id='ds_456'

Dify 的 Middleware 层自动注入这个过滤条件,应用层代码不需要关心谁在访问。

API Key 管理

  • 每个应用可以生成多个 API Key
  • 分App Secret Key(后端使用,有管理权限)和App Public Key(前端 SDK 使用,只读)
  • 支持设置过期时间、IP 白名单、速率限制

第5课总结

模块一句话面试话术
工作流引擎“Dify 基于 DAG 调度,节点有 dependencies 依赖检查,就绪节点异步并发执行”
变量系统“三层作用域:节点→会话→环境,模板引擎注入,作用域链查找”
RAG Pipeline“异步 Embedding(Celery)+ 三段检索(向量+全文+重排序)”
Agent FC 流程“7 步闭环:构建 prompt→调 LLM→解析→执行→反馈→循环→返回”
多租户“逻辑隔离,tenant_id 列 + Middleware 自动注入”

好,先小测,再更新记录。


Dify 第5课课后小测(5 题)

1. Dify 的工作流引擎是顺序执行还是 DAG 执行?如果一个节点有两个前置节点,其中一个失败了,这个节点还会执行吗?

2. 变量作用域查找顺序是什么?如果一个节点变量和环境变量同名,谁优先?

3. Dify 的 RAG Pipeline 中,Embedding 为什么用 Celery 异步执行而不是同步执行?

4. Agent Function Calling 执行流程中,工具参数校验发生在什么时候?是 LLM 说了算还是 Dify 会校验?

5. 多租户隔离的核心实现手段是什么?只用一个 tenant_id 列够不够?


1. 工作流引擎:顺序还是 DAG?

DAG(有向无环图)执行。

Dify 的 Workflow Engine 在启动时先做拓扑排序,生成 DAG,然后按依赖关系逐层推进。

两个前置节点,一个失败了,这个节点还执行吗?

默认不执行。Dify 的节点依赖关系是AND 合并语义——所有前置节点都完成(status=success),下游节点才会触发。只要有一个前置 failure,下游节点会被标记为skipped,不执行。

不过 Dify 提供了错误分支(failure branch)机制:你可以在节点上显式配置错误处理策略(继续/终止/降级),这时候即使前置失败了也能按你设定的路径走下去。这是通过 Edge 的error_handling字段控制的。


2. 变量作用域查找顺序

严格来说,Dify 变量是通过前缀区分的,不存在「同名冲突」的模糊情况:

引用方式变量类型作用域
{{节点名.变量名}}节点输出变量该节点及下游可见
{{env.变量名}}环境变量整个应用
{{sys.变量名}}系统变量(时间/用户ID等)全局
{{conversation.变量名}}对话变量对话生命周期

所以如果你的节点变量叫name,环境变量也叫name,各走各的引用路径——{{node1.name}}优先匹配节点输出,{{env.name}}匹配环境变量,不存在模糊查找顺序问题。

但如果在Code 节点(Python/JS)内部编程访问变量,作用域是:

  1. 局部变量(代码内部定义)>
  2. 前置节点注入变量 >
  3. 环境变量

代码节点里如果手动定义了跟注入变量同名的局部变量,局部变量会覆盖。


3. Embedding 为什么用 Celery 异步执行?

一句话:不是不能同步,而是不应该同步。

对比同步执行Celery 异步
用户体验用户 HTTP 请求挂起几秒甚至几十秒秒级返回「任务已提交」,前端轮询进度
容错网络抖动直接让请求失败Celery 自带重试机制 + 死信队列
资源控制Worker 进程被单个请求占死可以通过 Celery concurrency 控制并发度
批量处理单次请求处理所有 chunk,OOM 风险一个文档拆成多个 task 分布式处理
水平扩展加机器没用,模型 API 调用是同步阻塞的加 Worker 节点直接线性提升吞吐

关键点:Embedding 调用外部 API,一次调用可能几百毫秒到几秒。如果一个文档有 100 个 chunk,同步执行一个 HTTP 请求就要等 100 次 API 往返。用 Celery 可以把每个 chunk 的 embedding 分散到多个 worker 并行处理,吞吐量提升 10x 以上。


4. Agent Function Calling 工具参数校验时机

LLM 先出参数,Dify 再校验。时序是这样的:

1. LLM 输出 function_call(含 parameters 参数 JSON) 2. Dify 解析 parameters JSON 3. Dify 拿着 JSON Schema(OpenAPI spec)做参数校验 ✅ ← 这里 4. 校验通过 → 执行工具 5. 校验失败 → 把错误信息包装回 LLM(告诉 LLM「参数不对,重新生成」)

所以答案是:LLM 说了不算,Dify 会校验。而且它是"先校验后执行"。具体来说:

  • 参数校验用的是工具定义时的JSON Schema(required、type、enum、pattern等约束)
  • 校验失败时,Dify 不会直接给用户报错,而是会发回给 LLM让 TA 重试修正
  • 但如果反复失败超过max_iteration限制,Agent 才会抛出错误

面试加分点:虽然 Dify 会校验参数类型和结构,但它不做参数值的语义校验——比如传了个「河南省」给一个要求 city_code 的工具,Dify 只看 city_code 是不是 string 类型,不会检查省名是否合法。这是留给 Tool 内部逻辑的事情。


5. 多租户隔离的核心手段

一句话:多层级隔离,光靠一个 tenant_id 列远远不够。

Dify 的租户隔离是分层叠加的:

数据层(只看 tenant_id 够不够?)

隔离层级Dify 实现只靠 tenant_id 够?
应用/工作流SELECT * FROM workflows WHERE tenant_id = :tid+ middleware 自动注入✅ 够
对话记录WHERE tenant_id = :tid+ middleware 自动注入✅ 够
知识库文档WHERE tenant_id = :tid⚠️ 勉强够
向量检索每个 tenant 使用独立的Collection/Index❌不够!

真正的完整隔离策略:

1. 关系型数据隔离 └─ tenant_id 列 + middleware 强制注入 ── 所有查询自动 WHERE tenant_id = xxx 2. API Key 隔离 └─ 每个应用有自己的 app_id + API Key └─ 不要用万能 API Key 3. 向量数据库隔离(最容易出问题的地方) └─ 每个租户独立 Collection/Index └─ 原因:向量检索 API 不总是支持 WHERE tenant_id 过滤 └─ 即使用 pgvector,加 tenant_id 过滤也会影响 Recall 精度 4. 文件存储隔离 └─ 每个租户独立 Bucket 或前缀目录 5. 模型/额度隔离 └─ 租户级别的 Rate Limiter + Quota 控制

面试结论:光靠 tenant_id 列,在关系型数据层面够用(配合 middleware 自动注入),但在向量数据库层面不够,因为向量检索的 filter(元数据过滤)不是所有向量数据库都可靠。Dify 的做法是关系型数据用 tenant_id 列 + 中间件过滤,向量数据库用独立 Collection,双重保障。


相关新闻

  • MiniCPM-o 4.5本地部署实战:4.5B轻量模型+Gradio工业落地指南
  • 双曲嵌入技术与混合检索框架在生物医学本体中的应用
  • Kinovea视频分析软件:三步掌握专业运动分析的完整指南

最新新闻

  • ControlFoley:统一可控的视频到音频生成框架,解决跨模态冲突
  • Hanime1Plugin完整指南:如何在Android设备上实现纯净观影体验
  • 终极Windows驱动管理指南:DriverStore Explorer完整使用教程
  • PNX2015视频解码芯片寄存器配置实战:从时序到ITU656流生成
  • Linux 系统编程 · 第 34 章:定时器与时间
  • 飞思卡尔TWR-MCF51MM开发板硬件配置与实战指南

日新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号