Dify 插件开发实验(01):开发环境与首个工具插件——从零开发第一个 Dify 插件需要什么?
1. 业务场景
先讲一个我们实际遇到的场景。
客服工单 SaaS 的客服每天要建几十张工单:用户报障进来,客服点「创建工单」,系统要记下创建时间;排障时前后端各说各的时间,运维要拿一个基准时间去对齐日志;看板上的定时任务,还要按固定节奏触发调度。这些动作全都依赖同一个东西——一个可靠的「当前时间」。
我们第一次接这类需求时,第一反应也是「时间嘛,写个脚本取一下不就行了」。真正动手才发现——「能取到时间」和「做成一个谁都能用的标准件」是两回事:本地 Python 环境装不上 SDK、daemon 端口对不上、签名校验过不去,每一步都在提醒你——插件开发的地基不是写代码,而是先把「本地开发 → 远程安装」这条链路打通。
这不是个例。任何系统里「记录、对齐、调度」都逃不开时间戳:电商下单要打时间戳、日志平台要对齐各服务时钟、定时报表要按调度基准触发。时间这种「人人都会用、处处都要用」的能力,恰恰值得做成一个标准件——而不是每个业务各写各的。
2. 场景痛点
这个流程的痛点,在客服团队身上体现得最直接:
- 手工填时间不可靠:建单时间靠客服手工填,填错一个字段,工单时效统计就失真,后续 SLA 考核跟着全错——时间错了,整个业务判断都建立在错误地基上。
- 时区口径混乱:服务器跑 UTC、客服在国内、客户在国外,同一张工单三个时间口径,排障时「这条记录到底几点创建的」要来回换算——换算一次,就多一次出错的机会。
- 格式五花八门:有人写「2026-08-05 15:59:50」,有人写「08/05 下午 4 点」,时间字段进了系统没法直接比较、排序——格式不统一,数据就废了一半。
- 调度基准各写各的:每个定时任务各自实现时间获取逻辑,想统一时区策略就要改遍所有任务——越分散,越没人敢动。
本质上,时间戳是系统里最基础的信息,却因为「太简单」而被每个人各自实现一遍——最该统一成标准件的能力,反而最混乱。
3. 方案:为什么是插件
选插件这条路,我们实际对比过:Dify 内置节点里没有「获取当前时间」这类基础工具,但插件机制正好补这个位。
- 平台原生扩展点:插件是 Dify 官方的一等公民,装进控制台后所有工作流/Agent 都能用,一次开发、处处受益;
- 本实验是最好的练手对象:
get_current_time无外部依赖、参数少、结果确定,正好把「脚手架 → 代码 → 本地调试 → 打包 → 安装 → 使用」全生命周期完整走一遍; - 为后续实验打地基:02/03 实验直接复用它——环境不打通,后续所有「本地开发 → 远程安装」的假设都不成立。
这篇文章我们就用它搭第一个工具插件,打通插件开发的环境地基与全生命周期。
4. 整体架构
链路很清晰:本地开发 → 远程调试 → 打包上传 → 工作流消费。其中环境三件套(Python 版本/daemon 对接/签名强制)是整个链路的地基——地基不打通,后面每一步都走不动。
5. 模块设计
5.1 环境三件套(本实验核心)
- 本地 Python 环境:Python 3.14 装不了
SDK(gevent/tiktoken 无 wheel),实测用 uv 管理 cpython-3.11.15 建
venv;SDK 用
dify-plugin 0.7.4——注意 PyPI 包名是 dify-plugin,不是 dify-plugin-sdk(后者 404)。 - daemon 对接:
PLUGIN_DAEMON_KEY从 daemon 容器 env 拿(docker inspect),值为 SERVER_KEY;daemon 只监听 5003(5002 拒绝),docker 已映射,本地 127.0.0.1:5003 可达。 - 签名强制:
FORCE_VERIFYING_SIGNATURE=true时需第三方签名方案——generate/sign 生成密钥对 + 公钥白名单部署 + compose override 加环境变量 + 重启 daemon,之后签名包才能安装。
5.2 插件声明(manifest.yaml)
name: dify106_01_time_tool
version: 0.1.0
type: plugin
icon: icon.svg
plugins:
tools:
- provider/time_tool.yaml
meta:
arch:
- amd64
- arm64
runner:
entrypoint: main
language: python
version: "3.12"5.3 工具声明(tools/get_current_time.yaml)
identity:
name: get_current_time
description:
human: 获取当前时间,可选时区(UTC、UTC+8、UTC-5、UTC+9)
llm: Get current time. Parameter timezone is optional, must be one of UTC, UTC+8, UTC-5, UTC+9. Returns JSON with time, timezone, utc_offset.
parameters:
- name: timezone
type: string
required: false
form: llm
llm_description: 'Optional timezone. Must be one of: UTC, UTC+8, UTC-5, UTC+9. Default is UTC.'
extra:
python:
source: tools/get_current_time.py注意:参数必填
form: llm;description(human/llm)是顶层字段(provider
的才在 identity 内)。
5.4 工具实现(tools/get_current_time.py 核心)
class GetCurrentTimeTool(Tool):
def _invoke(self, tool_parameters: dict[str, Any]):
tz_key = tool_parameters.get("timezone") or "UTC"
if _SUPPORTED_TZ.get(tz_key) is None:
yield self.create_text_message(json.dumps({"error": "unsupported_timezone", ...}))
return
now = datetime.now(timezone(timedelta(seconds=_SUPPORTED_TZ[tz_key])))
result = {"time": now.strftime(fmt), "timezone": tz_key,
"utc_offset": f"{_SUPPORTED_TZ[tz_key] // 3600:+d}h"}
yield self.create_text_message(json.dumps(result, ensure_ascii=False))6. 运行验证
| 输入 | 预期 | 结果 |
|---|---|---|
| workflow 运行(timezone=UTC+8) | 输出含精确到秒的当前时间戳 | 通过(实测
{"time": "2026-08-05 15:59:50", "timezone": "UTC+8", "utc_offset": "+8h"}) |
| agent 应用提问「现在几点」 | 触发 get_current_time 并回答正确 | 通过(实测两次调用均触发工具) |
| 卸载 → 重装 | 列表消失 → 重装后冒烟恢复 | 通过(uninstall 200,同 sha 复用) |
7. 实战坑
| 坑 | 现象 | 修复 |
|---|---|---|
| daemon 端口 | 5002 拒绝连接,daemon 只监听 5003 | docker 映射后本地 127.0.0.1:5003 可达(实测) |
| Python 3.14 装 SDK | gevent/tiktoken 无 wheel 装不上 | 3.11.15 venv(uv 管理)+ dify-plugin 0.7.4(实测) |
| PyPI 包名 | dify-plugin-sdk 404 | 包名是 dify-plugin;示例仓库 dify-plugin-sdks / dify-official-plugins(实测) |
| Windows CLI 下载被墙 | GitHub releases HTTP 000 | 用容器内 /app/commandline,package/signature 全有(实测) |
| icon 引用 | 打包报 tool icon not found | yaml 写 icon.svg,不带 _assets/ 前缀(实测) |
| 签名强制 | FORCE_VERIFYING_SIGNATURE=true 下签名包装不上 | 第三方签名方案:generate/sign + 公钥白名单 + compose override + 重启 daemon(实测) |
| tool yaml 字段 | 参数缺 form: llm 校验不过 | 参数必填 form: llm;description 是顶层字段(实测) |
| UI 节点空白 | 手写 DSL 缺 UI 渲染字段打开空白 | 照 UI 导出格式补 height/width/selected/desc/dragging(实测) |
8. 实验文档及源码获取
- 实验文档(完整操作步骤):DIFY-106-01:插件开发环境与首个工具插件.md
- 源码(可直接导入):dify106_01_验证应用.yml(workflow)、dify106_01_验证对话.yml(agent)
- 插件包(签名安装包,控制台上传用):dify106_01_time_tool.signed.difypkg
- 全部源码目录:dify-106/dsl
文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。
- Dify 插件开发实验(01):开发环境与首个工具插件——从零开发第一个 Dify 插件需要什么?
- Dify 插件开发实验(02):参数与凭证体系——插件参数和凭证如何声明、配置与管理?
- Dify 插件开发实验(03):工具接入工作流与Agent——插件工具如何在工作流和 Agent 中使用?
- Dify 插件开发实验(04):企业系统对接工具——如何用插件对接企业 ERP/CRM?
- Dify 插件开发实验(05):有状态与幂等——插件如何安全地保持状态和处理重复调用?
- Dify 插件开发实验(06):通知渠道插件——如何把 Dify 推送到钉钉/企业微信等渠道?
- Dify 插件开发实验(07):私有模型网关接入——如何让 Dify 用上私有模型网关?
- Dify 插件开发实验(08):外部知识库插件——如何把外部检索能力做成插件?
- Dify 插件开发实验(09):Agent策略插件——如何控制 Agent 的工具使用策略?
- Dify 插件开发实验(10):自定义节点扩展——不改平台代码,插件如何补节点能力?
- Dify 插件开发实验(11):打包分发与离线安装——插件如何打包签名、分发与离线安装?
- Dify 插件开发实验(12):企业级交付验收——插件交付如何做验收?