数据截至 (上游 commit b78a3462c9a6)
入口的另一半与外部能力:触发器与插件运行时
30 秒导读: 前面几章讲的都是"人在页面上点了运行,然后发生了什么"。这一章补两块拼图—— 谁还能启动这张图(webhook / 定时 / 第三方事件),以及图里的节点凭什么真的会干活 (工具、模型、数据源的实现全在另一个进程里,Dify 只是它的 HTTP 客户端)。
本章的 path:line 引用都相对克隆根 dify/,后端 Python 代码都在 api/ 目录下。
想读相邻内容:一次运行从 HTTP 到 SSE 见 01-request-lifecycle.md; JSON 图怎么变成可执行图、节点对象怎么构造见 03-node-assembly-graphon-boundary.md; Layer 钩子与留痕落库的通用机制见 04-execution-layers-and-tracing.md。
1. 这章解决什么问题(零基础也能懂)
先说结论:一张 Dify 工作流图,有两个外界依赖,而它们都不在图本身里。
第一个依赖:谁按下了开始。
默认答案是"用户在页面上点了运行"。但真实生产里更常见的是这三种:
| 启动方式 | 白话场景 | 对应节点类型 |
|---|---|---|
| Webhook | 别的系统往一个 URL 上 POST 一坨 JSON,工作流就跑一次 | trigger-webhook |
| 定时 | "每天早上 9 点跑一次日报" | trigger-schedule |
| 插件事件 | "Slack 有人 @我" / "GitHub 开了新 issue" 就跑一次 | trigger-plugin |
第二个依赖:节点凭什么会干活。
一个"调用 Serper 搜索"的工具节点、一个"调用 Claude"的 LLM 节点,它们的实现代码不在 Dify 仓库里。 Dify 主进程只负责把参数拼好,通过 HTTP 发给一个叫 plugin daemon(插件守护进程) 的独立服务, 再把返回的流转成节点输出。
一句话直觉:把 Dify 主进程想成一个"排程中心 + 电话总机"——排程中心接外面打进来的电话(触发器), 总机把每个节点的活儿转接给真正干活的分机(插件守护进程)。
2. 顶层全景
三条外部入口,最后都汇进同一个口子。先看这张图,它是本章前半段的骨架:
怎么读: 左边三列是三条互不相干的入口路线,中间那道横线是它们的唯一汇流点, 右边是异步执行。注意左边两条走的是"HTTP 进来 → 落 Celery 队列",只有定时那条是"Celery beat 主动醒来"。
外部世界 Dify api 进程 Celery worker
───────────── ────────────────────────── ──────────────────────
别的系统 POST ──HTTP──> /triggers/webhook/<id>
WebhookService ──> trigger_workflow_async
解析+校验 body/header/query (webhook 队列)
第三方 SaaS ──HTTP──> /triggers/plugin/<endpoint_id>
(Slack/GitHub) TriggerService.process_endpoint
①问插件"这请求算哪些事件" ──> dispatch_triggered_
②立刻回 200,剩下的丢队列 workflows_async
③再问插件"把请求翻成变量"
④trigger_workflow_async
(无) ─beat 定时─> poll_workflow_schedules
扫 next_run_at <= now ──> run_schedule_trigger
trigger_workflow_async
╔══════════════════════════════════════════════════╗
║ AsyncWorkflowService.trigger_workflow_async ║
║ 写 WorkflowTriggerLog(PENDING) + 投递执行队列 ║
╚══════════════════════════════════════════════════╝
│
v
WorkflowAppGenerator.generate(root_node_id=触发节点id,
layers=[TriggerPostLayer])
部件一句话职责:
| 部件 | 干什么 | 在哪 |
|---|---|---|
| 三个触发节点类 | 图上的可视化起点,运行时只是"把池子里的值摊开给下游" | api/core/workflow/nodes/trigger_*/ |
TriggerManager | 和插件守护进程谈订阅:列 provider、订阅、退订、刷新、调事件 | api/core/trigger/trigger_manager.py:30 |
services/trigger/* | 把一次外部 HTTP / 一次定时到点,落成一次工作流运行 | api/services/trigger/ |
TriggerPostLayer | 运行结束后回填触发日志(状态、耗时、token、输出) | api/core/app/layers/trigger_post_layer.py:24 |
core/plugin/impl/* | 对插件守护进程的一组 HTTP 客户端(工具/模型/数据源/触发器/Agent/端点/OAuth) | api/core/plugin/impl/ |
backwards_invocation/* | 反方向:插件回头调 Dify(用模型、用工具、跑节点、调 app) | api/core/plugin/backwards_invocation/ |