Dify工具插件開發(fā)和智能體開發(fā)全流程實(shí)戰(zhàn)

前言
Dify是一款開源的大語言模型應(yīng)用開發(fā)平臺(tái),旨在降低AI應(yīng)用的開發(fā)門檻,幫助開發(fā)者和企業(yè)快速構(gòu)建、部署及管理生成式AI應(yīng)用。
Dify自1.0.0引入全新插件化架構(gòu),模型(Models)與工具(Tools)遷移為插件(Plugins),引入 Agent 策略(Agent Strategies)、擴(kuò)展(Extensions)類型插件和插件集(Bundles)。通過全新的插件機(jī)制,能夠增強(qiáng) AI 應(yīng)用的感知和執(zhí)行能力,拓寬AI在軟件操作領(lǐng)域的應(yīng)用能力。
本文將介紹如下內(nèi)容:
- 搭建基于Docker的MySQL數(shù)據(jù)庫環(huán)境
 - 開發(fā)Dify工具插件實(shí)現(xiàn)MySQL數(shù)據(jù)庫操作
 - 基于Dify搭建智能體通過插件操作MySQL實(shí)現(xiàn)理財(cái)助手智能體
 
文末可獲取完整插件代碼下載地址
搭建基于Docker的MySQL數(shù)據(jù)庫環(huán)境
1) 啟動(dòng)Docker容器
- 建立docker_compose.yaml,內(nèi)容如下:
 
services:
  mysql:
    image: mysql:5.7
    container_name: mysql5.7
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_ALLOW_EMPTY_PASSWORD=yes
      - TZ=Asia/Shanghai
    volumes:
      - ./volumes:/var/lib/mysql
    command: --character-set-server=utf8mb4- 執(zhí)行
docker compose up -d啟動(dòng)數(shù)據(jù)庫 
2) 創(chuàng)建數(shù)據(jù)庫和表
- 下載MySQL客戶端軟件,例如dbeaver (https://dbeaver.io/download)
 - 連接數(shù)據(jù)庫,創(chuàng)建數(shù)據(jù)庫和表
 
create database testdb;
use testdb;
CREATE TABLE `finance` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(36) NOT NULL DEFAULT '' COMMENT '用戶ID',
  `date` datetime NOT NULL COMMENT '金額發(fā)生日期',
  `amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '收入支出金額(收入記為正數(shù),支出記為負(fù)數(shù))',
  `category` varchar(32) NOT NULL DEFAULT '' COMMENT '收支類別',
  `remark` varchar(100) NOT NULL DEFAULT '' COMMENT '收支具體類目',
  PRIMARY KEY (`id`),
  KEY `idx_user_date` (`user_id`,`date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='日常收支';開發(fā)Dify工具插件實(shí)現(xiàn)MySQL數(shù)據(jù)庫操作
以windows開發(fā)環(huán)境為例:
1) 下載插件開發(fā)腳手架工具
從https://github.com/langgenius/dify-plugin-daemon/releases下載適用于windows的dify-plugin-windows-amd64.exe。把程序所在目錄加到系統(tǒng)PATH路徑下,方便執(zhí)行命令。運(yùn)行命令查看版本信息,有輸出版本信息則說明安裝成功。

2) 創(chuàng)建項(xiàng)目
執(zhí)行命令dify-plugin-windows-amd64.exe plugin init創(chuàng)建項(xiàng)目,輸入插件名(mysql),作者和描述

按Enter確認(rèn)后,選擇python做為開發(fā)語言

按Enter確認(rèn)后,選擇插件類型,這里我們選tool

按Enter確認(rèn)后,選擇插件權(quán)限。mysql插件不需要勾選任何權(quán)限,一直按down鍵移到最后一行,然后按回車即可完成項(xiàng)目創(chuàng)建,系統(tǒng)將自動(dòng)生成插件項(xiàng)目代碼,目錄為mysql。

生成的目錄結(jié)構(gòu)如下:

