应用类型总览:一套引擎,五种"应用长相"
Dify 界面上创建应用时让你选"聊天助手 / Agent / 文本生成 / 工作流 / Chatflow"。这些在源码里就是 core/app/apps/ 下五个并列的包:chat、agent_chat、completion、workflow、advanced_chat。今天弄清:①它们由一个 AppMode 枚举统一标识;②每个包都长得很像(Generator + Runner + ConfigManager + ResponseConverter 四件套);③它们其实分成"会话式"和"工作流式"两大家族。搞懂这张分类表,Day04 走请求旅程就不会迷路。
AppMode 枚举就像菜单上的商品编号——收银台(AppGenerateService)扫一眼编号,就知道该走哪条制作线。没有这个编号,后厨就不知道该做哪款。痛点:为什么不是"一个应用类"打天下
if app_type == ... 的意大利面条。AppMode 挑对应的类,剩下的交给多态——每个类自己知道怎么干。这样新增一种应用,只要加一个包、在分发处加一个分支,不用去改别人的代码。这是"开闭原则"的经典落地。AppMode:五种类型的"身份证"
所有类型由一个枚举统一标识(api/models/model.py:364):
# api/models/model.py:364
class AppMode(StrEnum):
COMPLETION = "completion" # 文本生成(一问一答,无记忆)
WORKFLOW = "workflow" # 工作流(跑流程图,非对话)
CHAT = "chat" # 聊天助手(多轮对话,有记忆)
ADVANCED_CHAT = "advanced-chat" # Chatflow(对话 + 工作流 混合)
AGENT_CHAT = "agent-chat" # Agent 对话(能自己调工具循环)
AGENT = "agent" # 新版 Agent 应用(基于 Dify Agent 运行时)
CHANNEL = "channel"
RAG_PIPELINE = "rag-pipeline"
@classmethod
def value_of(cls, value: str) -> "AppMode":
for mode in cls:
if mode.value == value:
return mode
raise ValueError(f"invalid mode value {value}")
StrEnum它继承字符串枚举,所以 AppMode.CHAT == "chat" 成立——数据库里 App.mode 存的就是 "chat" 这种字符串,取出来能直接和枚举比。五个主类型本教程大纲说的五种应用就是前五个:completion / workflow / chat / advanced-chat / agent-chat。它们各自对应 apps/ 下一个包(L03)。value_of()字符串转枚举的工具方法:从请求或数据库拿到 "chat" 字符串,用它转成 AppMode.CHAT,认不出就直接报错(不静默)。Day04 的 controller 就用它。AGENT / CHANNEL / RAG_PIPELINE较新或特殊的类型(新版 Agent 应用、渠道、RAG 流水线),本教程聚焦前五个经典类型,这几个了解即可。AGENT 是"基于 Dify Agent 运行时的新应用类型",和老的 agent-chat(ReAct 风格)不同。这解释了为什么会有 apps/agent_chat/ 和 apps/agent_app/ 两个目录并存。apps/ 目录:每种类型一个"长相一致"的包
ls api/core/app/apps/ 真实输出(节选),能看到五种类型是并列的子目录:
# api/core/app/apps/
chat/ # 聊天助手
agent_chat/ # Agent 对话
completion/ # 文本生成
workflow/ # 工作流
advanced_chat/ # Chatflow(高级对话)
base_app_generator.py # 所有 Generator 的基类
base_app_runner.py # 所有 Runner 的基类
base_app_queue_manager.py # 事件队列基类
message_based_app_generator.py # 会话式应用的 Generator 基类
随便点开一个类型(如 chat/),会发现四件套结构(ls api/core/app/apps/chat/):
# api/core/app/apps/chat/
app_generator.py # Generator:组装请求 + 起线程 + 返回响应
app_runner.py # Runner:在后台线程里真正调模型
app_config_manager.py # ConfigManager:把应用配置解析成运行时配置
generate_response_converter.py # ResponseConverter:把内部事件转成对外格式
app_generator.py入口类,如 ChatAppGenerator(api/core/app/apps/chat/app_generator.py:36)。负责"接请求参数、组装成生成实体、起后台线程跑 Runner、把响应流式返回"。Day04 精读它。app_runner.py如 ChatAppRunner(api/core/app/apps/chat/app_runner.py:28),在后台线程里真正干活:组织提示词、审核、调模型、把结果塞进队列。app_config_manager.py把用户在界面上的配置(用哪个模型、什么提示词、开不开检索)解析成运行时能用的 AppConfig。generate_response_converter.py把内部统一的事件,转成该入口需要的格式(比如 service_api 和 web 的响应字段略有不同)。workflow / advanced_chat 多一个文件这两类目录里还有 generate_task_pipeline.py(ls 可见),因为工作流的流式输出更复杂——它要吐"节点开始/结束"等事件,需要专门的 pipeline。这正是 L05 说的"两大家族"的分野。app_generator.py / app_runner.py,像重复。但它们都继承自公共基类:Generator 继承 BaseAppGenerator(api/core/app/apps/base_app_generator.py:61)或更具体的 MessageBasedAppGenerator,Runner 继承 AppRunner(api/core/app/apps/base_app_runner.py:55)。共性(解析用户输入、审核、组织提示词)在基类里写一份,个性(各类型独有的流程)在子类里覆写。这是"模板方法模式":骨架公用、步骤定制,不是复制粘贴。代价是要读懂继承链才知道完整逻辑,收益是共性代码零重复。共享骨架:Generator 与 Runner 的分工
五种类型共享同一套"两段式"骨架。看 ChatAppGenerator 的继承与 ChatAppRunner 的入口签名,就能理解分工:
# api/core/app/apps/chat/app_generator.py:36
class ChatAppGenerator(MessageBasedAppGenerator):
def generate(self, session, app_model, user, args, invoke_from, streaming=True):
# 组装请求、起后台线程、返回响应流(Day04 逐行看)
...
# api/core/app/apps/chat/app_runner.py:28
class ChatAppRunner(AppRunner):
def run(self, session, application_generate_entity, queue_manager, conversation, message):
# 后台线程里真正干活:组织提示词 → 审核 → 调模型 → 事件入队
...
Generator.generate()"前台接单":解析参数、建对话/消息记录、初始化队列,然后另起一个线程让 Runner 去跑,自己立刻返回一个"响应流"给调用方。所以用户能马上开始收到流式输出。Runner.run()"后厨做菜":在后台线程里组织提示词、做内容审核、调用 LLM,把每一段输出作为事件塞进队列。它不直接 return 给用户,而是"发事件"。queue_manager 连接两端Generator 起的响应流从队列读事件,Runner 往队列写事件。队列把"生产"和"消费"解耦——这正是 Day04 的核心机制。两大家族:会话式 vs 工作流式
五种类型看似并列,其实按"底层是不是跑图"分成两大家族。这从它们的 Generator 基类和目录差异能看出来:
| 应用类型 | 家族 | 特点 | Generator 基类线索 |
|---|---|---|---|
chat | 会话式 (message-based) | 多轮对话、有记忆 | MessageBasedAppGenerator |
agent_chat | 对话 + 自动调工具循环 | ||
completion | 一问一答、无记忆 | ||
workflow | 工作流式 (graph-based) | 跑流程图、非对话 | 带 generate_task_pipeline.py(专门的图事件流水线) |
advanced_chat | 对话外壳 + 内部工作流 |
generate_task_pipeline.py:workflow/ 和 advanced_chat/ 有(ls 可验证),因为它们底层跑工作流图、要吐"节点开始/结束/输出"这类图事件,需要专门的流水线;chat/agent_chat/completion/ 没有,它们共用 task_pipeline/ 里的通用 easy-ui pipeline(Day04 会遇到)。家族不同 → 流式输出的事件种类不同 → 需要的 pipeline 不同。advanced_chat 特殊在——它披着聊天的外衣,肚子里却是一张工作流图,所以归工作流家族。对号入座 + 今日小结
App 记录的 mode = "chat"。发消息时,Service 用 AppMode.value_of("chat") 得到 AppMode.CHAT,据此选中 ChatAppGenerator(api/core/app/apps/chat/app_generator.py:36)→ 它起线程跑 ChatAppRunner(api/core/app/apps/chat/app_runner.py:28)→ 用会话式通用 pipeline 流式返回。换成"工作流"应用,则 mode="workflow" → WorkflowAppGenerator → 带图事件的 generate_task_pipeline.py。一条从"UI 选择"到"具体源码文件"的清晰链路。👶 小白:agent-chat 和 chat 都能聊天,源码上最大区别在哪?
👨🏫 老师:两者都是"会话式家族"、都继承 MessageBasedAppGenerator,Generator 层几乎一样。区别在 Runner:ChatAppRunner 组织好提示词就调一次模型出结果;AgentChatAppRunner 则会进入"调模型→模型说要用工具→执行工具→把结果再喂回模型"的循环,直到模型给出最终答复。也就是说,差异主要沉在 Runner 里的"要不要循环调工具",这也是 Day17 讲 Agent 时的重点。
🧠 今天你应该能回答
- 五种经典应用类型是哪些?(chat / agent-chat / completion / workflow / advanced-chat)
- 它们由什么统一标识?(
AppMode枚举,api/models/model.py:364) - 每个类型包里的"四件套"是什么?(Generator / Runner / ConfigManager / ResponseConverter)
- Generator 和 Runner 的分工?(前台接单起线程 vs 后厨调模型发事件)
- 两大家族怎么分?(会话式 vs 工作流式;看有没有
generate_task_pipeline.py) - 为什么不用一个大类 + if 判断?(多态替代大 if,符合开闭原则;共性走基类)
✋ 10 分钟动手
cd /Users/bitmart/work/codes/github/AI_WORK/dify
# 1. 看类型枚举
sed -n '364,388p' api/models/model.py
# 2. 看五种类型是并列的包 + 每个包的四件套
ls api/core/app/apps/
ls api/core/app/apps/chat/
ls api/core/app/apps/workflow/ # 注意多了 generate_task_pipeline.py
# 3. 看继承链(家族分野)
grep -rn "class .*Generator(" api/core/app/apps/*/app_generator.py
grep -n "class BaseAppGenerator\|class AppRunner" api/core/app/apps/base_app_*.py
controllers/.../completion.py 的 ChatApi.post → services/app_generate_service.py 按 AppMode 分发 → ChatAppGenerator.generate 起线程 → ChatAppRunner 调模型 → task_pipeline 把结果 SSE 流回前端。今天的"两段式骨架"届时会真正动起来。