← 返回文章列表

Dify 插件开发实验(10):自定义节点扩展——不改平台代码,插件如何补节点能力?

1. 业务场景

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

客服工单 SaaS 的工单编号有一套业务规则:DTF-YYYYMMDD-序号(序号每日从 001 递增,跨天归零)。比如今天第一张工单是 DTF-20260805-001,第二张是 DTF-20260805-002,明天又从 DTF-20260806-001 开始。这个编号是工单的对外门面——客户报修、财务对账、跨系统引用都靠它。

我们第一次接这类需求时,第一反应也是「编号嘛,代码节点里写个计数器不就行了」。真正动手才发现——代码节点每次运行相互独立,根本记不住「今天发到第几个号了」;让 LLM 生成编号,既可能重复也可能跑偏。我们最初想做「自定义工作流节点」插件,实测 1.16.1 daemon 根本没有 extensions 节点类型——平台不支持,只能找等效路径。

这不是个例。任何业务系统都有自己的编号规则:订单号、发票号、流水号、审批单号——「前缀 + 日期 + 当日自增序号」是最常见的形态,而「跨运行有状态」恰恰是普通工作流节点最难满足的需求。

2. 场景痛点

这个流程的痛点,在编号生成时体现得最直接:

本质上,编号生成是「有状态 + 确定性」的需求——现有节点凑不出,需要的是一个「拖入即用、跨运行有状态」的能力。

3. 方案:为什么先探边界再选等效路径

选这个方案,我们实际对比过:

这篇文章我们就用它搭一个「工单编号生成器」:工具插件 dify106_10_ticket_no_tool(generate_ticket_no + KV 序列),跑通「当日自增、跨天归零、跨运行有状态」的完整链路,并实测平台边界给出结论。

4. 整体架构

graph TD subgraph expected["【预期方案(平台不支持)】"] canvas["工作流画布"] --> custom["自定义节点「工单编号生成器」(extension 类型)"] --> panel["出现在节点面板"] end subgraph actual["【等效方案(实测)】"] start["开始(date/prefix 可选)"] --> gen["工具节点 generate_ticket_no"] --> out["输出 ticket_no"] --> end1["end"] gen --> inner["工具内部:读 KV ticket_seq_YYYYMMDD → seq+1 → 写回 → 返回 {ticket_no, date, sequence}"] end

链路很清晰:收日期/前缀 → 读当日 KV 计数 → 自增 → 写回 → 返回编号。关键设计是「日期键」——KV 键里带日期,换键即重计,跨天归零是天然行为,不是额外逻辑。

5. 模块设计

5.1 工具参数声明(tools/generate_ticket_no.yaml)

date 与 prefix 都是可选参数,默认值在实现里兜底:

parameters:

  - name: date

    type: string

    required: false

    form: llm

    llm_description: 'Date in YYYYMMDD format, e.g. 20260805, default today'

  - name: prefix

    type: string

    required: false

    form: llm

    llm_description: 'Ticket number prefix, 1-8 chars, default DTF'

5.2 日期键 + 参数校验(tools/generate_ticket_no.py)

日期键换键即重计,天然支持跨天归零:

DATE_PATTERN = re.compile(r"^\d{8}$")

PREFIX_PATTERN = re.compile(r"^[A-Za-z0-9_-]{1,8}$")

date_str = (tool_parameters.get("date") or "").strip() or datetime.now(timezone.utc).strftime("%Y%m%d")

prefix = (tool_parameters.get("prefix") or "").strip() or "DTF"

if not DATE_PATTERN.match(date_str):

    yield self.create_text_message(err("param_invalid", "date must be YYYYMMDD (e.g. 20260805)"))

    return

key = "ticket_seq_" + date_str          # 日期键:换键即归零,天然跨天

seq = read_kv_seq(kv_url, key)          # 读当日计数(KV 持久化,复用 106-05 模式)

seq += 1

write_kv_seq(kv_url, key, seq, date_str)  # 先查后写(与 106-05 同款竞态语义)

yield self.create_text_message(json.dumps(

    {"ticket_no": f"{prefix}-{date_str}-{seq:03d}", "date": date_str, "sequence": seq},

    ensure_ascii=False))

6. 运行验证

验证项 输入/场景 预期 结果
注册 安装工具插件 工具目录出现 generate_ticket_no
基本 默认参数运行 输出 DTF-20260810-001
序列递增 连续运行 3 次 001/002/003
跨天归零 date=20260806 换键重计,序号 001
并发 5 线程同日期 序号不重复 ⚠️ 全 seq 1(竞态窗口实测,预期内;生产 Redis INCR)
对照 代码节点 vs 工具插件 6 维度 差异表

与代码节点对照结论(6 维度差异表):跨运行状态——代码节点无(每次独立),工具插件 KV 持久化 ✅;复用性——代码节点每工作流复制粘贴,工具插件一次安装全局可用 ✅;可配置性——代码节点改代码,工具插件参数+凭证配置 ✅;维护性——代码节点多处副本难同步,工具插件单点更新 ✅;开发成本——代码节点低(但做不了有状态),工具插件中(插件流程已熟);可测试性——代码节点无独立测试,工具插件本地单测+冒烟 ✅。有状态需求(编号序列/计数/审批状态)是代码节点盲区——工具插件(KV 持久化)是官方支持路径下的等效解法;真·自定义节点(面板新节点类型)需等 Dify 支持节点扩展插件。

7. 实战坑

现象 修复
节点扩展不支持(核心) 1.16.1 daemon PluginExtensions 无 extensions 类型(仅六类插件);main 分支源码同样无 工具插件等效替代(generate_ticket_no + KV 序列),等 Dify 升级支持节点扩展再实测
节点接口不熟 sdk 0.7.4 workflow_node 仅支持内置节点运行时(parameter_extractor/question_classifier) 不写自定义节点;按工具插件标准结构开发,工作流用工具节点调用
本地持久化失败 节点/工具内写本地文件被沙箱拦截(104-01 实测 code 沙箱禁写) 序列计数走 KV 外部存储(复用 106-05 模式)
并发重复序号 先查后写竞态,5 线程并发全 seq 1 生产用 Redis INCR 原子自增替代
跨天不归零 若用固定键,需要显式判断日期变化才能归零 日期键设计 ticket_seq_YYYYMMDD,换键即重计,天然跨天归零

8. 实验文档及源码获取

文章聚焦核心配置与采坑点,完整分步操作与代码节点对照实验记录见实验文档原文。

联系我

15088711270

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

微信二维码

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