3) 創(chuàng)建python虛擬環(huán)境
進(jìn)入生成的目錄mysql,修改生成的requirements.txt,添加用到的python包: mysql-connector-python。

創(chuàng)建python虛擬環(huán)境并安裝依賴包:
python -m venv .venv
# 激活環(huán)境
.\.venv\Scripts\activate
# 安裝依賴包
pip install -r requirements.txt4) 封裝數(shù)據(jù)庫功能
在tools目錄下增加db.py,通過類DbManagerSingleton封裝數(shù)據(jù)庫操作。DbManagerSingleton實(shí)現(xiàn)為單例模式,以便插件內(nèi)不同的代碼共用類的實(shí)例對象。
import json
import mysql.connector
from contextlib import contextmanager
from threading import Lock
# 單例模式
class DbManagerSingleton:
    _instance = None
    _lock = Lock()  # 線程鎖,確保線程安全
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            with cls._lock:
                if not cls._instance:
                    cls._instance = super().__new__(cls)
                    cls._instance.__init__(*args, **kwargs)
        return cls._instance
    def __init__(self, host, port, user, password, database):
        self.connection_pool = mysql.connector.pooling.MySQLConnectionPool(
            pool_name="db_pool",
            pool_size=5,
            pool_reset_sessinotallow=True,
            host=host,  # 數(shù)據(jù)庫服務(wù)器Host
            port=port,  # 數(shù)據(jù)庫服務(wù)器端口
            user=user,  # 數(shù)據(jù)庫用戶名
            password=password,  # 數(shù)據(jù)庫密碼
            database=database,  # 數(shù)據(jù)庫名
        )
    @contextmanager
    def get_cursor(self):
        with self.connection_pool.get_connection() as connection:
            cursor = None
            try:
                cursor = connection.cursor()
                yield cursor
                connection.commit()
            except Exception as e:
                connection.rollback()
                raise e
            finally:
                if cursor:
                    cursor.close()
    def execute_sql(self, sql: str) -> str:
        with self.get_cursor() as cursor:
            cursor.execute(sql)
            if cursor.description is not None:
                rows = cursor.fetchall()
                result = {
                    "columns": [desc[0] for desc in cursor.description],
                    "rows": rows,
                }
                return json.dumps(result, default=str)
            else:
                return f"row affected:{cursor.rowcount}"5) 實(shí)現(xiàn)授權(quán)配置
修改provider/mysql.yaml。其中,credentials_for_provider的信息用于配置插件授權(quán)(配置數(shù)據(jù)庫連接相關(guān)信息)。內(nèi)容如下:
identity:
  author: testuser
  name: mysql
  label:
    en_US: mysql
    zh_Hans: mysql
  description:
    en_US: mysql tools
    zh_Hans: mysql tools
  icon: icon.svg
tools: # 插件包含的工具列表
  - tools/get_table_definition.yaml
  - tools/execute_sql.yaml
extra:
  python:
    source: provider/mysql.py
credentials_for_provider:
  host: # 數(shù)據(jù)庫HOST
    type: text-input # 輸入類型為普通文本
    required: true # 此憑證是必需的
    label:  # 在 Dify UI 中顯示的標(biāo)簽 (支持多語言)
        en_US: MySQL Server Host
        zh_Hans: MySQL Server主機(jī)
  port: # 數(shù)據(jù)庫端口
    type: text-input # 輸入類型為普通文本
    required: true # 此憑證是必需的
    label: # 在 Dify UI 中顯示的標(biāo)簽 (支持多語言)
        en_US: MySQL Server Port
        zh_Hans: MySQL Server端口
  user: # 數(shù)據(jù)庫用戶名
    type: text-input # 輸入類型為普通文本
    required: true # 此憑證是必需的
    label: # 在 Dify UI 中顯示的標(biāo)簽 (支持多語言)
        en_US: user name
        zh_Hans: 用戶名
  password: # 數(shù)據(jù)庫密碼
    type: secret-input # 輸入類型為密碼框
    required: true # 此憑證是必需的
    label: # 在 Dify UI 中顯示的標(biāo)簽 (支持多語言)
        en_US: password
        zh_Hans: 密碼
  database: # 數(shù)據(jù)庫名
    type: text-input # 輸入類型為普通文本
    required: true # 此憑證是必需的
    label: # 在 Dify UI 中顯示的標(biāo)簽 (支持多語言)
        en_US: database name
        zh_Hans: 數(shù)據(jù)庫名修改provider/mysql.py,實(shí)現(xiàn)配置校驗(yàn),通過建立連接執(zhí)行show tables驗(yàn)證參數(shù)是否正確。代碼如下:
