Dify 深度解读
“RAG 只是工具箱里的一把螺丝刀”——全栈 LLM 应用平台的事实标准
一句话定位
144K star 的全栈 LLM 应用开发平台,核心价值在于可视化工作流编排 + 模型管理 + Prompt IDE,RAG 是其众多能力模块之一,实现为 BM25 + 向量双路混合检索。
调研元信息
| 项目 | 值 |
|---|---|
| 调研日期 | 2026-06-23 |
| 仓库 | langgenius/dify |
| Stars | ~144K |
| 主语言 | Python(后端 api/) / React+TypeScript(前端 web/) |
| 代码量 | ~5,000+ 文件(前后端合计),后端核心约 2,000+ Python 文件 |
| 许可证 | Apache-2.0(含附加条款:禁止商业竞品 SaaS 托管) |
| 架构风格 | 微服务(API Server + Worker + Web + PostgreSQL + Redis + Nginx) |
| 犀牛鸟状态 | 候选竞赛项目(D 线 RAG 赛道) |
| 首次发版 | 2023-04 |
| 核心贡献者 | ~600+ |
| 主要部署方式 | Docker Compose / Kubernetes |
设计哲学
“低代码 LLM 应用工厂”
Dify 的核心哲学可以浓缩为一个词:民主化——让不会写代码的人也能搭建 LLM 应用。
类比:WeKnora 像一把手工打造的日本厨刀——锋利无比但只能切菜。Dify 像一整套西门子厨房设备——烤箱、洗碗机、灶台、冰箱都有,每个单品不一定是顶级,但组合起来能满足从早餐到宴席的全部需求。对大多数团队来说,”能快速搭建一个够用的 LLM 应用”比”检索精度多 5%”更有价值。
这个哲学体现在三个层面:
第一层:拖拽即开发。 可视化工作流编排器让产品经理也能搭建一个功能完整的 RAG 对话机器人。你拖拽定义”先检索知识库 → 判断相关度 → 高相关走精准回答分支 → 低相关走追问分支 → 最终格式化输出”这样的复杂管线,完全不写代码。
第二层:模型即配置。 一个界面统一管理 OpenAI / Anthropic / 本地模型 / 国产大模型,切换模型只需改一行配置,不需要改业务逻辑。这消除了”模型锁定”的恐惧——你今天用 GPT-4,明天想换 Claude 3.5,改个下拉框就行。
第三层:生态即壁垒。 插件市场生态是第二个护城河。社区贡献了大量 Tool、Model Provider、Extension,形成了正反馈飞轮:用户多 → 插件多 → 更多用户选择 Dify → 更多插件。这种生态效应让后来者即使技术更好也难以追赶。
设计哲学的技术代价
平台化思维带来的代价是深度让位于广度。RAG 模块实现是标准做法——BM25 + 向量双路混合,支持自定义权重,可选 Rerank 模型。没有知识图谱第三路,没有复合 Rerank 三信号设计。在面对专业领域(如医学文献、法律条文)时,精度可能不足——没有知识图谱做多跳推理,对”A 和 B 之间的关系”类问题表现较弱。
部署复杂度是另一个代价:一个完整的 Dify 实例需要 PostgreSQL + Redis + Celery Worker + API Server + Web + Nginx,六个组件协同工作。对于只想”跑一个 RAG 应用”的个人用户来说过于重了。代码量大意味着贡献门槛高——想理解 RAG 模块的工作原理需要先理解平台架构、消息队列、异步任务等基础设施。
核心架构详解
全局架构图
┌──────────────────────────────────────────────────┐
│ Dify 系统全景 │
└─────────────────────┬────────────────────────────┘
│
┌──────────────────────────────────────┼──────────────────────────────────────┐
│ │ │
┌────────▼────────┐ ┌──────────▼──────────┐ ┌──────────▼──────────┐
│ Web 前端 │ │ API Server │ │ Celery Worker │
│ (Next.js) │ │ (Flask) │ │ (异步任务执行) │
│ │ │ │ │ │
│ ┌────────────┐ │ │ ┌─────────────────┐ │ │ ┌────────────────┐ │
│ │ 工作流编排器 │ │ │ │ REST API 路由 │ │ │ │ 文档摄入任务 │ │
│ │ Workflow │ │ │ │ /v1/workflows/ │ │ │ │ Embedding 任务 │ │
│ │ Editor │ │ │ │ /v1/chat/ │ │ │ │ 数据集处理 │ │
│ └────────────┘ │ │ └─────────────────┘ │ │ └────────────────┘ │
│ ┌────────────┐ │ │ ┌─────────────────┐ │ └──────────┬──────────┘
│ │ Prompt IDE │ │ │ │ 业务逻辑层 │ │ │
│ └────────────┘ │ │ │ services/ │ │ │
└─────────────────┘ │ └─────────────────┘ │ │
│ ┌─────────────────┐ │ │
│ │ 核心引擎层 │ │ │
│ │ core/ │ │ │
│ └─────────────────┘ │ │
└──────────┬──────────┘ │
│ │
┌──────────────────────────────┼──────────────────────────────────────┐
│ │ │
┌────────▼────────┐ ┌──────────▼──────────┐ ┌──────────▼──────────┐
│ Workflow Engine │ │ RAG Pipeline │ │ Model Runtime │
│ (工作流引擎) │ │ (RAG 管线) │ │ (模型抽象层) │
│ │ │ │ │ │
│ DSL 解析 │ │ 文档摄入 │ │ 100+ 模型适配 │
│ 节点执行 │ │ 分块策略 │ │ OpenAI / Anthropic │
│ 分支路由 │ │ BM25 + 向量混合 │ │ 本地 / 国产模型 │
│ 错误处理 │ │ Rerank │ │ 统一接口 │
└─────────────────┘ └───────────────────────┘ └──────────────────────┘
│ │ │
│ ┌──────────▼──────────┐ ┌──────────────────────┐
│ │ Tool System │ │ Agent Framework │
│ │ (工具系统) │ │ (Agent 框架) │
│ │ │ │ │
│ │ 内建工具集 │ │ ReAct 模式 │
│ │ 自定义工具 │ │ Function Calling │
│ │ API Tool │ │ 工具编排 │
│ └───────────────────────┘ └──────────────────────┘
│
┌────────▼──────────────────────────────────────────────────────────────────────┐
│ 共享基础设施层 │
│ │
│ PostgreSQL Redis Celery Nginx
│ (数据持久化) (缓存/会话/队列) (异步任务调度) (反向代理)
└─────────────────────────────────────────────────────────────────────────────┘
架构分层说明
Dify 的架构分为五层,从上到下依次为:
第一层:前端展示层(web/)。Next.js 应用,包含工作流编排器、Prompt IDE、数据集管理界面、对话界面。工作流编排器是整个前端最复杂的组件——它需要渲染节点、处理拖拽、维护连线关系、实时预览执行结果。技术上使用 React Flow 库实现画布,Zustand 管理状态。
第二层:API 网关层(api/controllers/)。Flask 路由,负责请求验证、参数校验、限流。所有 API 遵循 RESTful 风格,版本化路径(/v1/)。API Server 是无状态的——所有状态存在 PostgreSQL 和 Redis 中,因此可以水平扩展。
第三层:业务逻辑层(api/services/)。核心业务编排——对话管理、工作流执行、数据集操作、用户权限。这一层是 Dify 最”厚”的部分,包含大量的业务规则和异常处理。
第四层:核心引擎层(api/core/)。Dify 的”引擎室”——Workflow Engine、RAG Pipeline、Model Runtime、Tool System、Agent Framework 都在这里。这一层是技术含量最高的部分,也是本精读的重点分析对象。
第五层:基础设施层。PostgreSQL 存储业务数据、Redis 做缓存和消息队列、Celery 处理异步任务、Nginx 做反向代理和负载均衡。
Workflow Engine 深度解析
Workflow Engine 是 Dify 最大的差异化特性,也是理解整个平台的钥匙。
为什么需要可视化工作流?
日常类比:想象你在工厂里造一辆汽车。你可以把所有零件扔给一个工人,让他从零开始组装(对应”单次 Prompt 调用”)。但如果制造流程复杂——先焊车架、再装发动机、然后测试、最后喷漆——你需要一条流水线,每个工位做特定的事,工位之间有明确的物料传递关系。
LLM 应用也是一样。一个”客服问答机器人”的真实流程可能包含:
- 判断用户意图(闲聊 / 咨询 / 投诉)
- 如果是咨询 → 检索知识库 → 判断检索结果质量
- 如果质量高 → 直接回答
- 如果质量低 → 追问用户澄清需求
- 如果是投诉 → 调用 CRM 系统查询用户订单 → 生成安抚话术
这个流程用纯代码写不难,但维护和迭代很难。产品经理想加一个”投诉分级”的节点,需要开发改代码、重新部署。Dify 的可视化工作流让这类调整变成拖拽操作。
DSL 格式
工作流在 Dify 中以 JSON DSL(Domain Specific Language)存储和执行。一个简化的 DSL 示例:
{
"version": "1.0",
"graph": {
"nodes": [
{
"id": "start",
"type": "start",
"position": {"x": 100, "y": 200},
"data": {
"title": "开始",
"variables": [
{"variable": "query", "type": "string", "required": true}
]
}
},
{
"id": "llm_1",
"type": "llm",
"position": {"x": 400, "y": 200},
"data": {
"title": "意图识别",
"model": {"provider": "openai", "name": "gpt-4"},
"prompt": "判断以下用户意图:闲聊/咨询/投诉\n用户输入: {{#start.query#}}",
"output_schema": {"intent": "string"}
}
},
{
"id": "if_1",
"type": "if-else",
"position": {"x": 700, "y": 200},
"data": {
"title": "意图分支",
"conditions": [
{"variable": "{{#llm_1.intent#}}", "operator": "equals", "value": "咨询"}
]
}
},
{
"id": "knowledge_1",
"type": "knowledge-retrieval",
"position": {"x": 1000, "y": 100},
"data": {
"title": "检索知识库",
"dataset_ids": ["ds_abc123"],
"query": "{{#start.query#}}",
"retrieval_mode": "hybrid",
"top_k": 5,
"score_threshold": 0.5
}
},
{
"id": "llm_2",
"type": "llm",
"position": {"x": 1300, "y": 100},
"data": {
"title": "生成回答",
"model": {"provider": "openai", "name": "gpt-4"},
"prompt": "基于以下检索结果回答用户问题:\n上下文: {{#knowledge_1.result#}}\n问题: {{#start.query#}}"
}
},
{
"id": "end",
"type": "end",
"position": {"x": 1600, "y": 200},
"data": {
"title": "结束",
"outputs": ["{{#llm_2.text#}}"]
}
}
],
"edges": [
{"source": "start", "target": "llm_1"},
{"source": "llm_1", "target": "if_1"},
{"source": "if_1", "sourceHandle": "true", "target": "knowledge_1"},
{"source": "knowledge_1", "target": "llm_2"},
{"source": "llm_2", "target": "end"}
]
}
}
DSL 的关键设计决策:
-
节点引用语法
{{#node_id.output_key#}}:大括号 + 井号包裹的变量引用,允许节点之间传递数据。这类似模板引擎(如 Jinja2 的{{ variable }}),但扩展为跨节点引用。 -
有向无环图(DAG):工作流不支持循环——一个节点的输出不能直接或间接地回到自己。这是为了避免无限循环。如果需要迭代,使用”迭代节点”(Iteration Node),它内部维护一个有限循环。
-
前端的 position 字段:DSL 中保存了节点在画布上的坐标位置。这看似”不纯粹”(DSL 应该只关心逻辑),但实际上是用户体验的必要——重载工作流时,节点需要回到上次的位置。
节点类型详解
Dify 的节点类型是其工作流表达力的核心。每种节点对应一种计算单元:
| 节点类型 | 用途 | 输入 | 输出 | 典型场景 |
|---|---|---|---|---|
| Start | 工作流入口 | 用户定义变量 | 所有变量 | 每个工作流必须有一个 |
| End | 工作流出口 | 任意变量 | 最终输出 | 每个工作流必须有一个 |
| LLM | 大模型调用 | Prompt + 模型配置 | 生成文本 + 结构化输出 | 意图识别、文本生成、摘要 |
| Knowledge Retrieval | RAG 检索 | 查询文本 + 数据集 ID | 检索到的 chunks | 知识库问答 |
| Code | 执行 Python/JS | 代码 + 输入变量 | 代码输出 | 数据转换、字符串处理 |
| HTTP Request | 外部 API 调用 | URL + Method + Body | 响应数据 | 调用 CRM/ERP 等外部系统 |
| IF-ELSE | 条件分支 | 条件表达式 | true/false 两条出边 | 意图分流、质量判断 |
| Variable Aggregator | 合并变量 | 多个同类型变量 | 合并后的变量 | 分支汇聚 |
| Variable Assigner | 变量赋值 | 原变量 + 新值 | 更新后的变量 | 循环内更新状态 |
| Iteration | 迭代循环 | 列表变量 + 子工作流 | 每次迭代的输出列表 | 批量处理文档 |
| Tool | 调用内置/自定义工具 | 工具参数 | 工具输出 | 搜索、计算、API 调用 |
| Template Transform | Jinja2 模板渲染 | 模板 + 变量 | 渲染结果 | 格式化输出 |
| Question Classifier | 问题分类 | 用户问题 | 分类标签 | 路由到不同处理分支 |
| Parameter Extractor | 参数提取 | 自然语言文本 | 结构化参数 | 从对话中提取订单号、日期 |
LLM 节点深度解析:
LLM 节点是最核心也最复杂的节点类型。它的执行流程如下:
1. 模板渲染
Prompt 模板中的 {{#node_id.key#}} 被替换为实际值
→ 生成最终 Prompt 字符串
2. 模型选择
根据 data.model 配置,从 Model Runtime 获取对应模型实例
→ 支持 100+ 模型,统一接口
3. 上下文构建
如果配置了上下文(如从 Knowledge Retrieval 节点引用),
将检索结果拼接到 Prompt 中
→ 注意:上下文长度受模型 context window 限制
4. 生成调用
调用模型 API,流式/非流式返回结果
→ 支持 function calling、JSON mode 等高级特性
5. 输出解析
如果配置了 output_schema,解析模型输出为结构化数据
→ 使用 JSON mode 或正则提取
Code 节点的安全模型:
Code 节点允许用户执行自定义 Python/JS 代码,这带来了安全风险。Dify 的做法是:
# 伪代码,基于 Dify 源码结构推导
class CodeNode:
def execute(self, code: str, inputs: dict) -> dict:
# 1. 沙箱执行
# Python 使用 RestrictedPython 做静态检查
# JS 使用 vm2 做沙箱隔离
sandbox = create_sandbox()
# 2. 资源限制
sandbox.set_timeout(10) # 最多执行 10 秒
sandbox.set_memory_limit(64) # 最多使用 64MB 内存
# 3. 可用库白名单
# Python: math, json, datetime, re, random, collections
# 不允许: os, sys, subprocess, file I/O, network
# 4. 输入输出校验
validated_inputs = self.validate_inputs(inputs)
result = sandbox.execute(code, validated_inputs)
return self.validate_outputs(result)
这种设计意味着 Code 节点不能做文件 I/O、网络请求或调用系统命令。如果需要这些能力,应该使用 HTTP Request 节点或 Tool 节点。
执行模型
Workflow Engine 的执行模型基于 DAG 拓扑排序:
1. 构建阶段
┌─ 解析 DSL → 构建 DAG(邻接表表示)
├─ 校验:无环、入口/出口节点存在、变量引用合法
└─ 确定执行顺序:Kahn 算法拓扑排序
2. 执行阶段
┌─ 从 Start 节点开始
├─ 按拓扑序依次执行每个节点
│ ├─ 单个节点内部:同步执行
│ └─ 无依赖的节点:理论上可并行(当前实现为顺序)
├─ IF-ELSE 节点:根据条件选择一条出边继续
├─ Iteration 节点:串行执行子工作流
└─ 任何节点失败 → 工作流整体失败
3. 状态管理
┌─ 每个节点执行后,输出存入变量池(Variable Pool)
├─ 变量池是一个 {node_id: {output_key: value}} 的字典
└─ 后续节点通过 {{#node_id.key#}} 从变量池取值
变量池(Variable Pool) 是执行模型的核心数据结构:
# 伪代码
class VariablePool:
def __init__(self):
self._pool = {} # {node_id: {key: value}}
def set(self, node_id: str, key: str, value: Any):
if node_id not in self._pool:
self._pool[node_id] = {}
self._pool[node_id][key] = value
def get(self, reference: str) -> Any:
# 解析 "{{#node_id.key#}}" 格式
node_id, key = parse_reference(reference)
return self._pool.get(node_id, {}).get(key)
def resolve_template(self, template: str) -> str:
# 递归替换模板中所有变量引用
return re.sub(r'\{\{#([^#]+)#\}\}', lambda m: str(self.get(m.group(1))), template)
错误处理策略
工作流的错误处理是一个容易被忽视但实际非常重要的设计:
节点级重试:LLM 节点和 HTTP Request 节点支持配置重试策略——最大重试次数、重试间隔。LLM 调用失败(如限流 429、服务端 500)时自动重试,指数退避。
工作流级失败:任何节点执行失败(重试耗尽后),整个工作流标记为 failed,错误信息记录到工作流运行日志中。用户可以在界面上看到哪个节点失败了、失败原因是什么。
分支容错:IF-ELSE 节点没有”失败”的概念——它只有 true/false 两条路径。但如果条件表达式本身无法求值(如引用了不存在节点的输出),则 IF-ELSE 节点失败,工作流终止。
没有 try-catch 节点:这是 Dify 工作流目前的一个设计缺口。如果想在某个节点失败时走备用逻辑(如”LLM 调用失败则返回固定话术”),当前无法在可视化层面表达,需要在节点内部处理。
RAG 管线实现
RAG 是 Dify 最常用的能力之一,但实现上是”够用就好”的标准做法。
Ingest 管线
文档从上传到可检索的完整流程:
用户上传文档
│
├─ 1. 文档解析(Extractor)
│ ├─ PDF: PyMuPDF / pdfplumber
│ ├─ Word: python-docx
│ ├─ Excel: openpyxl
│ ├─ PPT: python-pptx
│ ├─ Markdown: 直接解析
│ ├─ HTML: BeautifulSoup
│ └─ 图片: OCR(需配置)
│ → 输出:纯文本
│
├─ 2. 文本清洗
│ ├─ 去除多余空白
│ ├─ 合并断行
│ └─ 保留段落结构标记
│ → 输出:清洗后文本
│
├─ 3. 文本分块(Splitter)
│ ├─ 自动模式:按段落 + 长度自适应切分
│ ├─ 自定义模式:用户指定 chunk_size / overlap
│ └─ 分块后每个 chunk 保留元数据
│ ├─ 文档 ID
│ ├─ 来源页码/位置
│ ├─ 创建时间
│ └─ 数据集 ID
│ → 输出:chunk 列表
│
├─ 4. 向量化(Embedding)
│ ├─ 调用配置的 Embedding 模型
│ │ (如 OpenAI text-embedding-3-small)
│ ├─ 每个 chunk → 1536 维向量
│ └─ 批量调用,控制并发
│ → 输出:向量列表
│
├─ 5. 索引写入
│ ├─ 向量索引 → 写入向量数据库
│ │ (Qdrant / Weaviate / Milvus / pgvector 等)
│ ├─ 全文索引 → 写入关键词索引
│ │ (用于 BM25 检索)
│ └─ 元数据 → 写入 PostgreSQL
│ → 输出:索引就绪
│
└─ 6. 完成
文档状态标记为"已索引"
可供检索
Ingest 管线的关键实现细节:
-
异步执行:文档摄入是异步的——上传后立即返回,后台 Celery Worker 逐步处理。大型文档(100+ 页 PDF)可能需要数分钟完成。
-
断点续传:如果 ingest 过程中某个环节失败(如 Embedding API 限流),系统会记录失败位置,可以从断点继续,不需要从头开始。
-
批量优化:Embedding 调用按批次(batch)进行,减少 API 调用次数。通常 batch_size = 16 或 32。
分块策略详解
分块(Chunking)是 RAG 系统中对检索质量影响最大的环节之一。Dify 提供了三种分块模式:
模式一:自动分块
系统根据文档格式自动选择最佳策略:
| 文档格式 | 自动选择策略 | 原因 |
|---|---|---|
| Markdown | 按标题层级切分 | 标题是天然的结构边界 |
| 按段落 + 固定长度切分 | PDF 缺少结构信息 | |
| Word | 按标题 + 段落切分 | Word 有样式信息可用 |
| Excel | 按行切分 | 每行是一条记录 |
| 代码文件 | 按函数/类切分 | 代码有语法结构 |
模式二:自定义分块
用户可以指定以下参数:
chunk_size: 每个 chunk 的最大 token 数(默认 500)
chunk_overlap: 相邻 chunk 的重叠 token 数(默认 50)
separator: 切分分隔符(默认 \n\n,即段落分隔)
日常类比:切蛋糕——chunk_size 是每块的大小,chunk_overlap 是相邻两块的重叠部分。重叠确保你不会因为在切分处把一行关键信息分成两半而丢失上下文。
模式三:父子分块(Parent-Child Chunking)
这是 Dify 较新引入的分块模式,思想是:
父 chunk(大,如 1000 tokens)
├─ 子 chunk 1(小,如 200 tokens)
├─ 子 chunk 2(小,如 200 tokens)
└─ 子 chunk 3(小,如 200 tokens)
检索时:
1. 用子 chunk 做检索(粒度细,精度高)
2. 找到匹配的子 chunk 后
3. 返回其父 chunk 的完整内容给 LLM(保留上下文)
这个设计很精妙——它解决了”检索粒度”和”上下文完整性”之间的矛盾。检索时需要细粒度才能精确匹配,但 LLM 生成时需要完整上下文才能理解含义。父子分块让两者兼得。
与 WeKnora 的对比:
WeKnora 的 4 级自适应分块(文档级/章节级/段落级/语义级)更精细,特别是语义级分块使用向量相似度动态判断切分点,能处理跨段落但语义连贯的内容。Dify 的分块策略更简单——基本是固定大小 + 规则驱动,但覆盖了 80% 的实际场景需求。参见 技术挑战 — 分块困境。
Embedding 实现
Dify 的 Embedding 层通过 Model Runtime 统一抽象,支持以下 Embedding 模型:
| 模型 | 维度 | 提供商 | 适用场景 |
|---|---|---|---|
| text-embedding-3-small | 1536 | OpenAI | 通用,性价比高 |
| text-embedding-3-large | 3072 | OpenAI | 高精度需求 |
| text-embedding-ada-002 | 1536 | OpenAI | 旧版,兼容性 |
| bge-large-zh-v1.5 | 1024 | BAAI | 中文场景 |
| bge-m3 | 1024 | BAAI | 多语言 |
| Cohere embed-v3 | 1024 | Cohere | 高质量 |
| 本地模型 | 可变 | Ollama 等 | 隐私敏感 |
Embedding 调用链路:
# 伪代码,基于 Dify 源码结构推导
class EmbeddingService:
def embed_documents(self, tenant_id: str, texts: list[str],
model_config: ModelConfig) -> list[list[float]]:
# 1. 获取模型实例
model_instance = self.model_runtime.get_model_instance(
provider=model_config.provider,
model=model_config.model_name
)
# 2. 批量调用
batch_size = 16
embeddings = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i+batch_size]
result = model_instance.invoke(
texts=batch,
timeout=60
)
embeddings.extend(result.embeddings)
# 3. 维度检查
expected_dim = model_config.embedding_dim
for emb in embeddings:
assert len(emb) == expected_dim
return embeddings
检索模式详解
Dify 支持 4 种检索模式,用户可以在数据集设置中选择:
模式一:向量检索(Vector Search)
查询文本 → Embedding → 查询向量
│
▼
向量数据库做 ANN 搜索
│
▼
返回 top-K 最相似的 chunks
│
▼
过滤 score < threshold 的结果
纯向量检索,只用语义相似度衡量相关性。优点是能理解同义词和语义关联,缺点是对精确关键词不敏感。
模式二:全文检索(Full-Text Search)
查询文本 → 分词/Tokenize
│
▼
BM25 关键词检索
│
▼
返回 top-K 包含关键词的 chunks
│
▼
过滤 score < threshold 的结果
纯 BM25 检索,只用关键词匹配衡量相关性。优点是精确关键词匹配强,缺点是无法理解同义词。
模式三:混合检索(Hybrid Search) — 推荐模式
查询文本
│
├──→ 向量检索 → top-K 向量结果 (score_v)
│
├──→ BM25 检索 → top-K 关键词结果 (score_b)
│
└──→ 加权融合
│
│ Dify 融合公式:
│ final_score = α × normalize(score_v) + (1-α) × normalize(score_b)
│ 默认 α = 0.7(向量权重 70%,BM25 权重 30%)
│
▼
合并排序 → top-N 最终结果
│
▼
过滤 score < threshold 的结果
Dify 的混合检索与 WeKnora 的 RRF 融合的关键差异:
| 对比维度 | Dify 混合检索 | WeKnora RRF 融合 |
|---|---|---|
| 融合方法 | 分数加权(线性组合) | 排名融合(RRF) |
| 归一化 | min-max 归一化 | 不需要归一化(只用排名) |
| 权重调整 | α 参数(0-1) | k=60 + 各路权重 |
| 信号路数 | 2 路(向量 + BM25) | 3 路(向量 + BM25 + 图谱) |
| 对极端值鲁棒性 | 差(归一化方式影响大) | 好(排名天然归一化) |
Dify 使用分数加权而非 RRF 的原因可能是实现简单——分数加权只需要一行公式,而 RRF 需要从各路检索中获取排名信息。但在实际效果上,RRF 在多路融合中通常表现更稳定(参见 赛道深度分析 中 RRF 对比部分)。
模式四:自定义检索
通过 API 直接传入 chunks,跳过 Dify 的检索管线。适用于用户已有自己的检索系统,只需要用 Dify 的对话和生成能力。
Rerank 实现
Dify 的 Rerank 是可选的——在混合检索之后叠加一个 Cross-Encoder 模型做精排:
混合检索 top-N 结果
│
▼
Cross-Encoder Rerank
(可选模型:Cohere Rerank / bge-reranker / 自定义)
│
▼
对每个 (query, chunk) 对重新打分
│
▼
按新分数重新排序 → top-K 最终结果
Cross-Encoder vs Bi-Encoder:
日常类比:Bi-Encoder(Embedding 检索用的模型)像是一个评分员只看简历(文档)不看岗位描述(查询),给每份简历一个通用评分。Cross-Encoder 像是评分员同时拿着简历和岗位描述,仔细对照每一项要求来打分。后者更准但更慢。
技术差异:
Bi-Encoder(检索阶段):
Embed(query) → q_vec
Embed(doc) → d_vec
score = cosine(q_vec, d_vec) # 只算向量距离,快
Cross-Encoder(Rerank 阶段):
score = Model([query; doc]) # query 和 doc 一起送入模型,慢但准
Cross-Encoder 更准因为它让 query 和 doc 之间有了”交互”——模型可以理解”这个查询中的’它’指的是文档中的哪个实体”这种跨文本的语义关联。Bi-Encoder 无法做到这一点,因为它把 query 和 doc 分别编码成独立的向量。
Dify Rerank 的局限:只有一个信号(模型分数),没有 WeKnora 的三信号复合设计(0.6 模型 + 0.3 基础 + 0.1 来源可信度)。在面对来源质量参差不齐的知识库时,可能把高排名的低质量内容排在前面。
模型抽象层
Model Runtime 是 Dify 隐藏最深但价值最大的架构层。
为什么需要模型抽象层?
日常类比:你有一个万能遥控器,按”频道1”看新闻,按”频道2”看电影。你不需要知道频道1是央视还是卫视,频道2是 HBO 还是 Netflix——你只需要按按钮。模型抽象层就是这个万能遥控器——上层业务代码只说”我要用 GPT-4”,不需要关心 GPT-4 的 API 格式、计费方式、限流策略。
没有模型抽象层时,每个调用 LLM 的地方都需要:
- 硬编码 API endpoint 和 key
- 处理不同模型的请求格式差异
- 分别处理不同模型的错误码和重试策略
- 手动管理 token 计费
有模型抽象层后:
- 所有模型通过统一接口调用
- 配置一次,全局可用
- 模型切换不需要改业务代码
架构设计
┌─────────────────────────────────────────────┐
│ 上层业务(Workflow / Chat / RAG) │
└────────────────────┬────────────────────────┘
│
│ 统一接口调用
▼
┌─────────────────────────────────────────────┐
│ Model Runtime Manager │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Model │ │ Provider │ │
│ │ Instance │ │ Factory │ │
│ │ (运行时实例) │ │ (提供商工厂) │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ │ ┌───────────┼───────────┐ │
│ │ │ │ │ │
│ ┌──────▼────▼───┐ ┌──────▼──────┐ ┌──▼─────────┐
│ │ OpenAI │ │ Anthropic │ │ 本地模型 │
│ │ Provider │ │ Provider │ │ Provider │
│ └───────────────┘ └─────────────┘ └────────────┘
│ ┌───────────────┐ ┌─────────────┐ ┌────────────┐
│ │ 通义千问 │ │ 文心一言 │ │ Ollama │
│ │ Provider │ │ Provider │ │ Provider │
│ └───────────────┘ └─────────────┘ └────────────┘
└─────────────────────────────────────────────┘
Provider 接口设计
每个模型提供商(Provider)必须实现以下接口:
# 伪代码,基于 Dify 源码结构推导
class ModelProvider(ABC):
"""模型提供商基类"""
@abstractmethod
def validate_credentials(self, credentials: dict) -> bool:
"""验证 API 凭证是否有效"""
pass
@abstractmethod
def get_models(self) -> list[ModelInfo]:
"""返回该提供商支持的所有模型"""
pass
class LLMModel(ABC):
"""LLM 模型基类"""
@abstractmethod
def invoke(self, model: str, prompt: str,
parameters: LLMParameters) -> LLMResult:
"""同步调用 LLM"""
pass
@abstractmethod
def invoke_stream(self, model: str, prompt: str,
parameters: LLMParameters) -> Iterator[LLMChunk]:
"""流式调用 LLM"""
pass
def get_num_tokens(self, model: str, text: str) -> int:
"""计算 token 数(用于预估费用和截断)"""
# 默认实现用 tiktoken,各 Provider 可覆写
return tiktoken.count(text)
class EmbeddingModel(ABC):
"""Embedding 模型基类"""
@abstractmethod
def invoke(self, model: str, texts: list[str]) -> EmbeddingResult:
"""批量 Embedding"""
pass
class RerankModel(ABC):
"""Rerank 模型基类"""
@abstractmethod
def invoke(self, model: str, query: str,
documents: list[str]) -> RerankResult:
"""对文档列表做 Rerank"""
pass
一个 Provider 的完整实现示例(OpenAI):
# 伪代码,简化展示核心逻辑
class OpenAIProvider(ModelProvider):
def validate_credentials(self, credentials: dict) -> bool:
try:
client = OpenAI(api_key=credentials["api_key"])
client.models.list() # 尝试列出模型
return True
except AuthenticationError:
return False
def get_models(self) -> list[ModelInfo]:
return [
ModelInfo("gpt-4", LLMModel, context_window=8192),
ModelInfo("gpt-4-turbo", LLMModel, context_window=128000),
ModelInfo("gpt-4o", LLMModel, context_window=128000),
ModelInfo("gpt-4o-mini", LLMModel, context_window=128000),
ModelInfo("text-embedding-3-small", EmbeddingModel, dim=1536),
ModelInfo("text-embedding-3-large", EmbeddingModel, dim=3072),
]
class OpenAILLM(LLMModel):
def invoke(self, model: str, prompt: str,
parameters: LLMParameters) -> LLMResult:
client = OpenAI(api_key=self.credentials["api_key"])
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=parameters.temperature,
max_tokens=parameters.max_tokens,
top_p=parameters.top_p,
)
return LLMResult(
text=response.choices[0].message.content,
usage=TokenUsage(
prompt_tokens=response.usage.prompt_tokens,
completion_tokens=response.usage.completion_tokens,
),
finish_reason=response.choices[0].finish_reason,
)
def invoke_stream(self, model: str, prompt: str,
parameters: LLMParameters) -> Iterator[LLMChunk]:
client = OpenAI(api_key=self.credentials["api_key"])
stream = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=parameters.temperature,
max_tokens=parameters.max_tokens,
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content:
yield LLMChunk(text=chunk.choices[0].delta.content)
模型调用的统一参数
无论底层是 OpenAI、Anthropic 还是本地模型,上层业务代码使用的参数始终是统一的:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| temperature | float | 0.7 | 控制随机性,0=确定性最高 |
| top_p | float | 1.0 | 核采样概率阈值 |
| max_tokens | int | 512 | 最大生成 token 数 |
| frequency_penalty | float | 0.0 | 重复惩罚(-2.0~2.0) |
| presence_penalty | float | 0.0 | 话题多样性惩罚 |
| stop | list[str] | [] | 停止词列表 |
Model Runtime 负责将这些统一参数映射到各 Provider 的 API 格式。例如,Anthropic 的 API 使用 max_tokens_to_sample 而非 max_tokens,Model Runtime 在底层做了转换,上层无感知。
Token 计费与管理
Dify 内建了 Token 使用量统计——每次模型调用后,token 消耗记录到数据库,按租户(tenant)和模型分类累计。管理员可以在界面上看到每个应用、每个模型的 token 消耗趋势。
每次 LLM 调用后记录:
├─ tenant_id: 租户 ID
├─ app_id: 应用 ID
├─ model: 模型名称
├─ provider: 提供商
├─ prompt_tokens: 输入 token 数
├─ completion_tokens: 输出 token 数
├─ total_tokens: 总 token 数
├─ unit_price: 单价(每 1K tokens)
├─ total_price: 总费用
└─ created_at: 调用时间
这个功能对企业用户尤其重要——LLM API 调用是按 token 计费的,没有用量监控,费用可能失控。
Agent 模式
Dify 的 Agent 框架支持两种模式:ReAct 和 Function Calling。
ReAct 模式
ReAct(Reasoning + Acting)是一种”思考-行动-观察”循环的 Agent 模式:
用户: "公司去年的营收是多少?"
Agent 思考: 我需要查找公司去年的财务数据
Agent 行动: 调用知识库检索工具,查询"公司 2025 年营收"
工具结果: "2025 年公司总营收为 12.3 亿元..."
Agent 观察: 找到了营收数据
Agent 思考: 我已经找到了答案,可以回复用户
Agent 回复: "根据公司财务数据,2025 年总营收为 12.3 亿元"
ReAct 的执行流程:
┌─────────────────────────────────────────────────────┐
│ ReAct 循环 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 思考 │───→│ 行动 │───→│ 观察 │ │
│ │ Reason │ │ Act │ │ Observe │ │
│ └─────────┘ └────┬────┘ └────┬────┘ │
│ ▲ │ │ │
│ │ ▼ │ │
│ │ ┌─────────┐ │ │
│ │ │ 工具执行 │ │ │
│ │ │ Tool │─────────┘ │
│ │ └─────────┘ │
│ │ │
│ └─────────────────────────────────────────────┘
│ │
│ 终止条件: │
│ 1. Agent 输出最终答案 │
│ 2. 达到最大迭代次数(默认 10) │
│ 3. 工具执行报错且 Agent 无法恢复 │
└─────────────────────────────────────────────────────┘
ReAct 的核心优势是透明性——你可以看到 Agent 在每一步的推理过程。这在调试和审计场景下非常有用。用户能看到 Agent “为什么”选择调用某个工具,而不是只看到最终答案。
Function Calling 模式
Function Calling 是 OpenAI 提出的一种更结构化的 Agent 模式:
用户: "公司去年的营收是多少?"
1. 用户输入 → LLM
2. LLM 返回: tool_calls=[{"name": "knowledge_search", "arguments": {"query": "2025年营收"}}]
3. 系统执行工具 → 返回结果
4. 工具结果 + 对话历史 → LLM
5. LLM 返回最终答案: "2025 年公司总营收为 12.3 亿元"
Function Calling 与 ReAct 的关键区别:
| 对比维度 | ReAct | Function Calling |
|---|---|---|
| 推理方式 | 文本形式的”思考” | 结构化的 JSON 参数 |
| 工具选择 | LLM 自己决定调用哪个工具 | LLM 通过 function_call 指定 |
| 可读性 | 高(思考过程可读) | 中(JSON 格式可读但不如自然语言) |
| 可靠性 | 中(LLM 可能输出格式错误的思考) | 高(JSON 格式由模型保证) |
| 并行工具调用 | 不支持 | 支持(一次返回多个 tool_calls) |
| 模型支持 | 所有 LLM | 仅支持 function calling 的模型 |
Dify 同时支持两种模式,用户可以在应用设置中选择。对于 OpenAI GPT-4 / Claude 3.5 等支持 Function Calling 的模型,推荐使用 Function Calling 模式;对于不支持 Function Calling 的模型(如某些本地模型),使用 ReAct 模式。
工具编排
Agent 的能力边界取决于它能调用哪些工具。Dify 的工具系统分为三层:
第一层:内置工具
Dify 预装了多种常用工具:
| 工具类别 | 具体工具 | 用途 |
|---|---|---|
| 搜索 | Google Search / Bing Search / DuckDuckGo | 联网搜索 |
| 天气 | OpenWeatherMap | 天气查询 |
| 数学 | Calculator / WolframAlpha | 计算和数学推理 |
| 代码 | Code Interpreter | 执行 Python 代码 |
| 知识库 | Knowledge Retrieval | RAG 检索 |
| 图片 | DALL-E / Stable Diffusion | 图片生成 |
第二层:API 工具
用户可以通过 OpenAPI/Swagger 规范导入任何 REST API 作为工具:
# 示例:导入一个 CRM API 作为工具
openapi: 3.0.0
info:
title: CRM API
paths:
/customers/{id}:
get:
summary: 获取客户信息
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
200:
description: 客户详情
导入后,Agent 可以在对话中自动调用这个 API——”帮我查一下客户 A001 的信息”。
第三层:自定义工具
开发者可以编写 Python 插件来创建自定义工具。Dify 的插件市场(Plugin Marketplace)允许社区分享和安装自定义工具。
工具调用的安全模型:
Agent 的工具调用是有风险的——如果 Agent 决定调用”删除所有数据”的 API,后果不堪设想。Dify 的安全策略:
- 工具白名单:只有在应用设置中启用的工具才能被 Agent 调用
- 敏感操作确认:对于写操作(POST/PUT/DELETE),可以配置需要用户确认
- 沙箱执行:Code Interpreter 在沙箱中执行,不允许文件系统和网络访问
- 速率限制:对工具调用频率做限流,防止 Agent 陷入死循环
Agent 与工作流的关系
Dify 的 Agent 和 Workflow 是两种不同的应用类型,但可以组合使用:
Chatbot 类型(纯对话):
用户 → LLM → 回复
(无工具调用,无工作流)
Agent 类型(ReAct / Function Calling):
用户 → LLM → [思考 → 工具调用 → 观察] × N → 回复
(循环执行,LLM 自主决策)
Workflow 类型(可视化编排):
用户 → Start → Node1 → Node2 → ... → End → 回复
(DAG 执行,人工预定义流程)
组合模式(Workflow 中嵌入 Agent):
用户 → Start → Knowledge Retrieval → IF-ELSE
├─ 高相关 → LLM 生成回答 → End
└─ 低相关 → Agent 节点(自主决策是否追问/检索)→ End
组合模式是 Dify 最强大的能力——它允许用户在可视化工作流的某个节点上”放手”,让 Agent 自主决策,然后在下一个节点重新”接管”控制。这兼顾了工作流的可预测性和 Agent 的灵活性。参见 Agentic RAG 模式 中各种 Agent-RAG 结合模式。
与竞品的差异化矩阵
| 能力维度 | Dify | WeKnora | RAGFlow | AnythingLLM | FastGPT |
|---|---|---|---|---|---|
| 检索路数 | 2 路(BM25+向量) | 3 路(BM25+向量+图谱) | 2 路(BM25+向量) | 1 路(向量) | 1 路(向量) |
| 融合策略 | 分数加权 | RRF(k=60) | 权重融合 | 无 | 无 |
| 知识图谱 | 无原生支持 | PMI 自动构建 | 有(实验性) | 无 | 无 |
| Rerank | 单模型 Rerank | 三信号复合 | 单模型 Rerank | 无 | 单模型 Rerank |
| 分块策略 | 自动/自定义/父子 | 4 级自适应 | 模板驱动 | 固定大小 | 固定大小 |
| 工作流编排 | 可视化 DSL | 无 | 可视化(简单) | 无 | 可视化 |
| Agent 模式 | ReAct + Function Calling | ReAct | ReAct | 无 | ReAct |
| 模型管理 | 100+ 模型统一接口 | 有限 | 有限 | 30+ 适配器 | 有限 |
| 插件生态 | 丰富(Plugin Marketplace) | EventManager + Plugin | 有限 | 无 | 有限 |
| Prompt IDE | 完整 | 无 | 无 | 基础 | 基础 |
| 部署复杂度 | 高(6 组件) | 中高 | 中 | 低(单文件) | 中 |
| 向量库支持 | 4-5 种 | 10 种 | 3-4 种 | 11 种 | 2-3 种 |
| 开发语言 | Python + TypeScript | Go | Python | Node.js | TypeScript |
| Stars | ~144K | ~14.3K | ~83K | ~61K | ~28K |
| 代码量 | ~5000+ 文件 | ~800+ 文件 | ~1500+ 文件 | ~500+ 文件 | ~300+ 文件 |
Dify 的核心差异化总结:
- 生态最大:144K star + 600+ 贡献者 + 丰富的插件市场 = 后来者难以追赶的网络效应
- 工作流最完整:可视化 DSL 支持复杂分支/循环/错误处理,这是其他项目没有的
- 模型管理最强:100+ 模型统一接口 + Token 计费监控,企业级需求覆盖最全
- 检索深度不是最强:双路混合 + 单模型 Rerank,不如 WeKnora 三路 + 复合 Rerank
选型建议(详见 RAG 平台选型决策树):
- 需要快速搭建 LLM 应用 → Dify
- 需要极致检索质量 → WeKnora
- 需要深度文档解析 → RAGFlow
- 需要本地部署/隐私 → AnythingLLM
- 需要企业多租户 → FastGPT
源码关键实体索引
Python 后端核心模块
| 模块路径 | 核心功能 | 关键类/函数 | 精读优先级 |
|---|---|---|---|
api/core/workflow/ |
工作流引擎 | WorkflowEngine, Graph, Node, VariablePool |
★★★★★ |
api/core/workflow/nodes/ |
工作流节点实现 | LLMNode, CodeNode, IfElseNode, KnowledgeRetrievalNode |
★★★★★ |
api/core/rag/ |
RAG 管线入口 | RetrievalService, DatasetService |
★★★★ |
api/core/rag/retrieval/ |
检索策略 | VectorRetriever, KeywordRetriever, HybridRetriever |
★★★★ |
api/core/rag/splitter/ |
文档分块 | FixedSplitter, RecursiveSplitter, AutoSplitter |
★★★ |
api/core/rag/extractor/ |
文档解析 | PDFExtractor, WordExtractor, MarkdownExtractor |
★★★ |
api/core/rag/datasource/ |
数据源连接器 | QdrantDatasource, WeaviateDatasource, MilvusDatasource |
★★★ |
api/core/model_runtime/ |
模型抽象层 | ModelRuntimeFactory, ModelProvider, LLMModel |
★★★★ |
api/core/model_runtime/model_providers/ |
各模型提供商实现 | OpenAIProvider, AnthropicProvider, OllamaProvider |
★★★ |
api/core/tools/ |
工具调用框架 | ToolEngine, ToolProvider, ApiTool |
★★★ |
api/core/agent/ |
Agent 框架 | ReActAgent, FunctionCallingAgent |
★★★★ |
api/services/ |
业务逻辑层 | ConversationService, DatasetService, AppService |
★★ |
api/models/ |
数据模型 | Conversation, Message, Document, Dataset |
★★ |
api/controllers/ |
API 路由 | ChatApi, WorkflowApi, DatasetApi |
★ |
TypeScript 前端核心模块
| 模块路径 | 核心功能 | 精读优先级 |
|---|---|---|
web/app/components/workflow/ |
工作流编排器 | ★★★★ |
web/app/components/base/chat/ |
对话界面 | ★★ |
web/app/components/dataset/ |
数据集管理界面 | ★★ |
web/app/components/tools/ |
工具管理界面 | ★★ |
关键配置文件
| 文件 | 用途 |
|---|---|
docker/docker-compose.yaml |
全栈部署编排(6 服务) |
api/core/model_runtime/model_providers/*/provider.yaml |
各 Provider 的模型列表和参数定义 |
api/configs/feature/__init__.py |
功能开关(哪些功能启用/禁用) |
竞赛贡献切入点
Dify 作为犀牛鸟竞赛的候选项目,以下方向具有较高的贡献价值和可行性:
方向一:RAG 检索质量增强(高优先级)
问题:Dify 当前使用简单的分数加权做混合检索融合,在专业领域场景下精度不足。
建议贡献:
- 引入 RRF 融合替代分数加权,提升混合检索稳定性
- 在 Rerank 阶段引入多信号复合(参考 WeKnora 的 0.6/0.3/0.1 设计)
- 支持自定义 Rerank 权重,让用户根据领域特征调优
技术路径:修改 api/core/rag/retrieval/hybrid.py 中的融合逻辑,新增 RRFFusion 类。
关联:参见 RAG 评估方法论 中 RAGAS 四维评估如何衡量这个改进的效果。
方向二:语义级分块策略(中高优先级)
问题:Dify 的分块策略以固定大小和规则驱动为主,缺少基于语义相似度的动态分块。
建议贡献:
- 实现语义级分块:相邻句子向量相似度低于阈值时切分
- 支持自定义分块策略插件:用户可以编写自己的分块逻辑
- 分块质量评估:加入分块后的信息完整性检查
技术路径:在 api/core/rag/splitter/ 下新增 SemanticSplitter 类,利用 Model Runtime 的 Embedding 能力计算句子间相似度。
关联:参见 技术挑战 中”分块 one-size-fits-all 困境”部分。
方向三:工作流 Agent 增强(中优先级)
问题:当前 Workflow 中的 Agent 节点功能有限,无法支持复杂的多轮推理。
建议贡献:
- 在工作流中支持”Agent 子工作流”:Agent 可以调用另一个工作流作为工具
- 支持工作流级 try-catch:某个节点失败时走备用分支
- Agent 的记忆持久化:跨对话保持 Agent 状态
技术路径:扩展 Workflow Engine 的 DSL,新增 SubWorkflowNode 和 TryCatchNode 类型。
关联:参见 Agentic RAG 模式 中 6 种 RAG-Agent 结合模式;参见 Memory vs RAG 边界 中 Agent 记忆与 RAG 知识的分工。
方向四:评估框架集成(中优先级)
问题:Dify 缺少内置的 RAG 评估能力——用户无法量化检索和生成质量。
建议贡献:
- 在数据集管理界面加入”评估”功能:自动生成测试问题 + 参考答案
- 集成 RAGAS 四维评估:忠实度 / 答案相关性 / 上下文精确度 / 上下文召回率
- 评估结果可视化:热力图展示哪些类型的查询表现差
技术路径:新增 api/services/eval_service.py,使用 Model Runtime 调用 LLM 做 RAGAS 评估计算。
关联:参见 RAG 评估方法论 中 RAGAS 四指标详解。
方向五:知识图谱检索路(低优先级,高难度)
问题:Dify 没有知识图谱第三路检索,面对多跳推理问题时精度不足。
建议贡献:
- 实现基于 PMI 的实体关系自动抽取
- 新增知识图谱存储和查询模块
- 在混合检索中集成图谱检索路
技术路径:在 api/core/rag/retrieval/ 下新增 GraphRetriever 类,依赖图数据库(如 Neo4j)存储实体关系。
关联:这个方向在 WeKnora 中已有成熟实现,参见 WeKnora 精读中的知识图谱部分。不建议在 Dify 中重复造轮子,而是考虑通过 Plugin 形式集成 WeKnora 的图谱能力。参见 Agent 与 RAG 平台集成 中 RAG 即服务的集成模式。
交叉引用
| 相关页面 | 关系 |
|---|---|
| RAG 知识库全景 | Dify 在 5 项目比较中的定位 |
| 赛道深度分析 | 检索策略、融合方式的跨项目比较 |
| 技术挑战 | 分块困境、检索最后一公里等共性难题 |
| Agentic RAG 模式 | 6 种 RAG 模式与 Dify Agent 的结合 |
| RAG 评估方法论 | RAGAS 评估如何应用于 Dify |
| Agent 与 RAG 平台集成 | Dify 作为 RAG 即服务的集成模式 |
| 选型决策树 | 什么场景选 Dify |
| Memory vs RAG 边界 | D 线(RAG)与 B 线(Memory)的分工,Agent 记忆不在 Dify 范围内 |
| 腾讯记忆生态 | Dify 在 DB-Agent-Memory-WeKnora 生态中的可能位置 |
学习建议
源码阅读路线
如果你是第一次接触 Dify 的源码,建议按以下顺序阅读:
第一阶段:理解全局(1-2 天)
- 读
docker/docker-compose.yaml—— 理解 6 个服务的角色和依赖关系 - 读
api/controllers/中的几个关键路由 —— 理解 API 层怎么分发请求 - 读
api/models/中的核心数据模型 —— 理解数据是怎么存的
第二阶段:理解核心引擎(3-5 天)
- 读
api/core/workflow/graph/graph.py—— 工作流的 DAG 表示和拓扑排序 - 读
api/core/workflow/nodes/llm/llm_node.py—— LLM 节点的完整执行流程 - 读
api/core/workflow/nodes/knowledge_retrieval/—— RAG 检索如何在工作流中被调用 - 读
api/core/rag/retrieval/—— BM25 + 向量混合检索的实现 - 读
api/core/model_runtime/model_runtime_factory.py—— 模型工厂如何创建 Provider 实例
第三阶段:理解高级特性(2-3 天)
- 读
api/core/agent/—— ReAct 和 Function Calling 的实现 - 读
api/core/tools/—— 工具注册和调用流程 - 读
api/core/rag/splitter/—— 各种分块策略的实现
第四阶段:前端编排器(2-3 天)
- 读
web/app/components/workflow/—— 工作流编排器的 React 实现
调试技巧
- 本地启动:
docker compose up -d一键启动所有服务。修改代码后只需重启对应容器。 - 工作流调试:在界面上创建工作流后,点击”运行”可以看到每个节点的输入输出。这比看日志直观得多。
- API 调试:所有 API 都有 Swagger 文档,访问
/docs查看。可以用 curl 或 Postman 直接调用。 - 日志查看:
docker logs dify-api-1 -f实时查看 API Server 日志。Worker 日志用docker logs dify-worker-1 -f。
常见坑
- Embedding 维度不一致:换 Embedding 模型后,已有数据集的向量维度可能和新模型不匹配,需要重建索引。
- Redis 队列积压:Celery Worker 如果处理不过来,Redis 队列会积压。监控
celery_queue_length指标。 - 模型超时:某些模型(特别是本地模型)响应慢,需要调整
MODEL_TIMEOUT配置。 - PostgreSQL 连接池:高并发下连接池可能耗尽,需要调整
SQLALCHEMY_POOL_SIZE。
补充:Dify 与 Memory 的边界
本精读聚焦 D 线(RAG 赛道)。关于 Agent 记忆(B 线)的内容,不在本篇覆盖范围内——Dify 本身不提供 Agent 长期记忆能力(跨会话的状态持久化、用户偏好学习等),它处理的是外部知识的检索和生成,不是交互经验的积累和复用。
两者的边界判断:
| 维度 | Dify(RAG / D 线) | Memory(B 线) |
|---|---|---|
| 知识来源 | 外部文档(用户上传) | 交互经验(对话中学习) |
| 更新方式 | 文档重新摄入 | 自动从对话中提取 |
| 持久性 | 文档不变则知识不变 | 随交互持续演进 |
| 典型问题 | “检索结果不相关” | “Agent 记不住用户偏好” |
| 代表项目 | Dify / WeKnora / RAGFlow | DB-Agent-Memory / MemGPT |
如果竞赛方向涉及”Agent 如何结合 RAG 和 Memory 两种能力”,参见 Memory vs RAG 边界 和 腾讯记忆生态 中的详细分析。本篇不展开 B 线内容,避免重复。