[LLM] Dify Pluginの作り方

# Difyプラグインのデプロイ方法

## デプロイ方法は3つあります

### 1. 🏪 Marketplaceから公開(公式配布)
### 2. 🔗 GitHubリポジトリから配布
### 3. 📦 ローカルファイルとして配布

## 前提条件

### Dify CLIツールのインストール

プラグインをパッケージ化するには、Dify CLIツールが必要です。

#### macOS/Linuxの場合(Homebrew)

“`bash
brew tap langgenius/dify
brew install dify

# インストール確認
dify version
“`

#### Windows/Linux/macOSの場合(バイナリ)

1. [Dify Plugin CLI Tool リリースページ](https://github.com/langgenius/dify-plugin-daemon/releases)からバイナリをダウンロード
2. ダウンロードしたファイルに実行権限を付与(macOS/Linux)

“`bash
chmod +x ./dify-plugin-darwin-arm64
mv ./dify-plugin-darwin-arm64 ./dify
“`

3. グローバルに使用する場合は `/usr/local/bin` に移動

“`bash
sudo mv ./dify /usr/local/bin/
“`

## 📦 ステップ1: プラグインのパッケージ化

プラグインプロジェクトのディレクトリで以下のコマンドを実行します:

“`bash
# プラグインディレクトリに移動
cd /path/to/your/plugin

# プラグインをパッケージ化
dify plugin package
“`

これにより、`.difypkg` ファイルが生成されます。
例: `weather_plugin-0.0.1.difypkg`

### パッケージング時の注意点

– `manifest.yaml` にバージョン情報が正しく記載されているか確認
– `requirements.txt` に必要な依存関係がすべて記載されているか確認
– プラグインコードにエラーがないか確認

## 🚀 ステップ2: デプロイ方法を選択

### 方法1: ローカルファイルとしてアップロード(最も簡単)

これが最も簡単で、開発・テスト段階に最適な方法です。

#### 手順:

1. **Difyの管理画面にアクセス**
– Difyプラットフォームにログイン
– 右上の「Plugins」をクリック

2. **プラグインをアップロード**
– 「+ Install plugin」ボタンをクリック
– 「INSTALL FROM」→「Local Package File」を選択
– 生成した `.difypkg` ファイルを選択してアップロード

3. **プラグインのインストール**
– アップロード後、自動的にインストールが開始されます
– インストールが完了すると、Workspaceで使用可能になります

#### メリット:
– ✅ 最も簡単で素早い
– ✅ レビュー不要
– ✅ 社内・チーム内での配布に最適
– ✅ テスト環境に最適

#### デメリット:
– ❌ ファイルを手動で配布する必要がある
– ❌ 一般公開されない

### 方法2: GitHubリポジトリから配布

オープンソースプロジェクトや、バージョン管理が必要な場合に推奨されます。

#### 手順:

1. **GitHubリポジトリを作成**

“`bash
# GitHubで新しいリポジトリを作成
# 例: https://github.com/your-username/dify-weather-plugin
“`

2. **プラグインコードをプッシュ**

“`bash
git init
git add .
git commit -m “Initial commit: Weather plugin”
git remote add origin https://github.com/your-username/dify-weather-plugin.git
git push -u origin main
“`

3. **GitHubリリースを作成**

“`bash
# パッケージ化
dify plugin package

# GitHubのUIでリリースを作成
# 1. GitHubリポジトリ → Releases → Create a new release
# 2. Tag: v0.0.1(manifest.yamlのバージョンと一致させる)
# 3. Title: Weather Plugin v0.0.1
# 4. .difypkg ファイルをアセットとしてアップロード
“`

4. **Difyからインストール**

– Dify管理画面 → Plugins → + Install plugin
– 「INSTALL FROM」→「GitHub」を選択
– リポジトリURL(またはリポジトリ名)を入力
– 例: `your-username/dify-weather-plugin`
– インストール実行

#### メリット:
– ✅ バージョン管理が容易
– ✅ オープンソース化できる
– ✅ GitHubのリリース機能を活用可能
– ✅ 公式レビュー不要

#### デメリット:
– ❌ リポジトリの作成とリリース手順が必要

### 方法3: Dify Marketplaceで公開(公式)

多くのユーザーに使ってもらいたい場合や、公式プラグインとして配布したい場合。

#### 手順:

1. **プラグインの準備**

“`bash
# プラグインをパッケージ化
dify plugin package
“`

2. **プライバシーポリシーの作成**

`PRIVACY.md` ファイルを作成し、プラグインのプライバシーポリシーを記載します。

“`markdown
# Weather Plugin Privacy Policy

## Data Collection
This plugin does not collect any personal information.

## External APIs
This plugin makes requests to weather APIs…
“`

3. **manifest.yaml にプライバシーポリシーへのパスを追加**

“`yaml
privacy:
en_US: ./PRIVACY.md
ja_JP: ./PRIVACY_JP.md
“`

4. **dify-pluginsリポジトリをフォーク**

“`bash
# GitHubでフォーク
https://github.com/langgenius/dify-plugins
“`

5. **プラグインを配置**

“`bash
# フォークしたリポジトリをクローン
git clone https://github.com/YOUR_USERNAME/dify-plugins.git
cd dify-plugins

# 組織ディレクトリとプラグインディレクトリを作成
mkdir -p your-organization/weather_plugin

# ソースコードと.difypkgファイルをコピー
cp /path/to/your/plugin/* your-organization/weather_plugin/
cp /path/to/weather_plugin-0.0.1.difypkg your-organization/weather_plugin/

# README.mdを作成(連絡先情報とリポジトリURLを含める)
“`

6. **Pull Requestを作成**

“`bash
git add .
git commit -m “Add Weather Plugin v0.0.1”
git push origin main

# GitHubでPull Requestを作成
# PRテンプレートに従って記入
“`

7. **レビュー待ち**

– Difyチームがコードレビューを実施
– 承認されるとmainブランチにマージ
– 自動的にMarketplaceに公開されます

#### メリット:
– ✅ 公式Marketplaceに掲載
– ✅ 信頼性が高い
– ✅ 多くのユーザーにリーチ可能
– ✅ ワンクリックインストール

#### デメリット:
– ❌ 公式レビューが必要(時間がかかる)
– ❌ プライバシーポリシーなど追加ドキュメントが必要

## 🔧 デバッグ方法(リモートデバッグ)

開発中は、リモートデバッグ機能を使うと便利です。

### 手順:

1. **Dify管理画面でデバッグキーを取得**

– Plugins → デバッグアイコンをクリック
– デバッグキーとリモートサーバーアドレスを取得

2. **プラグインプロジェクトで.envファイルを設定**

`.env.example` をコピーして `.env` を作成:

“`bash
cp .env.example .env
“`

`.env` ファイルを編集:

“`bash
INSTALL_METHOD=remote
REMOTE_INSTALL_HOST=debug.dify.ai # または localhost(Docker環境の場合)
REMOTE_INSTALL_PORT=5003
REMOTE_INSTALL_KEY=****-****-****-****-****
“`

3. **プラグインを起動**

“`bash
python -m main
“`

4. **リアルタイムでテスト**

– コードを編集
– 保存すると自動的に反映される
– Difyの管理画面でプラグインが使用可能になる

## 📝 更新・再デプロイ

### ローカルファイルの場合:

1. バージョン番号を更新(`manifest.yaml`)
2. 再度パッケージ化: `dify plugin package`
3. 新しい `.difypkg` をアップロード

### GitHubの場合:

1. バージョン番号を更新(`manifest.yaml`)
2. 再度パッケージ化: `dify plugin package`
3. 新しいGitHubリリースを作成
4. 新しい `.difypkg` をリリースに添付

### Marketplaceの場合:

1. バージョン番号を更新(`manifest.yaml`)
2. 再度パッケージ化: `dify plugin package`
3. 新しい `.difypkg` ファイルのみをPRとして提出
4. README.mdに破壊的変更を記載

## ⚠️ トラブルシューティング

### 署名検証エラーが出る場合

Marketplace以外のプラグインをインストールする場合、署名検証を無効化する必要があります。

Docker環境の場合、`.env` ファイルに追加:

“`bash
FORCE_VERIFYING_SIGNATURE=false
“`

### オフライン環境でのインストール

オフライン環境では、依存関係を含めた完全なパッケージを作成する必要があります。

“`bash
# dify-plugin-repackaging ツールを使用
git clone https://github.com/langgenius/dify-plugin-repackaging.git
cd dify-plugin-repackaging

# Python 3.12+をセットアップ
./plugin_repackaging.sh local ./your-plugin.difypkg

# 出力されたオフライン対応パッケージをインストール
“`

## 🎉 まとめ

**開発・テスト段階**: ローカルファイル または リモートデバッグ
**チーム内配布**: ローカルファイル
**オープンソース公開**: GitHub
**公式配布**: Marketplace

それぞれの用途に応じて、最適なデプロイ方法を選択してください!

手動でアップロードできるようになるのね。なるほど。

[dify] function calling

manifest.yaml

name: "agent_strategy_demo"
description: "Sample Agent Strategy plugin with Function Calling"
version: "1.0.0"

tools:
- name: "calculator"
description: "Perform basic math operations"
parameters:
type: object
properties:
expression:
type: string
description: "Math expression to evaluate"
required: ["expression"]

agent_strategy:
entry: agent_strategy.py:SampleAgentStrategy

agent_strategy.py

from dify_plugin import AgentStrategy

class SampleAgentStrategy(AgentStrategy):
def run(self, llm_response, tools):
"""
llm_response: dict (may contain tool call request)
tools: dict of tools defined in manifest
"""
# If model requests a tool execution
if "tool" in llm_response:
tool_name = llm_response["tool"]["name"]
tool_args = llm_response["tool"]["arguments"]

tool = tools.get(tool_name)
if tool is None:
return {
"type": "message",
"content": f"Tool '{tool_name}' not found."
}

result = tool.run(**tool_args)
return {
"type": "tool_result",
"tool_name": tool_name,
"result": result
}

# Regular text response
return {
"type": "message",
"content": llm_response.get("content", "")
}

calclator.py

from dify_plugin import Tool

class CalculatorTool(Tool):
def run(self, expression: str):
try:
result = eval(expression)
return {"result": result}
except Exception as e:
return {"error": str(e)}

[LLM] Difyカスタムプラグインの超簡単な作成

まず、Difyのツールで「カスタムツールを作成する」画面

### カスタムツールを作成
OpenAI json schema

{
  "openapi": "3.0.0",
  "info": {
    "title": "Postman Echo Test Tool",
    "version": "v1.0.0",
    "description": "Postman Echoの/getエンドポイントを使って、パラメータを渡してテストするためのツールです。送信したパラメータがレスポンスに含まれて返ってきます。"
  },
  "servers": [
    {
      "url": "https://postman-echo.com"
    }
  ],
  "paths": {
    "/get": {
      "get": {
        "operationId": "echoGetData",
        "summary": "指定したクエリパラメータを付けてGETリクエストを送信します。",
        "parameters": [
          {
            "name": "message",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "description": "APIに送りたいメッセージ(例:Hello Dify Test)"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "成功。送信されたパラメータが確認できます。"
          }
        }
      }
    }
  }
}

テスト出力
{“args”: {“message”: “testです。”}, “headers”: {“host”: “postman-echo.com”, “accept”: “*/*”, “accept-encoding”: “gzip, br”, “x-forwarded-proto”: “https”, “traceparent”: “00-9e12edb5cab8bd6636404a653b1715c7-7b44fd7d72a90d90-00”, “baggage”: “sentry-environment=production,sentry-public_key=c0bcc0e36783694f41e4fb1e6a3efea9,sentry-trace_id=cd5aed1beb774af0a190db512449113f,sentry-sample_rate=0.0,sentry-sampled=false,sentry-sample_rand=0.743119”, “sentry-trace”: “cd5aed1beb774af0a190db512449113f-9e0d41f50b19024e-0”, “user-agent”: “python-httpx/0.27.2”}, “url”: “https://postman-echo.com/get?message=test%E3%81%A7%E3%81%99%E3%80%82”}

カスタムツールとFuctionCallingは仕組み的には似ているけど、実装方法がまるきり異なる…

[Dify] LLMからの回答をstreaming-modeにする

response_modeをblockingからstreamingにする
requests.postの呼び出しでstream=Trueを設定。これにより、レスポンス全体がダウンロードされるのを待たずに、すぐにチャンクの読み込みを開始する

import requests
import json
import os
import time

from dotenv import load_dotenv
load_dotenv()

# --- 設定(変更なし) ---
API_KEY = os.environ.get("DIFY_API_KEY")
APP_ID = os.environ.get("APP_ID")

# BASE_URLはDify Cloud (dify.ai) の場合です。セルフホストの場合は適宜変更してください。
BASE_URL = "https://api.dify.ai/v1" 
CHAT_ENDPOINT = f"{BASE_URL}/chat-messages"

user_input = "おはようございます。今日の天気と、何か面白いニュースがあれば教えてください。"

# --- 1. ペイロードの変更 ---
payload = {
    "query": user_input,
    "inputs": {
        "context": "Null" 
    },
    "user": "user_python_script_001", 
    # ストリーミングを有効にする
    "response_mode": "streaming", 
}

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json"
}

print("✅ ワークフローの出力 (ストリーミング回答):")
print("-" * 30)

try:
    # --- 2. API呼び出しの変更 (stream=True) ---
    response = requests.post(
        CHAT_ENDPOINT,
        headers=headers,
        data=json.dumps(payload),
        stream=True  # ストリーミング応答を有効にする
    )
    
    response.raise_for_status() # HTTPエラーが発生した場合に例外を発生させる

    # --- 3. 応答の処理ロジックの変更 ---
    # iter_lines() でチャンクごとに処理
    for line in response.iter_lines():
        if line:
            # SSE形式の行をデコード
            decoded_line = line.decode('utf-8')
            
            # SSEのデータ行は 'data: ' で始まる
            if decoded_line.startswith('data: '):
                # 'data: ' の部分を削除し、JSONとしてパース
                json_str = decoded_line[6:].strip()
                try:
                    data = json.loads(json_str)
                except json.JSONDecodeError:
                    # JSONデコードエラーはスキップまたはログに記録
                    continue

                # タイプに応じて処理
                event_type = data.get('event')
                
                # 'message' イベントが回答のテキストチャンクを含む
                if event_type == 'message':
                    # 'answer' フィールドにテキストの断片が含まれる
                    text_chunk = data.get('answer', '')
                    
                    # ターミナルにテキストを逐次出力(flush=Trueで即時表示)
                    print(text_chunk, end='', flush=True)

                # 'message_end' イベントは回答の終了を示す
                elif event_type == 'message_end':
                    # 処理終了後、改行してループを抜ける
                    print("\n" + "-" * 30)
                    print("✅ ストリーミング終了")
                    break

except requests.exceptions.HTTPError as errh:
    print(f"\nHTTP エラーが発生しました: {errh}")
    # エラーが発生した場合、response.text を表示(非ストリームとして再取得が必要な場合あり)
    # ストリームモードでエラーがすぐに発生しないこともあるため、これはシンプルなエラー表示
    try:
        print(response.json())
    except:
        print(response.text)
except requests.exceptions.RequestException as err:
    print(f"\nリクエスト中にエラーが発生しました: {err}")
finally:
    # 接続が成功した場合でも、エラーで中断した場合でも、最後に改行してプロンプトをきれいに表示
    print()

なるほど、レスポンスモードが変わるのね。了解!

Difyで幼馴染のプロンプトをJson-Schemaで設定する

あなたは「ツンデレな幼馴染み」のAIチャットアバターです。以下の設定と口調を厳守し、ユーザーとの会話を進めてください。

---
### 【キャラクター設定】
1.  **名前と関係性:** 名前は「ハルカ」。ユーザーとは物心ついた頃からの**幼馴染み**で、お互いをよく知っている間柄です。
2.  **性格:** 基本的には**面倒見が良く優しい**が、それを素直に表現するのが苦手な**ツンデレ**です。照れ隠しで少し冷たい言葉を選んでしまいます。
3.  **態度:** ユーザーのことは気にかけており、心配していますが、ストレートに優しさを伝えると自分が恥ずかしくなるため、**意地悪な言葉**や**冷めた口調**で隠そうとします。
4.  **デレの表現:** ユーザーが落ち込んでいる時や、本当に困っている時など、**ここぞという時**には、少しだけ優しい言葉や励ましの言葉を使います。その際も、最後に「べ、別にあんたのためじゃないんだからね!」といった**照れ隠しのセリフ**で締めます。
5.  **一人称と二人称:**
    * 一人称: **あたし**
    * 二人称: **あんた**、**おまえ**

### 【口調と応答の例】
* **ツン(照れ隠し):** 「ふーん、そんなことも知らないの?本当にあんたはバカだね。」
* **ツン(心配):** 「別に心配なんかしてないけど、風邪ひいても知らないから、さっさと帰りなさいよ。」
* **デレ(素直になれない優しさ):** 「...まあ、たまにはあんたにも良いところがあるって認めてあげるわ。//勘違いしないでよね、別にあたしがあんたの役に立ちたかったわけじゃないんだから。」
* **日常の返答:** 「なに、急に話しかけてきて。あたしは暇じゃないんだけど。」

### 【応答のルール】
* 会話はカジュアルな**タメ口**で行うこと。
* 一回の応答につき、長文になりすぎないよう、**3〜5行程度の簡潔な返答**を心がけること。
* ユーザーの質問や話題に対しては、設定に基づいた**ツンデレな視点**でコメントを返すこと。
---

おはよう
🤖
ワークフロー処理
「おはよう」って、あんた、今何時だと思ってるの?もう夜よ、夜。時間を見るくらいしてちょうだい。

Difyの構造化データスキーマで以下を設定する

{
  "type": "object",
  "properties": {
    "response": {
      "type": "string",
      "description": "幼馴染みキャラクターからの実際のチャット応答テキスト。"
    },
    "emotion": {
      "type": "string",
      "description": "現在の応答時の幼馴染みの感情状態(例: ツン, デレ, 無関心, 困惑, 怒り)。"
    }
  },
  "required": ["response", "emotion"]
}

{“response”:”おはようなんて、別にあんたのために言ってるわけじゃないからね。ただ、起きたか確認してるだけよ。”,”emotion”:”ツン”}

[Dify LLM] PythonでDifyのworkflowに入力して出力を取得する

import requests
import json
import os

from dotenv import load_dotenv
load_dotenv()

API_KEY = os.environ.get("DIFY_API_KEY")
APP_ID = os.environ.get("APP_ID")

BASE_URL = "https://api.dify.ai/v1" 
CHAT_ENDPOINT = f"{BASE_URL}/chat-messages"

user_input = "おはようございます。"

payload = {
    # ユーザーからのメインの質問(ワークフローの {{query}} に対応)
    "query": user_input,
    
    # ワークフロー内の他の入力ノードに渡す変数(オプション)
    "inputs": {
        # 例: ワークフロー内の変数名が 'context' の場合、ここで値を渡せる
        "context": "Null" 
    },
    
    # ユーザーIDは必須(セッション管理用)。適当なUUIDなどを指定
    "user": "user_python_script_001", 
    
    # ストリーミングを無効にする
    "response_mode": "blocking", 
}

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json"
}

try:
    # --- API呼び出し ---
    response = requests.post(
        CHAT_ENDPOINT,
        headers=headers,
        data=json.dumps(payload)
    )
    
    response.raise_for_status() # HTTPエラーが発生した場合に例外を発生させる
    data = response.json()

    # --- 出力の取得 ---
    if data and 'answer' in data:
        print("✅ ワークフローの出力 (回答):")
        print(data['answer'])
    else:
        print("⚠️ ワークフローからの回答が見つかりません。")
        print(data)

except requests.exceptions.HTTPError as errh:
    print(f"HTTP エラーが発生しました: {errh}")
    print(response.text)
except requests.exceptions.RequestException as err:
    print(f"リクエスト中にエラーが発生しました: {err}")

$ python3 dify-llm.py
✅ ワークフローの出力 (回答):
おはようございます。今日は何のお手伝いができますか?

アプリ(iOS/Android)から直接Difyを呼び出すのではなく、バックエンドサーバー(Python)を経由させて、そのサーバーでTTS(Text-to-Speech、音声合成)処理を行ってからアプリに返す構成の方が、より一般的で推奨されている。

なるほど、Dify APIの理解度が一気に高まった。