from typing import Any
from dify_plugin import ToolProvider
from dify_plugin.errors.tool import ToolProviderCredentialValidationError
class MysqlProvider(ToolProvider):
    def _validate_credentials(self, credentials: dict[str, Any]) -> None:
        try:
            """
            IMPLEMENT YOUR VALIDATION HERE
            """
            from tools.db import DbManagerSingleton
            dbManager = DbManagerSingleton(
                host=credentials["host"],
                port=credentials["port"],
                user=credentials["user"],
                password=credentials["password"],
                database=credentials["database"],
            )
            dbManager.execute_sql("show tables")
        except Exception as e:
            raise ToolProviderCredentialValidationError(str(e))6) 實(shí)現(xiàn)工具get_table_definition獲取表結(jié)構(gòu)定義
provider/mysql.yaml的tools字段定義了插件包含的工具列表。
tools: # 插件包含的工具列表
  - tools/get_table_definition.yaml
  - tools/execute_sql.yaml每個(gè)工具需要一個(gè)yaml文件進(jìn)行描述,包含工具的名稱、描述、參數(shù)列表等。
將自動(dòng)生成的tools目錄下的mysql.yaml和mysql.py分別重命名為get_table_definition.yaml和get_table_definition.py
get_table_definition.yaml修改為如下內(nèi)容:
identity:
  name: get_table_definition
  author: testuser
  label: # 在 Dify UI 中顯示的工具名稱 (多語言)
    en_US: get database table definition
    zh_Hans: 獲取數(shù)據(jù)庫表定義
description:
  human: # 給人類用戶看的工具描述 (多語言)
    en_US: get database table definition
    zh_Hans: 獲取數(shù)據(jù)庫表定義
  llm: get database table definition # 給 LLM 看的工具描述 (用于 Agent 模式) 
parameters: # 定義工具的輸入?yún)?shù)列表
  - name: table
    type: string
    required: true
    label: # 在 Dify UI 中顯示的參數(shù)標(biāo)簽 (多語言)
      en_US: database table name
      zh_Hans: 數(shù)據(jù)庫表名
    human_description: # 給人類用戶看的參數(shù)描述 (多語言)
      en_US: database table name
      zh_Hans: 數(shù)據(jù)庫表名
    llm_description: database table name # 給 LLM 看的參數(shù)描述 (指導(dǎo) Agent 如何填充)
    form: llm # 參數(shù)表單類型 ('llm' 或 'form')
extra:
  python:
    source: tools/get_table_definition.pyget_table_definition.py修改為如下內(nèi)容:
from collections.abc import Generator
from typing import Any
from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
from tools.db import DbManagerSingleton
class GetTableDefinitionTool(Tool):
    def __init__(self, runtime, session):
        super().__init__(runtime, session)
        self.dbManager = DbManagerSingleton(
            host=runtime.credentials["host"],
            port=runtime.credentials["port"],
            user=runtime.credentials["user"],
            password=runtime.credentials["password"],
            database=runtime.credentials["database"],
        )
    def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
        table = tool_parameters["table"]
        sql = f"show create table {table}"
        yield self.create_text_message(self.dbManager.execute_sql(sql))7) 實(shí)現(xiàn)工具execute_sql執(zhí)行SQL語句
