项目全景与前后端分包:先拿到地图,再进城
面对 Dify 这种上万文件的大项目,最怕的不是看不懂某段代码,而是不知道自己在哪。今天不读一行复杂逻辑,只做一件事:把整个项目的骨架画清楚——后端 api/ 和前端 web/ 各管什么、api/ 内部怎么分层、业务大脑 api/core/ 里各个子目录分别负责什么。读完你会有一张"心里的地图",后面 19 天每读一个文件都知道它在地图哪个格子。
api/(后端政务中心,所有决策在这)、web/(前端商业街,用户逛的门面)、api/core/(政务中心里的"核心决策部门")。类比二:api/core/ 各子目录像一家公司的部门——model_manager 是"对外采购部(管模型供应商)"、workflow 是"流程编排部"、rag 是"资料检索部"、agent 是"能自己决策的项目经理"。今天就是拿到这张组织架构图。痛点:几万个文件,到底从哪读起
ls 一看:api/、web/、docker/、sdks/、packages/、e2e/……几十个顶层目录,光 api/core/ 里就有 agent、app、workflow、rag、tools、plugin、mcp、memory、prompt 十几个子目录。直接一头扎进某个 .py 文件,读两屏就迷路了:这个类被谁调用?这个目录和那个目录什么关系?缺的不是耐心,是一张地图。先看顶层长什么样(在项目根目录执行 ls,真实输出节选):
# /Users/bitmart/work/codes/github/AI_WORK/dify/ 顶层
api/ # ← 后端(Python/Flask),本教程 90% 在这
web/ # ← 前端(Next.js)
docker/ # ← 部署编排(docker-compose 等),Day02 讲
sdks/ # 各语言 SDK(Python/Node/PHP...)
packages/ # 前端可复用包
e2e/ # 端到端测试
README.md Makefile pnpm-workspace.yaml ...
api/(后端)和 web/(前端)。其余的(docker/sdks/e2e/packages)都是"周边配套"。所以第一刀就切干净了:先只看 api/ 和 web/。顶层分工:api/(后端)与 web/(前端)
Dify 是典型的前后端分离架构。看两个目录各自的顶层结构就一目了然(真实 ls 输出节选):
# api/ —— 后端(Flask 应用)
app.py # 进程入口(Day02 细读)
app_factory.py # 应用工厂:造 Flask app + 装扩展
dify_app.py # DifyApp 类(Flask 的子类)
controllers/ # 路由层:console / web / service_api / openapi
services/ # 服务层:业务编排
core/ # ★业务大脑:模型/工作流/rag/agent/tools/...
models/ # 数据库表模型(ORM)
tasks/ # Celery 异步任务
extensions/ # 各种扩展(db/redis/celery/login...)
migrations/ # 数据库迁移
configs/ # 配置
# web/ —— 前端(Next.js App Router)
app/ # 页面路由
service/ # 调后端 API 的封装
context/ hooks/ models/ i18n/ ...
api/controllers/HTTP 路由入口。里面按"谁来调"分成 console(后台管理界面)、web(发布出去的应用页)、service_api(开放 API)、openapi。同一个功能常有三套入口,这是 Dify 的特点。api/services/服务层。controller 拿到参数后,把活交给 service 去"编排"。比如 app_generate_service.py 负责"看这是什么应用、该调哪个生成器"(Day04 主角)。api/core/★真正的业务核心。模型调用、工作流引擎、RAG、Agent、工具全在这。本教程 D05 起长期驻扎这里。api/tasks/Celery 异步任务。像"文档索引""发邮件""删应用"这种耗时活,不在请求里同步做,扔进队列后台跑(Day02、Day19 讲)。web/service/前端调后端接口的封装。前端不直接写 fetch,而是走这里统一的封装,和 controllers/ 一一呼应。api/ 内部:一次请求从上到下穿过哪几层
把 api/ 摆成"从外到内"的分层,一次请求的穿透路径就清楚了:
controllers/,就预期它没什么复杂逻辑(几十行,取参数转发);看到在 core/,就预期它是硬骨头。这个预期能帮你分配注意力——不在门卫室浪费时间。models/(数据库表定义,比如 App、Conversation、Message)、extensions/(把 db/redis/celery 等挂到 app 上,Day02 讲)、configs/(读 .env 的配置)、factories/(构造复杂对象,如文件对象)。core/ 地图:业务大脑的各个"部门"
这是本教程最该记住的一张图。api/core/ 的真实子目录(ls api/core/ 输出节选):
# api/core/ —— 业务大脑
model_manager.py # 模型实例:怎么拿到一个能调的模型(D05)
provider_manager.py # 供应商:管几十家 LLM 厂商的配置/凭据(D05)
app/ # 应用运行时:五种应用类型 + 生成器 + task_pipeline(D03/D04)
workflow/ # ★工作流引擎:节点/图/变量池(D08-12,Dify 最核心)
rag/ # 检索增强:切片/索引/检索(D13-15)
indexing_runner.py # RAG 索引流程的总调度
tools/ # 工具系统(D16)
agent/ # Agent:cot / function-calling 两种(D17)
plugin/ mcp/ # 插件与 MCP 协议扩展(D18)
memory/ # 对话记忆
prompt/ # 提示词构造(D07)
llm_generator/ # 基于 LLM 的小工具(起标题等,D07)
ops/ # 可观测/链路追踪(D19)
callback_handler/ moderation/ file/ entities/ ...
app/"应用"这层是入口枢纽:一次对话请求进 core 后,先落在 app/apps/ 里对应类型的生成器。D03 拆它的类型、D04 走它的完整流程。model_manager.py / provider_manager.py模型运行时的两根支柱。前者产出"一个能 invoke_llm() 的模型实例",后者管"这个租户配了哪些供应商、凭据是什么"。D05 精读。workflow/★Dify 的招牌能力。拖拽式工作流最终都变成这里的一张"图",由引擎逐节点执行。整个阶段3(D08-12)专攻它。rag/ + indexing_runner.py知识库背后的引擎:文档进来先被 indexing_runner 切片、向量化入库,检索时再多路召回。阶段4(D13-15)。agent/能"自己决定下一步"的智能体,两种范式:cot_agent_runner.py(思维链)和 fc_agent_runner.py(函数调用)。D17。core/ 想成一栋写字楼,每个子目录是一个部门。你现在不用懂每个部门具体怎么运作,只要记住"哪个牌子挂哪个门"。后面每天,我们就推开其中一扇门进去看。地图不是空话——给三个"真实锚点",让你现在就能翻到源码里对上号:
锚点①:后端 app 就是一个 Flask 子类(api/dify_app.py:11)——证明 api/ 是 Flask 应用。
# api/dify_app.py:11
class DifyApp(Flask):
"""Flask application type with Dify-specific extension attributes."""
login_manager: DifyLoginManager
锚点②:五种应用类型是一个枚举(api/models/model.py:364)——这就是 D03 要展开的 app/apps/ 各个子目录对应的类型。
# api/models/model.py:364
class AppMode(StrEnum):
COMPLETION = "completion"
WORKFLOW = "workflow"
CHAT = "chat"
ADVANCED_CHAT = "advanced-chat"
AGENT_CHAT = "agent-chat"
锚点③:模型运行时的"入口对象"叫 ModelInstance(api/core/model_manager.py:35)——D05 会拿它 invoke_llm()。现在只需知道"调模型从这个类开始"。
# api/core/model_manager.py:35
class ModelInstance:
"""
Model instance class.
"""
def __init__(self, provider_model_bundle: ProviderModelBundle, model: str, credentials: dict | None = None) -> None:
...
| core 子目录 | 负责什么 | 本教程哪天精读 |
|---|---|---|
app/ | 应用类型 + 请求生成 + 流式输出 | D03 / D04 |
model_manager.py / provider_manager.py | 模型运行时 | D05 / D06 |
prompt/ / llm_generator/ | 提示词与生成 | D07 |
workflow/ | 工作流引擎(核心) | D08-12 |
rag/ / indexing_runner.py | RAG 知识库 | D13-15 |
tools/ / agent/ / plugin/ / mcp/ | 工具·Agent·插件 | D16-18 |
tasks/(在 api/根) / ops/ | 异步任务与可观测 | D19 |
前端 web/ 一瞥:知道它在哪、怎么和后端对话
本教程重点是后端,但你得知道前端长什么样、怎么和后端接上。web/ 是一个 Next.js(App Router)项目(web/app 是页面、web/service 是接口封装):
# web/ 关键目录
app/ # 页面(Next.js App Router):应用列表、编辑器、对话界面
service/ # 调后端的封装:每个函数对应一个后端接口
context/ # 全局状态(当前用户、当前 workspace、模型供应商...)
hooks/ models/ i18n/ themes/ ...
web/service/ 里的每个请求,最终打到后端 api/controllers/ 里的某个路由。比如"发一条聊天消息":前端调 service → 打到后端 controllers/.../completion.py 的 ChatApi.post(Day04 你会看到这个真实入口)。所以调试一个功能时,你可以从前端 service 找到接口路径,再去 controllers 里搜同名路由,两头就接上了。建立地图 + 今日小结
👶 小白:我一定要按这个目录顺序全读完吗?感觉好多。
👨🏫 老师:不用。这张地图的价值是"随用随查"——你不用背下来,只要以后看到一个类,能快速判断"它大概在哪个层、哪个部门"就够了。真正要精读的是主链路:controllers → services → core/app → core/model_manager → core/workflow。旁支(moderation、file、callback)用到再看。先掌握主干,枝叶自然生长。
core/ 里几十个子目录就想"每个都点开看看",结果一天过去只记住一堆目录名、没建立任何联系。正确姿势:盯住"一次请求怎么走"这条线(Day04 会完整跑一遍),沿线经过的目录才深入,没经过的先跳过。地图是用来导航的,不是用来背诵的。🧠 今天你应该能回答
- Dify 顶层真正装业务的是哪两个目录?(
api/后端、web/前端) api/的分层口诀是什么?(controller 薄、service 中、core 厚)controllers/为什么分 console / web / service_api?(鉴权、限流、来源不同,核心逻辑收拢到 core)api/core/里工作流引擎在哪个目录?(workflow/,Dify 最核心)- 模型运行时的两根支柱是哪两个文件?(
model_manager.py/provider_manager.py) - 前端功能怎么和后端接口对上?(web/service 的请求 → api/controllers 的路由)
✋ 10 分钟动手
# 1. 亲眼看顶层与 api 分层
cd /Users/bitmart/work/codes/github/AI_WORK/dify
ls # 看顶层:api / web / docker / sdks ...
ls api # 看后端分层:controllers services core models tasks ...
# 2. 把 core 地图打印出来贴墙上
ls api/core # agent app workflow rag tools plugin mcp memory prompt ...
# 3. 找一条前后端对应关系(体会"遥控器→机顶盒")
grep -rn "class ChatApi" api/controllers # 后端聊天入口在哪个文件
ls web/service # 前端接口封装
api/app.py、app_factory.py、dify_app.py 和 docker/——看 Dify 是怎么用"应用工厂"造出 Flask app、按顺序装上 20 多个扩展,以及同一份代码怎么分身成 api / worker / beat 三种进程角色。