← 返回文章列表

Dify 插件开发实验(01):开发环境与首个工具插件——从零开发第一个 Dify 插件需要什么?

1. 业务场景

先讲一个我们实际遇到的场景。

客服工单 SaaS 的客服每天要建几十张工单:用户报障进来,客服点「创建工单」,系统要记下创建时间;排障时前后端各说各的时间,运维要拿一个基准时间去对齐日志;看板上的定时任务,还要按固定节奏触发调度。这些动作全都依赖同一个东西——一个可靠的「当前时间」。

我们第一次接这类需求时,第一反应也是「时间嘛,写个脚本取一下不就行了」。真正动手才发现——「能取到时间」和「做成一个谁都能用的标准件」是两回事:本地 Python 环境装不上 SDK、daemon 端口对不上、签名校验过不去,每一步都在提醒你——插件开发的地基不是写代码,而是先把「本地开发 → 远程安装」这条链路打通。

这不是个例。任何系统里「记录、对齐、调度」都逃不开时间戳:电商下单要打时间戳、日志平台要对齐各服务时钟、定时报表要按调度基准触发。时间这种「人人都会用、处处都要用」的能力,恰恰值得做成一个标准件——而不是每个业务各写各的。

2. 场景痛点

这个流程的痛点,在客服团队身上体现得最直接:

本质上,时间戳是系统里最基础的信息,却因为「太简单」而被每个人各自实现一遍——最该统一成标准件的能力,反而最混乱。

3. 方案:为什么是插件

选插件这条路,我们实际对比过:Dify 内置节点里没有「获取当前时间」这类基础工具,但插件机制正好补这个位。

这篇文章我们就用它搭第一个工具插件,打通插件开发的环境地基与全生命周期。

4. 整体架构

graph TD subgraph dev["本地开发机"] src["plugin 项目源码"] sdk["dify-plugin SDK"] cmd["dify plugin 命令"] pkg["产物 .difypkg"] end subgraph srv["Dify 服务器(Docker Compose)"] api["api(FastAPI)"] daemon["plugin_daemon(插件运行时,端口 5003)"] web["web(控制台:插件管理页)"] console["控制台「插件」"] db["PostgreSQL / Redis"] end cmd -- "remote debug" --> daemon pkg -- "上传安装" --> console subgraph app["验证应用(workflow,dify106_01_验证应用)"] start["开始(无输入)"] t1["工具节点 get_current_time"] out["文本输出(拼接时间戳文案)"] end1["结束"] end start --> t1 --> out --> end1

链路很清晰:本地开发 → 远程调试 → 打包上传 → 工作流消费。其中环境三件套(Python 版本/daemon 对接/签名强制)是整个链路的地基——地基不打通,后面每一步都走不动。

5. 模块设计

5.1 环境三件套(本实验核心)

  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)。
  2. daemon 对接PLUGIN_DAEMON_KEY 从 daemon 容器 env 拿(docker inspect),值为 SERVER_KEY;daemon 只监听 5003(5002 拒绝),docker 已映射,本地 127.0.0.1:5003 可达。
  3. 签名强制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: llmdescription(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. 实验文档及源码获取

文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。

联系我

15088711270

手机端点击号码可直接拨打 · 桌面端可复制

微信二维码

扫码加微信 · 备注「门户」更快通过