在tools目錄新建execute_sql.yaml和execute_sql.py
execute_sql.yaml修改為如下內(nèi)容:
identity:
  name: execute_sql
  author: testuser
  label: # 在 Dify UI 中顯示的工具名稱 (多語言)
    en_US: execute sql
    zh_Hans: 執(zhí)行sql語句
description:
  human: # 給人類用戶看的工具描述 (多語言)
    en_US: execute sql
    zh_Hans: 執(zhí)行sql語句
  llm: execute sql # 給 LLM 看的工具描述 (用于 Agent 模式) 
parameters: # 定義工具的輸入?yún)?shù)列表
  - name: sql
    type: string
    required: true
    label:  # 在 Dify UI 中顯示的參數(shù)標(biāo)簽 (多語言)
      en_US: sql
      zh_Hans: sql語句
    human_description: # 給人類用戶看的參數(shù)描述 (多語言)
      en_US: the sql to execute
      zh_Hans: 要執(zhí)行的sql語句
    llm_description: sql # 給 LLM 看的參數(shù)描述 (指導(dǎo) Agent 如何填充)
    form: llm # 參數(shù)表單類型 ('llm' 或 'form')
extra:
  python:
    source: tools/execute_sql.pyexecute_sql.py修改為如下內(nèi)容:
from collections.abc import Generator
from typing import Any
from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
from tools.db import DbManagerSingleton
class ExecuteSqlTool(Tool):
    def __init__(self, runtime, session):
        super().__init__(runtime, session)
        self.dbManager = DbManagerSingleton(
            host=runtime.credentials["host"],
            port=runtime.credentials["port"],
            user=runtime.credentials["user"],
            password=runtime.credentials["password"],
            database=runtime.credentials["database"],
        )
    def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
        sql = tool_parameters["sql"]
        yield self.create_text_message(self.dbManager.execute_sql(sql))8) 修改manifest.yaml
manifest.yaml定義了插件最基礎(chǔ)的信息,包括插件名稱、作者、包含的工具、模型等信息。
本插件雖然沒用到storage持久化存儲(chǔ)的權(quán)限,但需要將storage里的size字段從0改為大于等于1024,否則啟動(dòng)插件時(shí)會(huì)報(bào)錯(cuò)。
storage:
      enabled: false
      size: 10249) 調(diào)試
在Dify的插件管理頁面,點(diǎn)擊圖中紅框部分,彈出調(diào)試的URL和Key。

復(fù)制.env.example到.env,修改REMOTE_INSTALL_HOST和REMOTE_INSTALL_KEY。

執(zhí)行命令python main.py啟動(dòng)插件,等待至顯示"dify_plugin.plugin:Installed tool",工具安裝成功。

此時(shí),在Dify的插件管理頁面可以看到mysql插件。選擇mysql插件,在右側(cè)點(diǎn)擊“去授權(quán)”

填上相關(guān)參數(shù)并保存。

新建測試Agent,添加mysql插件的兩個(gè)工具,模型選擇doubao-1.5-pro-32k,模型會(huì)根據(jù)用戶提問自動(dòng)調(diào)用數(shù)據(jù)庫工具,并根據(jù)工具的響應(yīng)生成回復(fù)。效果如下圖:

10) 打包
確認(rèn)插件能夠正常運(yùn)行后,可以通過以下命令行工具打包插件,生成mysql.difypkg。
dify-plugin-windows-amd64.exe plugin package mysql
11) 發(fā)布
- 發(fā)布到Dify Marketplace參考: https://docs.dify.ai/zh-hans/plugins/publish-plugins/publish-to-dify-marketplace
 - 發(fā)布到個(gè)人GitHub倉庫參考: https://docs.dify.ai/zh-hans/plugins/publish-plugins/publish-plugin-on-personal-github-repo
 - 本地發(fā)布與分享
 
在Dify的插件管理頁面,點(diǎn)擊“安裝插件”=>“本地插件”。

選擇打包生成的mysql.difypkg,會(huì)提示簽名錯(cuò)誤。

需要修改docker/.env,將FORCE_VERIFYING_SIGNATURE改為false,然后重建docker。修改該字段后,Dify平臺(tái)將允許安裝所有未在Dify Marketplace上架(審核)的插件,可能存在安全隱患。
docker compose down
docker compose up -ddocker重建后,重新安裝本地插件。

基于Dify搭建智能體通過插件操作MySQL實(shí)現(xiàn)理財(cái)助手智能體
1) 安裝Agent策略插件
點(diǎn)擊右上角“插件”按鈕,進(jìn)入插件頁面,選擇“探索Marketplace“。
選擇插件Dify Agent 策略進(jìn)行安裝。

2) 創(chuàng)建應(yīng)用
- 創(chuàng)建一個(gè)空白應(yīng)用,類型為Chatflow。
 

- 調(diào)整工作流,把默認(rèn)的LLM節(jié)點(diǎn)替換為Agent節(jié)點(diǎn)。
 

- 設(shè)置Agent節(jié)點(diǎn)的Agent策略,并添加MySQL工具策略選擇Function Calling。
 

- Agent節(jié)點(diǎn)的模型選擇doubao-1.5-pro-32k
 - 設(shè)置Agent節(jié)點(diǎn)的指令(系統(tǒng)提示詞)
 
# 角色
你是記賬助手,可以通過調(diào)用數(shù)據(jù)庫工具完成記錄日常收入和支出并作分析。
為了完成記賬操作,需要先獲取數(shù)據(jù)庫表finance的定義。
記賬的用戶ID取值為{{#sys.user_id#}}
# 收支類別
收入:工資薪金,勞務(wù)報(bào)酬,投資收益,分紅收入,租金收入,其它收入
支出:住房,交通,通訊,保險(xiǎn),餐飲,電子產(chǎn)品,日用品,服飾,旅行,娛樂,醫(yī)療,學(xué)習(xí),其它支出
# 技能
## 技能1:記錄日常開支
將開支信息記錄到數(shù)據(jù)庫表finance
## 技能2:統(tǒng)計(jì)日常開支
根據(jù)用戶輸入信息分析統(tǒng)計(jì)日常開支
# 限制
僅處理記賬相關(guān)問題,不回復(fù)其它問題- 設(shè)置Agent節(jié)點(diǎn)的查詢和最大迭代次數(shù)Agent完成一項(xiàng)任務(wù)可能需要迭代多次調(diào)用工具,最大迭代次數(shù)設(shè)置過小可能導(dǎo)致無法正常完成任務(wù)。
 

- 預(yù)覽調(diào)試輸入“昨天吃飯用了50元,還花了35元買了拖鞋。今天買手機(jī)花了2999元,吃飯花了60元”,驗(yàn)證輸出為成功記錄支出。
 

另外,通過數(shù)據(jù)庫表驗(yàn)證數(shù)據(jù)正常插入。

輸入“匯總各個(gè)類別的金額”,驗(yàn)證數(shù)據(jù)查詢。

確認(rèn)無誤后,點(diǎn)擊右上角的“發(fā)布”按鈕發(fā)布應(yīng)用。
總結(jié)
本文以實(shí)現(xiàn)MySQL數(shù)據(jù)庫操作插件詳細(xì)介紹開發(fā)Dify工具插件的全流程,并使用該插件搭建理財(cái)智能體,展示了Agent從語義理解到工具調(diào)用的完整決策鏈路。
跟MCP工具插件開發(fā)比較,Dify工具插件開發(fā)步驟相對復(fù)雜,且僅能使用Python開發(fā),僅能用于Dify生態(tài)。但Dify插件的整體鏈路開銷較MCP插件低,如果你對系統(tǒng)時(shí)延和成本更敏感,且無需使用MCP的動(dòng)態(tài)發(fā)現(xiàn)工具能力,Dify工具插件也許是個(gè)更好的選擇。
完整代碼地址:https://github.com/copilot-coder/dify-plugin-mysql















 
 
 















 
 
 
 