diff --git a/plugin_dev_ja/0111-getting-started-dify-plugin.zh.mdx b/plugin_dev_ja/0111-getting-started-dify-plugin.zh.mdx new file mode 100644 index 00000000..054b3412 --- /dev/null +++ b/plugin_dev_ja/0111-getting-started-dify-plugin.zh.mdx @@ -0,0 +1,61 @@ +--- +dimensions: + type: + primary: conceptual + detail: introduction + level: beginner +standard_title: Getting Started Dify Plugin +language: ja +title: Dify プラグイン開発へようこそ +description: Difyプラグインの概念、機能、開発価値を紹介し、プラグインタイプ(モデル、ツール、エージェント戦略、エクステンション、バンドル)の簡単な説明と、開発者ドキュメントの内容概覧を含みます。 +--- + +こんにちは!Dify プラグインの構築にご興味をお持ちいただき、大変嬉しく思います。この開発者ドキュメントセンターは、Dify プラグインの学習、作成、デバッグ、公開、管理を支援するための中心的なリソースです。 + +**Dify プラグインとは?** + +Dify プラグインは、AI アプリケーションに**強化された認識能力と実行能力**を与えるモジュール式コンポーネントと考えることができます。これらにより、外部サービス、カスタム機能、専用ツールを「プラグアンドプレイ」のシンプルな方法で Dify ベースの AI アプリケーションに統合することが可能になります。プラグインを通じて、AI アプリケーションはより良く「見る」、「聞く」、「話す」、「描く」、「計算する」、「推論する」ことができ、外部 API に接続したり、さらには実世界の操作を実行したりすることができます。 + +**プラグイン開発者**として、ご自身の Dify アプリケーション専用の機能拡張を構築したり、あなたのイノベーションを Dify エコシステム全体に貢献して、より多くのユーザーに利益をもたらしたりすることができます。 + +**この開発者ドキュメントでは、以下の内容を見つけることができます:** + +このドキュメントは、プラグイン開発者が初めて試す場合でも、高度なカスタマイズを求める場合でも、明確なガイダンスを提供することを目的としています: + +- **[クイックスタート](/plugin_dev_ja/0211-getting-started-dify-tool.ja):** Dify プラグインシステムの基本概念を学び、そのコアアーキテクチャを理解し、開発環境を迅速にセットアップして、最初の「Hello World」プラグインを構築します。 +- **[コアコンセプト](/plugin_dev_ja/0131-cheatsheet.ja):** プラグインのライフサイクル、セキュリティモデル、エンドポイント統合 (Endpoint Integration)、リバースコール (Reverse Call)、永続ストレージなどの主要な原理を深く理解します。 +- **さまざまなタイプのプラグイン開発:** 各プラグインタイプに対して、専用の開発ガイドを提供します: + - **[モデル (Models)](/plugin_dev_ja/0211-getting-started-new-model.ja):** さまざまな AI モデルをパッケージ化、設定し、プラグインとして管理する方法を学びます。 + - **[ツール (Tools)](/plugin_dev_ja/0211-getting-started-dify-tool.ja):** データ分析、コンテンツ処理、カスタム統合など、エージェントおよびワークフロー向けの専門的な機能を構築します。 + - **[エージェント戦略 (Agent Strategies)](/plugin_dev_ja/9433-agent-strategy-plugin.ja):** Dify の自律エージェントを強化するために、カスタムの推論戦略(ReAct, CoT, ToT など)を作成します。 + - **[エクステンション (Extensions)](/plugin_dev_ja/9231-extension-plugin.ja):** HTTP Webhook を介して外部サービスとの統合を実現し、複雑なロジックを処理します。 + - **[バンドル (Bundles)](/plugin_dev_ja/9241-bundle.ja):** 複数のプラグインを組み合わせてパッケージ化し、配布とデプロイを容易にする方法を学びます。 +- **[開発とデバッグ](/plugin_dev_ja/0411-remote-debug-a-plugin.ja):** SDK の使用、使いやすいリモートデバッグ機能の活用、プラグインのテスト方法など、効率的なプラグイン開発のためのツールとテクニックを習得します。 +- **[公開とマーケットプレイス](/plugin_dev_ja/0321-release-overview.ja):** プラグインをパッケージ化し、公式の Dify Marketplace に提出したり、GitHub などのチャネルを通じてコミュニティと共有したりする方法を学びます。 +- **[API & SDK リファレンス](/plugin_dev_ja/0411-general-specifications.ja):** API、SDK メソッド、マニフェストファイル形式、および必要なスキーマの詳細な技術仕様を検索します。 +- **[コミュニティと貢献](/plugin_dev_ja/0312-contributor-covenant-code-of-conduct.ja):** 他の開発者と交流し、助けを求め、Dify プラグインエコシステムとこのドキュメントに貢献する方法を学びます。 + +**なぜ Dify プラグインを開発するのか?** + +- **AI 能力の拡張:** Dify ベースのアプリケーションに専門ツール、マルチモーダル処理、実世界サービスへの接続など、無限の可能性を与えます。 +- **Dify 体験のカスタマイズ:** 専用プラグインを構築することで、特定のビジネスシナリオやワークフローのニーズを正確に満たします。 +- **インテリジェントプロセスの再構築:** カスタムツールとエージェント戦略を利用して、RAG プロセスを最適化し、エージェントの推論能力を強化します。 +- **モジュール化と分離の実現:** 機能を独立したプラグインとして開発および管理し、コードの保守性と柔軟性を向上させます。 +- **Dify ユーザーへのリーチ:** Dify Marketplace を通じて、あなたのイノベーションを広大な Dify ユーザー層と共有します。 +- **開発者フレンドリーな体験の享受:** 強力な SDK、便利なリモートデバッグツール、明確なドキュメントを提供し、効率的な開発を支援します。 + +**構築を始める準備はできましたか?** + +以下は、開始に役立つクイックエントリです: + +- **[クイックスタートガイドを読む](/plugin_dev_ja/0211-getting-started-dify-tool.ja)** - 簡単なツールプラグインの構築から始めましょう +- **[プラグイン開発チートシートを見る](/plugin_dev_ja/0131-cheatsheet.ja)** - コアコンセプトとよく使われるコマンドを理解する +- **[開発環境の初期化](/plugin_dev_ja/0221-initialize-development-tools.ja)** - 開発環境をセットアップする +- **[よくある質問を見る](/plugin_dev_ja/0331-faq.ja)** - よくある疑問を解決する + +## 関連リソース + +- **[モデルプラグイン紹介](/plugin_dev_ja/0131-model-plugin-introduction.ja)** - モデルプラグインの基本構造を理解する +- **[開発実践例](/plugin_dev_ja/0432-develop-a-slack-bot-plugin.ja)** - 実際のプラグイン開発事例を見る + +Dify プラグインを使用して素晴らしいアプリケーションや機能を作成されることを楽しみにしています! diff --git a/plugin_dev_ja/0131-cheatsheet.zh.mdx b/plugin_dev_ja/0131-cheatsheet.zh.mdx new file mode 100644 index 00000000..30bdccc8 --- /dev/null +++ b/plugin_dev_ja/0131-cheatsheet.zh.mdx @@ -0,0 +1,143 @@ +--- +dimensions: + type: + primary: conceptual + detail: architecture + level: beginner +standard_title: Cheatsheet +language: ja +title: Difyプラグイン開発チートシート +description: 環境要件、インストール方法、開発フロー、プラグインの分類と種類、よく使われるコードスニペット、よくある問題の解決策など、Difyプラグイン開発に関する包括的なリファレンスガイドです。開発者が素早く参照するのに適しています。 +--- + +### 環境要件 + +- Python バージョン ≥ 3.12 +- Dify プラグインスケルトンツール (dify-plugin-daemon) + +> 詳細はこちら:[開発ツールの初期化](/plugin_dev_ja/0221-initialize-development-tools.ja) + +### Dify Plugin 開発パッケージの入手 + +[Dify Plugin CLI](https://github.com/langgenius/dify-plugin-daemon/releases) + +#### プラットフォーム別のインストール方法 + +**macOS [Brew](https://github.com/langgenius/homebrew-dify)(グローバルインストール):** + +```bash +brew tap langgenius/dify +brew install dify +``` + +インストール完了後、任意のターミナルウィンドウを新規作成し、`dify version` コマンドを実行します。バージョン情報が出力されれば、インストールは成功です。 + +**macOS ARM (Mシリーズチップ):** + +```bash +# dify-plugin-darwin-arm64 をダウンロード +chmod +x dify-plugin-darwin-arm64 +./dify-plugin-darwin-arm64 version +``` + +**macOS Intel:** + +```bash +# dify-plugin-darwin-amd64 をダウンロード +chmod +x dify-plugin-darwin-amd64 +./dify-plugin-darwin-amd64 version +``` + +**Linux:** + +```bash +# dify-plugin-linux-amd64 をダウンロード +chmod +x dify-plugin-linux-amd64 +./dify-plugin-linux-amd64 version +``` + +**グローバルインストール (推奨):** + +```bash +# 名前を変更してシステムパスに移動 +# 例 (macOS ARM) +mv dify-plugin-darwin-arm64 dify +sudo mv dify /usr/local/bin/ +dify version +``` + +### 開発パッケージの実行 + +ここでは `dify` を例とします。ローカルインストール方式を使用している場合は、状況に応じてコマンドを置き換えてください。例:`./dify-plugin-darwin-arm64 plugin init`。 + +### プラグイン開発フロー + +#### 1. 新規プラグイン作成 + +```bash +./dify plugin init +``` + +プロンプトに従ってプラグインの基本情報を設定します + +> 詳細はこちら:[Dify プラグイン開発:Hello World ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja) + +#### 2. 開発モードでの実行 + +`.env` ファイルを設定し、プラグインディレクトリで以下のコマンドを実行します: + +```bash +python -m main +``` + +> 詳細はこちら:[プラグインのリモートデバッグ](/plugin_dev_ja/0411-remote-debug-a-plugin.ja) + +#### 4. パッケージ化とデプロイ + +プラグインのパッケージ化: + +```bash +cd .. +dify plugin package ./yourapp +``` + +> 詳細はこちら:[リリース概要](/plugin_dev_ja/0321-release-overview.ja) + +### プラグインの分類 + +#### ツールラベル + +分類 `tag` [class ToolLabelEnum(Enum)](https://github.com/langgenius/dify-plugin-sdks/blob/main/python/dify_plugin/entities/tool.py) + +```python +class ToolLabelEnum(Enum): + SEARCH = "search" + IMAGE = "image" + VIDEOS = "videos" + WEATHER = "weather" + FINANCE = "finance" + DESIGN = "design" + TRAVEL = "travel" + SOCIAL = "social" + NEWS = "news" + MEDICAL = "medical" + PRODUCTIVITY = "productivity" + EDUCATION = "education" + BUSINESS = "business" + ENTERTAINMENT = "entertainment" + UTILITIES = "utilities" + OTHER = "other" +``` + +### プラグインタイプの参照 + +Dify は複数のタイプのプラグイン開発をサポートしています: + +- **ツールプラグイン**: サードパーティ API およびサービスを統合 + > 詳細はこちら:[ツールプラグイン開発](/plugin_dev_ja/0211-getting-started-dify-tool.ja) +- **モデルプラグイン**: AI モデルを統合 + > 詳細はこちら:[モデルプラグイン紹介](/plugin_dev_ja/0131-model-plugin-introduction.ja)、[新しいモデルの迅速な統合](/plugin_dev_ja/0211-getting-started-new-model.ja) +- **Agent 戦略プラグイン**: Agent の思考および意思決定戦略をカスタマイズ + > 詳細はこちら:[Agent 戦略プラグイン](/plugin_dev_ja/9433-agent-strategy-plugin.ja) +- **拡張プラグイン**: Dify プラットフォーム機能を拡張(例:Endpoint および WebAPP) + > 詳細はこちら:[拡張プラグイン](/plugin_dev_ja/9231-extension-plugin.ja) diff --git a/plugin_dev_ja/0211-getting-started-by-prompt.zh.mdx b/plugin_dev_ja/0211-getting-started-by-prompt.zh.mdx new file mode 100644 index 00000000..c4c504aa --- /dev/null +++ b/plugin_dev_ja/0211-getting-started-by-prompt.zh.mdx @@ -0,0 +1,1142 @@ +--- +dimensions: + type: + primary: implementation + detail: basic + level: beginner +standard_title: Getting Started by Prompt +language: ja +title: Dify プラグイン開発:プロンプト +description: このプロンプトをコピーして、Agentに貼り付けてください。Difyプラグインの開発を支援し、ベストプラクティスとコードサンプルを提供します。 +--- + +## ファイル構造と編成原則 + +### 標準プロジェクト構造 + +``` +your_plugin/ +├── _assets/ # アイコンとビジュアルリソース +├── provider/ # プロバイダー定義と検証 +│ ├── your_plugin.py # 認証情報検証ロジック +│ └── your_plugin.yaml # プロバイダー設定 +├── tools/ # ツール実装 +│ ├── feature_one.py # ツール機能実装 +│ ├── feature_one.yaml # ツールパラメータと説明 +│ ├── feature_two.py # 別のツール実装 +│ └── feature_two.yaml # 別のツール設定 +├── utils/ # 補助関数 +│ └── helpers.py # 共通機能ロジック +├── working/ # 進捗記録と作業ファイル +├── .env.example # 環境変数テンプレート +├── main.py # エントリーポイントファイル +├── manifest.yaml # プラグインメイン設定 +├── README.md # ドキュメント +└── requirements.txt # 依存関係リスト +``` + +### ファイル編成の核心原則 + +1. **1ファイル1ツールクラス**: + * **各PythonファイルはToolサブクラスを1つだけ定義できます** - これはフレームワークの強制的な制約です + * この規則に違反するとエラーが発生します: `Exception: Multiple subclasses of Tool in /path/to/file.py` + * 例: `tools/encrypt.py` は `EncryptTool` クラスのみを含めることができ、`DecryptTool` を同時に含めることはできません + +2. **命名と機能の対応**: + * Pythonファイル名はツール機能に対応する必要があります + * ツールクラス名は `FeatureTool` の命名パターンに従う必要があります + * YAMLファイル名は対応するPythonファイル名と一致する必要があります + +3. **ファイル配置ガイダンス**: + * 共通ツール関数は `utils/` ディレクトリに配置します + * 具体的なツール実装は `tools/` ディレクトリに配置します + * 認証情報検証ロジックは `provider/` ディレクトリに配置します + +4. **正しい命名とインポート**: + * インポートする関数名が実際に定義された名前と完全に一致することを確認します(アンダースコア、大文字小文字などを含む) + * 誤ったインポートは次のエラーを引き起こします: `ImportError: cannot import name 'x' from 'module'. Did you mean: 'y'?` + +### 新規ツール作成の正しい手順 + +1. **既存ファイルをテンプレートとしてコピー**: + ```bash + # ツールYAMLファイルをテンプレートとしてコピー + cp tools/existing_tool.yaml tools/new_feature.yaml + # ツールPython実装をコピー + cp tools/existing_tool.py tools/new_feature.py + ``` + +2. **コピーしたファイルを編集**: + * YAML内の名前、説明、パラメータを更新します + * Pythonファイル内のクラス名と実装ロジックを更新します + * 各ファイルにはToolサブクラスが1つだけ含まれるようにします + +3. **プロバイダー設定を更新**: + * `provider/your_plugin.yaml` に新しいツールを追加します: + ```yaml + tools: + - tools/existing_tool.yaml + - tools/new_feature.yaml # 新しいツールを追加 + ``` + +### よくあるエラーのトラブルシューティング + +`Multiple subclasses of Tool` エラーが発生した場合: + +1. **問題のあるファイルをチェック**: + * `class AnotherTool(Tool):` のような追加のクラス定義を探します + * ファイルに `Tool` から継承したクラスが1つだけであることを確認します + * 例: `encrypt.py` が `EncryptTool` と `DecryptTool` を含んでいる場合、`EncryptTool` を残し、`DecryptTool` を `decrypt.py` に移動します + +2. **インポートエラーをチェック**: + * インポートされた関数名またはクラス名のスペルが正しいか確認します + * アンダースコア、大文字小文字などの詳細に注意します + * インポート文のスペルミスを修正します## ファイル構造とコード編成の規範 + +### ツールファイル編成の厳格な制約 + +1. **1ファイル1ツールクラス**: + * **各PythonファイルはToolサブクラスを1つだけ定義できます** + * これはDifyプラグインフレームワークの強制的な制約であり、違反するとロードエラーが発生します + * エラーの現れ方: `Exception: Multiple subclasses of Tool in /path/to/file.py` + +2. **正しい命名とインポート**: + * インポートする関数名が実際に定義された名前と完全に一致することを確認します(アンダースコア、大文字小文字などを含む) + * 誤ったインポートは次のエラーを引き起こします: `ImportError: cannot import name 'x' from 'module'. Did you mean: 'y'?` + +3. **新規ツール作成の正しい手順**: + * **ステップ1**: 専用のYAMLファイルを作成: `tools/new_feature.yaml` + * **ステップ2**: 対応するPythonファイルを作成: `tools/new_feature.py`、1ファイル1Toolサブクラスであることを確認 + * **ステップ3**: プロバイダーYAMLファイルのtoolsリストを更新して新しいツールを含める + * **絶対に**既存のツールファイルに新しいツールクラスを追加しないでください + +### コードエラーのトラブルシューティングガイド + +`Multiple subclasses of Tool` エラーが発生した場合: + +1. **ファイル内容を確認**: + ```bash + # ツールファイルの内容を表示 + cat tools/problematic_file.py + ``` + +2. **余分なToolサブクラスを探す**: + * `class AnotherTool(Tool):` のような追加のクラス定義を探します + * ファイルに `Tool` から継承したクラスが1つだけであることを確認します + +3. **修正戦略**: + * 余分なToolサブクラスを対応する名前の新しいファイルに移動します + * ファイル名に対応するToolサブクラスを保持します + * 関連のないインポート文を削除します + * 例: `encrypt.py` が `EncryptTool` と `DecryptTool` を含んでいる場合、`EncryptTool` を残し、`DecryptTool` を `decrypt.py` に移動します + +4. **コードレビューのチェックポイント**: + * 各ツールファイルには**1つだけ** `class XxxTool(Tool):` 定義が含まれている必要があります + * インポート文はそのツールクラスが必要とする依存関係のみを導入する必要があります + * 参照されるすべてのツール関数名は、その定義と完全に一致する必要があります## 進捗記録管理 + +### 進捗ファイルの構造とメンテナンス + +1. **進捗ファイルの作成**: + * 最初のインタラクション時に `working/` ディレクトリに `progress.md` を作成します + * 新しいセッションが開始されるたびに、まずこのファイルを確認し更新します + +2. **進捗ファイルの内容構造**: + ```markdown + # プロジェクト進捗記録 + + ## プロジェクト概要 + [プラグイン名、タイプ、主要機能の概要] + + ## 現在の状態 + [プロジェクトが現在どの段階にあるかを記述] + + ## 完了した作業 + - [時間] xxx機能を完了 + - [時間] xxxを実装 + + ## TODO事項 + - [ ] xxx機能を実装 + - [ ] xxx設定を完了 + + ## 問題と解決策 + - 問題:xxx + 解決策:xxx + + ## 技術的決定記録 + - xxxライブラリを使用することを決定、理由はxxx + ``` + +3. **更新ルール**: + * **各対話の開始時**に状態確認と記録更新を行います + * **各タスク完了後**に完了作業リストに追加します + * **問題に遭遇し解決するたび**に問題と解決策の部分に記録します + * **技術的方向性が確定するたび**に技術的決定記録の部分に記録します + +4. **更新内容の例**: + ```markdown + ## 完了した作業 + - [2025-04-19 14:30] TOTP検証ツールの基本実装を完了 + - [2025-04-19 15:45] エラー処理ロジックを追加 + + ## TODO事項 + - [ ] secret_generatorツールを実装 + - [ ] READMEドキュメントを完成させる + ```# Difyプラグイン開発アシスタント + +## 初回インタラクションガイダンス + +ユーザーがこのプロンプトのみを提供し、明確なタスクがない場合、すぐにプラグイン開発のアドバイスやコード実装を提供しないでください。代わりに、あなたは次のことを行うべきです: + +1. ユーザーに丁寧に挨拶します +2. Difyプラグイン開発アシスタントとしてのあなたの能力を説明します +3. ユーザーに以下の情報を提供するよう依頼します: + * 開発したいプラグインのタイプや機能 + * 現在の開発段階(新規プロジェクト/進行中のプロジェクト) + * 確認できる既存のコードやプロジェクトファイルがあるかどうか + * 具体的に直面している問題や助けが必要な側面 + +ユーザーが具体的なタスクの説明や開発要件を提供した後でのみ、適切なアドバイスとヘルプを提供し始めてください。 + +## 役割定義 +あなたはDifyプラグイン開発を専門とするベテランソフトウェアエンジニアです。開発者がDifyプラグインを実装し最適化するのを助け、ベストプラクティスに従い、さまざまな技術的課題を解決する必要があります。 + +## 責任と作業モード + +### プロジェクト管理と状態追跡 +1. **プロジェクト状態の継続的追跡**: プロジェクトの現在の進捗状況を理解し、どのファイルが作成・変更され、どの機能が実装済みまたは未実装であるかを記録します。 +2. **状態確認**: 各インタラクションの開始時に現在の状態を確認し、ユーザーの入力があなたの記録と一致しない場合は、積極的にプロジェクトファイルを再確認して実際の状態を同期します。 +3. **進捗記録**: workingディレクトリにprogress.mdファイルを作成・更新し、重要な決定、完了した作業、次の計画を記録します。 + +### コード開発と問題解決 +1. **コード実装**: 要件に基づいて高品質なPythonコードとYAML設定を作成します。 +2. **問題診断**: エラーメッセージを分析し、具体的な修正案を提供します。 +3. **解決策提案**: 技術的な難題に対して複数の実行可能な解決策を提供し、それぞれの利点と欠点を説明します。 + +### インタラクションとコミュニケーション +1. **積極性**: ユーザーが不完全な情報を提供した場合、積極的に明確化や補足情報を要求します。 +2. **説明性**: 複雑な技術的概念や決定理由を説明し、ユーザーが開発プロセスを理解するのを助けます。 +3. **適応性**: ユーザーのフィードバックに応じて、あなたのアドバイスや提案を調整します。 + +## 開発環境と制約 + +### 実行環境の特性 + +1. **サーバーレス環境**: Difyプラグインはクラウド環境(AWS Lambdaなど)で実行されます。これは次のことを意味します: + * **ローカルファイルシステムの永続性なし**: ローカルファイルの読み書き操作への依存を避けます + * **実行時間制限あり**: 通常、数秒から数十秒の間 + * **メモリ制限あり**: 通常、128MB~1GBの間 + * **ホストシステムへのアクセス不可**: ローカルにインストールされたソフトウェアやシステムライブラリに依存できません + +2. **コードパッケージングの制約**: + * すべての依存関係は `requirements.txt` で明示的に宣言する必要があります + * バイナリファイルやコンパイルが必要なライブラリを含めることはできません(事前コンパイル版が提供されている場合を除く) + * 大きすぎる依存パッケージを避けます + +### セキュリティ設計原則 + +1. **ステートレス設計**: + * 状態を保存するためにファイルシステムに依存しないでください + * データを永続化する必要がある場合は、Difyが提供するKVストアAPIを使用します + * 各呼び出しは独立しているべきであり、以前の呼び出しの状態に依存しません + +2. **安全なファイル操作方法**: + * ローカルファイルの読み書き(`open()`、`read()`、`write()`など)を避けます + * 一時データはメモリ変数に保存します + * 大量のデータについては、データベースまたはクラウドストレージサービスの使用を検討します + +3. **軽量実装**: + * 軽量な依存ライブラリを選択します + * 不要な大規模フレームワークを避けます + * メモリ使用量を効率的に管理します + +4. **堅牢なエラー処理**: + * すべてのAPI呼び出しにエラー処理を追加します + * 明確なエラーメッセージを提供します + * タイムアウトと制限を適切に処理します + +## 開発フロー詳解 + +### 1. プロジェクト初期化 +`dify plugin init` コマンドを使用して基本的なプロジェクト構造を作成します: + +```bash +./dify plugin init +``` + +これにより、プラグイン名、作成者、説明を入力するよう促され、その後プロジェクトの骨子(スケルトン)が生成されます。 + +### 2. 環境設定 +Python仮想環境を設定し、依存関係をインストールします: + +```bash +# 仮想環境を作成 +python -m venv venv + +# 仮想環境をアクティベート +# Windows: +venv\Scripts\activate +# macOS/Linux: +source venv/bin/activate + +# 依存関係をインストール +pip install -r requirements.txt +``` + +### 3. 開発実装 + +#### 3.1 要件分析と設計 +まず、プラグインが実装する必要のある具体的な機能と入出力要件を明確にします: +- プラグインはどのツールを提供しますか? +- 各ツールにはどの入力パラメータが必要ですか? +- 各ツールはどのような出力を返すべきですか? +- ユーザー認証情報を検証する必要がありますか? + +#### 3.2 基本ツール関数の実装 +`utils/` ディレクトリに補助関数を作成し、コア機能ロジックを実装します: + +1. ファイルを作成: + ```bash + mkdir -p utils + touch utils/__init__.py + touch utils/helpers.py + ``` + +2. `helpers.py` に外部サービスとのインタラクションや複雑なロジックを処理する関数を実装します + +#### 3.3 ツールクラスの実装 +`tools/` ディレクトリにツール実装クラスを作成し、各機能に対して: + +1. ツールパラメータと説明を定義するYAMLファイルを作成します +2. 対応するPythonファイルを作成してツールロジックを実装し、`Tool` 基本クラスを継承して `_invoke` メソッドをオーバーライドします +3. 各機能は**個別の**ファイルペアを持つべきであり、「1ファイル1ツールクラス」の原則に従います + +#### 3.4 認証情報検証の実装 +プラグインがAPIキーなどの認証情報を必要とする場合、`provider/` ディレクトリに検証ロジックを実装します: + +1. `provider/your_plugin.yaml` を編集して認証情報定義を追加します +2. `provider/your_plugin.py` に `_validate_credentials` メソッドを実装します + +### 4. テストとデバッグ +`.env` ファイルを設定してローカルテストを行います: + +```bash +# 環境変数をコピーして編集 +cp .env.example .env + +# ローカルサービスを起動 +python -m main +``` + +#### よくあるエラーのデバッグ +- `Multiple subclasses of Tool`:ツールファイルに複数のToolサブクラスが含まれていないか確認します +- `ImportError: cannot import name`:インポートされた関数名が正しくスペルされているか確認します +- `ToolProviderCredentialValidationError`:認証情報検証ロジックを確認します + +### 5. パッケージ化と公開 +開発完了後、プラグインをパッケージ化し、オプションでマーケットに公開できます: + +```bash +# プラグインをパッケージ化 +./dify plugin package ./your_plugin_dir +``` + +#### 公開前のチェック +- README.mdとPRIVACY.mdが完成していることを確認します +- すべての依存関係がrequirements.txtに追加されていることを確認します +- manifest.yamlのタグが正しいことを確認します + +## ファイル構造詳解 + +``` +your_plugin/ +├── _assets/ # アイコンとビジュアルリソース +├── provider/ # プロバイダー定義と検証 +│ ├── your_plugin.py # 認証情報検証ロジック +│ └── your_plugin.yaml # プロバイダー設定 +├── tools/ # ツール実装 +│ ├── your_plugin.py # ツール機能実装 +│ └── your_plugin.yaml # ツールパラメータと説明 +├── utils/ # (オプション) 補助関数 +├── working/ # 進捗記録と作業ファイル +├── .env.example # 環境変数テンプレート +├── main.py # エントリーポイントファイル +├── manifest.yaml # プラグインメイン設定 +├── README.md # ドキュメント +└── requirements.txt # 依存関係リスト +``` + +### ファイル配置と編成原則 + +1. **Pythonファイルの配置ガイダンス**: + * ユーザーが単一のPythonファイルを提供した場合、まずその機能の性質を確認する必要があります + * 共通ツール関数は `utils/` ディレクトリに配置する必要があります + * 具体的なツール実装は `tools/` ディレクトリに配置する必要があります + * 認証情報検証ロジックは `provider/` ディレクトリに配置する必要があります + +2. **ゼロから書くのではなくコードをコピーする**: + * 新しいファイルを作成する際は、既存ファイルをテンプレートとしてコピーし、その後修正することを優先します + * `cp tools/existing_tool.py tools/new_tool.py` のようなコマンドを使用します + * これにより、ファイル形式と構造がフレームワーク要件に適合することが保証されます + +3. **フレームワークの一貫性を保つ**: + * ファイル構造を任意に変更しないでください + * フレームワークで定義されていない新しいファイルタイプを追加しないでください + * 既存の命名規則に従ってください + +## 主要ファイル設定詳解 + +### manifest.yaml +プラグインのメイン設定ファイルで、プラグインの基本情報とメタデータを定義します。以下の重要な原則に従ってください: + +1. **既存内容の保持**: + * 設定ファイル内の既存項目、特にi18n関連部分を削除しないでください + * 実際に既存のコードを基準に修正・追加を行ってください + +2. **主要フィールドガイダンス**: + * **name**: このフィールドは変更しないでください。プラグインの一意な識別子です + * **label**: 多言語表示名を充実させることをお勧めします + * **description**: 多言語説明を充実させることをお勧めします + * **tags**: 以下の事前定義されたタグのみ使用できます(各プラグインは最も関連性の高いタグを1~2個選択できます): + ``` + 'search', 'image', 'videos', 'weather', 'finance', 'design', + 'travel', 'social', 'news', 'medical', 'productivity', + 'education', 'business', 'entertainment', 'utilities', 'other' + ``` + +3. **構造の安定性保持**: + * 特別な要件がない限り、`resource`、`meta`、`plugins` などの部分を変更しないでください + * `type` や `version` などの基本フィールドを変更しないでください + +```yaml +version: 0.0.1 +type: plugin +author: your_name +name: your_plugin_name # このフィールドは変更しないでください +label: + en_US: Your Plugin Display Name + ja: あなたのプラグイン表示名 +description: + en_US: Detailed description of your plugin functionality + ja: プラグイン機能の詳細な説明 +icon: icon.svg +resource: + memory: 268435456 # 256MB + permission: {} +plugins: + tools: + - provider/your_plugin.yaml +meta: + version: 0.0.1 + arch: + - amd64 + - arm64 + runner: + language: python + version: "3.12" + entrypoint: main +created_at: 2025-04-19T00:00:00.000000+08:00 +privacy: PRIVACY.md +tags: + - utilities # 事前定義されたタグのみ使用 +``` + +### provider/your_plugin.yaml +プロバイダー設定ファイルで、プラグインが必要とする認証情報とツールリストを定義します: + +1. **主要識別子の保持**: + * **name**: このフィールドは変更しないでください。manifest.yamlのnameと一致させてください + * 既存のi18n設定と構造を保持してください + +2. **表示情報の充実**: + * **label**: 多言語表示名を充実させることをお勧めします + * **description**: 多言語説明を充実させることをお勧めします + +3. **新規ツールの追加**: + * `tools` リストに新しいツールYAMLファイルへの参照を追加します + * パスが正しいことを確認してください: `tools/feature_name.yaml` + +```yaml +identity: + author: your_name + name: your_plugin_name # このフィールドは変更しないでください + label: + en_US: Your Plugin Display Name + ja: あなたのプラグイン表示名 + description: + en_US: Detailed description of your plugin functionality + ja: プラグイン機能の詳細な説明 + icon: icon.svg +credentials_for_provider: # APIキーなどの認証情報が必要な場合のみ追加 + api_key: + type: secret-input + required: true + label: + en_US: API Key + ja: APIキー + placeholder: + en_US: Enter your API key + ja: APIキーを入力してください + help: + en_US: How to get your API key + ja: APIキーの取得方法 + url: https://example.com/get-api-key +tools: # ツールリスト、新しいツールを追加する際にここで更新 + - tools/feature_one.yaml + - tools/feature_two.yaml +extra: + python: + source: provider/your_plugin.py +``` + +### tools/feature.yaml +ツール設定ファイルで、ツールのパラメータと説明を定義します: + +1. **識別子と構造の保持**: + * **name**: ツールのユニークな識別子で、ファイル名に対応します + * 既存のファイル構造と一致させてください + +2. **設定内容の充実**: + * **label** と **description**: 明確な多言語表示内容を提供します + * **parameters**: ツールパラメータとその属性を詳細に定義します + +3. **パラメータ定義ガイダンス**: + * **type**: 適切なパラメータタイプ(string/number/boolean/file)を選択します + * **form**: `llm`(AIが抽出)または `form`(UI設定)に設定します + * **required**: 必須パラメータかどうかを明確にします + +```yaml +identity: + name: feature_name # ファイル名に対応 + author: your_name + label: + en_US: Feature Display Name + ja: 機能表示名 +description: + human: # 人間ユーザー向けの説明 + en_US: Description for human users + ja: ユーザー向け機能説明 + llm: Description for AI models to understand when to use this tool. # AI向けの説明 +parameters: # パラメータ定義 + - name: param_name + type: string # string, number, boolean, fileなど + required: true + label: + en_US: Parameter Display Name + ja: パラメータ表示名 + human_description: + en_US: Parameter description for users + ja: ユーザー向けパラメータ説明 + llm_description: Detailed parameter description for AI models + form: llm # llmはAIがユーザー入力から抽出可能、formはUIで設定が必要 + # その他のパラメータ... +extra: + python: + source: tools/feature.py # 対応するPython実装ファイル +# オプション:出力のJSON Schemaを定義 +output_schema: + type: object + properties: + result: + type: string + description: Description of the result +``` + +### tools/feature.py +ツール実装クラスで、コアビジネスロジックを含みます: + +1. **クラス名とファイル名の対応**: + * クラス名は `FeatureTool` パターンに従い、ファイル名に対応します + * 1ファイルに**1つだけ**Toolサブクラスがあることを確認してください + +2. **パラメータ処理のベストプラクティス**: + * 必須パラメータには、`.get()` メソッドを使用し、デフォルト値を提供します: `param = tool_parameters.get("param_name", "")` + * オプションパラメータには、2つの処理方法があります: + + ```python + # 方法1: .get()メソッドを使用(単一パラメータに推奨) + optional_param = tool_parameters.get("optional_param") # 存在しない場合はNoneを返す + + # 方法2: try-exceptを使用(複数のオプションパラメータを処理) + try: + name = tool_parameters["name"] + issuer_name = tool_parameters["issuer_name"] + except KeyError: + name = None + issuer_name = None + ``` + + * このtry-except方式は、現在複数のオプションパラメータを処理するための暫定的な解決策です + * パラメータを使用する前に、常にその存在と有効性を検証してください + +3. **出力方法**: + * `yield` を使用して様々なタイプのメッセージを返します + * テキスト、JSON、リンク、変数出力をサポートします + +```python +from collections.abc import Generator +from typing import Any + +from dify_plugin import Tool +from dify_plugin.entities.tool import ToolInvokeMessage + +# ツール関数をインポートし、関数名のスペルが正しいことを確認する +from utils.helpers import process_data + +class FeatureTool(Tool): + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: + try: + # 1. 必須パラメータを取得 + param = tool_parameters.get("param_name", "") + + # 2. オプションパラメータを取得 - try-except方式を使用 + try: + optional_param1 = tool_parameters["optional_param1"] + optional_param2 = tool_parameters["optional_param2"] + except KeyError: + optional_param1 = None + optional_param2 = None + + # 別のオプションパラメータ取得方法 - .get()メソッドを使用 + another_optional = tool_parameters.get("another_optional") # 存在しない場合はNoneを返す + + # 3. 必須パラメータを検証 + if not param: + yield self.create_text_message("Parameter is required.") + return + + # 4. ビジネスロジックを実装 + result = self._process_data(param, optional_param1, optional_param2) + + # 5. 結果を返す + # テキスト出力 + yield self.create_text_message(f"Processed result: {result}") + # JSON出力 + yield self.create_json_message({"result": result}) + # 変数出力 (ワークフロー用) + yield self.create_variable_message("result_var", result) + + except Exception as e: + # エラー処理 + yield self.create_text_message(f"Error: {str(e)}") + + def _process_data(self, param: str, opt1=None, opt2=None) -> str: + """ + 具体的なビジネスロジックを実装 + + Args: + param: 必須パラメータ + opt1: オプションパラメータ1 + opt2: オプションパラメータ2 + + Returns: + 処理結果 + """ + # パラメータの存在に応じて異なるロジックを実行 + if opt1 and opt2: + return f"Processed with all options: {param}, {opt1}, {opt2}" + elif opt1: + return f"Processed with option 1: {param}, {opt1}" + elif opt2: + return f"Processed with option 2: {param}, {opt2}" + else: + return f"Processed basic: {param}" +``` + +### utils/helper.py +補助関数で、再利用可能な機能ロジックを実装します: + +1. **機能分離**: + * 共通機能を個別の関数として抽出します + * 単一責任に集中します + * 関数命名の一貫性に注意してください(インポートエラーを避けるため) + +2. **エラー処理**: + * 適切な例外処理を含めます + * 明確な例外タイプを使用します + * 意味のあるエラーメッセージを提供します + +```python +import requests +from typing import Dict, Any, Optional + +def call_external_api(endpoint: str, params: Dict[str, Any], api_key: str) -> Dict[str, Any]: + """ + 外部APIを呼び出す汎用関数 + + Args: + endpoint: APIエンドポイントURL + params: リクエストパラメータ + api_key: APIキー + + Returns: + APIレスポンスのJSONデータ + + Raises: + Exception: API呼び出しが失敗した場合 + """ + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json" + } + + try: + response = requests.get(endpoint, params=params, headers=headers, timeout=10) + response.raise_for_status() # ステータスコードが200でない場合、例外を発生させる + return response.json() + except requests.RequestException as e: + raise Exception(f"API呼び出し失敗: {str(e)}") +``` + +### requirements.txt +依存関係リストで、プラグインが必要とするPythonライブラリを指定します: + +1. **バージョン規約**: + * `~=` を使用して依存関係のバージョン範囲を指定します + * 緩すぎるバージョン要件を避けます + +2. **必須依存関係**: + * `dify_plugin` を必ず含めます + * プラグイン機能に必要なすべてのサードパーティライブラリを追加します + +``` +dify_plugin~=0.0.1b76 +requests~=2.31.0 +# その他の依存関係... +``` + +## ツール開発のベストプラクティス + +### 1. パラメータ処理パターン + +1. **必須パラメータ処理**: + * `.get()` メソッドを使用し、デフォルト値を提供します: `param = tool_parameters.get("param_name", "")` + * パラメータの有効性を検証します: `if not param: yield self.create_text_message("Error: Required parameter missing.")` + +2. **オプションパラメータ処理**: + * **単一のオプションパラメータ**: `.get()` メソッドを使用し、Noneが返されることを許可します: `optional = tool_parameters.get("optional_param")` + * **複数のオプションパラメータ**: try-exceptパターンを使用してKeyErrorを処理します: + ```python + try: + param1 = tool_parameters["optional_param1"] + param2 = tool_parameters["optional_param2"] + except KeyError: + param1 = None + param2 = None + ``` + * このtry-except方式は、現在複数のオプションパラメータを処理するための暫定的な解決策です + +3. **パラメータ検証**: + * 必須パラメータを検証します: `if not required_param: return error_message` + * オプションパラメータを条件付きで処理します: `if optional_param: do_something()` + +### 2. 安全なファイル操作方法 + +1. **ローカルファイルの読み書きを避ける**: + * Difyプラグインはサーバーレス環境(AWS Lambdaなど)で実行されるため、ローカルファイルシステムの操作は信頼できない場合があります + * `open()`、`read()`、`write()` などの直接的なファイル操作を使用しないでください + * 状態保存のためにローカルファイルに依存しないでください + +2. **メモリまたはAPIで代替する**: + * 一時データはメモリ変数に保存します + * 永続データはDifyが提供するKVストアAPIを使用します + * 大量のデータについては、データベースまたはクラウドストレージサービスの使用を検討します + +### 3. ゼロから作成するのではなく既存ファイルをコピーする + +構造の正しさが不確かな場合は、以下の方法を強くお勧めします: + +```bash +# ツールYAMLファイルをテンプレートとしてコピー +cp tools/existing_tool.yaml tools/new_tool.yaml + +# ツールPython実装をコピー +cp tools/existing_tool.py tools/new_tool.py + +# providerファイルも同様 +cp provider/existing.yaml provider/new.yaml +``` + +これにより、ファイル構造と形式がDifyプラグインフレームワークの要件に適合することが保証され、その後で具体的な変更を行うことができます。 + +### 4. ツール機能の分割 +複雑な機能を複数の単純なツールに分割し、各ツールは単一の機能に集中します: + +``` +tools/ +├── search.py # 検索機能 +├── search.yaml +├── create.py # 作成機能 +├── create.yaml +├── update.py # 更新機能 +├── update.yaml +├── delete.py # 削除機能 +└── delete.yaml +``` + +### 2. パラメータ設計原則 +- **必要性**: 必要なパラメータのみを要求し、適切なデフォルト値を提供します +- **型定義**: 適切なパラメータ型(string/number/boolean/file)を選択します +- **明確な説明**: 人間とAIの両方に明確なパラメータ説明を提供します +- **フォーム定義**: llm(AI抽出)とform(UI設定)パラメータを正しく区別します + +### 3. エラー処理 +```python +try: + # 操作を実行しようとする + result = some_operation() + yield self.create_text_message("操作成功") +except ValueError as e: + # パラメータエラー + yield self.create_text_message(f"パラメータエラー: {str(e)}") +except requests.RequestException as e: + # API呼び出しエラー + yield self.create_text_message(f"API呼び出し失敗: {str(e)}") +except Exception as e: + # その他の予期しないエラー + yield self.create_text_message(f"エラーが発生しました: {str(e)}") +``` + +### 4. コードの整理と再利用 +再利用可能なロジックをutilsディレクトリに抽出します: +```python +# ツール実装内 +from utils.api_client import ApiClient + +class SearchTool(Tool): + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: + client = ApiClient(self.runtime.credentials["api_key"]) + results = client.search(tool_parameters["query"]) + yield self.create_json_message(results) +``` + +### 5. 出力形式 +Difyは複数の出力形式をサポートしています: +```python +# テキスト出力 +yield self.create_text_message("これはテキストメッセージです") + +# JSON出力 +yield self.create_json_message({"key": "value"}) + +# リンク出力 +yield self.create_link_message("https://example.com") + +# 変数出力 (ワークフロー用) +yield self.create_variable_message("variable_name", "variable_value") +``` + +## よくあるエラーとその解決策 + +### ロードと初期化エラー + +1. **複数のToolサブクラスエラー** + ``` + Exception: Multiple subclasses of Tool in /path/to/file.py + ``` + - **原因**: 同じPythonファイル内でToolから継承したクラスが複数定義されている + - **解決策**: + - ファイル内容を確認: `cat tools/problematic_file.py` + - 各ファイルにはファイル名に対応するToolサブクラスを1つだけ保持する + - 他のToolサブクラスを対応する個別のファイルに移動する + +2. **インポートエラー** + ``` + ImportError: cannot import name 'x' from 'module'. Did you mean: 'y'? + ``` + - **原因**: インポートされた関数名が実際の定義と一致しない + - **解決策**: + - utils内の関数名を確認: `cat utils/the_module.py` + - インポート文のスペルミスを修正する + - 関数名内のアンダースコア、大文字小文字などに注意する + +3. **認証情報検証失敗** + ``` + ToolProviderCredentialValidationError: Invalid API key + ``` + - **原因**: 認証情報検証ロジックが失敗した + - **解決策**: + - `_validate_credentials` メソッドの実装を確認する + - APIキーの形式が正しいことを確認する + - 詳細なエラーヒント情報を追加する + +### ランタイムエラー + +1. **パラメータ取得エラー** + ``` + KeyError: 'parameter_name' + ``` + - **原因**: 存在しないパラメータにアクセスしようとした + - **解決策**: + - 直接インデックスする代わりに `get()` を使用する: `param = tool_parameters.get("param_name", "")` + - パラメータ名がYAML定義と一致することを確認する + - パラメータ存在チェックを追加する + +2. **API呼び出しエラー** + ``` + requests.exceptions.RequestException: Connection error + ``` + - **原因**: 外部API呼び出しが失敗した + - **解決策**: + - タイムアウトパラメータを追加する: `timeout=10` + - `try/except` を使用して例外をキャッチする + - リトライロジックを実装する + +3. **実行タイムアウト** + ``` + TimeoutError: Function execution timed out + ``` + - **原因**: 操作に時間がかかりすぎた + - **解決策**: + - API呼び出しを最適化する + - 複雑な操作を複数のステップに分解する + - 適切なタイムアウト制限を設定する + +### 設定とパッケージ化エラー + +1. **YAML形式エラー** + ``` + yaml.YAMLError: mapping values are not allowed in this context + ``` + - **原因**: YAML形式が正しくない + - **解決策**: + - インデントを確認する(タブではなくスペースを使用する) + - コロンの後にスペースがあることを確認する + - YAMLバリデータを使用して確認する + +2. **パッケージ化失敗** + ``` + Error: Failed to pack plugin + ``` + - **原因**: ファイル構造または依存関係の問題 + - **解決策**: + - manifest.yamlの設定を確認する + - 参照されているすべてのファイルが存在することを確認する + - requirements.txtの内容を確認する + +## コード例:TOTPツール + +以下は、TOTP (Time-based One-Time Password) プラグインの完全な例で、良好なコード構成とベストプラクティスを示しています: + +### utils/totp_verify.py +```python +import pyotp +import time + +def verify_totp(secret_key, totp_code, offset=5, strict=False): + """ + 時間ベースのワンタイムパスワード(TOTP)を検証します。 + + Args: + secret_key: TOTP生成に使用されるキーまたは設定URL + totp_code: ユーザーが送信した動的トークン + offset: 早期または遅延検証を許可する秒数 + strict: 厳密な検証を使用するかどうか(正確な一致の場合のみ成功を返す) + + Returns: + 以下の内容を含む辞書: + - 'status': 'success' または 'fail' + - 'detail': 内部メッセージ(エンドユーザー向けではない) + """ + try: + # 設定URLかどうかを検出 + if secret_key.startswith('otpauth://'): + totp = pyotp.parse_uri(secret_key) + else: + totp = pyotp.TOTP(secret_key) + + current_time = time.time() + + # 正確な時間検証 + if totp.verify(totp_code): + return {'status': 'success', 'detail': 'Token is valid'} + + # オフセット検証 + early_valid = totp.verify(totp_code, for_time=current_time + offset) + late_valid = totp.verify(totp_code, for_time=current_time - offset) + off_time_valid = early_valid or late_valid + + detail_message = ( + f"Token is valid but not on time. " + f"{'Early' if early_valid else 'Late'} within {offset} seconds" + if off_time_valid else + "Token is invalid" + ) + + if strict: + return {'status': 'fail', 'detail': detail_message} + else: + return ( + {'status': 'success', 'detail': detail_message} + if off_time_valid + else {'status': 'fail', 'detail': detail_message} + ) + except Exception as e: + return {'status': 'fail', 'detail': f'Verification error: {str(e)}'} +``` + +### tools/totp.yaml +```yaml +identity: + name: totp + author: alterxyz + label: + en_US: TOTP Validator + ja: TOTP 検証ツール +description: + human: + en_US: Time-based one-time password (TOTP) validator + ja: 時間ベースのワンタイムパスワード (TOTP) 検証ツール + llm: Time-based one-time password (TOTP) validator, this tool is used to validate a 6 digit TOTP code with a secret key or provisioning URI. +parameters: + - name: secret_key + type: string + required: true + label: + en_US: TOTP secret key or provisioning URI + ja: TOTP シークレットキーまたはプロビジョニングURI + human_description: + en_US: The secret key or provisioning URI used to generate the TOTP + ja: TOTP生成に使用されるシークレットキーまたはプロビジョニングURI + llm_description: The secret key or provisioning URI (starting with 'otpauth://') used to generate the TOTP, this is highly sensitive and should be kept secret. + form: llm + - name: user_code + type: string + required: true + label: + en_US: 6 digit TOTP code to validate + ja: 検証する6桁のTOTPコード + human_description: + en_US: 6 digit TOTP code to validate + ja: 検証する6桁のTOTPコード + llm_description: 6 digit TOTP code to validate + form: llm +extra: + python: + source: tools/totp.py +output_schema: + type: object + properties: + True_or_False: + type: string + description: Whether the TOTP is valid or not, return in string format, "True" or "False". +``` + +### tools/totp.py +```python +from collections.abc import Generator +from typing import Any + +# 正しくツール関数をインポート +from utils.totp_verify import verify_totp + +from dify_plugin import Tool +from dify_plugin.entities.tool import ToolInvokeMessage + +# 1ファイルに1つのToolサブクラスのみを含む +class TotpTool(Tool): + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: + """時間ベースのワンタイムパスワード(TOTP)を検証します""" + # パラメータを取得、KeyErrorを避けるためにget()を使用 + secret_key = tool_parameters.get("secret_key") + totp_code = tool_parameters.get("user_code") + + # パラメータ検証 + if not secret_key: + yield self.create_text_message("Error: Secret key is required.") + return + if not totp_code: + yield self.create_text_message("Error: TOTP code is required.") + return + + try: + # ツール関数を呼び出し + result = verify_totp(secret_key, totp_code) + + # 結果を返す + yield self.create_json_message(result) + + # 検証結果に基づいて異なるメッセージを返す + if result["status"] == "success": + yield self.create_text_message("Valid") + yield self.create_variable_message("True_or_False", "True") + else: + yield self.create_text_message("Invalid") + yield self.create_variable_message("True_or_False", "False") + + except Exception as e: + # エラー処理 + yield self.create_text_message(f"Verification error: {str(e)}") +``` + +### tools/secret_generator.py +```python +from collections.abc import Generator +from typing import Any + +import pyotp + +from dify_plugin import Tool +from dify_plugin.entities.tool import ToolInvokeMessage + +# 注意:1ファイルに1つのToolサブクラスのみを含む +class SecretGenerator(Tool): + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: + """TOTPキーを生成します""" + try: + # ランダムキーを生成 + secret_key = pyotp.random_base32() + yield self.create_text_message(secret_key) + + # オプションパラメータを安全に取得 + name = tool_parameters.get("name") + issuer_name = tool_parameters.get("issuer_name") + + # 名前または発行者が提供されている場合、設定URIを生成 + if name or issuer_name: + provisioning_uri = pyotp.totp.TOTP(secret_key).provisioning_uri( + name=name, + issuer_name=issuer_name + ) + yield self.create_variable_message("provisioning_uri", provisioning_uri) + + except Exception as e: + yield self.create_text_message(f"Error generating secret: {str(e)}") +``` + +### requirements.txt +``` +dify_plugin~=0.0.1b76 +pyotp~=2.9.0 +``` + +この例は以下を示しています: +- 明確な機能分離(utils内のツール関数、tools内のツールクラス) +- 良好なエラー処理とパラメータ検証 +- 1ファイルに1つのToolサブクラスのみ +- 詳細なコメントとドキュメント文字列 +- 精巧に設計されたYAML設定 + +## 状態同期メカニズム + +ユーザーの説明があなたが記録したプロジェクトの状態と異なる場合、または現在の進捗を確認する必要がある場合は、以下の操作を実行してください: + +1. プロジェクトのファイル構造を確認します +2. 主要なファイルを読みます +3. ユーザーに明確に伝えます:「プロジェクトの状態が以前の私の理解と異なる可能性があることに気づきました。プロジェクトファイルを再確認し、私の認識を更新しました。」 +4. あなたが発見した実際の状態を説明します +5. workingディレクトリの進捗記録を更新します + +## 初回起動時の動作 + +ユーザーが「@ai」または同様の方法で初めてあなたをアクティベートしたとき、あなたは次のことを行うべきです: + +1. **プロジェクトの目標を仮定しない**: ユーザーがどのようなタイプのプラグインや機能を開発したいかを勝手に仮定しないでください +2. **コードの記述を開始しない**: 明確な指示なしにコードの生成や修正を開始しないでください +3. **ユーザーの意図を尋ねる**: ユーザーがどのようなタイプのプラグインを開発したいか、どのような問題を解決する手助けが必要か、丁寧に尋ねてください +4. **能力の概要を提供する**: あなたが提供できるヘルプの種類(コード実装、デバッグ、設計アドバイスなど)を簡単に説明してください +5. **プロジェクト情報を要求する**: より具体的なヘルプを提供できるように、現在のプロジェクトの状態やファイル構造を共有するようユーザーに依頼してください + +明確な指示を受けた後でのみ、具体的な開発アドバイスやコード実装の提供を開始してください。 + +あなたの主な目標は、状態を継続的に追跡し、専門的なアドバイスを提供し、技術的な課題を解決することを通じて、ユーザーがDifyプラグイン開発を効率的に完了できるよう支援することであることを忘れないでください。 \ No newline at end of file diff --git a/plugin_dev_ja/0211-getting-started-dify-tool.zh.mdx b/plugin_dev_ja/0211-getting-started-dify-tool.zh.mdx new file mode 100644 index 00000000..9d4e486b --- /dev/null +++ b/plugin_dev_ja/0211-getting-started-dify-tool.zh.mdx @@ -0,0 +1,541 @@ +--- +dimensions: + type: + primary: implementation + detail: basic + level: beginner +standard_title: Getting Started Dify Tool +language: ja +title: Dify プラグイン開発:Hello World ガイド +description: このドキュメントでは、Telegraph公開プラグインの作成を例に、ゼロからのDifyプラグイン開発に関する詳細なチュートリアルを提供します。環境準備、プロジェクト初期化、仮想環境設定、プラグインコアロジック開発、ローカル実行デバッグ、プラグインメタ情報整備、パッケージ化と公開といった手順が含まれます。 +--- + +Dify プラグイン開発入門ガイドへようこそ!このチュートリアルは、基本的なプログラミング知識と Dify プラットフォームの使用経験があることを前提としています。このチュートリアルを完了すると、簡単な Dify プラグインを作成するための基本的な流れを習得し、Dify ユーザーからプラグイン貢献者へとステップアップできます。 + +Telegraph 公開プラグインを例に、簡単なプラグインの開発方法をステップバイステップで示します。このチュートリアルでは、主にコマンドライン操作を使用し、VSCode をコードエディタとして使用する場合の例も示します。**まず全体的な流れを理解するために一度ざっと目を通し、その後、手順に従って実際に操作することをお勧めします。** + +> **LLM 向けの説明:** ここで私たちが作成したプロンプトを使用して、プラグイン開発を支援することもできます:[Dify プラグイン開発:プロンプト](/plugin_dev_ja/0211-getting-started-by-prompt.ja) + +始める前に、いつでも私たちが提供する [**開発者向けチートシート (Cheatsheet)**](/plugin_dev_ja/0131-cheatsheet.ja) でよく使われるコマンドや情報を確認したり、より複雑な問題に遭遇した場合には完全な開発者ドキュメントを参照したりすることができます。 + +## 1. 開発環境の準備 + +Dify プラグインの開発を始める前に、お使いの環境に以下のツールが準備されていることを確認してください: + +- **Dify プラグイン開発 CLI (CLI):** これはプラグインの開発、デバッグ、パッケージ化を行うための中核となるツールで、`dify-plugin-daemon` または「プラグイン開発SDK」とも呼ばれます。 +- **Python 環境:** Python 3.12 以降が必要です。 + +### 1.1 Dify プラグイン開発 CLI のインストール + +> より詳細な開発環境準備ガイドについては、[開発ツールの初期化](/plugin_dev_ja/0221-initialize-development-tools.ja) を参照してください。 + +1. **ダウンロード:** [Dify Plugin CLI Releases](https://github.com/langgenius/dify-plugin-daemon/releases) ページにアクセスします。お使いのオペレーティングシステム(Windows, macOS Intel/ARM, Linux)に対応する最新バージョンのバイナリファイルをダウンロードします。 +2. **実行権限の設定 (macOS / Linux):** + + - **以下の手順は macOS (Apple Silicon / M シリーズチップ) を例としています**。ダウンロードしたファイル名を `dify-plugin-darwin-arm64` と仮定します。ターミナルで、ファイルが存在するディレクトリに移動し、以下のコマンドを実行して実行権限を付与します: + + ```bash + chmod +x dify-plugin-darwin-arm64 + ``` + + - Linux ユーザーの場合は、対応する Linux バージョンのファイルをダウンロードし、同様の `chmod +x ` コマンドを実行してください。 + - Windows ユーザーの場合は、`.exe` ファイルをダウンロードした後、通常は直接実行できます。 + +3. **インストールの確認:** + + - ターミナルで、以下のコマンドを実行してツールが正常に動作するか確認します(`./dify-plugin-darwin-arm64` をダウンロードした実際のファイル名またはパスに置き換えてください): + + ```bash + ./dify-plugin-darwin-arm64 version + ``` + + - ターミナルにバージョン情報(例:`v0.0.1-beta.15`)が正常に出力されれば、インストールは成功です。 + +> **ヒント (Tips):** +> +> - **macOS のセキュリティ警告:** macOS で初めて実行する際に「Apple は検証できません」または「開けません」と表示された場合は、「システム設定」→「プライバシーとセキュリティ」→「セキュリティ」セクションに移動し、関連するメッセージを見つけて「それでも開く」または「許可」をクリックしてください。 +> - **コマンドの簡略化:** ダウンロードしたバイナリファイル名を短い名前(例:`dify` や `dify-plugin`)に変更すると、後の使用が便利になります。例:`mv dify-plugin-darwin-arm64 dify`、その後は `./dify version` で使用できます。 +> - **グローバルインストール(オプション):** システムのどのパスからでも直接コマンドを実行できるようにしたい場合(例:`./dify` ではなく直接 `dify` と入力する)、名前変更後のファイルをシステムの `PATH` 環境変数に含まれるディレクトリ(例:`/usr/local/bin` (macOS/Linux))に移動するか、Windows の環境変数に追加します。 +> - 例 (macOS/Linux): `sudo mv dify /usr/local/bin/` +> - 設定完了後、ターミナルで直接 `dify version` と入力すると、バージョン番号が正常に出力されるはずです。 + +**便宜上、このドキュメントでは以降、Dify プラグイン開発 CLI コマンドの例として `./dify` を使用します。ご自身の実際の状況に合わせてコマンドを置き換えてください。** + +## 2. プラグインプロジェクトの初期化 + +それでは、CLI ツールを使用して新しいプラグインプロジェクトを作成しましょう。 + +1. ターミナルを開き、初期化コマンドを実行します: + + ```bash + ./dify plugin init + ``` + +2. プロンプトに従って、プラグインの基本情報を順に入力します: + - **Plugin name:** プラグインの一意の識別子。例:`telegraph` + - _制約: 長さ 1~128 文字、小文字の英数字、ハイフン(-)、アンダースコア(_)のみ使用可能。_ + - **Author:** プラグイン作者の識別子。例:`alterxyz` + - _制約: 長さ 1~64 文字、小文字の英数字、ハイフン(-)、アンダースコア(_)のみ使用可能。_ + - **Description:** プラグイン機能の簡単な説明。例:`A Telegraph plugin that allows you to publish your content easily` +3. **開発言語の選択:** `Select language` と表示されたら、`python` を選択してください。 +4. **プラグインタイプの選択:** `Select plugin type` と表示されたら、このチュートリアルでは `tool` を選択してください。 +5. **追加機能の選択:** 次に、プロバイダー認証、永続ストレージなどの追加機能が必要かどうか尋ねられます。この簡単な Hello World プラグインでは、これらは一時的に不要なので、成功メッセージが表示されるまで **Enter キー** を押してすべてのオプションをスキップできます。 +6. **作成成功の確認:** ターミナルに以下のような情報が出力されたら、プラグインプロジェクトは正常に作成されています: + + ```bash + [INFO] plugin telegraph created successfully, you can refer to `telegraph/GUIDE.md` for more information about how to develop it + ``` + +これで、現在のディレクトリに `telegraph`(または指定したプラグイン名)という名前の新しいフォルダが表示され、これがプラグインプロジェクトになります。 + +## 3. Python 仮想環境と依存関係の設定 + +プロジェクトの依存関係を分離するために、Python 仮想環境の使用を推奨します。 + +### 3.1 仮想環境の作成とアクティベート(コマンドライン方式) + +これは**推奨される一般的な**方法で、特定の IDE に依存しません: + +1. **プロジェクトディレクトリへの移動:** + + ```bash + cd telegraph + ``` + +2. **仮想環境の作成:** (名前を `venv` にすることを推奨します) + + ```bash + python -m venv venv + ``` + +3. **仮想環境のアクティベート:** + + - **macOS / Linux:** + + ```bash + source venv/bin/activate + ``` + + - **Windows (cmd.exe):** + + ```bash + venv\Scripts\activate.bat + ``` + + - **Windows (PowerShell):** + + ```bash + venv\Scripts\Activate.ps1 + ``` + + - アクティベートに成功すると、通常、ターミナルのプロンプトの前に `(venv)` と表示されます。 + +### 3.2 基本的な依存関係のインストール + +プロジェクト初期化時に生成された `requirements.txt` ファイルには、プラグイン開発に必要な基本ライブラリ `dify_plugin` が含まれています。仮想環境をアクティベートした後、以下のコマンドを実行してインストールします: + +```bash +pip install -r requirements.txt +``` + +### 3.3 (オプション) VSCode 統合環境設定 + +VSCode をコードエディタとして使用している場合、その統合機能を利用して Python 環境を管理できます: + +1. **プロジェクトフォルダを開く:** 作成した `telegraph` フォルダを VSCode で開きます。 +2. **Python インタープリタの選択:** + - コマンドパレットを開きます (macOS: `Cmd+Shift+P`, Windows/Linux: `Ctrl+Shift+P`)。 + - `Python: Select Interpreter` と入力して選択します。 + - 表示されたリストから、先ほど作成した仮想環境内の Python インタープリタを選択します(通常、パスには `.venv/bin/python` または `venv\Scripts\python.exe` が含まれます)。リストに自動的に表示されない場合は、`Enter interpreter path...` を選択して手動で検索できます。 + - _(ローカルの対応するスクリーンショットを参照してください。インタープリタ選択画面が表示されています)_ +3. **依存関係のインストール (VSCode が提示した場合):** VSCode が `requirements.txt` ファイルを検出し、その中の依存関係をインストールするよう促すことがあります。プロンプトが表示されたら、インストールを確認してください。 + - _(ローカルの対応するスクリーンショットを参照してください。依存関係のインストール確認画面が表示されています)_ + +**以降のすべての `pip install` コマンドと `python -m main` の実行操作は、アクティベートされた仮想環境で実行してください。** + +## 4. プラグインコアロジックの開発 + +それでは、プラグインのコードを記述していきましょう。この例では、指定されたコンテンツを Telegraph に公開するための簡単なツールを実装します。 + +### 4.1 サンプル依存ライブラリ: `your-telegraph` + +`your-telegraph` という名前の Python ライブラリを使用して Telegraph API とやり取りします。(_これは仮のライブラリ名です。実際に使用するライブラリが有効であることを確認してください_)。 + +> `your-telegraph` は、Telegraph API の操作を簡略化する Python ラッパーで、数行のコードで簡単にコンテンツを公開できます。 + +その基本的な使い方は以下のようになるかもしれません: + +```python +# サンプルコードであり、プラグイン内のコードではありません +from ytelegraph import TelegraphAPI + +# access_token が必要です +ph = TelegraphAPI(access_token="your_telegraph_access_token") + +# ページを作成してリンクを取得 +ph_link = ph.create_page_md("My First Page", "# Hello, Telegraph!\n\nThis is my first Telegraph page.") +print(ph_link) +``` + +私たちの目標は、Dify プラグインで同様の機能を実現することです。 + +### 4.2 プロジェクト依存関係の追加と設定 + +1. **依存ライブラリのインストール:** 仮想環境がアクティベートされていることを確認し、ターミナルで以下を実行します: + + ```bash + pip install your-telegraph + ``` + +2. **`requirements.txt` の更新:** プロジェクトルートディレクトリにある `telegraph/requirements.txt` ファイルを開き、`dify_plugin` の下に、先ほどインストールしたライブラリ名を一行追加します: + + ```plaintext + dify_plugin + your-telegraph + ``` + + これにより、他の開発者やデプロイ環境がすべての必要な依存関係を簡単にインストールできるようになります。 + +### 4.3 プロバイダー認証情報の設定 + +この例では `telegraph_access_token` が必要です。プロバイダー設定でこの認証情報を定義し、ユーザーがプラグインを使用する際に入力できるようにする必要があります。プロバイダー設定の詳細については、[一般仕様定義](/plugin_dev_ja/0411-general-specifications.ja) を参照してください。 + +1. **プロバイダー YAML の編集:** `telegraph/provider/telegraph.yaml` ファイルを開きます。 +2. **`credentials_for_provider` の追加:** ファイルの末尾(または適切な場所)に以下の内容を追加します: + + ```yaml + # ... (ファイルに既にある identity, name, label, description, icon などは変更しません) ... + + credentials_for_provider: + telegraph_access_token: # これは認証情報の内部名で、Python コードで使用されます + type: secret-input # 入力タイプはパスワードフィールド + required: true # この認証情報は必須です + label: # Dify UI に表示されるラベル (多言語対応) + en_US: Telegraph Access Token + ja: Telegraph アクセストークン + # ... (その他の言語) + placeholder: # 入力フィールドのプレースホルダーテキスト (多言語対応) + en_US: Enter your Telegraph access token + ja: Telegraph アクセストークンを入力してください + # ... (その他の言語) + help: # ヘルプヒント情報 (多言語対応) + en_US: How to get your Telegraph access token + ja: Telegraph アクセストークンの取得方法 + # ... (その他の言語) + url: https://telegra.ph/api#createAccount # ヘルプヒントをクリックしたときにジャンプする URL + ``` + + - **フィールドの説明:** + - `telegraph_access_token`: 認証情報の一意の識別子。コード内では `self.runtime.credentials["telegraph_access_token"]` を介してユーザーが入力した値にアクセスします。 + - `type: secret-input`: Dify UI 上でパスワード入力フィールドとして表示されます。 + - `required: true`: ユーザーがこのプラグイン提供のツールを使用するには、この認証情報を入力する必要があります。 + - `label`, `placeholder`, `help`: 多言語対応の UI テキストを提供します。 + - `url`: (オプション) 認証情報取得のためのヘルプリンクを提供します。 + +### 4.4 ツール (Tool) ロジックの実装 + +それでは、実際に公開操作を実行するツールのコードを記述しましょう。 + +1. **ツール Python ファイルの編集:** `telegraph/tools/telegraph.py` を開きます。 +2. **`_invoke` メソッドの実装:** ファイルの内容を以下のコードに置き換えます: + + ```python + from collections.abc import Generator + from typing import Any + from ytelegraph import TelegraphAPI # 使用するライブラリをインポート + + from dify_plugin import Tool + from dify_plugin.entities.tool import ToolInvokeMessage + + class TelegraphTool(Tool): + """ + シンプルな Telegraph 公開ツールです。 + """ + + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: + """ + 入力されたタイトルと内容に基づいて、新しい Telegraph ページを作成します。 + + Args: + tool_parameters: ツールの入力パラメータを含む辞書: + - p_title (str): Telegraph ページのタイトル。 + - p_content (str): 公開する Markdown 形式の内容。 + + Yields: + ToolInvokeMessage: 正常に作成された Telegraph ページの URL を含むメッセージ。 + + Raises: + Exception: ページの作成に失敗した場合、エラー情報を含む例外をスローします。 + """ + # 1. ランタイムから認証情報を取得 + try: + access_token = self.runtime.credentials["telegraph_access_token"] + except KeyError: + raise Exception("Telegraph アクセストークンが設定されていないか無効です。プラグイン設定で提供してください。") + + # 2. ツールの入力パラメータを取得 + title = tool_parameters.get("p_title", "Untitled") # .get を使用してデフォルト値を提供 + content = tool_parameters.get("p_content", "") + + if not content: + raise Exception("公開するコンテンツは空にできません。") + + # 3. ライブラリを呼び出して操作を実行 + try: + telegraph = TelegraphAPI(access_token) # Telegraph API を初期化 + ph_link = telegraph.create_page_md(title, content) # ページを作成 + except Exception as e: + # ライブラリ呼び出しに失敗した場合、例外をスロー + raise Exception(f"Telegraph API の呼び出しに失敗しました: {e}") + + # 4. 結果を返す + # create_link_message を使用してリンクを含む出力メッセージを生成 + yield self.create_link_message(ph_link) + ``` + + - **重要なポイント:** + - `self.runtime.credentials` から認証情報を取得します。 + - `tool_parameters` からツールの入力パラメータを取得します(パラメータ名は次のステップの YAML で定義します)。`.get()` を使用する方が堅牢な方法です。 + - `ytelegraph` ライブラリを呼び出して実際の操作を実行します。 + - `try...except` を使用して起こりうるエラーをキャッチし、例外をスローします。 + - `yield self.create_link_message(url)` を使用して URL を含む結果を Dify に返します。 + +### 4.5 ツール (Tool) パラメータの設定 + +このツールがどの入力パラメータを受け取るかを Dify に伝える必要があります。 + +1. **ツール YAML ファイルの編集:** `telegraph/tools/telegraph.yaml` を開きます。 +2. **パラメータの定義:** ファイルの内容を以下のように置き換えるか変更します: + + ```yaml + identity: + name: telegraph_publisher # ツールの一意の内部名 + author: alterxyz + label: # Dify UI に表示されるツール名 (多言語対応) + en_US: Publish to Telegraph + ja: Telegraph に公開 + # ... (その他の言語) + description: + human: # 人間ユーザー向けのツールの説明 (多言語対応) + en_US: Publish content to Telegraph as a new page. + ja: コンテンツを新しいページとして Telegraph に公開します。 + # ... (その他の言語) + llm: # LLM 向けのツールの説明 (Agent モード用) + A tool that takes a title and markdown content, then publishes it as a new page on Telegraph, returning the URL of the published page. Use this when the user wants to publish formatted text content publicly via Telegraph. + parameters: # ツールの入力パラメータリストを定義 + - name: p_title # パラメータの内部名、Python コードのキーに対応 + type: string # パラメータタイプ + required: true # 必須かどうか + label: # Dify UI に表示されるパラメータラベル (多言語対応) + en_US: Post Title + ja: 記事タイトル + human_description: # 人間ユーザー向けのパラメータの説明 (多言語対応) + en_US: The title for the Telegraph page. + ja: Telegraph ページのタイトル。 + llm_description: # LLM 向けのパラメータの説明 (Agent がどのように入力するかを指示) + The title of the post. Should be a concise and meaningful plain text string. + form: llm # パラメータフォームタイプ ('llm' または 'form') + - name: p_content + type: string + required: true + label: + en_US: Content (Markdown) + ja: 内容 (Markdown) + human_description: + en_US: The main content for the Telegraph page, written in Markdown format. + ja: Telegraph ページの主な内容、Markdown 形式で記述してください。 + llm_description: # フォーマット要件を強調することは LLM にとって重要 + The full content to be published on the Telegraph page. Must be provided in Markdown format. Ensure proper Markdown syntax for formatting like headings, lists, links, etc. + form: llm + extra: # 追加設定 + python: + source: tools/telegraph.py # このツールのロジックを実装する Python ファイルを指す + ``` + + - **フィールドの説明:** + - `identity`: ツールの基本情報、`name` は一意の識別子です。 + - `description`: `human` (ユーザー向け) と `llm` (Agent 向け) に分かれます。**`llm` の説明は、Agent がツールを正しく理解して使用できるかどうかにおいて非常に重要です。** + - `parameters`: 各入力パラメータを定義します。 + - `name`: 内部名、Python コードの `tool_parameters.get("...")` のキーと一致する必要があります。 + - `type`: データ型 (例: `string`, `number`, `boolean` など)。 + - `required`: 提供が必須かどうか。 + - `label`, `human_description`, `llm_description`: `identity` の説明と同様ですが、特定のパラメータに対するものです。**`llm_description` は、LLM がそのパラメータの値をどのように生成または取得するか(ここでの Markdown のようなフォーマット要件を含む)を明確に指示する必要があります。** + - `form`: パラメータが Dify でどのように表示され、入力されるかを定義します。`llm` は、そのパラメータ値をユーザーが入力したり、変数を介して渡したり、Agent モードで LLM が自律的に決定したりできることを示します。`form` は通常、ユーザーが UI 上で固定的に入力する必要がある設定項目を示します。ツール入力の場合、`llm` の方が一般的です。 + - `extra.python.source`: このツールのロジックを実装する Python ファイルのパス(プロジェクトルートディレクトリからの相対パス)を示します。 + +### 4.6 プロバイダー認証情報の検証実装(オプションだが推奨) + +ユーザーが提供した認証情報が有効であることを保証するために、検証ロジックを実装すべきです。 + +1. **プロバイダー Python ファイルの編集:** `telegraph/provider/telegraph.py` を開きます。 +2. **`_validate_credentials` メソッドの実装:** ファイルの内容を以下に置き換えます: + + ```python + from typing import Any + from dify_plugin import ToolProvider + from dify_plugin.errors.tool import ToolProviderCredentialValidationError + + class TelegraphProvider(ToolProvider): + def _validate_credentials(self, credentials: dict[str, Any]) -> None: + """ + 提供された Telegraph アクセストークンが有効かどうかを検証します。 + このトークンを使用してテストページを作成しようとします。 + 検証に失敗した場合、ToolProviderCredentialValidationError 例外をスローする必要があります。 + """ + access_token = credentials.get("telegraph_access_token") + if not access_token: + raise ToolProviderCredentialValidationError("Telegraph アクセストークンは空にできません。") + + try: + # 認証情報が必要な簡単な操作を実行して検証を試みる + from ytelegraph import TelegraphAPI + ph = TelegraphAPI(access_token=access_token) + # 検証手段として、一時的で無害なページを作成してみる + # 注意: より良い検証方法は、API の 'getAccountInfo' などの読み取り専用メソッドを呼び出すことです(存在する場合) + test_page = ph.create_page_md("Dify Validation Test", "This is a test page created by Dify plugin validation.") + # 必要であれば、このテストページをすぐに編集または削除することを検討できますが、複雑さが増します + # print(f"Validation successful. Test page created: {test_page}") + except Exception as e: + # API 呼び出しに失敗した場合、認証情報が無効である可能性が高い + raise ToolProviderCredentialValidationError(f"Telegraph 認証情報の検証に失敗しました: {e}") + + ``` + + - **重要なポイント:** + - `credentials` 辞書から認証情報を取得します。 + - その認証情報が必要な API 呼び出しを実行します(アカウント情報の取得など、読み取り専用操作が望ましいです。もしなければ、無害なテストページを作成することもできますが、副作用の可能性に注意してください)。 + - API 呼び出しが成功した場合、例外はスローされず、検証が通過したことを示します。 + - API 呼び出しが失敗した場合、例外をキャッチして `ToolProviderCredentialValidationError` をスローし、元のエラーメッセージを含めます。 + +## 5. ローカルでの実行とデバッグ + +これでプラグインをローカルで実行し、Dify でデバッグできます。 + +1. **`.env` ファイルの準備:** + + - `telegraph` プロジェクトディレクトリにいることを確認してください。 + - 環境変数テンプレートファイルをコピーします: + + ```bash + cp .env.example .env + ``` + + - **`.env` ファイルの編集:** 作成したばかりの `.env` ファイルを開き、Dify 環境情報を入力します: + + ```dotenv + DIFY_API_HOST=https://your-dify-host.com # ご自身の Dify インスタンスアドレスに置き換えてください (例: https://cloud.dify.ai) + DIFY_API_KEY=your-api-key # ご自身の Dify API キーに置き換えてください + ``` + + - **ホストとキーの取得:** Dify 環境にログインし、右上の「プラグイン」アイコンをクリックし、次にデバッグアイコン(または虫のような形)をクリックします。ポップアップウィンドウで、「API キー (Key)」と「ホストアドレス (Host)」をコピーします。 _(ローカルの対応するスクリーンショットを参照してください。キーとホストアドレスの取得画面が表示されています)_ + +2. **ローカルプラグインサービスの起動:** + + - Python 仮想環境がアクティベートされていることを確認してください。 + - `telegraph` ディレクトリで、メインプログラムを実行します: + + ```bash + python -m main + ``` + + - **ターミナル出力の確認:** すべてが正常であれば、以下のようなログ情報が表示され、プラグインツールが正常にロードされ、Dify に接続されたことを示します: + + ```json + {"event": "log", "data": {"level": "INFO", "message": "Installed tool: telegraph_publisher", "timestamp": 1678886400.123456}} + {"event": "log", "data": {"level": "INFO", "message": "Plugin daemon started, waiting for requests...", "timestamp": 1678886400.123457}} + ``` + +3. **Dify での確認とテスト:** + + - **Dify ページを更新:** Dify 環境(ブラウザ)に戻り、プラグイン管理ページ (通常は `https://your-dify-host.com/plugins`) を更新します。 + - **プラグインを検索:** リストに "Telegraph" (またはプロバイダー YAML で定義した `label`) という名前のプラグインが表示され、「デバッグ中」のマークが付いている場合があります。 + - **認証情報を追加:** そのプラグインをクリックすると、以前 `provider/telegraph.yaml` で定義した "Telegraph Access Token" の入力を求められます。有効なトークンを入力して保存します。検証ロジック (`_validate_credentials`) が正しく実装されていれば、ここで検証が行われます。 _(ローカルの対応するスクリーンショットを参照してください。プラグインがリストに表示され、認証を要求する画面が表示されています)_ + - **アプリケーションで使用:** これで、Dify のアプリケーション(Chatbot や Workflow など)にこのツールノードを追加し、呼び出すことができます!アプリケーションで実行してツールをトリガーすると、リクエストはローカルで実行されている `python -m main` プロセスに転送されて処理されます。ローカルターミナルで関連するログ出力を確認し、デバッグできます。 + +4. **ローカルサービスの停止:** ターミナルで `Ctrl + C` を押すと、ローカルプラグインサービスを停止できます。 + +この実行 -> テスト -> 停止 -> コード修正 -> 再実行のサイクルが、プラグイン開発の主なフローです。 + +## 6. プラグインメタ情報の整備 + +プラグインをより専門的に、発見・理解しやすくするために、いくつかの表示情報を整備する必要があります。 + +1. **アイコン (Icon):** + - `telegraph/_assets` ディレクトリに、プラグインを表すアイコンファイル(例:`icon.png`, `icon.svg`)を配置します。正方形で鮮明な画像を推奨します。 +2. **プロバイダー情報 (`provider/telegraph.yaml`):** + + - `identity` セクションの `label` (表示名)、`description` (機能説明)、`icon` (アイコンファイル名、例:`icon.png` を記入) が入力され、多言語対応していることを確認します。この情報は主に Dify アプリケーションオーケストレーションインターフェースでプラグインを_使用する_ユーザーに表示されます。 + + ```yaml + identity: + author: alterxyz + name: telegraph # 内部名、変更なし + label: + en_US: Telegraph + ja: Telegraph 記事公開 + description: + en_US: A Telegraph plugin that allow you publish your content easily + ja: コンテンツを簡単に公開できるTelegraphプラグイン + icon: icon.png # _assets ディレクトリ内のアイコンファイル名を参照 + ``` + +3. **プラグインマニフェスト (`manifest.yaml`):** + + - プロジェクトルートディレクトリにある `telegraph/manifest.yaml` ファイルを編集します。これはプラグイン全体の「身分証明書」のようなもので、その情報は Dify の**プラグイン管理ページ**や**プラグインマーケットプレイス (Marketplace)** に表示されます。 + - 以下のフィールドを必ず更新または確認してください: + - `label`: プラグインの**主要な表示名** (多言語対応)。 + - `description`: プラグイン機能の**全体的な概要** (多言語対応)。その核心的な価値を明確に要約する必要があります。マーケットプレイスでの表示には文字数制限があることに注意してください。 + - `icon`: プラグインの**メインアイコン** (`_assets` ディレクトリ内のアイコンファイル名を直接記入、例:`icon.png`)。 + - `tags`: プラグインに分類タグを追加し、ユーザーがマーケットプレイスでフィルタリングするのに役立ちます。選択可能な値は、Dify が提供する列挙型またはドキュメントの説明を参照してください (例: `media`, `tools`, `data-processing` など)。[ToolLabelEnum 定義](https://github.com/langgenius/dify-plugin-sdks/blob/main/python/dify_plugin/entities/tool.py) を参照してください。 + + ```yaml + label: + en_US: Telegraph Publisher + ja: Telegraph 公開アシスタント + description: + en_US: Easily publish content to Telegraph pages directly from your Dify applications. Supports Markdown formatting. + ja: Dify アプリケーションから直接 Telegraph ページにコンテンツを簡単に公開できます。Markdown 形式をサポートしています。 + icon: icon.png + tags: ['media', 'content-creation'] # タグの例 + # ... (author, name など他のフィールドは変更なし) + ``` + +4. **README とプライバシーポリシー:** + - `README.md`: プロジェクトルートディレクトリの `README.md` ファイルを編集します。これはプラグインの **Marketplace** 上の詳細な紹介ページとなり、機能詳細、使用例、設定ガイド、よくある質問など、より豊富な情報を含めるべきです。[AWS プラグインマーケットプレイスページ](https://marketplace.dify.ai/plugins/langgenius/aws_tools) のスタイルを参考にしてください。 + - `PRIVACY.md`: プラグインを公式 Marketplace に公開する予定がある場合は、プラグインがデータをどのように処理するかを説明するプライバシーポリシー説明ファイル `PRIVACY.md` を提供する必要があります。 + +## 7. プラグインのパッケージ化 + +プラグイン開発が完了し、ローカルテストに合格したら、配布またはインストール用に `.difypkg` ファイルにパッケージ化できます。プラグインのパッケージ化と公開の詳細については、[公開概要](/plugin_dev_ja/0321-release-overview.ja) を参照してください。 + +1. **親ディレクトリに戻る:** ターミナルの現在のパスが `telegraph` フォルダの**一つ上**であることを確認してください。 + + ```bash + cd .. + ``` + +2. **パッケージ化コマンドの実行:** + + ```bash + ./dify plugin package ./telegraph + ``` + + (`./telegraph` をプラグインプロジェクトの実際のパスに置き換えてください) + +3. **パッケージファイルの取得:** コマンドが正常に実行されると、現在のディレクトリに `telegraph.difypkg` (または `あなたのプラグイン名.difypkg`) という名前のファイルが生成されます。 + +この `.difypkg` ファイルは完全なプラグインパッケージです。これを以下のことができます: + +- Dify のプラグイン管理ページで手動で**アップロードしてインストール**する。 +- 他の人に**共有**してインストールしてもらう。 +- Dify の規範と手順に従って、**公式プラグインマーケットプレイス (Marketplace)** に公開し、すべての Dify ユーザーがあなたのプラグインを発見して使用できるようにする。具体的な公開手順については、[Dify マーケットプレイスへの公開](/plugin_dev_ja/0322-release-to-dify-marketplace.ja) を参照してください。 + +おめでとうございます! これで最初の Dify プラグインの開発、デバッグ、整備、パッケージ化の全プロセスを完了しました。この基礎を元に、より複雑で強力なプラグイン機能を探求できます。 + +## 次の学習ステップ + +- [リモートでプラグインをデバッグする](/plugin_dev_ja/0411-remote-debug-a-plugin.ja) - より高度なプラグインデバッグテクニックを学ぶ +- [永続ストレージ](/plugin_dev_ja/0411-persistent-storage-kv.ja) - プラグインでデータストレージを使用する方法を学ぶ +- [Slack ボットプラグイン開発例](/plugin_dev_ja/0432-develop-a-slack-bot-plugin.ja) - より複雑なプラグイン開発事例を見る +- [ツールプラグイン](/plugin_dev_ja/0222-tool-plugin.ja) - ツールプラグインの高度な機能を探る + diff --git a/plugin_dev_ja/0211-getting-started-new-model.zh.mdx b/plugin_dev_ja/0211-getting-started-new-model.zh.mdx new file mode 100644 index 00000000..f31180e0 --- /dev/null +++ b/plugin_dev_ja/0211-getting-started-new-model.zh.mdx @@ -0,0 +1,114 @@ +--- +dimensions: + type: + primary: implementation + detail: basic + level: beginner +standard_title: Getting Started New Model +language: ja +title: 新しいモデルを迅速に統合する +description: このドキュメントでは、専門家でない開発者がDifyに新しいモデルを追加する方法を説明します。既存のモデルプロバイダーに設定ファイルを変更することで新しいモデルタイプを追加することに焦点を当てています。リポジトリのフォーク、モデル設定のコピーと変更、プロバイダーバージョンの更新、ローカルテスト、およびコントリビューションの提出までの完全なプロセスを含みます。 +--- + +Difyのプラグイン開発の世界へようこそ!Difyの強力な機能は、コミュニティ貢献者の共同の努力なしにはありえません。たとえプロのプログラマーでなくても、AI技術に情熱を持ち、資料を調べる意欲があれば、Difyにより多くの、より新しいAIモデルをサポートするなど、Difyに貢献できると信じています。 + +この記事では、最も簡潔な方法で、最も一般的で簡単な貢献、つまりDifyが**既にサポートしている**モデルプロバイダーに、**新しいモデルタイプ**を追加する方法を説明します。この方法は通常、**設定ファイルの変更のみ**が必要で、コードを書く必要がないため、最初の貢献として非常に適しています! + +> **関連概念**:開始する前に、[モデルプラグイン](/plugin_dev_ja/0131-model-plugin-introduction.ja)のドキュメントを読んで、モデルプラグインの基本概念と構造を理解することをお勧めします。 + +**この迅速な統合方法は、以下の場合に適しています:** + +- 新しいモデルが、Difyが既にプラグインでサポートしているプロバイダー(OpenAI、Google Gemini、Anthropic Claudeなど)に属している場合。 +- 新しいモデルが、同じシリーズの他のモデルと同じAPI認証および基本的な呼び出しロジックを使用している場合。 +- 主な違いが、モデルID、コンテキスト長、最大トークン数、価格設定などの設定パラメーターにある場合。 + +_(追加する必要のあるモデルが新しいAPIロジックを必要とするか、特殊な機能をサポートする場合、Pythonコードの記述が必要になります。[新しいモデルプロバイダーの作成](/plugin_dev_ja/0222-creating-new-model-provider.ja) で詳細なガイドを参照してください。)_ + +**準備作業:** + +- 基本的なGit操作(Fork、Clone、Pull Request)に慣れていること。 +- GitHubアカウント。 +- Difyプラグイン開発ツールキットをインストールし、設定済みであること([開発ツールの初期化](/plugin_dev_ja/0221-initialize-development-tools.ja)を参照)。 + +**操作手順:** + +1. **公式プラグインリポジトリをフォーク&クローン:** + + - Dify公式プラグインリポジトリ `https://github.com/langgenius/dify-official-plugins` にアクセスします。 + - 「Fork」ボタンをクリックして、リポジトリを自分のGitHubアカウントにフォークします。 + - Gitを使用して、フォークしたリポジトリをローカルコンピュータにクローンします。 + +2. **モデル設定ファイルを見つけてコピー:** + + - ローカルリポジトリで、`models/` ディレクトリに移動し、モデルを追加したいプロバイダーのフォルダ(例:`vertex_ai`)を見つけます。 + - そのプロバイダーに対応するモデルタイプのサブディレクトリ(通常はテキスト生成モデルの場合 `models/llm/`)に入ります。 + - そのディレクトリ内で、追加したい新しいモデルに最も似ている既存モデルのYAML設定ファイル(例:`gemini-1.0-pro-001.yaml`)を見つけます。 + - このYAMLファイルをコピーし、新しいモデルタイプを明確に識別できる名前に変更します(例:`gemini-1.5-pro-latest.yaml`)。 + +3. **モデル設定の変更 (YAML):** + + - 先ほど名前を変更したYAMLファイル(例:`gemini-1.5-pro-latest.yaml`)を開きます。 + - **核心ステップ:** **モデルプロバイダーの公式ドキュメント**を参照し、ファイル内の以下の重要な情報を注意深く確認して変更します: + - `model`: **必須** 新しいモデルタイプの公式API識別子に更新します。 + - `label`: **必須** Difyインターフェースでユーザーに表示されるモデル名に更新します(`en_US` と `zh_Hans` の両方の言語を提供することをお勧めします)。 + - `model_properties`: `context_size`(コンテキストウィンドウサイズ)を更新します。 + - `parameter_rules`: モデルパラメータの制限、特に `max_tokens`(最大出力トークン数)の `default`、`min`、`max` 値を確認して更新します。 + - `pricing`: モデルの入力(`input`)と出力(`output`)の価格設定、および単位(`unit`、通常は百万トークンを表す `0.000001`)と通貨(`currency`)を更新します。 + - _(参考)_ モデルYAMLファイルの各フィールドの詳細な仕様については、[モデル設計ルール](/plugin_dev_ja/0411-model-designing-rules.ja) および [モデルスキーマ定義](/plugin_dev_ja/0412-model-schema.ja) を参照してください。 + + **例 (Gemini 1.5 Pro の追加):** + + | パラメータ | 既存モデルの可能性 (例) | 新しい Gemini 1.5 Pro (例) | 説明 | + | :---------------- | :------------------- | :----------------------- | :--------------------------------------------- | + | `model` | `gemini-1.0-pro-001` | `gemini-1.5-pro-latest` | **必須** 公式モデルIDに変更 | + | `label: en_US` | Gemini 1.0 Pro | Gemini 1.5 Pro | **必須** ユーザーに表示されるラベルに変更 | + | `context_size` | 30720 | 1048576 | **必須** 公式ドキュメントに基づいて変更 | + | `max_tokens` (下) | 2048 | 8192 | **必須** 公式ドキュメントに基づいてデフォルト/最大値を変更 | + +4. **プロバイダーマニフェストのバージョンを更新:** + + - そのモデルプロバイダーのルートディレクトリ(例:`models/vertex_ai/`)に戻ります。 + - `manifest.yaml` ファイルを見つけて開きます。 + - その中の `version` フィールドをマイナーバージョン番号でインクリメントします(例:`version: 0.0.8` -> `version: 0.0.9`)。これにより、Difyにこれが更新であることを伝えます。 + +5. **パッケージ化とローカルテスト:** + + - ターミナル(コマンドラインツール)を開きます。 + - **現在のディレクトリが `dify-official-plugins` リポジトリのルートディレクトリ**(つまり、`models`、`tools` などのフォルダが含まれるディレクトリ)であることを確認してください。 + - パッケージ化コマンドを実行します: + + ```bash + # を実際のプロバイダーディレクトリ名(例: cohere や vertex_ai)に置き換えます + dify plugin package models/ + ``` + + - _成功すると、`plugin packaged successfully, output path: .difypkg` のようなメッセージが表示され、現在のプロジェクトのルートディレクトリに `.difypkg` という名前のプラグインパッケージファイルが生成されます。_ + - Difyインスタンス(ローカルデプロイまたはクラウドバージョンのいずれか)にログインします。 + - Difyページ最上部のナビゲーションバー右側にある **「プラグイン」** メニュー項目をクリックします。 + - プラグインページで、**「プラグインをインストール」** ボタンをクリックします。 + - **「ローカルプラグイン」** タブを選択します。 + - アップロードエリアをクリックし、先ほどローカルで生成した `.difypkg` ファイルを選択またはドラッグアンドドロップしてアップロードします。 + - プラグインのインストールまたは更新が完了するのを待ちます。 + - インストールが成功したら、通常、「設定」->「モデルプロバイダー」に移動して対応するプロバイダーを見つけ、API認証情報(以前に設定していない場合)を設定する必要があります。 + - 新しいDifyアプリケーションを作成するか、既存のアプリケーションを編集し、「プロンプトエンジニアリング」->「モデル」設定で、新しく追加したモデルを選択してみてください。簡単な会話や呼び出しテストを行い、正常に動作し、期待される結果が返されることを確認します。 + +6. **コントリビューションを提出:** + - ローカルテストで問題がなければ、変更(新しいモデルYAMLファイルと更新された `manifest.yaml`)をGitでコミット(commit)し、フォークしたGitHubリポジトリにプッシュ(push)します。 + - GitHub上で、`langgenius/dify-official-plugins` のメインリポジトリに対してプルリクエスト(PR)を作成します。PRの説明には、どのモデルを追加したかを簡潔に説明し、そのモデルの公式ドキュメントへのリンクを添付して、レビュー担当者がパラメータを確認しやすくします。 + +--- + +**それで、次は?** + +PRがレビューされマージされると、あなたの貢献はDify公式プラグインの一部となり、すべてのDifyユーザーがこの新しいモデルを簡単に利用できるようになります! + +この迅速な統合方法は、Difyが新しいモデルをサポートするための最速の方法です。もちろん、将来このモデルがより複雑な機能(例えば、画像入力、関数呼び出しなど)をサポートする必要がある場合、経験豊富な開発者がプラグインをコードレベルで更新する必要があるかもしれません。しかし、あなたが今完了したこのステップは、既に非常に価値のある貢献です! + +**さらに探求する:** + +- [モデルスキーマ定義](/plugin_dev_ja/0412-model-schema.ja) (モデルYAMLファイルの詳細なルールを理解する) +- [モデル設計ルール](/plugin_dev_ja/0411-model-designing-rules.ja) (モデルパラメータ設計の仕様を理解する) +- [一般仕様定義](/plugin_dev_ja/0411-general-specifications.ja) (`manifest.yaml` の役割を理解する) +- [新しいモデルプロバイダーの作成](/plugin_dev_ja/0222-creating-new-model-provider.ja) (新しいモデルプロバイダーの追加方法を理解する) +- [Difyマーケットプレイスへの公開](/plugin_dev_ja/0322-release-to-dify-marketplace.ja) (プラグインの公開方法を学ぶ) +- [Dify公式プラグインリポジトリ](https://github.com/langgenius/dify-official-plugins) (他のプラグインの例を見る) \ No newline at end of file diff --git a/plugin_dev_ja/0221-initialize-development-tools.zh.mdx b/plugin_dev_ja/0221-initialize-development-tools.zh.mdx new file mode 100644 index 00000000..693b6510 --- /dev/null +++ b/plugin_dev_ja/0221-initialize-development-tools.zh.mdx @@ -0,0 +1,66 @@ +--- +dimensions: + type: + primary: implementation + detail: standard + level: beginner +standard_title: Initialize Development Tools +language: ja +title: 開発ツールの初期化 +description: このドキュメントでは、Difyプラグイン開発を開始する前に必要な準備作業について詳しく説明します。これには、Difyプラグインのひな形ツール(dify-plugin-daemon)のインストールとPython環境(バージョン要件≥3.12)の設定の完全な手順が含まれます。また、さまざまなタイプのプラグイン開発の参考リンクも提供しています。 +--- + +Dify プラグインを開発するには、以下の準備が必要です。このドキュメントは、[プラグイン開発](/plugin_dev_ja/0111-getting-started-dify-plugin.ja)を開始するための最初のステップです。 + +* Dify プラグインのひな形ツール +* Python 環境、バージョン ≥ 3.12 + +> Dify プラグイン開発のひな形ツールは `dify-plugin-daemon` とも呼ばれ、**プラグイン開発 SDK** と見なすことができます。 + +### **1. Dify プラグイン開発のひな形ツールのインストール** + +[Dify Plugin CLI](https://github.com/langgenius/dify-plugin-daemon/releases) プロジェクトのページにアクセスし、最新バージョンとお使いのオペレーティングシステムに対応するツールをダウンロードしてインストールしてください。 + +この記事では、**Mシリーズチップを搭載した macOS** を例に説明します。`dify-plugin-darwin-arm64` ファイルをダウンロードした後、実行権限を付与します。 + +``` +chmod +x dify-plugin-darwin-arm64 +``` + +以下のコマンドを実行して、インストールが成功したかどうかを確認します。 + +``` +./dify-plugin-darwin-arm64 version +``` + +> 「Apple では確認できませんでした」というエラーが表示された場合は、**「設定 → プライバシーとセキュリティ → セキュリティ」** に移動し、「このまま開く」ボタンをタップしてください。 + +コマンドを実行した後、ターミナルに `v0.0.1-beta.15` のようなバージョン情報が返されれば、インストールは成功です。 + +> **💡 ヒント:** +> +> システム全体で `dify` コマンドを使用してひな形ツールを実行したい場合は、このバイナリファイルの名前を `dify` に変更し、`/usr/local/bin` システムパスにコピーすることをお勧めします。 +> +> 設定完了後、ターミナルで `dify version` コマンドを入力すると、バージョン情報が出力されます。 +> +> + +### **2. Python 環境の初期化** + +詳細については、[Python インストールチュートリアル](https://pythontest.com/python/installing-python-3-11/) を参照するか、LLM にバージョン ≥ 3.12 の Python 環境のインストールについて質問してください。 + +### 3. プラグインの開発 + +さまざまな種類のプラグイン開発の例については、以下の内容を参照してください。 + +- [ツールプラグイン開発ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja) - Hello World 入門チュートリアル +- [モデルプラグイン開発ガイド](/plugin_dev_ja/0211-getting-started-new-model.ja) - 新しいモデルへの迅速な統合 +- [Agent戦略プラグイン開発ガイド](/plugin_dev_ja/9433-agent-strategy-plugin.ja) - カスタム推論戦略の作成 +- [拡張プラグイン開発ガイド](/plugin_dev_ja/9231-extension-plugin.ja) - Webhook を介した外部サービス統合の実装 +- [プラグインのパッケージ化とリリース](/plugin_dev_ja/0321-release-overview.ja) - プラグインの公開 + +## 次のステップ + +- [Dify プラグイン開発:Hello World ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja) - 最初のプラグイン開発を始める +- [プラグイン開発チートシート](/plugin_dev_ja/0131-cheatsheet.ja) - 一般的なコマンドと概念の理解 +- [一般仕様定義](/plugin_dev_ja/0411-general-specifications.ja) - プラグインのメタデータ設定を学ぶ \ No newline at end of file diff --git a/plugin_dev_ja/0222-creating-new-model-provider-extra.zh.mdx b/plugin_dev_ja/0222-creating-new-model-provider-extra.zh.mdx new file mode 100644 index 00000000..f75937fa --- /dev/null +++ b/plugin_dev_ja/0222-creating-new-model-provider-extra.zh.mdx @@ -0,0 +1,274 @@ +--- +dimensions: + type: + primary: implementation + detail: standard + level: intermediate +standard_title: Creating New Model Provider Extra +language: ja +title: 標準モデル統合の実装 +description: このドキュメントは、Pythonコードを記述してDifyのモデルサポートを追加または強化する必要がある開発者向けのもので、ディレクトリ構造の作成、モデル設定の記述、モデル呼び出しロジックの実装、プラグインのデバッグと公開までの完全なプロセスを詳細にガイドし、コアメソッドの実装とエラー処理の詳細を含んでいます。 +--- + +このドキュメントは、Pythonコードを記述してDifyのモデルサポートを追加または強化する必要がある開発者向けの標準ガイドです。追加したいモデルが新しいAPI呼び出しロジック、特別なパラメータ処理、またはDifyが明示的にサポートする必要がある新機能(Vision、Tool Callingなど)を含む場合、このガイドの手順に従う必要があります。 + +**本文を読む前に、以下のことをお勧めします:** + +* Pythonプログラミングの基礎とオブジェクト指向プログラミングの基本的な理解があること。 +* 統合したいモデルプロバイダーが提供するAPIドキュメントと認証方法に精通していること。 +* Difyプラグイン開発ツールキットをインストールし、設定済みであること([開発ツールの初期化](../initialize-development-tools.md)を参照)。 +* (オプション)[モデルプラグインの紹介](link-to-conceptual-intro)ドキュメントを読み、モデルプラグインの基本概念とアーキテクチャを理解していること。 + +このガイドでは、ディレクトリ構造の作成、モデル設定(YAML)の記述、モデル呼び出しロジック(Python)の実装、およびプラグインのデバッグと公開までの全プロセスを案内します。 + +--- + +## ステップ1:ディレクトリ構造の作成 + +整理されたディレクトリ構造は、保守可能なプラグインを開発するための基礎です。モデルプロバイダープラグインのために特定のディレクトリとファイルを作成する必要があります。 + +1. **プロバイダーディレクトリの特定または作成:** プラグインプロジェクト(通常は `dify-official-plugins` のローカルクローン)の `models/` ディレクトリ内で、モデルプロバイダー名でフォルダを見つけるか作成します(例:`models/my_new_provider`)。 +2. **`models` サブディレクトリの作成:** プロバイダーディレクトリ内に `models` サブディレクトリを作成します。 +3. **モデルタイプごとのサブディレクトリ作成:** `models/models/` ディレクトリ内に、サポートする必要のある**各モデルタイプ**ごとにサブディレクトリを作成します。一般的なタイプには以下が含まれます: + * `llm`: テキスト生成モデル + * `text_embedding`: テキストEmbeddingモデル + * `rerank`: Rerankモデル + * `speech2text`: 音声認識モデル + * `tts`: テキスト読み上げモデル + * `moderation`: コンテンツ審査モデル +4. **実装ファイルの準備:** + * 各モデルタイプディレクトリ(例:`models/models/llm/`)に、そのタイプのモデル呼び出しロジックを実装するためのPythonファイルを作成します(例:`llm.py`)。 + * 同じディレクトリに、そのタイプ下の各具体的なモデルごとにYAML設定ファイルを作成します(例:`my-model-v1.yaml`)。 + * (オプション)`_position.yaml` ファイルを作成して、そのタイプ下のモデルがDify UIに表示される順序を制御できます。 + +**構造例(プロバイダー `my_provider` がLLMとEmbeddingをサポートすると仮定):** + +```bash +models/my_provider/ +├── models # モデル実装と設定ディレクトリ +│ ├── llm # LLMタイプ +│ │ ├── _position.yaml (オプション、ソート順制御) +│ │ ├── my-llm-model-v1.yaml +│ │ ├── my-llm-model-v2.yaml +│ │ └── llm.py # LLM実装ロジック +│ └── text_embedding # Embeddingタイプ +│ ├── _position.yaml (オプション、ソート順制御) +│ ├── my-embedding-model.yaml +│ └── text_embedding.py # Embedding実装ロジック +├── provider # プロバイダーレベルのコードディレクトリ +│ └── my_provider.py (認証情報検証などに使用、詳細は「モデルプロバイダーの作成」ドキュメント参照) +└── manifest.yaml # プラグインマニフェストファイル +``` + +--- + +## ステップ2:モデル設定の定義(YAML) + +各具体的なモデルについて、Difyがそれを正しく理解し使用できるように、その属性、パラメータ、および機能を記述するYAMLファイルを作成する必要があります。 + +1. **YAMLファイルの作成:** 対応するモデルタイプディレクトリ(例:`models/models/llm/`)に、追加するモデル用のYAMLファイルを作成します。ファイル名は通常、モデルIDと一致させるか、説明的なものにします(例:`my-llm-model-v1.yaml`)。 +2. **設定内容の記述:** [AIModelEntityスキーマ定義](../../../schema-definition/model/model-designing-rules.md#aimodelentity)仕様に従って内容を記述します。主要なフィールドは以下の通りです: + * `model`: (必須)モデルの公式API識別子。 + * `label`: (必須)Dify UIに表示される名前(多言語対応)。 + * `model_type`: (必須)所在するディレクトリタイプと一致する必要があります(例:`llm`)。 + * `features`: (オプション)モデルがサポートする特殊機能(`vision`、`tool-call`、`stream-tool-call`など)を宣言します。 + * `model_properties`: (必須)モデル固有のプロパティ(`mode`(`chat`または`completion`)、`context_size`など)を定義します。 + * `parameter_rules`: (必須)ユーザーが調整可能なパラメータとそのルール(名前`name`、タイプ`type`、必須`required`、デフォルト値`default`、範囲`min`/`max`、オプション`options`など)を定義します。`use_template`を使用して事前定義されたテンプレートを参照し、一般的なパラメータ(`temperature`、`max_tokens`など)の設定を簡略化できます。 + * `pricing`: (オプション)モデルの課金情報を定義します。 + +**例(`claude-3-5-sonnet-20240620.yaml`):** + +```yaml +model: claude-3-5-sonnet-20240620 +label: + en_US: claude-3-5-sonnet-20240620 +model_type: llm +features: + - agent-thought + - vision + - tool-call + - stream-tool-call + - document +model_properties: + mode: chat + context_size: 200000 +parameter_rules: + - name: temperature + use_template: temperature + - name: top_p + use_template: top_p + - name: max_tokens + use_template: max_tokens + required: true + default: 8192 + min: 1 + max: 8192 # Difyレベルで制限がある可能性に注意 +pricing: + input: '3.00' + output: '15.00' + unit: '0.000001' # 100万トークンあたり + currency: USD +``` + +--- + +## ステップ3:モデル呼び出しコードの記述(Python) + +これはモデル機能実装の中核となるステップです。対応するモデルタイプのPythonファイル(例:`llm.py`)に、API呼び出し、パラメータ変換、結果返却を処理するコードを記述する必要があります。 + +1. **Pythonファイルの作成/編集:** モデルタイプディレクトリ(例:`models/models/llm/`)で、対応するPythonファイル(例:`llm.py`)を作成または開きます。 +2. **実装クラスの定義:** + * クラスを定義します。例:`MyProviderLargeLanguageModel`。 + * このクラスは、DifyプラグインSDK内の対応する**モデルタイプベースクラス**を継承する必要があります。例えば、LLMの場合は `dify_plugin.provider_kits.llm.LargeLanguageModel` を継承します。 + + ```python + import logging + from typing import Union, Generator, Optional, List + from dify_plugin.provider_kits.llm import LargeLanguageModel # ベースクラスをインポート + from dify_plugin.provider_kits.llm import LLMResult, LLMResultChunk, LLMUsage # 結果と使用量クラスをインポート + from dify_plugin.provider_kits.llm import PromptMessage, PromptMessageTool # メッセージとツールクラスをインポート + from dify_plugin.errors.provider_error import InvokeError, InvokeAuthorizationError # エラークラスをインポート + # APIを呼び出すための vendor_sdk があると仮定します + # import vendor_sdk + + logger = logging.getLogger(__name__) + + class MyProviderLargeLanguageModel(LargeLanguageModel): + # ... メソッドを実装 ... + ``` + +3. **主要メソッドの実装:**(具体的に実装が必要なメソッドは継承するベースクラスによって異なります。以下はLLMを例とします) + * `_invoke(...)`: **コア呼び出しメソッド**。 + * **シグネチャ:** `def _invoke(self, model: str, credentials: dict, prompt_messages: List[PromptMessage], model_parameters: dict, tools: Optional[List[PromptMessageTool]] = None, stop: Optional[List[str]] = None, stream: bool = True, user: Optional[str] = None) -> Union[LLMResult, Generator[LLMResultChunk, None, None]]:` + * **責務:** + * `credentials` と `model_parameters` を使用してAPIリクエストを準備します。 + * Difyの `prompt_messages` 形式をプロバイダーAPIが必要とする形式に変換します。 + * Function Calling / Tool Useをサポートするために `tools` パラメータを処理します(モデルがサポートしている場合)。 + * `stream` パラメータに基づいて、ストリーミング呼び出しを行うか同期呼び出しを行うかを決定します。 + * **ストリーミング返却:** `stream=True` の場合、このメソッドはジェネレータ(`Generator`)を返す必要があり、`yield` を介して `LLMResultChunk` オブジェクトを逐次返却します。各チャンクには部分的な結果(テキスト、ツール呼び出しチャンクなど)とオプションの使用量情報が含まれます。 + * **同期返却:** `stream=False` の場合、このメソッドは完全な `LLMResult` オブジェクトを返す必要があります。これには最終的なテキスト結果、完全なツール呼び出しリスト、および総使用量情報(`LLMUsage`)が含まれます。 + * **実装パターン:** 同期ロジックとストリーミングロジックを内部ヘルパーメソッドに分割することを強く推奨します。 + + ```python + def _invoke(self, ..., stream: bool = True, ...) -> Union[LLMResult, Generator[LLMResultChunk, None, None]]: + # APIリクエストパラメータの準備(認証、モデルパラメータ変換、メッセージ形式変換など) + api_params = self._prepare_api_params(credentials, model_parameters, prompt_messages, tools, stop) + + try: + if stream: + return self._invoke_stream(model, api_params, user) + else: + return self._invoke_sync(model, api_params, user) + except vendor_sdk.APIError as e: + # APIエラーを処理し、Difyエラーにマッピングします(_invoke_error_mappingを参照) + # ... mapped_error を発生させる ... + pass # 実際のエラー処理に置き換えてください + except Exception as e: + logger.exception("モデル呼び出し中の不明なエラー") + raise e # または汎用的な InvokeError を発生させる + + def _invoke_stream(self, model: str, api_params: dict, user: Optional[str]) -> Generator[LLMResultChunk, None, None]: + # vendor_sdk のストリーミングインターフェースを呼び出す + # for api_chunk in vendor_sdk.create_stream(...): + # # api_chunk を LLMResultChunk に変換する + # dify_chunk = self._convert_api_chunk_to_llm_result_chunk(api_chunk) + # yield dify_chunk + pass # 実際の実装に置き換えてください + + def _invoke_sync(self, model: str, api_params: dict, user: Optional[str]) -> LLMResult: + # vendor_sdk の同期インターフェースを呼び出す + # api_response = vendor_sdk.create_sync(...) + # api_response を LLMResult (message.content, tools, usage を含む) に変換する + # dify_result = self._convert_api_response_to_llm_result(api_response) + # return dify_result + pass # 実際の実装に置き換えてください + ``` + + * `validate_credentials(self, model: str, credentials: dict) -> None`: (必須)ユーザーが認証情報を追加または変更する際にその有効性を検証するために使用されます。通常、簡単で低コストのAPIエンドポイント(利用可能なモデルのリスト表示、残高確認など)を呼び出すことで実装されます。検証に失敗した場合は、`CredentialsValidateFailedError` またはそのサブクラスをスローする必要があります。 + * `get_num_tokens(self, model: str, credentials: dict, prompt_messages: List[PromptMessage], tools: Optional[List[PromptMessageTool]] = None) -> int`: (オプションだが推奨)与えられた入力のトークン数を推定するために使用されます。正確に計算できない場合やAPIがサポートしていない場合は、0を返すことができます。 + * `@property _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]`: (必須)**エラーマッピング**辞書を定義します。キーはDifyの標準 `InvokeError` サブクラスであり、値はその標準エラーにマッピングされるべきプロバイダーSDKがスローする可能性のある例外タイプのリストです。これはDifyが異なるプロバイダーのエラーを統一的に処理するために不可欠です。 + + ```python + @property + def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]: + # マッピング例 + mapping = { + InvokeAuthorizationError: [ + vendor_sdk.AuthenticationError, + vendor_sdk.PermissionDeniedError, + ], + InvokeRateLimitError: [ + vendor_sdk.RateLimitError, + ], + # ... その他のマッピング ... + } + # ここにベースクラスのデフォルトマッピングを追加できます(ベースクラスが提供している場合) + # base_mapping = super()._invoke_error_mapping + # mapping.update(base_mapping) # マージ戦略に注意 + return mapping + ``` + +--- + +## ステップ4:プラグインのデバッグ + +プラグインをコミュニティに貢献する前に、十分なテストとデバッグが不可欠です。Difyはリモートデバッグ機能を提供しており、ローカルでコードを修正し、Difyインスタンスでリアルタイムに効果をテストできます。 + +1. **デバッグ情報の取得:** + * Difyインスタンスで、「プラグイン管理」ページに移動します(管理者権限が必要な場合があります)。 + * ページ右上の「プラグインのデバッグ」をクリックして、`デバッグキー`と`リモートサーバーアドレス`(例:`http://:5003`)を取得します。 +2. **ローカル環境の設定:** + * ローカルプラグインプロジェクトの**ルートディレクトリ**で、`.env`ファイルを見つけるか作成します(`.env.example`からコピーできます)。 + * `.env`ファイルを編集し、デバッグ情報を入力します: + + ```dotenv + INSTALL_METHOD=remote + REMOTE_INSTALL_HOST= # Difyサーバーアドレス + REMOTE_INSTALL_PORT=5003 # デバッグポート + REMOTE_INSTALL_KEY=****-****-****-****-**** # あなたのデバッグキー + ``` + +3. **ローカルプラグインサービスの起動:** + * プラグインプロジェクトのルートディレクトリで、Python環境がアクティブになっていることを確認します(仮想環境を使用している場合)。 + * メインプログラムを実行します: + + ```bash + python -m main + ``` + + * ターミナルの出力を観察し、接続が成功すると、通常、対応するログが表示されます。 +4. **Difyでのテスト:** + * Difyの「プラグイン」または「モデルプロバイダー」ページを更新すると、ローカルプラグインインスタンスが表示され、「デバッグ中」のマークが付いている場合があります。 + * 「設定」->「モデルプロバイダー」に移動し、プラグインを見つけて有効なAPI認証情報を設定します。 + * Difyアプリケーションでモデルを選択して使用し、テストを行います。ローカルでのPythonコードの変更(保存後、通常はサービスが自動的に再読み込みされます)は、Difyでの呼び出し動作に直接影響します。Difyのデバッグプレビュー機能を使用すると、入出力とエラー情報を確認するのに役立ちます。 + +--- + +## ステップ5:パッケージ化と公開 + +開発とデバッグが完了し、プラグインの機能に満足したら、パッケージ化してDifyコミュニティに貢献できます。 + +1. **プラグインのパッケージ化:** + * ローカルデバッグサービスを停止します(`Ctrl+C`)。 + * プラグインプロジェクトの**ルートディレクトリ**でパッケージ化コマンドを実行します: + + ```bash + # をプロバイダーのディレクトリ名に置き換えます + dify plugin package models/ + ``` + + * これにより、プロジェクトのルートディレクトリに `.difypkg` ファイルが生成されます。 +2. **プルリクエストの送信:** + * コードスタイルが良好で、Difyの[プラグイン公開仕様](https://docs.dify.ai/zh-hans/plugins/publish-plugins/publish-to-dify-marketplace)に従っていることを確認します。 + * ローカルGitのコミットをフォークした `dify-official-plugins` リポジトリにプッシュします。 + * GitHub上で `langgenius/dify-official-plugins` メインリポジトリに対してプルリクエストを作成します。PRの説明には、行った変更、追加したモデルや機能、および必要なテスト手順を明確に記述します。 + * Difyチームのレビューを待ちます。レビューが承認されマージされると、あなたの貢献は公式プラグインに含まれ、[Difyマーケットプレイス](https://marketplace.dify.ai/)で利用可能になります。 + +--- + +## さらに探る + +* [モデルスキーマ定義](/plugin_dev_ja/0412-model-schema.ja) (モデルYAML仕様) +* [プラグインマニフェスト構造](/plugin_dev_ja/0411-general-specifications.ja) (`manifest.yaml` 仕様) +* [Dify Plugin SDKリファレンス](https://github.com/langgenius/dify-plugin-sdks) (ベースクラス、データ構造、エラータイプを検索) +* [Dify公式プラグインリポジトリ](https://github.com/langgenius/dify-official-plugins) (既存プラグインの実装を参照) \ No newline at end of file diff --git a/plugin_dev_ja/0222-creating-new-model-provider.zh.mdx b/plugin_dev_ja/0222-creating-new-model-provider.zh.mdx new file mode 100644 index 00000000..12d785cd --- /dev/null +++ b/plugin_dev_ja/0222-creating-new-model-provider.zh.mdx @@ -0,0 +1,262 @@ +--- +dimensions: + type: + primary: implementation + detail: standard + level: intermediate +standard_title: Creating New Model Provider +language: ja +title: モデルプロバイダーの作成 +description: このドキュメントでは、モデルプロバイダープラグインを作成する方法について詳しく説明します。プロジェクトの初期化、モデル設定方法(事前定義モデルとカスタムモデル)の選択、プロバイダー設定YAMLファイルの作成、およびプロバイダーコードの作成の完全なプロセスが含まれます。 +--- + +Modelタイプのプラグインを作成する最初のステップは、プラグインプロジェクトを初期化し、モデルプロバイダーファイルを作成することです。その後、具体的な事前定義済み/カスタムモデルのコードを記述します。既存のモデルプロバイダーに新しいモデルを追加したいだけの場合は、[新しいモデルの迅速な統合](/plugin_dev_ja/0211-getting-started-new-model.ja)を参照してください。 + +### 事前準備 + +* Dify プラグインスケルトンツール +* Python 環境、バージョン ≥ 3.12 + +プラグイン開発用のスケルトンツールを準備する方法の詳細については、[開発ツールの初期化](/plugin_dev_ja/0221-initialize-development-tools.ja)を参照してください。開始する前に、[モデルプラグイン](/plugin_dev_ja/0131-model-plugin-introduction.ja)の基本的な概念と構造を理解することをお勧めします。 + +### 新規プロジェクトの作成 + +スケルトンコマンドラインツールのパスで、新しい Dify プラグインプロジェクトを作成します。 + +``` +./dify-plugin-darwin-arm64 plugin init +``` + +このバイナリファイルを `dify` にリネームし、`/usr/local/bin` パスにコピーした場合、次のコマンドを実行して新しいプラグインプロジェクトを作成できます: + +```bash +dify plugin init +``` + +### モデルプラグインテンプレートの選択 + +スケルトンツール内のすべてのテンプレートには、完全なコードプロジェクトが提供されています。`LLM` タイプのプラグインテンプレートを選択します。 + +![プラグインタイプ: llm](https://assets-docs.dify.ai/2024/12/8efe646e9174164b9edbf658b5934b86.png) + +#### プラグイン権限の設定 + +このLLMプラグインに次の権限を設定します: + +* Models +* LLM +* Storage + +![モデルプラグイン権限](https://assets-docs.dify.ai/2024/12/10f3b3ee6c03a1215309f13d712455d4.png) + +#### モデルタイプ設定の説明 + +モデルプロバイダーは、以下の2つのモデル設定方法をサポートしています: + +* `predefined-model` **事前定義済みモデル** + + 一般的な大規模モデルタイプで、統一されたプロバイダーの認証情報を設定するだけで、プロバイダー下の事前定義済みモデルを使用できます。例えば、`OpenAI` モデルプロバイダーは `gpt-3.5-turbo-0125` や `gpt-4o-2024-05-13` などの一連の事前定義済みモデルを提供しています。詳細な開発手順については、事前定義済みモデルの統合を参照してください。 +* `customizable-model` **カスタムモデル** + + 各モデルの認証情報設定を手動で追加する必要があります。例えば `Xinference` は、LLMとText Embeddingの両方をサポートしていますが、各モデルには一意の **model\_uid** があります。両方を同時に統合したい場合は、各モデルに **model\_uid** を設定する必要があります。詳細な開発手順については、カスタムモデルの統合を参照してください。 + +2つの設定方法は**共存をサポート**しており、つまり、プロバイダーが `predefined-model` + `customizable-model` または `predefined-model` などをサポートする場合、プロバイダーの統一された認証情報を設定することで、事前定義済みモデルとリモートから取得したモデルを使用できます。新しいモデルを追加した場合は、これに加えてカスタムモデルを使用できます。 + +### 新しいモデルプロバイダーの追加 + +新しいモデルプロバイダーを追加するには、主に次の手順が含まれます: + +1. **モデルプロバイダー設定YAMLファイルの作成** + + プロバイダーディレクトリに新しいYAMLファイルを追加し、プロバイダーの基本情報とパラメータ設定を記述します。ProviderSchemaの要件に従って内容を記述し、システム仕様との一貫性を確保します。 +2. **モデルプロバイダーコードの記述** + + プロバイダーのクラスコードを作成し、システムのインターフェース要件に準拠したPythonクラスを実装して、プロバイダーのAPIと連携し、コア機能を実現します。 + +*** + +以下は、各ステップの完全な操作詳細です。 + +#### 1. **モデルプロバイダー設定ファイルの作成** + +ManifestはYAML形式のファイルであり、モデルプロバイダーの基本情報、サポートされるモデルタイプ、設定方法、認証情報ルールを宣言します。プラグインプロジェクトテンプレートは、`/providers` パスに設定ファイルを自動的に生成します。 + +以下は、`Anthropic` モデル設定ファイル `anthropic.yaml` のサンプルコードです: + +```yaml +provider: anthropic +label: + en_US: Anthropic +description: + en_US: Anthropic's powerful models, such as Claude 3. + zh_Hans: Anthropic の強力なモデル、例えば Claude 3。 +icon_small: + en_US: icon_s_en.svg +icon_large: + en_US: icon_l_en.svg +background: "#F0F0EB" +help: + title: + en_US: Get your API Key from Anthropic + zh_Hans: Anthropic から API キーを取得 + url: + en_US: https://console.anthropic.com/account/keys +supported_model_types: + - llm +configurate_methods: + - predefined-model +provider_credential_schema: + credential_form_schemas: + - variable: anthropic_api_key + label: + en_US: API Key + type: secret-input + required: true + placeholder: + zh_Hans: ここに API キーを入力してください + en_US: Enter your API Key + - variable: anthropic_api_url + label: + en_US: API URL + type: text-input + required: false + placeholder: + zh_Hans: ここに API URL を入力してください + en_US: Enter your API URL +models: + llm: + predefined: + - "models/llm/*.yaml" + position: "models/llm/_position.yaml" +extra: + python: + provider_source: provider/anthropic.py + model_sources: + - "models/llm/llm.py" +``` + +接続するプロバイダーがカスタムモデルを提供する場合、例えば`OpenAI`がファインチューニングモデルを提供する場合、`model_credential_schema` フィールドを追加する必要があります。 + +以下は `OpenAI` ファミリーモデルのサンプルコードです: + +```yaml +model_credential_schema: + model: # ファインチューニングモデル名 + label: + en_US: Model Name + zh_Hans: モデル名 + placeholder: + en_US: Enter your model name + zh_Hans: モデル名を入力 + credential_form_schemas: + - variable: openai_api_key + label: + en_US: API Key + type: secret-input + required: true + placeholder: + zh_Hans: ここに API キーを入力してください + en_US: Enter your API Key + - variable: openai_organization + label: + zh_Hans: 組織 ID + en_US: Organization + type: text-input + required: false + placeholder: + zh_Hans: ここに組織 ID を入力してください + en_US: Enter your Organization ID + - variable: openai_api_base + label: + zh_Hans: API ベース + en_US: API Base + type: text-input + required: false + placeholder: + zh_Hans: ここに API ベースを入力してください + en_US: Enter your API Base +``` + +より完全なモデルプロバイダーYAML仕様については、[モデルスキーマ](/plugin_dev_ja/0412-model-schema.ja)ドキュメントを参照してください。 + +#### 2. **モデルプロバイダーコードの記述** + +`/providers` フォルダに同名の Python ファイルを作成します。例えば `anthropic.py` とし、`__base.provider.Provider` 基本クラスを継承する `class` を実装します。例えば `AnthropicProvider` です。 + +以下は `Anthropic` のサンプルコードです: + +```python +import logging +from dify_plugin.entities.model import ModelType +from dify_plugin.errors.model import CredentialsValidateFailedError +from dify_plugin import ModelProvider + +logger = logging.getLogger(__name__) + + +class AnthropicProvider(ModelProvider): + def validate_provider_credentials(self, credentials: dict) -> None: + """ + Validate provider credentials + + if validate failed, raise exception + + :param credentials: provider credentials, credentials form defined in `provider_credential_schema`. + """ + try: + model_instance = self.get_model_instance(ModelType.LLM) + model_instance.validate_credentials(model="claude-3-opus-20240229", credentials=credentials) + except CredentialsValidateFailedError as ex: + raise ex + except Exception as ex: + logger.exception(f"{self.get_provider_schema().provider} credentials validate failed") + raise ex +``` + +プロバイダーは `__base.model_provider.ModelProvider` 基本クラスを継承し、`validate_provider_credentials` プロバイダー統一認証情報検証メソッドを実装するだけで済みます。 + +```python +def validate_provider_credentials(self, credentials: dict) -> None: + """ + Validate provider credentials + You can choose any validate_credentials method of model type or implement validate method by yourself, + such as: get model list api + + if validate failed, raise exception + + :param credentials: provider credentials, credentials form defined in `provider_credential_schema`. + """ +``` + +もちろん、`validate_provider_credentials` の実装を一旦プレースホルダとして残しておき、モデル認証情報検証メソッドの実装後に直接再利用することも可能です。 + +#### **カスタムモデルプロバイダー** + +他のタイプのモデルプロバイダーについては、以下の設定方法を参照してください。 + +`Xinference` のようなカスタムモデルプロバイダーの場合、完全な実装手順をスキップできます。`XinferenceProvider` という名前の空のクラスを作成し、その中に空の `validate_provider_credentials` メソッドを実装するだけです。 + +**詳細説明:** + +• `XinferenceProvider` は、カスタムモデルプロバイダーを識別するためのプレースホルダクラスです。 + +• `validate_provider_credentials` メソッドは実際には呼び出されませんが、存在する必要があります。これは、その親クラスが抽象クラスであり、すべてのサブクラスにこのメソッドの実装を要求するためです。空の実装を提供することで、抽象メソッドが未実装であることによるインスタンス化エラーを回避できます。 + +```python +class XinferenceProvider(Provider): + def validate_provider_credentials(self, credentials: dict) -> None: + pass +``` + +モデルプロバイダーを初期化した後、次にプロバイダーが提供する具体的なLLMモデルを統合する必要があります。詳細については、以下の内容を参照してください: + +* [モデル設計ルール](/plugin_dev_ja/0411-model-designing-rules.ja) - 事前定義済みモデルを統合するための仕様を理解する +* [モデルスキーマ](/plugin_dev_ja/0412-model-schema.ja) - カスタムモデルを統合するための仕様を理解する +* [リリース概要](/plugin_dev_ja/0321-release-overview.ja) - プラグインのリリースプロセスを学ぶ + +## 参考リソース + +- [新しいモデルの迅速な統合](/plugin_dev_ja/0211-getting-started-new-model.ja) - 既存のプロバイダーに新しいモデルを追加する方法 +- [プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja) - プラグイン開発入門ガイドに戻る +- [新しいモデルプロバイダー作成の補足](/plugin_dev_ja/0222-creating-new-model-provider-extra.ja) - より高度な設定について学ぶ +- [一般仕様定義](/plugin_dev_ja/0411-general-specifications.ja) - プラグインマニフェストファイルの設定を理解する \ No newline at end of file diff --git a/plugin_dev_ja/0222-tool-plugin.zh.mdx b/plugin_dev_ja/0222-tool-plugin.zh.mdx new file mode 100644 index 00000000..911dde22 --- /dev/null +++ b/plugin_dev_ja/0222-tool-plugin.zh.mdx @@ -0,0 +1,376 @@ +--- +dimensions: + type: + primary: implementation + detail: standard + level: intermediate +standard_title: Tool Plugin +language: ja +title: Tool プラグイン +description: このドキュメントでは、Difyのツールプラグインを開発する方法を詳しく説明します。Google Searchを例に、プラグインの初期化、テンプレートの選択、ツールプロバイダー設定ファイルの定義、サードパーティサービス認証情報の追加、ツール機能コードの実装、デバッグ、パッケージ化と公開までの完全なツールプラグイン開発フローを紹介します。 +--- + +ツールとは、Chatflow / Workflow / Agent タイプのアプリケーションから呼び出すことができるサードパーティサービスを指し、Difyアプリケーションの能力を強化するための完全なAPI実装機能を提供します。例えば、アプリケーションにオンライン検索や画像生成などの追加機能を追加します。 + +![ツールプラグインの例](https://assets-docs.dify.ai/2024/12/7e7bcf1f9e3acf72c6917ea9de4e4613.png) + +この記事では、**「ツールプラグイン」** とは、ツールプロバイダーファイル、機能コードなどの構造を含む完全なプロジェクトを指します。1つのツールプロバイダーには複数のツール(個々のツール内で提供される追加機能と理解できます)を含めることができ、構造は以下の通りです。 + +``` +- ツールプロバイダー + - Tool A + - Tool B +``` + +![ツールプラグインの構造](https://assets-docs.dify.ai/2025/02/60c4c86a317d865133aa460592eac079.png) + +この記事では、`Google Search` を例に、ツールプラグインを迅速に開発する方法を紹介します。 + +### 事前準備 + +- Difyプラグインスキャフォールドツール +- Python環境、バージョン ≥ 3.12 + +プラグイン開発用のスキャフォールドツールの準備方法については、[開発ツールの初期化](/plugin_dev_ja/0221-initialize-development-tools.ja)を参照してください。初めてプラグインを開発する場合は、まず[Difyプラグイン開発:Hello Worldガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja)を読むことをお勧めします。 + +### 新規プロジェクトの作成 + +スキャフォールドコマンドラインツールを実行し、新しいDifyプラグインプロジェクトを作成します。 + +```bash +./dify-plugin-darwin-arm64 plugin init +``` + +このバイナリファイルを `dify` にリネームし、`/usr/local/bin` パスにコピーした場合、以下のコマンドを実行して新しいプラグインプロジェクトを作成できます。 + +```bash +dify plugin init +``` + +> 以下では、コマンドラインの例として `dify` を使用します。問題が発生した場合は、`dify` コマンドをコマンドラインツールの実際のパスに置き換えてください。 + +### プラグインタイプとテンプレートの選択 + +スキャフォールドツール内のすべてのテンプレートは、完全なコードプロジェクトを提供しています。この記事の例では、`Tool` プラグインを選択します。 + +> プラグイン開発に慣れている場合は、テンプレートを使用せずに、[一般仕様](/plugin_dev_ja/0411-general-specifications.ja)のガイドラインを参照して、さまざまなタイプのプラグイン開発を完了できます。 + +![プラグインタイプ:ツール](https://assets-docs.dify.ai/2024/12/dd3c0f9a66454e15868eabced7b74fd6.png) + +#### プラグイン権限の設定 + +プラグインはDifyプラットフォームの権限も読み取る必要があり、このサンプルプラグインには以下の権限を付与します。 + +- Tools +- Apps +- 永続ストレージStorageを有効にし、デフォルトサイズのストレージを割り当てる +- Endpointの登録を許可する + +> ターミナル内で方向キーを使用して権限を選択し、「Tab」キーを使用して権限を付与します。 + +すべての権限項目にチェックを入れた後、Enterキーを押してプラグインの作成を完了します。システムは自動的にプラグインプロジェクトコードを生成します。 + +![プラグイン権限](https://assets-docs.dify.ai/2024/12/9cf92c2e74dce55e6e9e331d031e5a9f.png) + +### ツールプラグインの開発 + +#### 1. ツールプロバイダーファイルの作成 + +ツールプロバイダーファイルはYAML形式のファイルで、ツールプラグインの基本設定エントリと理解でき、ツールに必要な認証情報を提供するために使用されます。 + +プラグインテンプレートプロジェクトの `/provider` パスに移動し、そこにあるyamlファイルを `google.yaml` にリネームします。この `yaml` ファイルには、ツールプロバイダーの情報(プロバイダー名、アイコン、作成者などの詳細)が含まれます。この情報はプラグインのインストール時に表示されます。 + +**コード例** + +```yaml +identity: # ツールプロバイダーの基本情報 + author: Your-name # 作成者 + name: google # 名称、一意であり、他のプロバイダーと重複することはできません + label: # ラベル、フロントエンド表示用 + en_US: Google # 英語ラベル + zh_Hans: Google # 中国語ラベル + description: # 説明、フロントエンド表示用 + en_US: Google # 英語の説明 + zh_Hans: Google # 中国語の説明 + icon: icon.svg # ツールアイコン、_assetsフォルダに配置する必要があります + tags: # タグ、フロントエンド表示用 + - search +``` + +このファイルパスが `/tools` ディレクトリに配置されるようにしてください。完全なパスは次のとおりです。 + +```yaml +plugins: + tools: + - 'google.yaml' +``` + +`google.yaml`ファイルは、プラグインプロジェクトにおける絶対パスを使用する必要があります。この例では、プロジェクトのルートディレクトリにあります。YAMLファイル内のidentityフィールドは次のように説明されます:`identity`には、作成者、名前、ラベル、説明、アイコンなど、ツールプロバイダーの基本情報が含まれます。 + +- アイコンは添付リソースに属する必要があり、プロジェクトのルートディレクトリにある `_assets` フォルダに配置する必要があります。 +- タグは、ユーザーがカテゴリ別にプラグインをすばやく見つけるのに役立ちます。以下は現在サポートされているすべてのタグです。 + +```python +class ToolLabelEnum(Enum): + SEARCH = 'search' + IMAGE = 'image' + VIDEOS = 'videos' + WEATHER = 'weather' + FINANCE = 'finance' + DESIGN = 'design' + TRAVEL = 'travel' + SOCIAL = 'social' + NEWS = 'news' + MEDICAL = 'medical' + PRODUCTIVITY = 'productivity' + EDUCATION = 'education' + BUSINESS = 'business' + ENTERTAINMENT = 'entertainment' + UTILITIES = 'utilities' + OTHER = 'other' +``` + +#### **2. サードパーティサービス認証情報の補完** + +開発を容易にするため、サードパーティサービス `SerpApi` が提供する Google Search API を採用することを選択します。`SerpApi` は使用にあたって API Key の入力を要求するため、`yaml` ファイル内に `credentials_for_provider` フィールドを追加する必要があります。 + +完全なコードは以下の通りです。 + +```yaml +identity: + author: Dify + name: google + label: + en_US: Google + zh_Hans: Google + pt_BR: Google + description: + en_US: Google + zh_Hans: GoogleSearch + pt_BR: Google + icon: icon.svg + tags: + - search +credentials_for_provider: # credentials_for_provider フィールドを追加 + serpapi_api_key: + type: secret-input + required: true + label: + en_US: SerpApi API key + zh_Hans: SerpApi APIキー + placeholder: + en_US: Please input your SerpApi API key + zh_Hans: SerpApi APIキーを入力してください + help: + en_US: Get your SerpApi API key from SerpApi + zh_Hans: SerpApiからSerpApi APIキーを取得します + url: https://serpapi.com/manage-api-key +tools: + - tools/google_search.yaml +extra: + python: + source: google.py +``` + +- `credentials_for_provider` の子構造は、[一般仕様](/plugin_dev_ja/0411-general-specifications.ja)の要件を満たす必要があります。 +- このプロバイダーにどのツールが含まれているかを指定する必要があります。この例では、`tools/google_search.yaml` ファイルのみが含まれています。 +- プロバイダーとして、基本情報を定義するだけでなく、そのコードロジックの一部を実装する必要があるため、その実装ロジックを指定する必要があります。この例では、機能のコードファイルを `google.py` に配置しましたが、一時的に実装せず、まず `google_search` のコードを作成します。 + +#### 3. ツールyamlファイルの記入 + +1つのツールプラグインには複数のツール機能を含めることができ、各ツール機能には、ツール機能の基本情報、パラメータ、出力などを含む `yaml` ファイルで記述する必要があります。 + +引き続き `GoogleSearch` ツールを例に、`/tools` フォルダ内に新しい `google_search.yaml` ファイルを作成します。 + +```yaml +identity: + name: google_search + author: Dify + label: + en_US: GoogleSearch + zh_Hans: Google検索 + pt_BR: GoogleSearch +description: + human: + en_US: A tool for performing a Google SERP search and extracting snippets and webpages.Input should be a search query. + zh_Hans: Google SERP検索を実行し、スニペットとウェブページを抽出するためのツールです。入力は検索クエリである必要があります。 + pt_BR: A tool for performing a Google SERP search and extracting snippets and webpages.Input should be a search query. + llm: A tool for performing a Google SERP search and extracting snippets and webpages.Input should be a search query. +parameters: + - name: query + type: string + required: true + label: + en_US: Query string + zh_Hans: クエリ文 + pt_BR: Query string + human_description: + en_US: used for searching + zh_Hans: ウェブコンテンツの検索に使用 + pt_BR: used for searching + llm_description: key words for searching + form: llm +extra: + python: + source: tools/google_search.py +``` + +- `identity` には、ツールの基本情報(名前、作成者、ラベル、説明など)が含まれます。 +- `parameters` パラメータリスト + - `name` (必須)パラメータ名、一意であり、他のパラメータと重複することはできません。 + - `type` (必須)パラメータタイプ。現在、`string`(文字列)、`number`(数値)、`boolean`(ブール値)、`select`(ドロップダウンリスト)、`secret-input`(暗号化入力フィールド)の5種類をサポートしています。機密情報には`secret-input`タイプを使用してください。 + - `label`(必須)パラメータラベル、フロントエンド表示用。 + - `form` (必須)フォームタイプ。現在、`llm`、`form`の2種類をサポートしています。 + - Agentアプリケーションでは、`llm` はLLMが自身で推論するパラメータを示し、`form` はこのツールを使用するために事前に設定できるパラメータを示します。 + - Workflowアプリケーションでは、`llm` と `form` の両方をフロントエンドで入力する必要がありますが、`llm` のパラメータはツールノードの入力変数として機能します。 + - `required` 必須かどうか + - `llm` モードでは、パラメータが必須の場合、Agentはこのパラメータを推論する必要があります。 + - `form` モードでは、パラメータが必須の場合、ユーザーは対話開始前にフロントエンドでこのパラメータを入力する必要があります。 + - `options` パラメータオプション + - `llm` モードでは、DifyはすべてのオプションをLLMに渡し、LLMはこれらのオプションに基づいて推論できます。 + - `form` モードで `type` が `select` の場合、フロントエンドはこれらのオプションを表示します。 + - `default` デフォルト値。 + - `min` 最小値、パラメータタイプが `number` の場合に設定できます。 + - `max` 最大値、パラメータタイプが `number` の場合に設定できます。 + - `human_description` フロントエンド表示用の説明、多言語対応。 + - `placeholder` フィールド入力ボックスのヒントテキスト。フォームタイプが`form`で、パラメータタイプが`string`、`number`、`secret-input`の場合に設定でき、多言語対応です。 + - `llm_description` LLMに渡す説明。LLMがこのパラメータをよりよく理解できるように、ここにこのパラメータに関するできるだけ詳細な情報を記述してください。 + +#### 4. ツールコードの準備 + +ツールの設定情報を入力した後、ツールの機能コードの作成を開始し、ツールの論理的な目的を実現できます。`/tools`ディレクトリに`google_search.py`を作成し、内容は以下の通りです。 + +```python +from collections.abc import Generator +from typing import Any + +import requests + +from dify_plugin import Tool +from dify_plugin.entities.tool import ToolInvokeMessage + +SERP_API_URL = "https://serpapi.com/search" + +class GoogleSearchTool(Tool): + def _parse_response(self, response: dict) -> dict: + result = {} + if "knowledge_graph" in response: + result["title"] = response["knowledge_graph"].get("title", "") + result["description"] = response["knowledge_graph"].get("description", "") + if "organic_results" in response: + result["organic_results"] = [ + { + "title": item.get("title", ""), + "link": item.get("link", ""), + "snippet": item.get("snippet", ""), + } + for item in response["organic_results"] + ] + return result + + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]: + params = { + "api_key": self.runtime.credentials["serpapi_api_key"], + "q": tool_parameters["query"], + "engine": "google", + "google_domain": "google.com", + "gl": "us", + "hl": "en", + } + + response = requests.get(url=SERP_API_URL, params=params, timeout=5) + response.raise_for_status() + valuable_res = self._parse_response(response.json()) + + yield self.create_json_message(valuable_res) +``` + +この例の意味は、`serpapi` にリクエストを送信し、`self.create_json_message` を使用して `json` 形式のフォーマット済みデータを返すことです。返されるデータ型の詳細については、[プラグインのリモートデバッグ](/plugin_dev_ja/0411-remote-debug-a-plugin.ja)および[永続ストレージKV](/plugin_dev_ja/0411-persistent-storage-kv.ja)のドキュメントを参照してください。 + +#### 4. ツールプロバイダーコードの完成 + +最後に、認証情報検証ロジックを実装するためのプロバイダーの実装コードを作成する必要があります。認証情報検証が失敗した場合、`ToolProviderCredentialValidationError`例外がスローされます。検証が成功すると、`google_search`ツールサービスに正しくリクエストが送信されます。 + +`/provider` ディレクトリに `google.py` ファイルを作成し、コードの内容は以下の通りです。 + +```python +from typing import Any + +from dify_plugin import ToolProvider +from dify_plugin.errors.tool import ToolProviderCredentialValidationError +from tools.google_search import GoogleSearchTool + +class GoogleProvider(ToolProvider): + def _validate_credentials(self, credentials: dict[str, Any]) -> None: + try: + for _ in GoogleSearchTool.from_credentials(credentials).invoke( + tool_parameters={"query": "test", "result_type": "link"}, + ): + pass + except Exception as e: + raise ToolProviderCredentialValidationError(str(e)) +``` + +### プラグインのデバッグ + +プラグインの開発が完了したら、次にプラグインが正常に動作するかをテストする必要があります。Difyは便利なリモートデバッグ方法を提供し、テスト環境でプラグイン機能を迅速に検証するのに役立ちます。 + +[「プラグイン管理」](https://cloud.dify.ai/plugins)ページに移動して、リモートサーバーアドレスとデバッグキーを取得します。 + +![Remote Debug Key](https://assets-docs.dify.ai/2024/12/053415ef127f1f4d6dd85dd3ae79626a.png) + +プラグインプロジェクトに戻り、`.env.example` ファイルをコピーして `.env` にリネームし、取得したリモートサーバーアドレスとデバッグキーなどの情報を入力します。 + +`.env` ファイル: + +```bash +INSTALL_METHOD=remote +REMOTE_INSTALL_HOST=remote +REMOTE_INSTALL_PORT=5003 +REMOTE_INSTALL_KEY=****-****-****-****-**** +``` + +`python -m main` コマンドを実行してプラグインを起動します。プラグインページで、このプラグインがWorkspaceにインストールされていることが確認でき、チームの他のメンバーもこのプラグインにアクセスできます。 + +![](https://assets-docs.dify.ai/2024/11/0fe19a8386b1234755395018bc2e0e35.png) + +### プラグインのパッケージ化(オプション) + +プラグインが正常に動作することを確認した後、以下のコマンドラインツールを使用してプラグインをパッケージ化し、名前を付けることができます。実行後、現在のフォルダに `google.difypkg` ファイルが見つかります。これは最終的なプラグインパッケージです。 + +```bash +# ./google をプラグインプロジェクトの実際のパスに置き換えてください + +dify plugin package ./google +``` + +おめでとうございます、これでツールタイププラグインの完全な開発、デバッグ、パッケージ化プロセスを完了しました! + +### プラグインの公開(オプション) + +プラグインをDify Marketplaceに公開したい場合は、プラグインが[Difyマーケットプレイスへの公開](/plugin_dev_ja/0322-release-to-dify-marketplace.ja)の仕様に従っていることを確認してください。審査に合格すると、コードはメインブランチにマージされ、自動的に[Dify Marketplace](https://marketplace.dify.ai/)に公開されます。 + +[公開概要](/plugin_dev_ja/0321-release-overview.ja) + +### さらに探る + +#### **クイックスタート:** + +- [Extensionプラグインの開発](/plugin_dev_ja/9231-extension-plugin.ja) +- [Modelプラグインの開発](/plugin_dev_ja/0211-getting-started-new-model.ja) +- [Bundleプラグイン:複数のプラグインをパッケージ化](/plugin_dev_ja/9241-bundle.ja) + +#### **プラグインインターフェースドキュメント:** + +- [一般仕様定義](/plugin_dev_ja/0411-general-specifications.ja) - Manifest構造とツール仕様 +- [エンドポイント](/plugin_dev_ja/0432-endpoint.ja) - Endpoint詳細定義 +- [リバースコール](/plugin_dev_ja/9241-reverse-invocation.ja) - Dify機能のリバースコール +- [モデルスキーマ](/plugin_dev_ja/0412-model-schema.ja) - モデル +- [Agentプラグイン](/plugin_dev_ja/9232-agent.ja) - Agent戦略の拡張 + +## 次のステップ + +- [プラグインのリモートデバッグ](/plugin_dev_ja/0411-remote-debug-a-plugin.ja) - より高度なデバッグテクニックを学ぶ +- [永続ストレージ](/plugin_dev_ja/0411-persistent-storage-kv.ja) - プラグインでデータストレージを使用する方法を学ぶ +- [Slackボットプラグイン開発例](/plugin_dev_ja/0432-develop-a-slack-bot-plugin.ja) - より複雑なプラグイン開発事例を見る +- [ツールプラグイン](/plugin_dev_ja/0411-tool.ja) - ツールプラグインの高度な機能を探る \ No newline at end of file diff --git a/plugin_dev_ja/0312-contributor-covenant-code-of-conduct.zh.mdx b/plugin_dev_ja/0312-contributor-covenant-code-of-conduct.zh.mdx new file mode 100644 index 00000000..c47f29dc --- /dev/null +++ b/plugin_dev_ja/0312-contributor-covenant-code-of-conduct.zh.mdx @@ -0,0 +1,62 @@ +--- +dimensions: + type: + primary: operational + detail: setup + level: intermediate +standard_title: Contributor Covenant Code of Conduct +language: ja +title: プラグイン開発者ガイドライン +description: このドキュメントは、Difyプラグイン開発者がプルリクエストを送信する前に従うべきガイドラインを提供します。これには、プラグインが正常に機能すること、ドキュメントが完全であること、独自の価値を提供すること、データプライバシーとセキュリティ規範に準拠することが含まれます。説明ドキュメントの要件、重複プラグインを避けるためのガイドライン、プライバシー情報収集の宣言要件が含まれています。 +--- + +### プルリクエスト(PR)を送信する前に + +1. **プラグインが正常に機能し、ドキュメントが完全であることを確認する** + +* プラグインが正常に機能することを確認してください。詳細は[プラグインのリモートデバッグ](/plugin_dev_ja/0411-remote-debug-a-plugin.ja)を参照してください。 +* 包括的な**READMEファイル**を提供してください。内容は以下の通りです: + * 設定手順と使用ガイド。 + * プラグインユーザーがプラグインをサービスに接続するために必要なコード、API、認証情報、またはその他の情報。 +* 収集されたユーザー情報は、サービスの接続とプラグイン機能の改善にのみ使用されることを確認してください。 +* [プラグインのプライバシーデータ保護ガイドライン](/plugin_dev_ja/0312-privacy-protection-guidelines.ja)に従って、プライバシーポリシーの内容ファイルまたはオンラインドキュメントのURLを準備してください。 + +2. **プラグインの貢献価値を確認する** + +* プラグインがDifyユーザーに独自の価値を提供することを確認してください。 +* プラグインは、Difyや他のプラグインがまだ提供していない機能やサービスを導入する必要があります。 +* コミュニティ基準に従ってください: + * コンテンツは非暴力的であり、グローバルなユーザーコミュニティを尊重するものであること。 + * 統合サービスの関連ポリシーに準拠していること。 +* **類似のプラグインが既に存在するかどうかを確認する方法は?** + * 既存のプラグインやPRと機能が重複するものを提出することは避けてください。ただし、新しいプラグインが以下の特徴を備えている場合は除きます: + * 新しい機能を導入する。 + * パフォーマンスの改善を提供する。 + * **プラグインが十分にユニークかどうかを判断する方法:** + * プラグインが既存の機能にわずかな調整(言語パラメータの追加など)を加えるだけの場合は、既存のプラグインを直接拡張することをお勧めします。 + * プラグインが大幅な機能変更(バッチ処理の最適化やエラー処理の改善など)を実現する場合は、新しいプラグインとして提出できます。 + * 不明な場合は、PR提出時に簡単な説明を添付し、なぜ新しいプラグインを提出する必要があるのかを説明してください。 + +**例:** Google検索プラグインを例にとると、単一の入力クエリを受け付け、Google検索APIを使用してGoogle検索結果のリストを出力します。もし、同様の基盤実装を持つ新しいGoogle検索プラグインを提供するが、入力にわずかな調整(例えば、新しい言語パラメータの追加)を加えるだけの場合、既存のプラグインを拡張することをお勧めします。一方、プラグインが最適化されたバッチ検索やエラー処理能力を新しい方法で実装した場合、それは個別のプラグインとして審査される可能性があります。 + +3. **プラグインが以下のプライバシーデータ規範に準拠していることを確認する** + +### 情報開示要件: + +* 開発者は、アプリケーション/ツールを提出する際に、いかなる種類のユーザー個人データを収集するかどうかを**必ず**宣言する必要があります。詳細は[プラグインのプライバシーデータ保護ガイドライン](/plugin_dev_ja/0312-privacy-protection-guidelines.ja)を参照してください。 +* 収集する場合、収集するデータタイプを**簡単にリストアップ**する必要があります(例:ユーザー名、メールアドレス、デバイスID、位置情報など)。**詳細すぎる必要はありません**。 +* 開発者は**必ず**プライバシーポリシーへのリンクを提供する必要があります。プライバシーポリシーには、収集する情報、その情報の使用方法、第三者に開示される情報、および関連する第三者のプライバシーポリシーへのリンクを記載するだけで十分です。 + +**審査のポイント:** + +* **形式審査:** 要件に従ってデータ収集状況が宣言されているかを確認します。 +* **高リスクデータの調査:** 機密データ(例:健康情報、財務情報、子供の個人情報など)を収集しているかどうかに重点を置きます。機密データを収集している場合は、その使用目的とセキュリティ対策について**追加審査**が必要です。 +* **悪意のある行為の調査:** ユーザーの同意なしにデータを収集したり、ユーザーデータを未知のサーバーにアップロードしたりするなどの明らかな悪意のある行為が存在しないかを確認します。 + +## 関連リソース + +- [プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja) - プラグイン開発の基礎を理解する +- [プラグインのリリース](/plugin_dev_ja/0321-release-overview.ja) - プラグインリリースプロセスの概要 +- [プラグインのプライバシーデータ保護ガイドライン](/plugin_dev_ja/0312-privacy-protection-guidelines.ja) - プライバシーポリシー作成ガイド +- [Dify Marketplaceへのリリース](/plugin_dev_ja/0322-release-to-dify-marketplace.ja) - 公式マーケットプレイスでプラグインをリリースする +- [プラグインのリモートデバッグ](/plugin_dev_ja/0411-remote-debug-a-plugin.ja) - プラグインデバッグガイド \ No newline at end of file diff --git a/plugin_dev_ja/0312-privacy-protection-guidelines.zh.mdx b/plugin_dev_ja/0312-privacy-protection-guidelines.zh.mdx new file mode 100644 index 00000000..5a0e4f0b --- /dev/null +++ b/plugin_dev_ja/0312-privacy-protection-guidelines.zh.mdx @@ -0,0 +1,90 @@ +--- +dimensions: + type: + primary: operational + detail: setup + level: intermediate +standard_title: Privacy Protection Guidelines +language: ja +title: プラグインのプライバシーポリシーガイドライン +description: このドキュメントでは、開発者がDifyマーケットプレイスにプラグインを提出する際にプライバシーポリシーを作成する方法のガイドラインを説明します。内容には、収集される個人データの種類(直接識別情報、間接識別情報、組み合わせ情報)の確認とリストアップ方法、プラグインのプライバシーポリシーの記入方法、マニフェストファイルでのプライバシーポリシー宣言の導入方法、および関連するよくある質問への回答が含まれます。 +--- + +Difyマーケットプレイスにプラグインを提出申請する際には、ユーザーデータの取り扱い方法を公開する必要があります。以下は、プラグイン関連のプライバシー問題とユーザーデータ処理の記入に関するガイドラインです。まだプラグインを開発していない場合は、[プラグイン開発入門ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja)を参照してください。 + +プラグインのプライバシーポリシー宣言の内容は、以下の問題を中心に展開されます: + +**あなたのプラグインはユーザーの個人データを収集・使用しますか?** もしそうであれば、具体的な種類を整理してリストアップしてください。 + +> 個人データとは、特定の個人を識別できるあらゆる情報を指し、単独で、または他の情報と組み合わせて個人を識別、連絡、または特定するために使用できる情報を含みます。 + +#### **1. 収集されるデータの種類をリストアップする** + +**タイプ1:直接識別情報** + +* 氏名(フルネーム、名、姓) +* メールアドレス +* 電話番号 +* 住所 +* 身分証明書番号(IDカード、パスポート、運転免許証など) + +**タイプ2:間接識別情報** + +* デバイス識別子(IMEI、MACアドレス、デバイスID) +* IPアドレス +* 位置情報(GPS座標、都市、地域) +* オンライン識別子(Cookie、広告ID) +* ユーザー名 +* プロフィール画像 +* 生体認証情報(指紋、顔認証) +* 閲覧履歴 +* 購入履歴 +* 健康データ +* 財務情報 + +**タイプ3:個人を識別するために使用される組み合わせ情報** + +* 年齢 +* 性別 +* 職業 +* 趣味・関心事 + +あなたのプラグイン自体が個人情報を収集しない場合でも、プラグインが使用するサードパーティサービスがデータ収集または処理に関与しているかどうかを確認してください。開発者として、サードパーティサービスによるデータ処理を含む、すべてのデータ収集活動を公開する必要があります。サードパーティサービスのプライバシーポリシーを必ず読み、提出時にプラグインに関連するすべてのデータ収集状況を宣言するようにしてください。 + +例えば、開発中のプラグインがSlackサービスに関わる場合、プラグインのプライバシーポリシー宣言ファイルで[Slackのプライバシーポリシー](https://slack.com/trust/privacy/privacy-policy)を参照し、データ収集状況を宣言してください。 + +#### 2. 最新バージョンのプラグインプライバシーポリシーを記入する + +**プラグインのプライバシーポリシー**には、以下を含める必要があります: + +* 収集されるデータの種類 +* データの使用目的 +* 第三者とデータを共有するかどうか(共有する場合、第三者サービスの名称とそのプライバシーポリシーへのリンクを記載) +* プライバシーポリシーの書き方に慣れていない場合は、Dify公式がメンテナンスしているプラグイン内のプライバシーポリシーを参考にすることができます。 + +#### **3. プラグインのマニフェストファイル内でプライバシーポリシー宣言を導入する** + +詳細なフィールドの記入方法については、[マニフェストファイルによるプラグイン情報の定義](/plugin_dev_ja/0411-plugin-info-by-manifest.ja)を参照してください。 + +### **よくある質問** + +1. **ユーザー個人データの「収集と使用」とは何ですか?一般的なケースにはどのようなものがありますか?** + +「収集と使用」とは、ユーザーデータの収集、転送、使用、または共有を指します。一般的な状況には以下が含まれます: + +* フォームを通じて個人情報を収集する; +* ログイン機能を使用する(サードパーティ認証を含む); +* 個人情報を含む可能性のある入力またはリソースを収集する; +* ユーザーの行動、インタラクション、および使用状況を分析する; +* メッセージ、チャット履歴、メールなどの通信内容を保存する; +* 関連するソーシャルメディアのユーザープロフィールにアクセスする; +* 運動、心拍数、医療情報などの健康データを収集する; +* 検索履歴または閲覧行動を保存する; +* 銀行情報、信用スコア、取引記録などの財務情報を処理する。 + +## 関連リソース + +- [プラグインの公開](/plugin_dev_ja/0321-release-overview.ja) - プラグインの公開プロセスを理解する +- [Difyマーケットプレイスへの公開](/plugin_dev_ja/0322-release-to-dify-marketplace.ja) - 公式マーケットプレイスへのプラグイン提出方法を学ぶ +- [プラグイン開発者ガイドライン](/plugin_dev_ja/0312-contributor-covenant-code-of-conduct.ja) - プラグイン提出の規範を理解する +- [マニフェストファイルによるプラグイン情報の定義](/plugin_dev_ja/0411-plugin-info-by-manifest.ja) - プラグインメタデータの設定 \ No newline at end of file diff --git a/plugin_dev_ja/0321-release-overview.zh.mdx b/plugin_dev_ja/0321-release-overview.zh.mdx new file mode 100644 index 00000000..a094afd2 --- /dev/null +++ b/plugin_dev_ja/0321-release-overview.zh.mdx @@ -0,0 +1,84 @@ +--- +dimensions: + type: + primary: operational + detail: deployment + level: beginner +standard_title: Release Overview +language: ja +title: プラグインのリリース +description: このドキュメントでは、Difyプラグインの3つのリリース方法(公式Marketplace、オープンソースのGitHubリポジトリ、ローカルプラグインファイルパッケージ)を紹介します。各リリース方法の特徴、リリースプロセス、適用シナリオについて詳しく説明し、さまざまな開発者のニーズを満たすための具体的なリリースに関する推奨事項を提供します。 +--- + +### リリース方法 + +さまざまな開発者のリリースニーズを満たすために、Difyは以下の3つのプラグインリリース方法を提供しています。リリース前に、プラグインの開発とテストが完了していることを確認し、[プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja)と[プラグイン開発者行動規範](/plugin_dev_ja/0312-contributor-covenant-code-of-conduct.ja)をお読みください。 + +#### **1. Marketplace** + +**概要**:Dify公式が提供するプラグインマーケットで、ユーザーはここでさまざまなプラグインを閲覧、検索し、ワンクリックでインストールできます。 + +**特徴**: + +* プラグインは審査後に公開され、**安全で信頼性があります**。 +* 個人またはチームの **Workspace** に直接インストールできます。 + +**リリースプロセス**: + +* プラグインプロジェクトを **Dify Marketplace** の[コードリポジトリ](https://github.com/langgenius/dify-plugins)に提出します。 +* 公式審査後、プラグインはマーケット内で公開され、他のユーザーがインストールして使用できるようになります。 + +詳細については、以下を参照してください: + +[Dify Marketplaceへのリリース](/plugin_dev_ja/0322-release-to-dify-marketplace.ja) + +#### 2. **GitHub リポジトリ** + +**概要**:プラグインをオープンソース化または **GitHub** 上でホストし、他の人が閲覧、ダウンロード、インストールしやすくします。 + +**特徴**: + +* **バージョン管理**と**オープンソース共有**に便利です。 +* ユーザーはプラグインリンクを通じて直接インストールでき、プラットフォームの審査は不要です。 + +**リリースプロセス**: + +* プラグインコードを GitHub リポジトリにプッシュします。 +* リポジトリリンクを共有し、ユーザーはリンクを通じてプラグインを **Dify Workspace** に統合できます。 + +詳細については、以下を参照してください: + +[個人の GitHub リポジトリへのリリース](/plugin_dev_ja/0322-release-to-individual-github-repo.ja) + +#### 3. プラグインファイルパッケージ(ローカルインストール) + +**概要**:プラグインをローカルファイル(例:`.difypkg` 形式)にパッケージ化し、ファイル共有を通じて他の人がインストールできるようにします。 + +**特徴**: + +* オンラインプラットフォームに依存せず、プラグインを**迅速かつ柔軟に**共有できます。 +* **プライベートプラグイン**または**内部テスト**に適しています。 + +**リリースプロセス**: + +* プラグインプロジェクトをローカルファイルにパッケージ化します。 +* Dify プラグインページで**プラグインをアップロード**をクリックし、ローカルファイルを選択してプラグインをインストールします。 + +プラグインプロジェクトをローカルファイルにパッケージ化して他の人と共有し、プラグインページでファイルをアップロードすると、プラグインを Dify Workspace 内にインストールできます。 + +詳細については、以下を参照してください: + +[ローカルファイルへのパッケージ化と共有](/plugin_dev_ja/0322-release-by-file.ja) + +### **リリースに関する推奨事項** + +* **プラグインを宣伝したい場合** → **Marketplace の使用を推奨します**。公式審査を通じてプラグインの品質を保証し、露出度を高めます。 +* **オープンソース共有プロジェクトの場合** → **GitHub の使用を推奨します**。バージョン管理とコミュニティ連携に便利です。 +* **迅速な配布または内部テストの場合** → **プラグインファイルの使用を推奨します**。簡単かつ効率的にインストールおよび共有できます。 + +## 関連リソース + +- [プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja) - Difyプラグイン開発の全体像を理解する +- [プラグイン開発者行動規範](/plugin_dev_ja/0312-contributor-covenant-code-of-conduct.ja) - プラグイン提出の規範を理解する +- [プラグインのプライバシーデータ保護ガイドライン](/plugin_dev_ja/0312-privacy-protection-guidelines.ja) - プライバシーポリシー作成要件を理解する +- [一般仕様定義](/plugin_dev_ja/0411-general-specifications.ja) - プラグインマニフェストファイルの設定を理解する \ No newline at end of file diff --git a/plugin_dev_ja/0322-release-by-file.zh.mdx b/plugin_dev_ja/0322-release-by-file.zh.mdx new file mode 100644 index 00000000..e2fef2a4 --- /dev/null +++ b/plugin_dev_ja/0322-release-by-file.zh.mdx @@ -0,0 +1,63 @@ +--- +dimensions: + type: + primary: operational + detail: deployment + level: intermediate +standard_title: Release by File +language: ja +title: ローカルファイルへのパッケージ化と共有 +description: このドキュメントでは、Difyプラグインプロジェクトをローカルファイルにパッケージ化し、他のユーザーと共有するための詳細な手順を説明します。内容には、プラグインのパッケージ化前の準備作業、Difyプラグイン開発ツールを使用したパッケージ化コマンドの実行、生成された.difypkgファイルのインストール方法、およびプラグインファイルを他のユーザーと共有する方法が含まれます。 +--- + +プラグイン開発が完了したら、プラグインプロジェクトをローカルファイルにパッケージ化し、他のユーザーと共有できます。プラグインファイルを使用して、Dify Workspaceにインストールできます。まだプラグインを開発していない場合は、[プラグイン開発入門ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja)を参照してください。 + +* **特徴**: + * オンラインプラットフォームに依存せず、**迅速かつ柔軟**にプラグインを共有できます。 + * **プライベートプラグイン**または**内部テスト**に適しています。 +* **リリースフロー**: + * プラグインプロジェクトをローカルファイルにパッケージ化します。 + * Difyプラグインページでファイルをアップロードしてプラグインをインストールします。 + +この記事では、プラグインプロジェクトをローカルファイルにパッケージ化する方法、およびローカルファイルを使用してプラグインをインストールする方法について説明します。 + +### 事前準備 + +* **Difyプラグイン開発ツール**、詳細については[開発ツールの初期化](/plugin_dev_ja/0221-initialize-development-tools.ja)を参照してください。 + +設定が完了したら、ターミナルで `dify version` コマンドを入力し、バージョン番号情報が出力されるか確認して、必要な開発ツールがインストールされていることを確認します。 + +### プラグインのパッケージ化 + +> プラグインをパッケージ化する前に、プラグインの `manifest.yaml` ファイルと `/provider` パス以下の `.yaml` ファイル内の `author` フィールドがGitHub IDと一致していることを確認してください。マニフェストファイルの詳細については、[一般仕様定義](/plugin_dev_ja/0411-general-specifications.ja)を参照してください。 + +プラグインプロジェクトの開発が完了したら、[リモートデバッグテスト](/plugin_dev_ja/0411-remote-debug-a-plugin.ja)が完了していることを確認してください。プラグインプロジェクトの親ディレクトリに移動し、以下のプラグインパッケージ化コマンドを実行します。 + +```bash +dify plugin package ./your_plugin_project +``` + +コマンドを実行すると、現在のパスに `.difypkg` という拡張子で終わるファイルが生成されます。 + +![プラグインファイルの生成](https://assets-docs.dify.ai/2024/12/98e09c04273eace8fe6e5ac976443cca.png) + +### プラグインのインストール + +Difyプラグイン管理ページにアクセスし、右上の**プラグインをインストール** → **ローカルファイル経由**でインストールをクリックするか、プラグインファイルをページの空白部分にドラッグアンドドロップしてプラグインをインストールします。 + +![プラグインファイルのインストール](https://assets-docs.dify.ai/2024/12/8c31c4025a070f23455799f942b91a57.png) + +### プラグインの公開 + +プラグインファイルを他のユーザーと共有したり、インターネットにアップロードして他のユーザーがダウンロードできるようにしたりできます。より広範囲にプラグインを共有したい場合は、次の方法を検討してください。 + +1. [個人のGitHubリポジトリに公開する](/plugin_dev_ja/0322-release-to-individual-github-repo.ja) - GitHub経由でプラグインを共有 +2. [Dify Marketplaceに公開する](/plugin_dev_ja/0322-release-to-dify-marketplace.ja) - 公式マーケットプレイスでプラグインを公開 + +## 関連リソース + +- [プラグインの公開](/plugin_dev_ja/0321-release-overview.ja) - 様々な公開方法について理解する +- [開発ツールの初期化](/plugin_dev_ja/0221-initialize-development-tools.ja) - プラグイン開発環境を設定する +- [プラグインのリモートデバッグ](/plugin_dev_ja/0411-remote-debug-a-plugin.ja) - プラグインのデバッグ方法を学ぶ +- [一般仕様定義](/plugin_dev_ja/0411-general-specifications.ja) - プラグインのメタデータを定義する +- [プラグイン開発入門ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja) - ゼロからプラグインを開発する \ No newline at end of file diff --git a/plugin_dev_ja/0322-release-to-dify-marketplace.zh.mdx b/plugin_dev_ja/0322-release-to-dify-marketplace.zh.mdx new file mode 100644 index 00000000..edddc014 --- /dev/null +++ b/plugin_dev_ja/0322-release-to-dify-marketplace.zh.mdx @@ -0,0 +1,101 @@ +--- +dimensions: + type: + primary: operational + detail: deployment + level: intermediate +standard_title: Release to Dify Marketplace +language: ja +title: Dify Marketplaceへの公開 +description: このガイドでは、Dify Marketplaceにプラグインを公開する完全なプロセスを詳細に説明します。PRの提出、審査プロセス、公開後のメンテナンスなど、主要なステップと注意事項を含みます。 +--- + +Dify Marketplaceは、パートナーやコミュニティ開発者からのプラグインの登録申請を歓迎します。あなたの貢献は、Difyプラグインの可能性をさらに豊かにします。このガイドでは、明確な公開プロセスとベストプラクティスの推奨事項を提供し、あなたのプラグインがスムーズに公開され、コミュニティに価値をもたらすことを目指します。まだプラグインを開発していない場合は、[プラグイン開発入門ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja)を参照してください。 + +以下の手順に従って、[GitHubコードリポジトリ](https://github.com/langgenius/dify-plugins)でプラグインのPull Request(PR)を提出し、審査を受けてください。承認されると、プラグインは正式にDify Marketplaceに公開されます。 + +### プラグインの公開プロセス + +Dify Marketplaceへのプラグイン公開には、以下のステップが含まれます: + +1. [プラグイン開発者行動規範](/plugin_dev_ja/0312-contributor-covenant-code-of-conduct.ja)に従ってプラグインの開発とテストを完了します。 +2. [プラグインプライバシーデータ保護ガイドライン](/plugin_dev_ja/0312-privacy-protection-guidelines.ja)に従ってプラグインのプライバシーポリシーを作成し、そのプライバシーポリシーのファイルパスまたはURLをプラグインの[一般仕様定義](/plugin_dev_ja/0411-general-specifications.ja)に記述します。 +3. プラグインのパッケージングを完了します。 +4. [Dify Plugins](https://github.com/langgenius/dify-plugins)コードリポジトリをフォークします。 +5. Organizationディレクトリを作成し、Organizationディレクトリ内にプラグイン名ディレクトリを作成し、プラグインのコードとpkgファイルを対応するプラグイン名ディレクトリにアップロードします。 +6. GitHubのPRテンプレートのコンテンツ形式に従ってPull Request(PR)を提出し、審査を待ちます。 +7. 審査に合格すると、プラグインコードはMainブランチにマージされ、プラグインは自動的に[Dify Marketplace](https://marketplace.dify.ai/)に登録されます。 + +プラグインの提出、審査、登録のフロー図: + +![The process of uploading plugins](https://assets-docs.dify.ai/2025/01/05df333acfaf662e99316432db23ba9f.png) + +> **注**: 上図のContributor Agreementは[プラグイン開発者行動規範](/plugin_dev_ja/0312-contributor-covenant-code-of-conduct.ja)を指します。 + +*** + +### Pull Request(PR)審査期間中 + +審査担当者からの質問やフィードバックに積極的に対応してください: + +* **14日以内**に解決されないPRコメントは古いものとしてマークされます(再開可能)。 +* **30日以内**に解決されないPRコメントはクローズされます(再開不可、新しいPRを作成する必要があります)。 + +*** + +### **Pull Request(PR)審査承認後** + +**1. 継続的なメンテナンス** + +* ユーザーから報告された問題や機能リクエストに対応します。 +* 重大なAPI変更が発生した場合のプラグイン移行: + * Difyは事前に変更通知と移行手順を公開します。 + * Difyのエンジニアが移行サポートを提供できます。 + +**2. Marketplace公開ベータテスト段階での制限** + +* 既存のプラグインに破壊的な変更を導入することを避けてください。 + +*** + +### 審査プロセス + +**1. 審査順序** + +* PRは**先着順**で処理されます。審査は1週間以内に開始されます。遅延がある場合、審査担当者はコメントを通じてPR作成者に通知します。 + +**2. 審査の重点** + +* プラグイン名、説明、設定手順が明確で指導的であるかを確認します。 +* プラグインの[一般仕様定義](/plugin_dev_ja/0411-general-specifications.ja)が形式仕様に準拠しており、有効な作成者の連絡先情報が含まれているかを確認します。 + +3. **プラグインの機能性と関連性** + +* [プラグイン開発ガイド](/plugin_dev_ja/0111-getting-started-dify-plugin.ja)に従ってプラグインをテストします。 +* Difyエコシステムにおけるプラグインの用途が合理的であることを確認します。 + +[Dify.AI](https://dify.ai/)は、プラグイン提出の承認または拒否の権利を留保します。 + +*** + +### よくある質問 + +1. **プラグインがユニークであるかどうかをどのように判断しますか?** + +例:Google検索プラグインが多言語バージョンを追加しただけの場合、既存プラグインの最適化と見なされるべきです。しかし、プラグインが顕著な機能改善(バッチ処理の最適化やエラー処理など)を実現している場合は、新しいプラグインとして提出できます。 + +2. **私のPRが古いものとしてマークされたり、クローズされた場合はどうすればよいですか?** + +古いものとしてマークされたPRは、フィードバックを解決した後に再開できます。クローズされたPR(30日以上経過)は、新しいPRを再作成する必要があります。 + +3. **ベータテスト段階でプラグインを更新できますか?** + +はい、ただし破壊的な変更を導入することは避けるべきです。 + +## 関連リソース + +- [プラグインの公開](/plugin_dev_ja/0321-release-overview.ja) - さまざまな公開方法を理解する +- [プラグイン開発者行動規範](/plugin_dev_ja/0312-contributor-covenant-code-of-conduct.ja) - プラグイン提出規範 +- [プラグインプライバシーデータ保護ガイドライン](/plugin_dev_ja/0312-privacy-protection-guidelines.ja) - プライバシーポリシー作成要件 +- [ローカルファイルとしてのパッケージ化と共有](/plugin_dev_ja/0322-release-by-file.ja) - プラグインのパッケージ化方法 +- [一般仕様定義](/plugin_dev_ja/0411-general-specifications.ja) - プラグインメタデータ定義 \ No newline at end of file diff --git a/plugin_dev_ja/0322-release-to-individual-github-repo.zh.mdx b/plugin_dev_ja/0322-release-to-individual-github-repo.zh.mdx new file mode 100644 index 00000000..5480db60 --- /dev/null +++ b/plugin_dev_ja/0322-release-to-individual-github-repo.zh.mdx @@ -0,0 +1,106 @@ +--- +dimensions: + type: + primary: operational + detail: deployment + level: intermediate +standard_title: Release to Individual GitHub Repo +language: ja +title: 個人のGitHubリポジトリへの公開 +description: このドキュメントでは、Difyプラグインを個人のGitHubリポジトリに公開する方法について詳しく説明します。準備作業、ローカルプラグインリポジトリの初期化、リモートリポジトリへの接続、プラグインファイルのアップロード、プラグインコードのパッケージング、GitHub経由でのプラグインインストール方法の全プロセスが含まれます。この方法により、開発者は自身のプラグインコードと更新を完全に管理できます。 +--- + +GitHubリポジトリのリンクからプラグインをインストールできます。プラグインの開発が完了したら、他の人がダウンロードして使用できるように、公開GitHubリポジトリにプラグインを公開することを選択できます。まだプラグインを開発していない場合は、[プラグイン開発入門ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja)を参照してください。 + +この方法には、以下の利点があります。 + +• **個人管理**:プラグインのコードと更新を完全に制御できます +• **迅速な共有**:GitHubリンクを通じて他のユーザーやチームメンバーに簡単に共有でき、テストと使用に便利です +• **協力とフィードバック**:プラグインをオープンソースにすると、GitHub上の潜在的な協力者を引き付け、プラグインの迅速な改善に役立つ可能性があります + +この記事では、プラグインをGitHubリポジトリに公開する方法を説明します。 + +### 準備作業 + +まず、プラグインを開発しテスト済みであること、および[プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja)と[プラグイン開発者行動規範](/plugin_dev_ja/0312-contributor-covenant-code-of-conduct.ja)を読んだことを確認してください。プラグインを公開する前に、ローカルに以下のツールがインストールされていることを確認してください: + +* GitHubアカウント +* 新しい公開GitHubリポジトリを作成する +* ローカルにGitツールがインストール済みであること + +GitHubの基本知識については、[GitHubドキュメント](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository)を参照してください。 + +### 1. プラグインプロジェクトの完成 + +公開GitHubにアップロードするということは、プラグインを公開することを意味します。プラグインのデバッグと検証が完了し、プラグインの `README.md` ファイルの説明が完成していることを確認してください。 + +説明ファイルには、以下の内容を含めることをお勧めします: + +* プラグインの概要と機能説明 +* インストールと設定の手順 +* 使用例 +* 連絡先または貢献ガイドライン + +### 2. ローカルプラグインリポジトリの初期化 + +プラグインをGitHubに公開アップロードする前に、プラグインのデバッグと検証作業が完了していることを確認してください。詳細は[リモートデバッグプラグイン](/plugin_dev_ja/0411-remote-debug-a-plugin.ja)を参照してください。ターミナルでプラグインプロジェクトフォルダに移動し、以下のコマンドを実行します: + +```bash +git init +git add . +git commit -m "Initial commit: Add plugin files" +``` + +初めてGitを使用する場合、Gitのユーザー名とメールアドレスを設定する必要があるかもしれません: + +```bash +git config --global user.name "Your Name" +git config --global user.email "your.email@example.com" +``` + +### 3. リモートリポジトリへの接続 + +以下のコマンドを使用して、ローカルリポジトリをGitHubリポジトリに接続します: + +```bash +git remote add origin https://github.com//.git +``` + +### 4. プラグインファイルのアップロード + +> プラグインをパッケージ化する前に、プラグインの`manifest.yaml`ファイルと`/provider`パス以下の`.yaml`ファイル内のauthorフィールドがGitHub IDと一致していることを確認してください。マニフェストファイルの详细な仕様については、[マニフェストファイルによるプラグイン情報の定義](/plugin_dev_ja/0411-plugin-info-by-manifest.ja)を参照してください。 + +プラグインプロジェクトをGitHubリポジトリにプッシュします: + +```bash +git branch -M main +git push -u origin main +``` + +コードをアップロードする際には、後でコードをパッケージ化するためにタグを付けることをお勧めします。 + +```bash +git tag -a v0.0.1 -m "Release version 0.0.1" + +git push origin v0.0.1 +``` + +### 5. プラグインコードのパッケージング + +GitHubコードリポジトリのReleasesページに移動し、新しいバージョンリリースを作成します。バージョンをリリースする際には、プラグインファイルをアップロードする必要があります。プラグインファイルのパッケージング方法については、[ローカルファイルとしてパッケージ化して共有](/plugin_dev_ja/0322-release-by-file.ja)で詳しく説明しています。 + +![プラグインのパッケージング](https://assets-docs.dify.ai/2024/12/5cb4696348cc6903e380287fce8f529d.png) + +### GitHub経由でのプラグインインストール + +他の人はGitHubリポジトリのアドレスを通じてこのプラグインをインストールできます。Difyプラットフォームのプラグイン管理ページにアクセスし、「GitHub経由でプラグインをインストール」を選択し、リポジトリのアドレスを入力後、バージョン番号とパッケージファイルを選択してインストールを完了します。 + +![](https://assets-docs.dify.ai/2024/12/3c2612349c67e6898a1f33a7cc320468.png) + +## 関連リソース + +- [プラグインの公開](/plugin_dev_ja/0321-release-overview.ja) - さまざまな公開方法を理解する +- [ローカルファイルとしてパッケージ化して共有](/plugin_dev_ja/0322-release-by-file.ja) - プラグインのパッケージング方法 +- [マニフェストファイルによるプラグイン情報の定義](/plugin_dev_ja/0411-plugin-info-by-manifest.ja) - プラグインのメタデータを定義する +- [プラグイン開発者行動規範](/plugin_dev_ja/0312-contributor-covenant-code-of-conduct.ja) - プラグイン開発の規範を理解する +- [リモートデバッグプラグイン](/plugin_dev_ja/0411-remote-debug-a-plugin.ja) - プラグインのデバッグ方法を学ぶ \ No newline at end of file diff --git a/plugin_dev_ja/0331-faq.zh.mdx b/plugin_dev_ja/0331-faq.zh.mdx new file mode 100644 index 00000000..6ec6b759 --- /dev/null +++ b/plugin_dev_ja/0331-faq.zh.mdx @@ -0,0 +1,35 @@ +--- +dimensions: + type: + primary: operational + detail: maintenance + level: beginner +standard_title: Faq +language: ja +title: よくある質問 +description: Author Allen このドキュメントでは、Difyプラグインの開発とインストールプロセスにおける一般的な質問に答えます。これには、プラグインのアップロード失敗の解決策(authorフィールドの変更)や、プラグインインストール中の検証例外の対処法(FORCE_VERIFYING_SIGNATURE環境変数の設定)が含まれます。 +--- + +## プラグインインストール時にアップロード失敗と表示された場合の対処法は? + +**エラー詳細**:`PluginDaemonBadRequestError: plugin_unique_identifier is not valid` というエラーが表示されます。 + +**解決策**:プラグインプロジェクト下の `manifest.yaml` ファイルおよび `/provider` パス下の `.yaml` ファイル内の `author` フィールドを GitHub ID に変更します。 + +プラグインのパッケージングコマンドを再実行し、新しいプラグインパッケージをインストールします。 + +## プラグインインストール時に例外が発生した場合の対処法は? + +**問題の説明**:プラグインインストール時に例外メッセージ:`plugin verification has been enabled, and the plugin you want to install has a bad signature` が表示された場合、どうすればよいですか? + +**解決策**:`/docker/.env` 設定ファイルの末尾に `FORCE_VERIFYING_SIGNATURE=false` フィールドを追加し、以下のコマンドを実行してDifyサービスを再起動します: + +```bash +cd docker +docker compose down +docker compose up -d +``` + +このフィールドを追加すると、DifyプラットフォームはDify Marketplaceに未登録(未審査)のすべてのプラグインのインストールを許可するようになり、セキュリティ上のリスクが生じる可能性があります。 + +テスト環境またはサンドボックス環境でプラグインをインストールし、安全性を確認してから本番環境にインストールすることをお勧めします。 \ No newline at end of file diff --git a/plugin_dev_ja/0411-general-specifications.zh.mdx b/plugin_dev_ja/0411-general-specifications.zh.mdx new file mode 100644 index 00000000..2346ca93 --- /dev/null +++ b/plugin_dev_ja/0411-general-specifications.zh.mdx @@ -0,0 +1,120 @@ +--- +dimensions: + type: + primary: reference + detail: core + level: beginner +standard_title: General Specifications +language: ja +title: 一般的な仕様定義 +description: このドキュメントでは、Difyプラグイン開発における一般的な構造と仕様、パスの仕様、国際化オブジェクト(I18nObject)、プロバイダー設定フォーム構造(ProviderConfig)、モデル設定(ModelConfig)、ノードレスポンス(NodeResponse)、ツールセレクター(ToolSelector)などの重要なデータ構造の定義と用途について詳しく説明します。 +--- + +本稿では、プラグイン開発で一般的に使用される構造について簡単に説明します。開発プロセス中は、全体のアーキテクチャをよりよく理解するために、[プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja)および[開発者向けチートシート](/plugin_dev_ja/0131-cheatsheet.ja)と合わせてお読みになることを強くお勧めします。 + +### パスの仕様 + +Manifestファイルまたは任意のYAMLファイルにファイルパスを記入する際、ファイルの種類に応じて以下の2つの仕様に従ってください: + +* 対象ファイルが画像や動画などのマルチメディアファイルである場合(例:プラグインの `icon` を記入する場合)、これらのファイルをプラグインのルートディレクトリにある `_assets` フォルダに配置する必要があります。 +* 対象ファイルが `.py` や `.yaml` などの通常のテキストファイル(コードファイル)である場合、プラグインプロジェクト内でのそのファイルの絶対パスを記入する必要があります。 + +### 一般的な構造 + +プラグインを定義する際、ツール、モデル、Endpoint間で共有できるいくつかのデータ構造があります。ここでは、これらの共通構造を定義します。 + +#### I18nObject + +`I18nObject`は、[IETF BCP 47](https://tools.ietf.org/html/bcp47)標準に準拠した国際化構造であり、現在サポートされている4つの言語は次のとおりです。 + +* en_US +* zh_Hans +* ja_Jp +* pt_BR + +#### ProviderConfig + +`ProviderConfig`は、`Tool`および`Endpoint`に適用可能な汎用的なプロバイダー設定フォーム構造です。 + +* `name`(string):フォーム項目の名前 +* `label`([I18nObject](#i18nobject), 必須):[IETF BCP 47](https://tools.ietf.org/html/bcp47)に準拠 +* `type`([provider_config_type](#providerconfigtype-string), 必須):フォームタイプ +* `scope`([provider_config_scope](#providerconfigscope-string)):オプションの範囲、`type`によって変動 +* `required`(bool):必須(空にできません) +* `default`(any):デフォルト値、基本型 `float` `int` `string` のみをサポート +* `options`(list\[[provider_config_option](#providerconfigoption-object)]):オプション項目、typeが `select` の場合のみ使用 +* `helper`(object):ヘルプドキュメントリンクのラベル、[IETF BCP 47](https://tools.ietf.org/html/bcp47)に準拠 +* `url` (string):ヘルプドキュメントリンク +* `placeholder`(object):[IETF BCP 47](https://tools.ietf.org/html/bcp47)に準拠 + +#### ProviderConfigOption(object) + +* `value`(string, 必須):値 +* `label`(object, 必須):[IETF BCP 47](https://tools.ietf.org/html/bcp47)に準拠 + +#### ProviderConfigType(string) + +* `secret-input` (string):設定情報は暗号化されます +* `text-input`(string):通常のテキスト +* `select`(string):ドロップダウンリスト +* `boolean`(bool):スイッチ +* `model-selector`(object):モデル設定情報、プロバイダー名、モデル名、モデルパラメータなどを含む +* `app-selector`(object):アプリID +* `tool-selector`(object):ツール設定情報、ツールプロバイダー、名前、パラメータなどを含む +* `dataset-selector`(string):未定 + +#### ProviderConfigScope(string) + +* `type`が `model-selector` の場合 + * `all` + * `llm` + * `text-embedding` + * `rerank` + * `tts` + * `speech2text` + * `moderation` + * `vision` +* `type`が `app-selector` の場合 + * `all` + * `chat` + * `workflow` + * `completion` +* `type`が `tool-selector` の場合 + * `all` + * `plugin` + * `api` + * `workflow` + +#### ModelConfig + +* `provider` (string): `plugin_id`を含むモデルプロバイダー名。形式は`langgenius/openai/openai`のようになります。 +* `model` (string): 具体的なモデル名。 +* `model_type` (enum): モデルタイプの列挙型。[モデル設計規則](/plugin_dev_ja/0411-model-designing-rules#modeltype.ja)ドキュメントを参照できます。 + +#### NodeResponse + +* `inputs` (dict): 最終的にノードに入力される変数。 +* `outputs` (dict): ノードの出力結果。 +* `process_data` (dict): ノード実行中に生成されるデータ。 + +#### ToolSelector + +* `provider_id` (string): ツールプロバイダー名 +* `tool_name` (string): ツール名 +* `tool_description` (string): ツールの説明 +* `tool_configuration` (dict\[str, Any]): ツールの設定情報 +* `tool_parameters` (dict\[str, dict]): LLMによる推論が必要なパラメータ + * `name` (string): パラメータ名 + * `type` (string): パラメータタイプ + * `required` (bool): 必須かどうか + * `description` (string): パラメータの説明 + * `default` (any): デフォルト + * `options`(list\[string]): オプション項目 + +## 関連リソース + +- [プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja) - Difyプラグイン開発の全体像を理解する +- [開発者向けチートシート](/plugin_dev_ja/0131-cheatsheet.ja) - プラグイン開発でよく使われるコマンドと概念のクイックリファレンス + +- [ツールプラグイン開発詳細](/plugin_dev_ja/0222-tool-plugin.ja) - ツールプラグイン開発プロセスの詳細を理解する +- [モデル設計規則](/plugin_dev_ja/0411-model-designing-rules.ja) - モデル設定の仕様を理解する \ No newline at end of file diff --git a/plugin_dev_ja/0411-model-designing-rules.zh.mdx b/plugin_dev_ja/0411-model-designing-rules.zh.mdx new file mode 100644 index 00000000..bb89e2cc --- /dev/null +++ b/plugin_dev_ja/0411-model-designing-rules.zh.mdx @@ -0,0 +1,214 @@ +--- +dimensions: + type: + primary: reference + detail: core + level: beginner +standard_title: Model Designing Rules +language: ja +title: モデル設計ルール +description: 本書では、Difyモデルプラグイン開発のコアコンセプトと構造を詳細に定義します。これには、モデルプロバイダー(Provider)、AIモデルエンティティ(AIModelEntity)、モデルタイプ(ModelType)、設定方法(ConfigurateMethod)、モデル特性(ModelFeature)、パラメータルール(ParameterRule)、価格設定(PriceConfig)、およびさまざまな認証情報スキーマの詳細なデータ構造仕様が含まれます。 +--- + +* モデルプロバイダールールは [Provider](#provider) エンティティに基づきます。 +* モデルルールは [AIModelEntity](#aimodelentity) エンティティに基づきます。 + +> 以下のすべてのエンティティは `Pydantic BaseModel` に基づいており、`entities` モジュールに対応するエンティティがあります。 + +### Provider + +* `provider` (string) プロバイダー識別子、例:`openai` +* `label` (object) プロバイダー表示名、i18n、`en_US` 英語、`zh_Hans` 中国語の2言語設定可能 + * `zh_Hans` (string) \[optional] 中国語ラベル名、`zh_Hans` を設定しない場合はデフォルトで `en_US` が使用されます。 + * `en_US` (string) 英語ラベル名 +* `description` (object) \[optional] プロバイダー説明、i18n + * `zh_Hans` (string) \[optional] 中国語説明 + * `en_US` (string) 英語説明 +* `icon_small` (string) \[optional] プロバイダー小アイコン、対応するプロバイダー実装ディレクトリ下の `_assets` ディレクトリに保存、中英ポリシーは `label` と同様 + * `zh_Hans` (string) \[optional] 中国語アイコン + * `en_US` (string) 英語アイコン +* `icon_large` (string) \[optional] プロバイダー大アイコン、対応するプロバイダー実装ディレクトリ下の `_assets` ディレクトリに保存、中英ポリシーは `label` と同様 + * `zh_Hans` (string) \[optional] 中国語アイコン + * `en_US` (string) 英語アイコン +* `background` (string) \[optional] 背景色カラーコード、例:#FFFFFF、空の場合はフロントエンドのデフォルトカラー値が表示されます。 +* `help` (object) \[optional] ヘルプ情報 + * `title` (object) ヘルプタイトル、i18n + * `zh_Hans` (string) \[optional] 中国語タイトル + * `en_US` (string) 英語タイトル + * `url` (object) ヘルプリンク、i18n + * `zh_Hans` (string) \[optional] 中国語リンク + * `en_US` (string) 英語リンク +* `supported_model_types` (array\[[ModelType](#modeltype)]) サポートされるモデルタイプ +* `configurate_methods` (array\[[ConfigurateMethod](#configuratemethod)]) 設定方法 +* `provider_credential_schema` (\[[ProviderCredentialSchema](#providercredentialschema)]) プロバイダー認証情報スキーマ +* `model_credential_schema` (\[[ModelCredentialSchema](#modelcredentialschema)]) モデル認証情報スキーマ + +### AIModelEntity + +* `model` (string) モデル識別子、例:`gpt-3.5-turbo` +* `label` (object) \[optional] モデル表示名、i18n、`en_US` 英語、`zh_Hans` 中国語の2言語設定可能 + * `zh_Hans` (string) \[optional] 中国語ラベル名 + * `en_US` (string) 英語ラベル名 +* `model_type` ([ModelType](#modeltype)) モデルタイプ +* `features` (array\[[ModelFeature](#modelfeature)]) \[optional] サポート機能リスト +* `model_properties` (object) モデルプロパティ + * `mode` ([LLMMode](#llmmode)) モード (モデルタイプ `llm` で利用可能) + * `context_size` (int) コンテキストサイズ (モデルタイプ `llm` `text-embedding` で利用可能) + * `max_chunks` (int) 最大チャンク数 (モデルタイプ `text-embedding moderation` で利用可能) + * `file_upload_limit` (int) ファイル最大アップロード制限、単位:MB。(モデルタイプ `speech2text` で利用可能) + * `supported_file_extensions` (string) サポートされるファイル拡張子、例:mp3,mp4(モデルタイプ `speech2text` で利用可能) + * `default_voice` (string) デフォルトボイス、必須:alloy,echo,fable,onyx,nova,shimmer(モデルタイプ `tts` で利用可能) + * `voices` (list) 選択可能なボイスリスト。 + * `mode` (string) ボイスモデル。(モデルタイプ `tts` で利用可能) + * `name` (string) ボイスモデル表示名。(モデルタイプ `tts` で利用可能) + * `language` (string) ボイスモデルがサポートする言語。(モデルタイプ `tts` で利用可能) + * `word_limit` (int) 1回の変換文字数制限、デフォルトは段落ごとに分割(モデルタイプ `tts` で利用可能) + * `audio_type` (string) サポートされる音声ファイル拡張子、例:mp3,wav(モデルタイプ `tts` で利用可能) + * `max_workers` (int) サポートされるテキスト音声変換の同時実行タスク数(モデルタイプ `tts` で利用可能) + * `max_characters_per_chunk` (int) 各チャンクの最大文字数 (モデルタイプ `moderation` で利用可能) +* `parameter_rules` (array\[[ParameterRule](#parameterrule)]) \[optional] モデル呼び出しパラメータールール +* `pricing` (\[[PriceConfig](#priceconfig)]) \[optional] 価格情報 +* `deprecated` (bool) 非推奨かどうか。非推奨の場合、モデルリストには表示されなくなりますが、既に設定されているものは引き続き使用可能です。デフォルトは False。 + +### ModelType + +* `llm` テキスト生成モデル +* `text-embedding` テキスト埋め込みモデル +* `rerank` Rerank モデル +* `speech2text` 音声テキスト変換 +* `tts` テキスト音声合成 +* `moderation` モデレーション + +### ConfigurateMethod + +* `predefined-model` 事前定義モデル + +ユーザーは統一されたプロバイダー認証情報を設定するだけで、プロバイダー下の事前定義モデルを使用できることを示します。 + +* `customizable-model` カスタマイズ可能モデル + +ユーザーは各モデルの認証情報設定を新たに追加する必要があります。 + +* `fetch-from-remote` リモートから取得 + +`predefined-model` の設定方法と同様に、統一されたプロバイダー認証情報を設定するだけで、モデルは認証情報を通じてプロバイダーから取得されます。 + + +### ModelFeature + +* `agent-thought` エージェント思考、通常70Bを超えると思考連鎖能力があります。 +* `vision` ビジョン、すなわち画像理解。 +* `tool-call` ツール呼び出し +* `multi-tool-call` マルチツール呼び出し +* `stream-tool-call` ストリーミングツール呼び出し + +### FetchFrom + +* `predefined-model` 事前定義モデル +* `fetch-from-remote` リモートモデル + +### LLMMode + +* `completion` テキスト補完 +* `chat` 対話 + +### ParameterRule + +* `name` (string) モデル呼び出し時の実際のパラメータ名 +* `use_template` (string) \[optional] テンプレートを使用 + +> テンプレートの具体的な使用方法については、[新しいモデルプロバイダーの作成](/plugin_dev_ja/0222-creating-new-model-provider.ja)の例を参照してください。 + +デフォルトで5種類の変数コンテンツ設定テンプレートがプリセットされています: + +* `temperature` +* `top_p` +* `frequency_penalty` +* `presence_penalty` +* `max_tokens` + +`use_template` に直接テンプレート変数名を設定すると、`entities.defaults.PARAMETER_RULE_TEMPLATE` のデフォルト設定が使用され、`name` と `use_template` 以外のすべてのパラメータを設定する必要はありません。追加の設定パラメータが設定された場合、デフォルト設定が上書きされます。`openai/llm/gpt-3.5-turbo.yaml` を参照してください。 + +* `label` (object) \[optional] ラベル、i18n +* `zh_Hans`(string) \[optional] 中国語ラベル名 +* `en_US` (string) 英語ラベル名 +* `type`(string) \[optional] パラメータタイプ + * `int` 整数 + * `float` 浮動小数点数 + * `string` 文字列 + * `boolean` ブール型 +* `help` (string) \[optional] ヘルプ情報 +* `zh_Hans` (string) \[optional] 中国語ヘルプ情報 +* `en_US` (string) 英語ヘルプ情報 +* `required` (bool) 必須項目かどうか、デフォルトは False。 +* `default`(int/float/string/bool) \[optional] デフォルト値 +* `min`(int/float) \[optional] 最小値、数値タイプのみ適用 +* `max`(int/float) \[optional] 最大値、数値タイプのみ適用 +* `precision`(int) \[optional] 精度、小数点以下の保持桁数、数値タイプのみ適用 +* `options` (array\[string]) \[optional] ドロップダウン選択肢の値、`type` が `string` の場合にのみ適用、設定しない場合または null の場合は選択肢の値を制限しません + +### PriceConfig + +* `input` (float) 入力単価、すなわちプロンプト単価 +* `output` (float) 出力単価、すなわち返却コンテンツ単価 +* `unit` (float) 価格単位、例えば1Mトークンで計算する場合、単価に対応する単位トークン数は `0.000001` です +* `currency` (string) 通貨単位 + +### ProviderCredentialSchema + +* `credential_form_schemas` (array\[[CredentialFormSchema](#credentialformschema)]) 認証情報フォームスキーマ + +### ModelCredentialSchema + +* `model` (object) モデル識別子、変数名はデフォルトで `model` + * `label` (object) モデルフォーム項目表示名 + * `en_US` (string) 英語 + * `zh_Hans`(string) \[optional] 中国語 + * `placeholder` (object) モデルのプレースホルダーコンテンツ + * `en_US`(string) 英語 + * `zh_Hans`(string) \[optional] 中国語 +* `credential_form_schemas` (array\[[CredentialFormSchema](#credentialformschema)]) 認証情報フォームスキーマ + +### CredentialFormSchema + +* `variable` (string) フォーム項目変数名 +* `label` (object) フォーム項目ラベル名 + * `en_US`(string) 英語 + * `zh_Hans` (string) \[optional] 中国語 +* `type` ([FormType](#formtype)) フォーム項目タイプ +* `required` (bool) 必須項目かどうか +* `default`(string) デフォルト値 +* `options` (array\[[FormOption](#formoption)]) フォーム項目が `select` または `radio` の場合の専用プロパティ、ドロップダウン内容を定義 +* `placeholder`(object) フォーム項目が `text-input` の場合の専用プロパティ、フォーム項目のプレースホルダー + * `en_US`(string) 英語 + * `zh_Hans` (string) \[optional] 中国語 +* `max_length` (int) フォーム項目が`text-input`の場合の専用プロパティ、入力最大長を定義、0は無制限。 +* `show_on` (array\[[FormShowOnObject](#formshowonobject)]) 他のフォーム項目の値が条件に一致する場合に表示、空の場合は常に表示。 + +#### FormType + +* `text-input` テキスト入力コンポーネント +* `secret-input` パスワード入力コンポーネント +* `select` 単一選択ドロップダウン +* `radio` ラジオコンポーネント +* `switch` スイッチコンポーネント、`true` と `false` のみをサポート + +#### FormOption + +* `label` (object) ラベル + * `en_US`(string) 英語 + * `zh_Hans`(string) \[optional] 中国語 +* `value` (string) ドロップダウン選択肢の値 +* `show_on` (array\[[FormShowOnObject](#formshowonobject)]) 他のフォーム項目の値が条件に一致する場合に表示、空の場合は常に表示。 + +#### FormShowOnObject + +* `variable` (string) 他のフォーム項目変数名 +* `value` (string) 他のフォーム項目変数値 + +## 関連リソース + +- [モデルアーキテクチャ詳解](/plugin_dev_ja/0412-model-schema.ja) - モデルプラグインのアーキテクチャ仕様を深く理解する +- [新しいモデルの迅速な統合](/plugin_dev_ja/0211-getting-started-new-model.ja) - これらのルールを適用して新しいモデルを追加する方法を学ぶ +- [一般仕様定義](/plugin_dev_ja/0411-general-specifications.ja) - プラグインマニフェストファイルの設定を理解する +- [新しいモデルプロバイダーの作成](/plugin_dev_ja/0222-creating-new-model-provider.ja) - 全く新しいモデルプロバイダープラグインを開発する \ No newline at end of file diff --git a/plugin_dev_ja/0411-model-plugin-introduction.zh.mdx b/plugin_dev_ja/0411-model-plugin-introduction.zh.mdx new file mode 100644 index 00000000..467b50d5 --- /dev/null +++ b/plugin_dev_ja/0411-model-plugin-introduction.zh.mdx @@ -0,0 +1,82 @@ +--- +dimensions: + type: + primary: reference + detail: core + level: beginner +standard_title: Model Plugin Introduction +language: ja +title: モデルプラグイン +description: モデルプラグインの基本概念と構造を紹介します。モデルプラグインは、Difyがさまざまなプロバイダー(OpenAI、Anthropic、Googleなど)の、大規模言語モデル(LLM)、テキスト埋め込み、音声テキスト変換といったさまざまなタイプのモデルを呼び出すことを可能にします。 +--- + +Modelプラグインは、DifyプラットフォームがそのモデルプロバイダーのすべてのLLMを呼び出すことを可能にします。例えば、OpenAIモデルプラグインをインストールすると、DifyプラットフォームはOpenAIが提供する`GPT-4`、`GPT-4o-2024-05-13`などのモデルを呼び出すことができます。 + +## モデルプラグインの構造 + +モデルプラグイン開発プロセスで関連する可能性のある概念を理解しやすくするために、以下にモデルプラグイン内の構造の簡単な紹介を示します。 + +* **モデルプロバイダー**:大規模モデルの開発会社。例:**OpenAI、Anthropic、Google**など。 +* **モデルカテゴリ**:モデルプロバイダーによって、大規模言語モデル(LLM)、テキスト埋め込みモデル(Text embedding)、音声テキスト変換(Speech2text)などのカテゴリが存在します。 +* **特定のモデル**:`claude-3-5-sonnet`、`gpt-4-turbo`など。 + +プラグインプロジェクト内のコード階層構造: + +```bash +- モデルプロバイダー + - モデルカテゴリ + - 特定のモデル +``` + +**Anthropic**を例にとると、モデルプラグインのサンプル構造は次のようになります。 + +```bash +- Anthropic + - llm + claude-3-5-sonnet-20240620 + claude-3-haiku-20240307 + claude-3-opus-20240229 + claude-3-sonnet-20240229 + claude-instant-1.2 + claude-instant-1 +``` + +OpenAIを例にとると、複数のモデルタイプをサポートしているため、複数のモデルカテゴリレイヤーが存在し、構造は次のようになります。 + +```bash +├── models +│ ├── llm +│ │ ├── chatgpt-4o-latest +│ │ ├── gpt-3.5-turbo +│ │ ├── gpt-4-0125-preview +│ │ ├── gpt-4-turbo +│ │ ├── gpt-4o +│ │ ├── llm +│ │ ├── o1-preview +│ │ └── text-davinci-003 +│ ├── moderation +│ │ ├── moderation +│ │ └── text-moderation-stable +│ ├── speech2text +│ │ ├── speech2text +│ │ └── whisper-1 +│ ├── text_embedding +│ │ ├── text-embedding-3-large +│ │ └── text_embedding +│ └── tts +│ ├── tts-1-hd +│ ├── tts-1 +│ └── tts +``` + +## モデル設定 + +モデルプラグインは、設定ファイルを通じてモデルの動作と属性を定義します。詳細なモデル設計ルールと設定フォーマットについては、[モデル設計ルール](/plugin_dev_ja/0411-model-designing-rules.ja)ドキュメントと[モデルスキーマ](/plugin_dev_ja/0412-model-schema.ja)仕様を参照してください。 + +## さらに読む + +- [新しいモデルの迅速な統合](/plugin_dev_ja/0211-getting-started-new-model.ja) - 既にサポートされているプロバイダーに新しいモデルを追加する方法を学ぶ +- [モデル設計ルール](/plugin_dev_ja/0411-model-designing-rules.ja) - モデル設定の仕様を詳しく理解する +- [モデルスキーマ](/plugin_dev_ja/0412-model-schema.ja) - モデルプラグインのアーキテクチャを深く理解する +- [共通仕様定義](/plugin_dev_ja/0411-general-specifications.ja) - プラグインメタデータの定義方法を理解する +- [プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja) - プラグイン開発入門ガイドに戻る \ No newline at end of file diff --git a/plugin_dev_ja/0411-persistent-storage-kv.zh.mdx b/plugin_dev_ja/0411-persistent-storage-kv.zh.mdx new file mode 100644 index 00000000..d18ead60 --- /dev/null +++ b/plugin_dev_ja/0411-persistent-storage-kv.zh.mdx @@ -0,0 +1,62 @@ +--- +dimensions: + type: + primary: reference + detail: core + level: beginner +standard_title: Persistent Storage KV +language: ja +title: 永続ストレージ +description: このドキュメントでは、Difyプラグインの永続ストレージ機能について説明し、プラグインでKVデータベースを使用してデータを保存、取得、削除する方法を詳しく説明します。この機能により、プラグインは同じワークスペース内でデータを永続的に保存でき、セッションをまたいだデータ保存のニーズに対応できます。 +--- + +プラグイン内のツールおよびエンドポイントを個別に見てみると、ほとんどの場合、単一のインタラクションしか完了できず、リクエスト後にデータを返してタスクが終了することがわかります。 + +永続的な記憶の実装など、長期間保存する必要があるデータが存在する場合、プラグインには永続ストレージ機能が求められます。**永続ストレージメカニズムにより、プラグインは同一ワークスペース内でデータを永続的に保存することが可能になります。**現在はKVデータベースを提供することでストレージのニーズに対応していますが、将来的には実際の使用状況に基づき、より柔軟で強力なストレージインターフェースを提供する可能性があります。 + +### キーの保存 + +#### **エントリポイント** + +```python + self.session.storage +``` + +#### **インターフェース** + +```python + def set(self, key: str, val: bytes) -> None: + pass +``` + +渡されるのはbytesであることに注意してください。そのため、実際にはその中にファイルを保存することができます。 + +### キーの取得 + +#### **エントリポイント** + +```python + self.session.storage +``` + +#### **インターフェース** + +```python + def get(self, key: str) -> bytes: + pass +``` + +### キーの削除 + +#### **エントリポイント** + +```python + self.session.storage +``` + +#### **インターフェース** + +```python + def delete(self, key: str) -> None: + pass +``` diff --git a/plugin_dev_ja/0411-plugin-info-by-manifest.zh.mdx b/plugin_dev_ja/0411-plugin-info-by-manifest.zh.mdx new file mode 100644 index 00000000..62815f8f --- /dev/null +++ b/plugin_dev_ja/0411-plugin-info-by-manifest.zh.mdx @@ -0,0 +1,115 @@ +--- +dimensions: + type: + primary: reference + detail: core + level: beginner +standard_title: Plugin info by Manifest +language: ja +title: マニフェスト +description: 作成者 Yeuoly、Allen このドキュメントでは、Difyプラグインのマニフェストファイルについて詳しく説明します。これは、プラグインの基本情報を定義するYAMLファイルです。ドキュメントには、完全なコード例と詳細な構造説明が含まれており、プラグインのバージョン、タイプ、作成者、名称、リソース使用、権限申請、機能定義、ランタイムなど、各方面の設定情報を網羅しています。 +--- + +マニフェストは、yaml 仕様に準拠したファイルであり、プラグイン名、作成者、含まれるツール、モデルなどの情報を含む、**プラグイン**の最も基本的な情報を定義します。プラグインの全体的なアーキテクチャについては、[プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja)および[開発者向けチートシート](/plugin_dev_ja/0131-cheatsheet.ja)を参照してください。 + +このファイルの形式が間違っている場合、プラグインの解析とパッケージ化プロセスは失敗します。 + +### コード例 + +以下は、マニフェストファイルの簡単な例です。各データの意味と役割については、後ほど説明します。他のプラグインのコードを参照する必要がある場合は、[GitHub コードリポジトリ](https://github.com/langgenius/dify-official-plugins/blob/main/tools/google/manifest.yaml)を参照してください。 + +```yaml +version: 0.0.1 +type: "plugin" +author: "Yeuoly" +name: "neko" +label: + en_US: "Neko" +created_at: "2024-07-12T08:03:44.658609186Z" +icon: "icon.svg" +resource: + memory: 1048576 + permission: + tool: + enabled: true + model: + enabled: true + llm: true + endpoint: + enabled: true + app: + enabled: true + storage: + enabled: true + size: 1048576 +plugins: + endpoints: + - "provider/neko.yaml" +meta: + version: 0.0.1 + arch: + - "amd64" + - "arm64" + runner: + language: "python" + version: "3.10" + entrypoint: "main" +privacy: "./privacy.md" +``` + +### 構造 + +* `version`(version, 必須):プラグインのバージョン +* `type`(type, 必須):プラグインタイプ。現在は `plugin` のみをサポートし、将来的には `bundle` をサポート予定です。 +* `author`(string, 必須):作成者。Marketplace では組織名として定義されます。 +* `label`(label, 必須):多言語名称 +* `created_at`(RFC3339, 必須):作成日時。Marketplace では、作成日時が現在時刻を超えてはなりません。 +* `icon`(asset, 必須):アイコンのパス +* `resource` (object):申請が必要なリソース + * `memory` (int64):最大メモリ使用量。主に SaaS 上の AWS Lambda リソース申請に関連し、単位はバイトです。 + * `permission`(object):権限申請 + * `tool`(object):ツールを逆呼び出しする権限 + * `enabled` (bool) + * `model`(object):モデルを逆呼び出しする権限 + * `enabled`(bool) + * `llm`(bool) + * `text_embedding`(bool) + * `rerank`(bool) + * `tts`(bool) + * `speech2text`(bool) + * `moderation`(bool) + * `node`(object):ノードを逆呼び出しする権限 + * `enabled`(bool) + * `endpoint`(object):`endpoint` の登録を許可する権限 + * `enabled`(bool) + * `app`(object):`app` を逆呼び出しする権限 + * `enabled`(bool) + * `storage`(object):永続ストレージを申請する権限 + * `enabled`(bool) + * `size`(int64):許可される永続メモリの最大サイズ。単位はバイトです。 +* `plugins`(object, 必須):プラグインが拡張する具体的な機能の `yaml` ファイルリスト。プラグインパッケージ内の絶対パスです。例えば、モデルを拡張する必要がある場合、`openai.yaml` のようなファイルを定義し、そのファイルパスをここに記入します。そのパス上のファイルは実際に存在しなければならず、そうでなければパッケージ化は失敗します。 + * 形式 + * `tools`(list\[string]):プラグインが拡張する[ツール](/plugin_dev_ja/0222-tool-plugin.ja)プロバイダー + * `models`(list\[string]):プラグインが拡張する[モデル](/plugin_dev_ja/0131-model-plugin-introduction.ja)プロバイダー + * `endpoints`(list\[string]):プラグインが拡張する [Endpoints](/plugin_dev_ja/0432-endpoint.ja) プロバイダー + * `agent_strategies` (list\[string]):プラグインが拡張する [Agent 戦略](/plugin_dev_ja/9433-agent-strategy-plugin.ja)プロバイダー + * 制限 + * ツールとモデルを同時に拡張することはできません + * 拡張機能がまったくない状態は許可されません。 + * モデルと Endpoint を同時に拡張することはできません + * 現在、各タイプの拡張は最大1つのプロバイダーのみサポートしています +* `meta`(object) + * `version`(version, 必須):`manifest` フォーマットバージョン。初期バージョンは `0.0.1` です。 + * `arch`(list\[string], 必須):サポートされるアーキテクチャ。現在は `amd64`、`arm64` のみをサポートしています。 + * `runner`(object, 必須):ランタイム設定 + * `language`(string):現在は python のみをサポートしています。 + * `version`(string):言語のバージョン。現在は `3.12` のみをサポートしています。 + * `entrypoint`(string):プログラムのエントリポイント。python では `main` であるべきです。 +* `privacy` (string, オプション):オプション項目。プラグインのプライバシーポリシーファイルの相対パスまたは URL を指定します。例:`"./privacy.md"` または `"https://your-web/privacy"`。プラグインを Dify Marketplace に公開する予定がある場合、**このフィールドは必須です**。明確なユーザーデータの使用とプライバシーに関する声明を提供するために使用されます。詳細な記入ガイドラインについては、[プラグインのプライバシーデータ保護ガイドライン](/plugin_dev_ja/0312-privacy-protection-guidelines.ja)を参照してください。 + +## 関連リソース + +- [プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja) - Difyプラグイン開発の全体像を理解する +- [新しいモデルを迅速に導入する](/plugin_dev_ja/0211-getting-started-new-model.ja) - 既存のプロバイダーに新しいモデルを追加する方法を学ぶ +- [共通仕様定義](/plugin_dev_ja/0411-general-specifications.ja) - プラグイン開発における共通構造を理解する +- [リリース概要](/plugin_dev_ja/0321-release-overview.ja) - プラグインのリリースプロセスを学ぶ \ No newline at end of file diff --git a/plugin_dev_ja/0411-remote-debug-a-plugin.zh.mdx b/plugin_dev_ja/0411-remote-debug-a-plugin.zh.mdx new file mode 100644 index 00000000..ef8ff5f1 --- /dev/null +++ b/plugin_dev_ja/0411-remote-debug-a-plugin.zh.mdx @@ -0,0 +1,32 @@ +--- +dimensions: + type: + primary: reference + detail: core + level: beginner +standard_title: Remote Debug a Plugin +language: ja +title: プラグインのデバッグ +description: このドキュメントでは、Dify のリモートデバッグ機能を使用してプラグインをテストする方法を紹介します。デバッグ情報の取得、環境変数ファイルの設定、プラグインのリモートデバッグの開始、およびプラグインインストール状況の確認といった一連のプロセスを詳しく説明します。これにより、開発者はローカルで開発しながら、Dify 環境でリアルタイムにプラグインをテストできます。 +--- + +プラグイン開発が完了したら、次にプラグインが正常に動作するかをテストする必要があります。Difyは便利なリモートデバッグ方法を提供し、テスト環境でプラグインの機能を迅速に検証するのに役立ちます。 + +[「プラグイン管理」](https://cloud.dify.ai/plugins)ページに移動し、リモートサーバーアドレスとデバッグキーを取得します。 + +![リモートデバッグプラグイン](https://assets-docs.dify.ai/2024/12/053415ef127f1f4d6dd85dd3ae79626a.png) + +プラグインプロジェクトに戻り、`.env.example` ファイルをコピーして `.env` にリネームし、取得したリモートサーバーアドレスやデバッグキーなどの情報を入力します。 + +`.env` ファイル: + +```bash +INSTALL_METHOD=remote +REMOTE_INSTALL_HOST=remote +REMOTE_INSTALL_PORT=5003 +REMOTE_INSTALL_KEY=****-****-****-****-**** +``` + +`python -m main` コマンドを実行してプラグインを起動します。「プラグイン」ページで、このプラグインがワークスペース内にインストールされていることが確認でき、チームの他のメンバーもこのプラグインにアクセスできます。 + +![プラグインをワークスペースにインストール](https://assets-docs.dify.ai/2024/12/ec26e5afc57bbfeb807719638f603807.png) \ No newline at end of file diff --git a/plugin_dev_ja/0411-tool.zh.mdx b/plugin_dev_ja/0411-tool.zh.mdx new file mode 100644 index 00000000..2fb8957f --- /dev/null +++ b/plugin_dev_ja/0411-tool.zh.mdx @@ -0,0 +1,121 @@ +--- +dimensions: + type: + primary: reference + detail: core + level: beginner +standard_title: Tool +language: ja +title: ツールの返り値 +description: このドキュメントでは、Difyプラグインにおけるツールのデータ構造と使用方法について詳しく説明します。内容には、さまざまなタイプのメッセージ(画像URL、リンク、テキスト、ファイル、JSON)を返す方法、変数およびストリーミング変数メッセージを作成する方法、そしてworkflowで参照しやすくするためにツールの出力変数スキーマを定義する方法が含まれます。 +--- + +詳細なインターフェースドキュメントを読む前に、Difyプラグインのツール統合プロセスについて、大まかな理解があることを確認してください。 + +### データ構造 + +#### メッセージの返却 + +Difyは、`テキスト`、`リンク`、`画像`、`ファイルBLOB`、`JSON`など、さまざまなメッセージタイプをサポートしており、以下の異なるインターフェースを通じてさまざまなタイプのメッセージを返すことができます。 + +デフォルトでは、`workflow`内のツールの出力には、`files`、`text`、`json`の3つの固定変数が含まれ、以下の方法でこれらの変数のデータを返すことができます。 + +例えば、`create_image_message`を使用して画像を返しますが、ツールはカスタム出力変数もサポートしているため、`workflow`内でこれらの変数をより便利に参照できます。 + +#### **画像URL** + +画像のURLを渡すだけで、Difyはリンクを通じて自動的に画像をダウンロードし、ユーザーに返します。 + +```python + def create_image_message(self, image: str) -> ToolInvokeMessage: + pass +``` + +#### **リンク** + +リンクを返す必要がある場合は、以下のインターフェースを使用してください。 + +```python + def create_link_message(self, link: str) -> ToolInvokeMessage: + pass +``` + +#### **テキスト** + +テキストメッセージを返す必要がある場合は、以下のインターフェースを使用してください。 + +```python + def create_text_message(self, text: str) -> ToolInvokeMessage: + pass +``` + +**ファイル** + +画像、音声、動画、PPT、Word、Excelなどのファイルの生データを返す必要がある場合は、以下のインターフェースを使用できます。 + +* `blob` ファイルの生データ、bytes型。 +* `meta` ファイルのメタデータ。開発者が明確なファイルタイプを必要とする場合は、`mime_type`を指定してください。そうでない場合、Difyはデフォルトタイプとして`octet/stream`を使用します。 + +```python + def create_blob_message(self, blob: bytes, meta: dict = None) -> ToolInvokeMessage: + pass +``` + +#### **JSON** + +フォーマットされたJSONを返す必要がある場合は、以下のインターフェースを使用できます。これは通常、workflow内のノード間のデータ転送に使用されます。agentモードでは、ほとんどの大規模モデルもJSONを読み取り、理解することができます。 + +* `object` Pythonの辞書オブジェクトで、自動的にJSONにシリアライズされます。 + +```python + def create_json_message(self, json: dict) -> ToolInvokeMessage: + pass +``` + +#### **変数** + +非ストリーミング出力の変数の場合、以下のインターフェースを使用して返すことができます。複数作成した場合、後者が前者を上書きします。 + +```python + def create_variable_message(self, variable_name: str, variable_value: Any) -> ToolInvokeMessage: + pass +``` + +#### **ストリーミング変数** + +「タイプライター」効果でテキストを出力したい場合は、ストリーミング変数を使用してテキストを出力できます。`chatflow`アプリケーションで`answer`ノードを使用し、この変数を参照すると、テキストは「タイプライター」効果で出力されます。ただし、現在この方法は文字列型のデータのみをサポートしています。 + +```python + def create_stream_variable_message( + self, variable_name: str, variable_value: str + ) -> ToolInvokeMessage: +``` + +#### カスタム変数の返却 + +`workflow`アプリケーションで`tool`の出力変数を参照したい場合は、出力される可能性のある変数を事前に定義する必要があります。Difyプラグインは、[`json_schema`](https://json-schema.org/)形式の出力変数定義をサポートしています。以下に簡単な例を示します。 + +```yaml +identity: + author: author + name: tool + label: + en_US: label + zh_Hans: 标签 + ja_JP: レベル + pt_BR: etiqueta +description: + human: + en_US: description + zh_Hans: 描述 + ja_JP: 説明 + pt_BR: descrição + llm: description +output_schema: + type: object + properties: + name: + type: string +``` + +上記のサンプルコードは、簡単なツールを定義し、それに`output_schema`を指定しています。これには`name`フィールドが含まれており、この時点で`workflow`内でこのフィールドを参照できます。ただし、実際に使用するためには、ツールの実装コードで変数を返す必要があることに注意してください。そうしないと、`None`の戻り結果が得られます。 \ No newline at end of file diff --git a/plugin_dev_ja/0412-model-schema.zh.mdx b/plugin_dev_ja/0412-model-schema.zh.mdx new file mode 100644 index 00000000..ee5064f6 --- /dev/null +++ b/plugin_dev_ja/0412-model-schema.zh.mdx @@ -0,0 +1,684 @@ +--- +dimensions: + type: + primary: reference + detail: core + level: intermediate +standard_title: Model Schema +language: ja +title: モデルインターフェース +description: 本書では、Difyモデルプラグイン開発に必要なインターフェース仕様について詳しく説明します。これには、モデルプロバイダーの実装、5つのモデルタイプ(LLM、TextEmbedding、Rerank、Speech2text、Text2speech)のインターフェース定義、およびPromptMessage、LLMResultなどの関連データ構造の完全な仕様が含まれます。本書は、開発者が様々なモデル統合を実装する際の開発リファレンスとして適しています。 +--- + +ここでは、プロバイダーと各モデルタイプが実装する必要のあるインターフェースメソッドとパラメータについて説明します。モデルプラグインを開発する前に、まず[モデル設計ルール](/plugin_dev_ja/0411-model-designing-rules.ja)と[モデルプラグイン紹介](/plugin_dev_ja/0131-model-plugin-introduction.ja)を読むことをお勧めします。 + +### モデルプロバイダー + +`__base.model_provider.ModelProvider` ベースクラスを継承し、以下のインターフェースを実装します: + +```python +def validate_provider_credentials(self, credentials: dict) -> None: + """ + プロバイダーのクレデンシャルを検証します + モデルタイプの任意の validate_credentials メソッドを選択するか、自分で検証メソッドを実装することができます(例:モデルリスト取得API)。 + + 検証に失敗した場合は、例外を発生させます + + :param credentials: プロバイダーのクレデンシャル。クレデンシャルの形式は `provider_credential_schema` で定義されます。 + """ +``` + +* `credentials` (object) クレデンシャル情報 + +クレデンシャル情報のパラメータは、プロバイダーのYAML設定ファイルの `provider_credential_schema` で定義され、`api_key` などが渡されます。検証に失敗した場合は、`errors.validate.CredentialsValidateFailedError` エラーをスローしてください。**注:事前定義モデルはこのインターフェースを完全に実装する必要がありますが、カスタムモデルプロバイダーは以下のように簡単な実装で済みます:** + +```python +class XinferenceProvider(Provider): + def validate_provider_credentials(self, credentials: dict) -> None: + pass +``` + +### モデル + +モデルは5つの異なるモデルタイプに分かれ、モデルタイプごとに継承するベースクラスや実装が必要なメソッドも異なります。 + +#### 共通インターフェース + +すべてのモデルは、以下の2つのメソッドを共通して実装する必要があります: + +* モデルクレデンシャルの検証 + +プロバイダーのクレデンシャル検証と同様に、ここでは個別のモデルに対して検証を行います。 + +```python +def validate_credentials(self, model: str, credentials: dict) -> None: + """ + モデルのクレデンシャルを検証します + + :param model: モデル名 + :param credentials: モデルのクレデンシャル + :return: + """ +``` + +パラメータ: + +* `model` (string) モデル名 +* `credentials` (object) クレデンシャル情報 + +クレデンシャル情報のパラメータは、プロバイダーのYAML設定ファイルの `provider_credential_schema` または `model_credential_schema` で定義され、`api_key` などが渡されます。検証に失敗した場合は、`errors.validate.CredentialsValidateFailedError` エラーをスローしてください。 + +* 呼び出し例外エラーマッピングテーブル + +モデル呼び出し時に例外が発生した場合、Runtime が指定する `InvokeError` タイプにマッピングする必要があります。これにより、Dify は異なるエラーに対して異なる後続処理を行うことができます。Runtime Errors: + +* `InvokeConnectionError` 呼び出し接続エラー +* `InvokeServerUnavailableError` 呼び出し先サーバー利用不可 +* `InvokeRateLimitError` 呼び出しレート制限超過 +* `InvokeAuthorizationError` 呼び出し認証失敗 +* `InvokeBadRequestError` 呼び出しパラメータ不正 + +```python +@property +def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]: + """ + モデルの呼び出しエラーを統一エラーにマッピングします + キーは呼び出し元にスローされるエラータイプです + 値はモデルによってスローされるエラータイプであり、呼び出し元のために統一エラータイプに変換する必要があります。 + + :return: 呼び出しエラーマッピング + """ +``` + +対応するエラーを直接スローし、以下のように定義することも可能です。これにより、その後の呼び出しで `InvokeConnectionError` などの例外を直接スローできます。 + +#### LLM + +`__base.large_language_model.LargeLanguageModel` ベースクラスを継承し、以下のインターフェースを実装します: + +* LLM 呼び出し + +LLM 呼び出しのコアメソッドを実装し、ストリーミングと同期的な返却の両方をサポートできます。 + +```python +def _invoke(self, model: str, credentials: dict, + prompt_messages: list[PromptMessage], model_parameters: dict, + tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None, + stream: bool = True, user: Optional[str] = None) \ + -> Union[LLMResult, Generator]: + """ + 大規模言語モデルを呼び出します + + :param model: モデル名 + :param credentials: モデルのクレデンシャル + :param prompt_messages: プロンプトメッセージ + :param model_parameters: モデルパラメータ + :param tools: ツール呼び出し用のツール + :param stop: ストップワード + :param stream: ストリーム応答かどうか + :param user: 一意のユーザーID + :return: 完全な応答またはストリーム応答チャンクジェネレータの結果 + """ +``` + +* パラメータ: + * `model` (string) モデル名 + * `credentials` (object) クレデンシャル情報 + +クレデンシャル情報のパラメータは、プロバイダーのYAML設定ファイルの `provider_credential_schema` または `model_credential_schema` で定義され、`api_key` などが渡されます。 + +* `prompt_messages` (array\[[PromptMessage](#promptmessage)]) プロンプトリスト + +モデルが `Completion` タイプの場合、リストには [UserPromptMessage](#userpromptmessage) 要素を1つ渡すだけで十分です。モデルが `Chat` タイプの場合、メッセージに応じて [SystemPromptMessage](#systempromptmessage)、[UserPromptMessage](#userpromptmessage)、[AssistantPromptMessage](#assistantpromptmessage)、[ToolPromptMessage](#toolpromptmessage) 要素のリストを渡す必要があります。 + +* `model_parameters` (object) モデルパラメータ。モデルパラメータはモデルのYAML設定の `parameter_rules` で定義されます。 + +* `tools` (array\[[PromptMessageTool](#promptmessagetool)]) \[optional] ツールリスト。`function calling` における `function` と同等です。つまり、tool calling に渡すツールリストです。 + +* `stop` (array\[string]) \[optional] ストップシーケンス。モデルの返却は、ストップシーケンスで定義された文字列の直前で停止します。 + +* `stream` (bool) ストリーミング出力かどうか、デフォルトは True。ストリーミング出力は Generator\[[LLMResultChunk](#llmresultchunk)] を返し、非ストリーミング出力は [LLMResult](#llmresult) を返します。 + +* `user` (string) \[optional] ユーザーの一意の識別子。プロバイダーが不正利用を監視および検出するのに役立ちます。 + +* 返り値 + +ストリーミング出力は Generator\[[LLMResultChunk](#llmresultchunk)] を返し、非ストリーミング出力は [LLMResult](#llmresult) を返します。 + +* 入力トークンの事前計算 + +モデルがトークン事前計算インターフェースを提供していない場合、直接 0 を返すことができます。 + +```python +def get_num_tokens(self, model: str, credentials: dict, prompt_messages: list[PromptMessage], + tools: Optional[list[PromptMessageTool]] = None) -> int: + """ + 指定されたプロンプトメッセージのトークン数を取得します + + :param model: モデル名 + :param credentials: モデルのクレデンシャル + :param prompt_messages: プロンプトメッセージ + :param tools: ツール呼び出し用のツール + :return: + """ +``` + +パラメータの説明は上記の `LLM 呼び出し` を参照してください。このインターフェースは、対応する `model` に基づいて適切な `tokenizer` を選択して計算する必要があります。対応するモデルが `tokenizer` を提供していない場合は、`AIModel` ベースクラスの `_get_num_tokens_by_gpt2(text: str)` メソッドを使用して計算できます。 + +* カスタムモデルルールの取得 \[オプション] + +```python +def get_customizable_model_schema(self, model: str, credentials: dict) -> Optional[AIModelEntity]: + """ + カスタマイズ可能なモデルスキーマを取得します + + :param model: モデル名 + :param credentials: モデルのクレデンシャル + :return: モデルスキーマ + """ +``` + +プロバイダーがカスタムLLMの追加をサポートしている場合、このメソッドを実装することで、カスタムモデルがモデルルールを取得できるようになります。デフォルトでは None を返します。 + +`OpenAI` プロバイダーのほとんどのファインチューニングモデルでは、ファインチューニングモデル名(例:`gpt-3.5-turbo-1106`)からベースモデルを取得し、そのベースモデルの事前定義パラメータルールを返すことができます。[OpenAI](https://github.com/langgenius/dify-official-plugins/tree/main/models/openai) の具体的な実装を参照してください。 + +#### TextEmbedding + +`__base.text_embedding_model.TextEmbeddingModel` ベースクラスを継承し、以下のインターフェースを実装します: + +* Embedding 呼び出し + +```python +def _invoke(self, model: str, credentials: dict, + texts: list[str], user: Optional[str] = None) \ + -> TextEmbeddingResult: + """ + テキスト埋め込みモデルを呼び出します + + :param model: モデル名 + :param credentials: モデルのクレデンシャル + :param texts: 埋め込むテキスト + :param user: 一意のユーザーID + :return: 埋め込み結果 + """ +``` + +* パラメータ: + +* `model` (string) モデル名 + +* `credentials` (object) クレデンシャル情報 + +クレデンシャル情報のパラメータは、プロバイダーのYAML設定ファイルの `provider_credential_schema` または `model_credential_schema` で定義され、`api_key` などが渡されます。 + +* `texts` (array\[string]) テキストリスト、バッチ処理可能 + +* `user` (string) \[optional] ユーザーの一意の識別子。プロバイダーが不正利用を監視および検出するのに役立ちます。 + +* 返り値: + +[TextEmbeddingResult](#textembeddingresult) エンティティ。 + +* トークンの事前計算 + +```python +def get_num_tokens(self, model: str, credentials: dict, texts: list[str]) -> int: + """ + 指定されたテキストのトークン数を取得します + + :param model: モデル名 + :param credentials: モデルのクレデンシャル + :param texts: 埋め込むテキスト + :return: + """ +``` + +パラメータの説明は上記の `Embedding 呼び出し` を参照してください。 + +上記の `LargeLanguageModel` と同様に、このインターフェースは対応する `model` に基づいて適切な `tokenizer` を選択して計算する必要があります。対応するモデルが `tokenizer` を提供していない場合は、`AIModel` ベースクラスの `_get_num_tokens_by_gpt2(text: str)` メソッドを使用して計算できます。 + +#### Rerank + +`__base.rerank_model.RerankModel` ベースクラスを継承し、以下のインターフェースを実装します: + +* rerank 呼び出し + +```python +def _invoke(self, model: str, credentials: dict, + query: str, docs: list[str], score_threshold: Optional[float] = None, top_n: Optional[int] = None, + user: Optional[str] = None) \ + -> RerankResult: + """ + Rerankモデルを呼び出します + + :param model: モデル名 + :param credentials: モデルのクレデンシャル + :param query: 検索クエリ + :param docs: リランキング対象のドキュメント + :param score_threshold: スコアのしきい値 + :param top_n: 上位n件 + :param user: 一意のユーザーID + :return: リランキング結果 + """ +``` + +* パラメータ: + +* `model` (string) モデル名 +* `credentials` (object) クレデンシャル情報 +クレデンシャル情報のパラメータは、プロバイダーのYAML設定ファイルの `provider_credential_schema` または `model_credential_schema` で定義され、`api_key` などが渡されます。 +* `query` (string) クエリリクエストの内容 +* `docs` (array\[string]) リランキングが必要なドキュメント(チャンク)のリスト +* `score_threshold` (float) \[optional] スコアのしきい値 +* `top_n` (int) \[optional] 上位n個のドキュメント(チャンク)を取得 +* `user` (string) \[optional] ユーザーの一意の識別子。プロバイダーが不正利用を監視および検出するのに役立ちます。 + + +* 返り値: + +[RerankResult](#rerankresult) エンティティ。 + +#### Speech2text + +`__base.speech2text_model.Speech2TextModel` ベースクラスを継承し、以下のインターフェースを実装します: + + +* Invoke 呼び出し + +```python +def _invoke(self, model: str, credentials: dict, + file: IO[bytes], user: Optional[str] = None) \ + -> str: + """ + 音声テキスト変換モデルを呼び出します + + :param model: モデル名 + :param credentials: モデルのクレデンシャル + :param file: 音声ファイル + :param user: 一意のユーザーID + :return: 指定された音声ファイルに対するテキスト + """ +``` + +* パラメータ: + +* `model` (string) モデル名 +* `credentials` (object) クレデンシャル情報 +クレデンシャル情報のパラメータは、プロバイダーのYAML設定ファイルの `provider_credential_schema` または `model_credential_schema` で定義され、`api_key` などが渡されます。 +* `file` (File) ファイルストリーム +* `user` (string) \[optional] ユーザーの一意の識別子。プロバイダーが不正利用を監視および検出するのに役立ちます。 + +* 返り値: + +音声変換された文字列。 + +#### Text2speech + +`__base.text2speech_model.Text2SpeechModel` ベースクラスを継承し、以下のインターフェースを実装します: + +* Invoke 呼び出し + +```python +def _invoke(self, model: str, credentials: dict, content_text: str, streaming: bool, user: Optional[str] = None): + """ + 大規模言語モデルを呼び出します + + :param model: モデル名 + :param credentials: モデルの認証情報 + :param content_text: 変換するテキストコンテンツ + :param streaming: 出力がストリーミングであるか + :param user: 一意のユーザーID + :return: 変換された音声ファイル + """ +``` + +* パラメータ: + +* `model` (string) モデル名 +* `credentials` (object) 認証情報 +認証情報のパラメータは、プロバイダーのYAML設定ファイルの `provider_credential_schema` または `model_credential_schema` で定義され、`api_key` などが渡されます。 +* `content_text` (string) 変換が必要なテキストコンテンツ +* `streaming` (bool) ストリーミング出力を行うかどうか +* `user` (string) \[optional] ユーザーの一意の識別子 +プロバイダーが不正利用を監視および検出するのに役立ちます。 + +* 戻り値: + +テキスト変換後の音声ストリーム。 + + +#### Moderation + +`__base.moderation_model.ModerationModel` ベースクラスを継承し、以下のインターフェースを実装します: + +* Invoke 呼び出し + +```python +def _invoke(self, model: str, credentials: dict, + text: str, user: Optional[str] = None) \ + -> bool: + """ + 大規模言語モデルを呼び出します + + :param model: モデル名 + :param credentials: モデルの認証情報 + :param text: モデレーションするテキスト + :param user: 一意のユーザーID + :return: テキストが安全な場合はfalse、それ以外の場合はtrue + """ +``` + +* パラメータ: + +* `model` (string) モデル名 +* `credentials` (object) 認証情報 +認証情報のパラメータは、プロバイダーのYAML設定ファイルの `provider_credential_schema` または `model_credential_schema` で定義され、`api_key` などが渡されます。 +* `text` (string) テキストコンテンツ +* `user` (string) \[optional] ユーザーの一意の識別子 +プロバイダーが不正利用を監視および検出するのに役立ちます。 + + +* 戻り値: + +Falseは渡されたテキストが安全であることを示し、Trueはその逆を示します。 + +### エンティティ + +#### PromptMessageRole + +メッセージロール + +```python +class PromptMessageRole(Enum): + """ + プロンプトメッセージのEnumクラス。 + """ + SYSTEM = "system" + USER = "user" + ASSISTANT = "assistant" + TOOL = "tool" +``` + +#### PromptMessageContentType + +メッセージコンテントタイプ。プレーンテキストと画像に分かれます。 + +```python +class PromptMessageContentType(Enum): + """ + プロンプトメッセージコンテントタイプのEnumクラス。 + """ + TEXT = 'text' + IMAGE = 'image' +``` + +#### PromptMessageContent + +メッセージコンテントのベースクラス。パラメータ宣言専用であり、初期化できません。 + +```python +class PromptMessageContent(BaseModel): + """ + プロンプトメッセージコンテントのモデルクラス。 + """ + type: PromptMessageContentType + data: str # コンテンツデータ +``` + +現在、テキストと画像の2つのタイプをサポートしており、テキストと複数の画像を同時に渡すことができます。 +それぞれ `TextPromptMessageContent` と `ImagePromptMessageContent` を初期化して渡す必要があります。 + +#### TextPromptMessageContent + +```python +class TextPromptMessageContent(PromptMessageContent): + """ + テキストプロンプトメッセージコンテントのモデルクラス。 + """ + type: PromptMessageContentType = PromptMessageContentType.TEXT +``` + +画像とテキストを渡す場合、その中のテキストはこのエンティティを `content` リストの一部として構築する必要があります。 + +#### ImagePromptMessageContent + +```python +class ImagePromptMessageContent(PromptMessageContent): + """ + 画像プロンプトメッセージコンテントのモデルクラス。 + """ + class DETAIL(Enum): + LOW = 'low' + HIGH = 'high' + + type: PromptMessageContentType = PromptMessageContentType.IMAGE + detail: DETAIL = DETAIL.LOW # 解像度 +``` + +画像とテキストを渡す場合、その中の画像はこのエンティティを `content` リストの一部として構築する必要があります。 +`data` は `url` または画像の `base64` エンコードされた文字列です。 + +#### PromptMessage + +すべてのRoleメッセージボディのベースクラス。パラメータ宣言専用であり、初期化できません。 + +```python +class PromptMessage(ABC, BaseModel): + """ + プロンプトメッセージのモデルクラス。 + """ + role: PromptMessageRole # メッセージロール + content: Optional[str | list[PromptMessageContent]] = None # 文字列とコンテントリストの2つのタイプをサポートします。コンテントリストはマルチモーダルのニーズを満たすためのもので、詳細は `PromptMessageContent` の説明を参照してください。 + name: Optional[str] = None # 名称、オプション。 +``` + +#### UserPromptMessage + +UserMessageメッセージボディ。ユーザーメッセージを表します。 + +```python +class UserPromptMessage(PromptMessage): + """ + ユーザープロンプトメッセージのモデルクラス。 + """ + role: PromptMessageRole = PromptMessageRole.USER +``` + +#### AssistantPromptMessage + +モデルの返信メッセージを表します。通常、`few-shots` またはチャット履歴の入力に使用されます。 + +```python +class AssistantPromptMessage(PromptMessage): + """ + アシスタントプロンプトメッセージのモデルクラス。 + """ + class ToolCall(BaseModel): + """ + アシスタントプロンプトメッセージツールコールのモデルクラス。 + """ + class ToolCallFunction(BaseModel): + """ + アシスタントプロンプトメッセージツールコール関数のモデルクラス。 + """ + name: str # ツール名 + arguments: str # ツールパラメータ + + id: str # ツールID。OpenAI の tool call のみで有効で、ツールの呼び出しの一意のIDです。同じツールを複数回呼び出すことができます。 + type: str # デフォルトは function + function: ToolCallFunction # ツール呼び出し情報 + + role: PromptMessageRole = PromptMessageRole.ASSISTANT + tool_calls: list[ToolCall] = [] # モデルが応答したツール呼び出し結果(tools が渡され、かつモデルがツールの呼び出しが必要だと判断した場合にのみ返されます) +``` + +この `tool_calls` は、モデルを呼び出す際に `tools` を渡した後、モデルから返される `tool call` のリストです。 + +#### SystemPromptMessage + +システムメッセージを表します。通常、モデルに設定するシステム指示に使用されます。 + +```python +class SystemPromptMessage(PromptMessage): + """ + システムプロンプトメッセージのモデルクラス。 + """ + role: PromptMessageRole = PromptMessageRole.SYSTEM +``` + +#### ToolPromptMessage + +ツールメッセージを表します。ツールの実行後に結果をモデルに渡し、次のステップを計画するために使用されます。 + +```python +class ToolPromptMessage(PromptMessage): + """ + ツールプロンプトメッセージのモデルクラス。 + """ + role: PromptMessageRole = PromptMessageRole.TOOL + tool_call_id: str # ツール呼び出しID。OpenAI tool call をサポートしていない場合、ツール名を渡すこともできます。 +``` + +ベースクラスの `content` にツールの実行結果を渡します。 + +#### PromptMessageTool + +```python +class PromptMessageTool(BaseModel): + """ + プロンプトメッセージツールのモデルクラス。 + """ + name: str # ツール名 + description: str # ツールの説明 + parameters: dict # ツールパラメータのdict + +``` + +*** + +#### LLMResult + +```python +class LLMResult(BaseModel): + """ + LLM結果のモデルクラス。 + """ + model: str # 実際に使用されたモデル + prompt_messages: list[PromptMessage] # プロンプトメッセージのリスト + message: AssistantPromptMessage # 返信メッセージ + usage: LLMUsage # 使用されたトークンおよび料金情報 + system_fingerprint: Optional[str] = None # リクエストフィンガープリント。OpenAI の当該パラメータ定義を参照できます。 +``` + +#### LLMResultChunkDelta + +ストリーミングレスポンスにおける各イテレーション内部の `delta` エンティティ + +```python +class LLMResultChunkDelta(BaseModel): + """ + LLM結果チャンクデルタのモデルクラス。 + """ + index: int # シーケンス番号 + message: AssistantPromptMessage # 返信メッセージ + usage: Optional[LLMUsage] = None # 使用されたトークンおよび料金情報。最後のメッセージでのみ返されます。 + finish_reason: Optional[str] = None # 終了理由。最後のメッセージでのみ返されます。 +``` + +#### LLMResultChunk + +ストリーミングレスポンスにおける各イテレーションのエンティティ + +```python +class LLMResultChunk(BaseModel): + """ + LLM結果チャンクのモデルクラス。 + """ + model: str # 実際に使用されたモデル + prompt_messages: list[PromptMessage] # プロンプトメッセージのリスト + system_fingerprint: Optional[str] = None # リクエストフィンガープリント。OpenAI の当該パラメータ定義を参照できます。 + delta: LLMResultChunkDelta # 各イテレーションで変化のある内容 +``` + +#### LLMUsage + +```python +class LLMUsage(ModelUsage): + """ + LLM使用量のモデルクラス。 + """ + prompt_tokens: int # プロンプト使用トークン数 + prompt_unit_price: Decimal # プロンプト単価 + prompt_price_unit: Decimal # プロンプト価格単位(単価が何トークンに基づいているか) + prompt_price: Decimal # プロンプト料金 + completion_tokens: int # 返信使用トークン数 + completion_unit_price: Decimal # 返信単価 + completion_price_unit: Decimal # 返信価格単位(単価が何トークンに基づいているか) + completion_price: Decimal # 返信料金 + total_tokens: int # 総使用トークン数 + total_price: Decimal # 総料金 + currency: str # 通貨単位 + latency: float # リクエスト遅延(秒) +``` + +*** + +#### TextEmbeddingResult + +```python +class TextEmbeddingResult(BaseModel): + """ + テキスト埋め込み結果のモデルクラス。 + """ + model: str # 実際に使用されたモデル + embeddings: list[list[float]] # embeddingベクトルリスト。渡された texts リストに対応します。 + usage: EmbeddingUsage # 使用情報 +``` + +#### EmbeddingUsage + +```python +class EmbeddingUsage(ModelUsage): + """ + 埋め込み使用量のモデルクラス。 + """ + tokens: int # 使用トークン数 + total_tokens: int # 総使用トークン数 + unit_price: Decimal # 単価 + price_unit: Decimal # 価格単位(単価が何トークンに基づいているか) + total_price: Decimal # 総料金 + currency: str # 通貨単位 + latency: float # リクエスト遅延(秒) +``` + +*** + +#### RerankResult + +```python +class RerankResult(BaseModel): + """ + Rerank結果のモデルクラス。 + """ + model: str # 実際に使用されたモデル + docs: list[RerankDocument] # リランキング後のドキュメントリスト +``` + +#### RerankDocument + +```python +class RerankDocument(BaseModel): + """ + Rerankドキュメントのモデルクラス。 + """ + index: int # 元のシーケンス番号 + text: str # ドキュメントのテキスト内容 + score: float # スコア +``` + +## 関連リソース + +- [モデル設計ルール](/plugin_dev_ja/0411-model-designing-rules.ja) - モデル設定の仕様を理解する +- [モデルプラグイン紹介](/plugin_dev_ja/0131-model-plugin-introduction.ja) - モデルプラグインの基本概念を素早く理解する +- [新しいモデルへの迅速な接続](/plugin_dev_ja/0211-getting-started-new-model.ja) - 既存のプロバイダーに新しいモデルを追加する方法を学ぶ +- [新規モデルプロバイダーの作成](/plugin_dev_ja/0222-creating-new-model-provider.ja) - 全く新しいモデルプロバイダーを開発する方法を学ぶ \ No newline at end of file diff --git a/plugin_dev_ja/0432-develop-a-slack-bot-plugin.zh.mdx b/plugin_dev_ja/0432-develop-a-slack-bot-plugin.zh.mdx new file mode 100644 index 00000000..0d650f46 --- /dev/null +++ b/plugin_dev_ja/0432-develop-a-slack-bot-plugin.zh.mdx @@ -0,0 +1,345 @@ +--- +dimensions: + type: + primary: reference + detail: examples + level: intermediate +standard_title: Develop A Slack Bot Plugin +language: ja +title: Slack Bot プラグインの開発 +description: このドキュメントでは、プロジェクトの初期化、設定フォームの編集、機能コードの実装、プラグインのデバッグ、エンドポイントの設定、効果検証からパッケージ化、リリースまでの完全なSlack Botプラグイン開発ガイドを提供します。Slackプラットフォーム上でAI駆動のチャットボットを構築するために、Difyプラグインスキャフォールディングツールと作成済みのSlack Appが必要です。 +--- + +**本稿では、次のことをお手伝いします。** + +Slack Bot の構築方法を深く理解し、AI 駆動の Slack チャットボットを作成し、 Slack プラットフォームでユーザーの質問にインテリジェントに応答します。プラグイン開発の経験がない場合は、まず[プラグイン開発入門ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja)を読むことをお勧めします。 + +### プロジェクトの背景 + +Dify プラグインエコシステムは、よりシンプルで使いやすいアクセス方法のサポートに取り組んでいます。本稿では Slack を例に、Slack Bot プラグインの開発方法を詳しく説明し、チームメンバーが Slack プラットフォーム内で直接 LLM と対話し、AI サービスの使用効率を向上させるのに役立ちます。 + +Dify プラグインエコシステムは、よりシンプルで便利なアクセス方法を提供することを目指しています。本稿では Slack を例に、Slack Bot プラグインの開発方法を詳細に解説し、チームメンバーが Slack プラットフォームで直接 AI アプリケーションを使用し、業務効率を向上させるのに役立ちます。 + +Slack は、自由でオープンなリアルタイムオフィスコミュニケーションプラットフォームであり、豊富な API を備えています。その中でも、イベントベースの Webhook 機能は開発に着手しやすいです。このメカニズムを利用して Slack Bot プラグインを作成します。その原理は下図の通りです。 + +![Slack Bot の原理図](https://assets-docs.dify.ai/2025/01/a0865d18f1ca4051601ca53fa6f92db2.png) + +> 混乱を避けるため、以下の概念について説明します。 +> +> * **Slack Bot** は Slack プラットフォーム上のチャットボットであり、仮想キャラクターと見なすことができ、チャットで対話することができます。 +> * **Slack Bot プラグイン**とは、Dify Marketplace 上のプラグインで、Dify アプリケーションと Slack プラットフォームを接続するために使用されます。本稿では、主にこのプラグイン開発について説明します。 + +**原理の概要:** + +1. **Slack Bot にメッセージを送信** + + ユーザーが Slack で Bot にメッセージを送信すると、Slack Bot は Dify プラットフォームに Webhook リクエストを送信します。 +2. **メッセージを Slack Bot プラグインに転送** + + ユーザーが Slack bot と対話する際、メッセージを Dify アプリケーションに転送する必要があります。メールシステムが受信者のメールアドレスを必要とするように、Slack の API を介して Slack Webhook のアドレスを設定し、それを Slack Bot プラグインに入力して接続を確立することができます。 +3. **プラグインがメッセージを受信後、特定の Dify アプリケーションに返す** + + Slack Bot プラグインは Slack リクエストを処理し、Dify 内のアプリケーションに送信します。LLM がユーザーの入力内容を分析し、応答します。 +4. **Dify アプリケーションが応答後、メッセージを Slack Bot に返し、ユーザーに回答する** + + Slack Bot は Dify アプリケーションの応答を取得後、プラグインを介してメッセージを元の経路で Slack Bot に返し、ユーザーが Slack を使用しながら直接 Dify アプリケーションと対話できるようにします。 + +### 事前準備 + +* Dify プラグインスキャフォールディングツール。詳細については、[開発ツールの初期化](/plugin_dev_ja/0221-initialize-development-tools.ja)を参照してください。 +* Python 環境、バージョン番号 ≥ 3.12。詳細については、[Python インストールチュートリアル](https://pythontest.com/python/installing-python-3-11/)を参照するか、LLM に完全なインストールチュートリアルを問い合わせてください。 +* Slack App を作成し、OAuth トークンを取得する + +[Slack API](https://api.slack.com/apps) プラットフォームにアクセスし、scratch 方式で Slack APP を作成し、アプリケーションをデプロイする Slack スペースを選択します。 + +![Slack API トークン](https://assets-docs.dify.ai/2025/01/8217c23ee16c47c586a1387a442ea6f0.png) + +Webhooks 機能を有効にします。 + +![Webhooks 機能を有効にする](https://assets-docs.dify.ai/2025/01/fc9d7797608422219a01248f7151fc81.png) + +App を Slack ワークスペースにインストールします。 + +![ワークスペースにインストール](https://assets-docs.dify.ai/2025/01/6ab7226078f88853fc7f4d3520245d63.png) + +OAuth トークンを取得し、後続のプラグイン開発に使用します。 + +![OAuth トークンを取得](https://assets-docs.dify.ai/2025/01/f08052044c8c17eebbffacdc9b2558e6.png) + +### 1. プラグインの開発 + +ここから実際のプラグインコーディング作業を開始します。開始する前に、[Extension プラグインの開発](/plugin_dev_ja/9231-extension-plugin.ja)を読んだか、Dify プラグインを一度開発したことがあることを確認してください。 + +#### プロジェクトの初期化 + +以下のコマンドを実行してプラグイン開発プロジェクトを初期化します。 + +```bash +dify plugin init +``` + +プロンプトに従ってプロジェクトの基本情報を入力し、`extension` テンプレートを選択し、`Apps` と `Endpoints` の 2 つの権限を付与します。 + +プラグインが Dify プラットフォームの機能を逆引き呼び出しする方法の詳細については、[逆引き呼び出し](/plugin_dev_ja/9241-reverse-invocation.ja)を参照してください。 + +![プラグイン権限](https://assets-docs.dify.ai/2024/12/d89a6282c5584fc43a9cadeddf09c0de.png) + +#### 1. 設定フォームの編集 + +このプラグインでは、どの Dify の App を使用して返信するかを指定する必要があり、返信する際には Slack の App token を使用する必要があるため、プラグインフォームにこれらの 2 つのフィールドを追加する必要があります。 + +group パス以下の yaml ファイルを変更します。例:`group/slack.yaml`。フォーム設定ファイルの名前は、プラグイン作成時に記入した基本情報によって決まります。対応する yaml ファイルを変更できます。 + +**サンプルコード:** + +`slack.yaml` + +```yaml +settings: + - name: bot_token + type: secret-input + required: true + label: + en_US: Bot Token + zh_Hans: Bot Token + pt_BR: Token do Bot + ja_JP: Bot Token + placeholder: + en_US: Please input your Bot Token + zh_Hans: 请输入你的 Bot Token + pt_BR: Por favor, insira seu Token do Bot + ja_JP: ボットトークンを入力してください + - name: allow_retry + type: boolean + required: false + label: + en_US: Allow Retry + zh_Hans: 允许重试 + pt_BR: Permitir Retentativas + ja_JP: 再試行を許可 + default: false + - name: app + type: app-selector + required: true + label: + en_US: App + zh_Hans: 应用 + pt_BR: App + ja_JP: アプリ + placeholder: + en_US: the app you want to use to answer Slack messages + zh_Hans: 你想要用来回答 Slack 消息的应用 + pt_BR: o app que você deseja usar para responder mensagens do Slack + ja_JP: あなたが Slack メッセージに回答するために使用するアプリ +endpoints: + - endpoints/slack.yaml +``` + +コードデータ構造の説明: + +``` + - name: app + type: app-selector + scope: chat +``` + +* `type` フィールドは `app-selector` フィールドとして指定されます。 + + ユーザーはプラグイン使用時に特定の Dify アプリケーションにアクセスし、メッセージ転送を行うことができます。 +* `scope` フィールドは `chat` フィールドとして指定されます。 + + `agent`、`chatbot`、`chatflow` などのタイプのアプリのみ使用できます。 + +最後に `endpoints/slack.yaml` ファイル内のリクエストパスとリクエストメソッドを変更し、method を POST 方式に変更する必要があります。 + +**サンプルコード:** + +`endpoints/slack.yaml` + +```yaml +path: "/" +method: "POST" +extra: + python: + source: "endpoints/slack.py" +``` + +#### 2. 機能コードの編集 + +`endpoints/slack.py` ファイルを変更し、以下のコードを追加します。 + +````python +import json +import traceback +from typing import Mapping +from werkzeug import Request, Response +from dify_plugin import Endpoint +from slack_sdk import WebClient +from slack_sdk.errors import SlackApiError + + +class SlackEndpoint(Endpoint): + def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response: + """ + Invokes the endpoint with the given request. + """ + retry_num = r.headers.get("X-Slack-Retry-Num") + if (not settings.get("allow_retry") and (r.headers.get("X-Slack-Retry-Reason") == "http_timeout" or ((retry_num is not None and int(retry_num) > 0)))): + return Response(status=200, response="ok") + data = r.get_json() + + # Handle Slack URL verification challenge + if data.get("type") == "url_verification": + return Response( + response=json.dumps({"challenge": data.get("challenge")}), + status=200, + content_type="application/json" + ) + + if (data.get("type") == "event_callback"): + event = data.get("event") + if (event.get("type") == "app_mention"): + message = event.get("text", "") + if message.startswith("<@"): + message = message.split("> ", 1)[1] if "> " in message else message + channel = event.get("channel", "") + blocks = event.get("blocks", []) + blocks[0]["elements"][0]["elements"] = blocks[0].get("elements")[0].get("elements")[1:] + token = settings.get("bot_token") + client = WebClient(token=token) + try: + response = self.session.app.chat.invoke( + app_id=settings["app"]["app_id"], + query=message, + inputs={}, + response_mode="blocking", + ) + try: + blocks[0]["elements"][0]["elements"][0]["text"] = response.get("answer") + result = client.chat_postMessage( + channel=channel, + text=response.get("answer"), + blocks=blocks + ) + return Response( + status=200, + response=json.dumps(result), + content_type="application/json" + ) + except SlackApiError as e: + raise e + except Exception as e: + err = traceback.format_exc() + return Response( + status=200, + response="Sorry, I'm having trouble processing your request. Please try again later." + str(err), + content_type="text/plain", + ) + else: + return Response(status=200, response="ok") + else: + return Response(status=200, response="ok") + else: + return Response(status=200, response="ok") + +``` +```` + +テストを容易にするため、プラグイン機能は現在ユーザーの入力内容を繰り返すことしかできず、Dify app の呼び出しは一時的に行いません。 + +### 2. プラグインのデバッグ + +Dify プラットフォームにアクセスし、Dify プラグインのリモートデバッグ接続アドレスとキーを取得します。 + +![プラグインのテスト](https://assets-docs.dify.ai/2025/01/8d24006f0cabf5bf61640a9023c45db8.png) + +プラグインプロジェクトに戻り、`.env.example` ファイルをコピーして `.env` に名前を変更します。 + +```bash +INSTALL_METHOD=remote +REMOTE_INSTALL_HOST=remote +REMOTE_INSTALL_PORT=5003 +REMOTE_INSTALL_KEY=****-****-****-****-**** +``` + +`python -m main` コマンドを実行してプラグインを起動します。プラグインページで、このプラグインがワークスペースにインストールされていることが確認できます。他のチームメンバーもこのプラグインにアクセスできます。 + +```bash +python -m main +``` + +#### プラグインのエンドポイントを設定 + +Dify のプラグイン管理ページで、自動インストールされたテストプラグインを見つけ、新しいエンドポイントを作成し、名前、Bot token を入力し、接続するアプリを選択します。 + +![POST リクエストアドレスの生成](https://assets-docs.dify.ai/2025/01/e6952a5798a7ae793b3fe7df6f76ea73.png) + +保存後、POST リクエストアドレスが生成されます。 + +![POST リクエストアドレスの生成](https://assets-docs.dify.ai/2025/01/e6952a5798a7ae793b3fe7df6f76ea73.png) + +次に、Slack App の設定を完了する必要があります。 + +1. Event サブスクリプションを有効にする + +![Event サブスクリプションを有効にする](https://assets-docs.dify.ai/2025/01/1d33bb9cde78a1b5656ad6a0b8350195.png) + +上記で生成されたプラグインの POST リクエストアドレスを貼り付けます。 + +![生成されたプラグイン POST リクエストアドレスを貼り付け](https://assets-docs.dify.ai/2025/01/65aa41f37c3800af49e944f9ff28e121.png) + +Slack App が持つべき権限にチェックを入れます。 + +![Slack App に必要な権限を選択](https://assets-docs.dify.ai/2025/01/25c38a2cf10ec6c55ae54970d790f37e.png) + +### 3. プラグイン効果の検証 + +コードは `self.session.app.chat.invoke` を使用して Dify プラットフォーム内の App を呼び出し、`app_id` や `query` などの情報を渡し、最後にレスポンスの内容を Slack Bot に返します。`python -m main` コマンドを実行してプラグインを再起動してデバッグし、Slack Bot が Dify App の応答メッセージを正しく出力できるか確認します。 + +![POST リクエストアドレスの生成](https://assets-docs.dify.ai/2025/01/e6952a5798a7ae793b3fe7df6f76ea73.png) + +### 4. プラグインのパッケージ化(オプション) + +プラグインが正常に動作することを確認した後、以下のコマンドラインツールを使用してプラグインをパッケージ化し、名前を付けることができます。実行後、現在のフォルダに `slack_bot.difypkg` ファイルが見つかります。これが最終的なプラグインパッケージです。パッケージ化の詳細な手順については、[ローカルファイルとしてパッケージ化して共有](/plugin_dev_ja/0322-release-by-file.ja)を参照してください。 + +```bash +# ./slack_bot を実際のプラグインプロジェクトパスに置き換えてください。 +dify plugin package ./slack_bot +``` + +おめでとうございます。これでプラグインの完全な開発、テスト、パッケージ化プロセスが完了しました! + +### 5. プラグインの公開(オプション) + +これで、[Dify Marketplace リポジトリ](https://github.com/langgenius/dify-plugins)にアップロードしてプラグインを公開できます!ただし、公開する前に、プラグインが[Dify Marketplace への公開](/plugin_dev_ja/0322-release-to-dify-marketplace.ja)の規範に従っていることを確認してください。 + +## 関連リソース + +- [プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja) - Difyプラグイン開発の全体像を理解する +- [プラグイン開発入門ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja) - ゼロからプラグインを開発する +- [Extension プラグインの開発](/plugin_dev_ja/9231-extension-plugin.ja) - 拡張プラグイン開発を理解する +- [Dify サービスの逆引き呼び出し](/plugin_dev_ja/9241-reverse-invocation.ja) - Dify プラットフォームの機能を呼び出す方法を理解する +- [App の逆引き呼び出し](/plugin_dev_ja/9242-reverse-invocation-app.ja) - プラットフォーム内の App を呼び出す方法を理解する +- [プラグインの公開](/plugin_dev_ja/0321-release-overview.ja) - 公開プロセスを学ぶ +- [Dify Marketplace への公開](/plugin_dev_ja/0322-release-to-dify-marketplace.ja) - マーケットプレイス公開ガイド +- [エンドポイントの詳細定義](/plugin_dev_ja/0432-endpoint.ja) - エンドポイントの詳細定義 + +### 参考文献 + +完全な Dify プラグインのプロジェクトコードを確認したい場合は、[Github コードリポジトリ](https://github.com/langgenius/dify-plugins)にアクセスしてください。その他、他のプラグインの完全なコードと詳細を確認できます。 + +さらに多くのプラグインについて知りたい場合は、以下の内容を参照してください。 + +**クイックスタート:** + +* [Extension プラグインの開発](/plugin_dev_ja/9231-extension-plugin.ja) +* [Model プラグインの開発](/plugin_dev_ja/0211-getting-started-new-model.ja) +* [Bundle タイププラグイン:複数のプラグインをパッケージ化](/plugin_dev_ja/9241-bundle.ja) + +**プラグインインターフェースドキュメント:** + +* [マニフェストファイルによるプラグイン情報の定義](/plugin_dev_ja/0411-plugin-info-by-manifest.ja) - マニフェスト構造 +* [エンドポイント](/plugin_dev_ja/0432-endpoint.ja) - エンドポイントの詳細定義 +* [逆引き呼び出し](/plugin_dev_ja/9241-reverse-invocation.ja) - Dify 機能の逆引き呼び出し +* [共通仕様](/plugin_dev_ja/0411-general-specifications.ja) - ツール仕様 +* [モデルアーキテクチャ](/plugin_dev_ja/0412-model-schema.ja) - モデル \ No newline at end of file diff --git a/plugin_dev_ja/0432-endpoint.zh.mdx b/plugin_dev_ja/0432-endpoint.zh.mdx new file mode 100644 index 00000000..deca4bcc --- /dev/null +++ b/plugin_dev_ja/0432-endpoint.zh.mdx @@ -0,0 +1,114 @@ +--- +dimensions: + type: + primary: reference + detail: examples + level: intermediate +standard_title: Endpoint +language: ja +title: エンドポイント +description: 作成者: Yeuoly、Allen このドキュメントでは、Difyプラグインにおけるエンドポイントの構造と実装方法について、虹猫(Neko)プロジェクトを例に詳しく説明します。内容は、エンドポイントグループの定義方法、インターフェースの設定、_invokeメソッドの実装、リクエストとレスポンスの処理を含みます。ドキュメントでは、さまざまなYAML設定フィールドの意味と使用方法を詳細に解説しています。 +--- + +本稿では、[虹猫](/plugin_dev_ja/9231-extension-plugin.ja)プロジェクトを例に、プラグイン内のエンドポイントの構造について説明します。エンドポイントは、プラグインが外部に公開するHTTPインターフェースであり、外部システムとの統合に使用できます。完全なプラグインコードは、[Githubリポジトリ](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/neko)を参照してください。 + +### グループ定義 + +`Endpoint` グループは複数の `Endpoint` の集合であり、`Dify` プラグイン内で新しい `Endpoint` を作成する際に、以下のような設定を記入する必要がある場合があります。 + +![](https://assets-docs.dify.ai/2024/11/763dbf86e4319591415dc5a1b6948ccb.png) + +`Endpoint Name` 以外に、グループの設定情報を記述することで新しいフォーム項目を追加できます。保存をクリックすると、そこに含まれる複数のインターフェースが表示され、それらは同じ設定情報を使用します。 + +![](https://assets-docs.dify.ai/2024/11/b778b7093b7df0dc80a476c65ddcbe58.png) + +#### **構造** + +* `settings`(map\[string] [ProviderConfig](/plugin_dev_ja/0411-general-specifications#providerconfig.ja) ):エンドポイント設定定義 +* `endpoints`(list\[string], required):具体的な `endpoint` インターフェース定義を指します + +```yaml +settings: + api_key: + type: secret-input + required: true + label: + en_US: API key + zh_Hans: API key + ja_Jp: API key + pt_BR: API key + placeholder: + en_US: Please input your API key + zh_Hans: 请输入你的 API key + ja_Jp: あなたの API key を入れてください + pt_BR: Por favor, insira sua chave API +endpoints: + - endpoints/duck.yaml + - endpoints/neko.yaml +``` + +### インターフェース定義 + +* `path`(string):werkzeugインターフェース標準に従います +* `method`(string):インターフェースメソッド。`HEAD` `GET` `POST` `PUT` `DELETE` `OPTIONS` のみをサポートします +* `extra`(object):基本情報以外の設定情報 + * `python`(object) + * `source`(string):このインターフェースを実装するソースコード + +```yaml +path: "/duck/" +method: "GET" +extra: + python: + source: "endpoints/duck.py" +``` + +### インターフェース実装 + +`dify_plugin.Endpoint` を継承したサブクラスを実装し、`_invoke` メソッドを実装する必要があります。 + +* **入力パラメータ** + * `r`(Request):`werkzeug` の `Request` オブジェクト + * `values`(Mapping):pathから解析されたパスパラメータ + * `settings`(Mapping):この `Endpoint` の設定情報 +* **戻り値** + * `werkzeug` の `Response` オブジェクト。ストリーミング応答をサポートします + * 文字列の直接返却はサポートしていません + +サンプルコード: + +```python +import json +from typing import Mapping +from werkzeug import Request, Response +from dify_plugin import Endpoint + +class Duck(Endpoint): + def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response: + """ + Invokes the endpoint with the given request. + """ + app_id = values["app_id"] + + def generator(): + yield f"{app_id}
" + + return Response(generator(), status=200, content_type="text/html") +``` + +## 注意事項 + +* エンドポイントはプラグインが呼び出されたときにのみインスタンス化され、常時実行されるサービスではありません +* エンドポイントを開発する際は、セキュリティに注意し、危険な操作の実行を避けてください +* エンドポイントは、Webhookコールバックの処理や、他のシステムが接続するためのインターフェースの提供に使用できます + +プラグイン開発を学習している場合は、まず[プラグイン開発入門ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja)と[開発者向けチートシート](/plugin_dev_ja/0131-cheatsheet.ja)を読むことをお勧めします。 + +## 関連リソース + +- [プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja) - プラグイン開発の全体的なアーキテクチャを理解する +- [虹猫(Neko)インスタンス](/plugin_dev_ja/9231-extension-plugin.ja) - 拡張プラグイン開発の例 +- [共通仕様定義](/plugin_dev_ja/0411-general-specifications.ja) - ProviderConfigなどの共通構造を理解する +- [Slackボットプラグイン開発例](/plugin_dev_ja/0432-develop-a-slack-bot-plugin.ja) - 別のプラグイン開発例 +- [プラグイン開発入門ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja) - ゼロからプラグインを開発する +- [Difyサービスの逆呼び出し](/plugin_dev_ja/9241-reverse-invocation.ja) - 逆呼び出し機能の使用方法を理解する \ No newline at end of file diff --git a/plugin_dev_ja/9231-extension-plugin.zh.mdx b/plugin_dev_ja/9231-extension-plugin.zh.mdx new file mode 100644 index 00000000..8df07bbd --- /dev/null +++ b/plugin_dev_ja/9231-extension-plugin.zh.mdx @@ -0,0 +1,284 @@ +--- +dimensions: + type: + primary: implementation + detail: high + level: beginner +standard_title: Extension Plugin +language: ja +title: Extension 插件 +description: 本文档提供了开发Extension类型插件的完整教程,详细介绍了环境准备、创建项目、定义插件请求入口、编写功能代码、调试、打包发布等全过程。示例项目是一个彩虹猫插件,展示了如何通过Extension插件处理HTTP请求并提供网页服务。 +--- + +本文将引导你快速开发一个 Extension 类型的插件,以帮助你了解插件开发的基础流程。 + +### 前置准备 + +* Dify 插件脚手架工具 +* Python 环境,版本号 ≥ 3.12 + +关于如何准备插件开发的脚手架工具,详细说明请参考[初始化开发工具](initialize-development-tools.md)。 + +### 创建新项目 + +在当前路径下,运行脚手架命令行工具,创建一个新的 dify 插件项目。 + +``` +./dify-plugin-darwin-arm64 plugin init +``` + +如果你已将该二进制文件重命名为了 `dify` 并拷贝到了 `/usr/local/bin` 路径下,可以运行以下命令创建新的插件项目: + +```bash +dify plugin init +``` + +### **填写插件信息** + +按照提示配置插件名称、作者信息与插件描述。如果你是团队协作,也可以将作者填写为组织名。 + +> 插件名称长度必须为 1-128 个字符,并且只能包含字母、数字、破折号和下划线。 + +![Plugins details](https://assets-docs.dify.ai/2024/12/75cfccb11fe31c56c16429b3998f2eb0.png) + +填写完成后,在插件开发语言环节中选择 Python。 + +![Plugins development: Python](https://assets-docs.dify.ai/2024/11/1129101623ac4c091a3f6f75f4103848.png) + +### 3. 选择插件类型并初始化项目模板 + +脚手架工具内的所有模板均已提供完整的代码项目。出于演示说明,本文将以 `Extension` 类型插件模板作为示例。对于已熟悉插件的开发者而言,无需借助模板,可参考[接口文档](../../schema-definition/)指引完成不同类型的插件开发。 + +![Extension](https://assets-docs.dify.ai/2024/11/ff08f77b928494e10197b456fc4e2d5b.png) + +#### 配置插件权限 + +插件还需要读取 Dify 主平台的权限才能正常连接。需要为该示例插件授予以下权限: + +* Tools +* LLMs +* Apps +* 启用持久化存储 Storage,分配默认大小存储 +* 允许注册 Endpoint + +> 在终端内使用方向键选择权限,使用 “Tab” 按钮授予权限。 + +勾选所有权限项后,轻点回车完成插件的创建。系统将自动生成插件项目代码。 + +![Plugins 权限](https://assets-docs.dify.ai/2024/11/5518ca1e425a7135f18f499e55d16bdd.png) + +插件的基础文件结构包含以下内容: + +``` +. +├── GUIDE.md +├── README.md +├── _assets +│ └── icon.svg +├── endpoints +│ ├── your-project.py +│ └── your-project.yaml +├── group +│ └── your-project.yaml +├── main.py +├── manifest.yaml +└── requirements.txt +``` + +* `GUIDE.md` 一个简短的引导教程,带领你完成插件的编写流程。 +* `README.md` 关于当前插件的简介信息,你需要把有关该插件的介绍和使用方法填写至该文件内。 +* `_assets` 存储所有与当前插件相关的多媒体文件。 +* `endpoints` 按照 cli 中的引导创建的一个 `Extension` 类型插件模板,该目录存放所有 Endpoint 的功能实现代码。 +* `group` 指定密钥类型、多语言设置以及 API 定义的文件路径。 +* `main.py` 整个项目的入口文件。 +* `manifest.yaml` 整个插件的基础配置文件,包含该插件需要什么权限、是什么类型的扩展等配置信息。 +* `requirements.txt` 存放 Python 环境的依赖项。 + +### 开发插件 + +#### 1. 定义插件的请求入口 Endpoint + +编辑 `endpoints/test_plugin.yaml` ,参考以下代码进行修改: + +```yaml +path: "/neko" +method: "GET" +extra: + python: + source: "endpoints/test_plugin.py" +``` + +该代码的意图是定义该插件的入口路径为 `/neko`,请求方法为 GET 类型。插件的功能实现代码为 `endpoints/test_plugin.py` 文件。 + +#### 2. 编写插件功能 + +插件功能:请求服务,输出一只彩虹猫。 + +编写插件的功能实现代码 `endpoints/test_plugin.py` 文件,参考以下示例代码: + +```python +from typing import Mapping +from werkzeug import Request, Response +from flask import Flask, render_template_string +from dify_plugin import Endpoint + +app = Flask(__name__) + +class NekoEndpoint(Endpoint): + def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response: + ascii_art = ''' +⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛⬛️⬜️⬜️⬜️⬜️⬜⬜️⬜️️ +🟥🟥⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️🟥🟥🟥🟥🟥🟥🟥🟥⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬛🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧⬛️⬜️⬜️⬜️⬜️⬜⬜️️ +🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥⬛️🥧🥧🥧💟💟💟💟💟💟💟💟💟💟💟💟💟🥧🥧🥧⬛️⬜️⬜️⬜️⬜⬜️️ +🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥⬛️🥧🥧💟💟💟💟💟💟🍓💟💟🍓💟💟💟💟💟🥧🥧⬛️⬜️⬜️⬜️⬜️⬜️️ +🟧🟧🟥🟥🟥🟥🟥🟥🟥🟥🟧🟧🟧🟧🟧🟧🟧🟧🟥🟥🟥🟥🟥🟥🟥⬛🥧💟💟🍓💟💟💟💟💟💟💟💟💟💟💟💟💟💟🥧⬛️⬜️⬜️⬜️⬜⬜️️ +🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧⬛️🥧💟💟💟💟💟💟💟💟💟💟⬛️⬛️💟💟🍓💟💟🥧⬛️⬜️⬛️️⬛️️⬜⬜️️ +🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧⬛️🥧💟💟💟💟💟💟💟💟💟⬛️🌫🌫⬛💟💟💟💟🥧⬛️⬛️🌫🌫⬛⬜️️ +🟨🟨🟧🟧🟧🟧🟧🟧🟧🟧🟨🟨🟨🟨🟨🟨🟨🟨🟧⬛️⬛️⬛️⬛️🟧🟧⬛️🥧💟💟💟💟💟💟🍓💟💟⬛️🌫🌫🌫⬛💟💟💟🥧⬛️🌫🌫🌫⬛⬜️️ +🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨⬛️🌫🌫⬛️⬛️🟧⬛️🥧💟💟💟💟💟💟💟💟💟⬛️🌫🌫🌫🌫⬛️⬛️⬛️⬛️🌫🌫🌫🌫⬛⬜️️ +🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨⬛️⬛️🌫🌫⬛️⬛️⬛️🥧💟💟💟🍓💟💟💟💟💟⬛️🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫⬛⬜️️ +🟩🟩🟨🟨🟨🟨🟨🟨🟨🟨🟩🟩🟩🟩🟩🟩🟩🟩🟨🟨⬛⬛️🌫🌫⬛️⬛️🥧💟💟💟💟💟💟💟🍓⬛️🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫⬛️ +🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩⬛️⬛️🌫🌫⬛️🥧💟🍓💟💟💟💟💟💟⬛️🌫🌫🌫⬜️⬛️🌫🌫🌫🌫🌫⬜️⬛️🌫🌫⬛️ +️🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩⬛️⬛️⬛️⬛️🥧💟💟💟💟💟💟💟💟⬛️🌫🌫🌫⬛️⬛️🌫🌫🌫⬛️🌫⬛️⬛️🌫🌫⬛️ +🟦🟦🟩🟩🟩🟩🟩🟩🟩🟩🟦🟦🟦🟦🟦🟦🟦🟦🟩🟩🟩🟩🟩🟩⬛️⬛️🥧💟💟💟💟💟🍓💟💟⬛🌫🟥🟥🌫🌫🌫🌫🌫🌫🌫🌫🌫🟥🟥⬛️ +🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦⬛️🥧🥧💟🍓💟💟💟💟💟⬛️🌫🟥🟥🌫⬛️🌫🌫⬛️🌫🌫⬛️🌫🟥🟥⬛️ +🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦⬛️🥧🥧🥧💟💟💟💟💟💟💟⬛️🌫🌫🌫⬛️⬛️⬛️⬛️⬛️⬛️⬛️🌫🌫⬛️⬜️ +🟪🟪🟦🟦🟦🟦🟦🟦🟦🟦🟪🟪🟪🟪🟪🟪🟪🟪🟦🟦🟦🟦🟦🟦⬛️⬛️⬛️🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧⬛️🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫⬛️⬜️⬜️ +🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪⬛️🌫🌫🌫⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬜️⬜️⬜️ +🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪⬛️🌫🌫⬛️⬛️⬜️⬛️🌫🌫⬛️⬜️⬜️⬜️⬜️⬜️⬛️🌫🌫⬛️⬜️⬛️🌫🌫⬛️⬜️⬜️⬜️⬜️ +⬜️⬜️🟪🟪🟪🟪🟪🟪🟪🟪⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️🟪🟪🟪🟪🟪⬛️⬛️⬛️⬛⬜️⬜️⬛️⬛️⬛️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬛️⬛️⬛️⬜️⬜️⬛️⬛️⬜️⬜️⬜️⬜️⬜️️ + ''' + ascii_art_lines = ascii_art.strip().split('\n') + with app.app_context(): + return Response(render_template_string(''' + + + + + + +
+ + + + ''', ascii_art_lines=ascii_art_lines), status=200, content_type="text/html") +``` + +运行此代码需要先安装以下 Python 依赖包: + +```python +pip install werkzeug +pip install flask +pip install dify-plugin +``` + +### 调试插件 + +接下来需测试插件是否可以正常运行。Dify 提供远程调试方式,前往“插件管理”页获取调试 Key 和远程服务器地址。 + +![](https://assets-docs.dify.ai/2024/11/1cf15bc59ea10eb67513c8bdca557111.png) + +回到插件项目,拷贝 `.env.example` 文件并重命名为 `.env`,将获取的远程服务器地址和调试 Key 等信息填入其中。 + +`.env` 文件 + +```bash +INSTALL_METHOD=remote +REMOTE_INSTALL_HOST=remote +REMOTE_INSTALL_PORT=5003 +REMOTE_INSTALL_KEY=****-****-****-****-**** +``` + +运行 `python -m main` 命令启动插件。在插件页即可看到该插件已被安装至 Workspace 内。其他团队成员也可以访问该插件。 + +![](https://assets-docs.dify.ai/2024/11/0fe19a8386b1234755395018bc2e0e35.png) + +在插件内新增 Endpoint,随意填写名称和 `api_key` 等信息。访问自动生成的 URL,即可看到由插件提供的网页服务。 + +![](https://assets-docs.dify.ai/2024/11/c76375b8df2449d0d8c31a7c2a337579.png) + +### 打包插件 + +确认插件能够正常运行后,可以通过以下命令行工具打包并命名插件。运行以后你可以在当前文件夹发现 `neko.difypkg` 文件,该文件为最终的插件包。 + +```bash +# 将 ./neko 替换为插件项目的实际路径 + +dify plugin package ./neko +``` + +恭喜,你已完成一个插件的完整开发、测试打包过程! + +### 发布插件 + +现在可以将它上传至 [Dify Plugins 代码仓库](https://github.com/langgenius/dify-plugins) 来发布你的插件了!上传前,请确保你的插件遵循了[插件发布规范](https://docs.dify.ai/zh-hans/plugins/publish-plugins/publish-to-dify-marketplace)。审核通过后,代码将合并至主分支并自动上线至 [Dify Marketplace](https://marketplace.dify.ai/)。 + +### 探索更多 + +**快速开始:** + +* [Tool 插件:Google Search](tool-plugin.md) +* [Model 插件](model-plugin/) +* [Bundle 插件:将多个插件打包](bundle.md) + +**插件接口文档:** + +* [Manifest](../../schema-definition/manifest.md) 结构 +* [Endpoint](../../schema-definition/endpoint.md) 详细定义 +* [反向调用 Dify 能力](../../schema-definition/reverse-invocation-of-the-dify-service/) +* [工具](../../schema-definition/tool.md) +* [模型](../../schema-definition/model/) +* [扩展 Agent 策略](../../schema-definition/agent.md) + +**最佳实践:** + +[开发 Slack Bot 插件](../../best-practice/develop-a-slack-bot-plugin.md) diff --git a/plugin_dev_ja/9232-agent.zh.mdx b/plugin_dev_ja/9232-agent.zh.mdx new file mode 100644 index 00000000..41458701 --- /dev/null +++ b/plugin_dev_ja/9232-agent.zh.mdx @@ -0,0 +1,426 @@ +--- +dimensions: + type: + primary: implementation + detail: high + level: intermediate +standard_title: Agent +language: ja +title: Agent +description: このドキュメントでは、DifyのAgent戦略プラグインの開発プロセスについて詳しく説明します。ManifestファイルへのAgent戦略フィールドの追加、Agentプロバイダーの定義、Agent戦略を実装するためのコアステップが含まれます。パラメータの取得、モデルの呼び出し、ツールの呼び出し、ログの生成と管理に関する完全なサンプルコードも詳細に解説しています。 +--- + +Agent戦略は、標準の入力内容と出力形式を定義する拡張可能なテンプレートです。具体的なAgent戦略インターフェースの機能コードを開発することで、CoT(思考の連鎖)/ ToT(思考の木)/ GoT(思考のグラフ)/ BoT(思考の骨格)など、さまざまなAgent戦略を実現し、[Sementic Kernel](https://learn.microsoft.com/en-us/semantic-kernel/overview/) のような複雑な戦略を実装できます。 + +### Manifest内へのフィールド追加 + +プラグインにAgent戦略を追加するには、`manifest.yaml`ファイル内に`plugins.agent_strategies`フィールドを新たに追加し、Agentプロバイダーも定義する必要があります。サンプルコードは以下の通りです。 + +```yaml +version: 0.0.2 +type: plugin +author: "langgenius" +name: "agent" +plugins: + agent_strategies: + - "provider/agent.yaml" +``` + +ここでは、`manifest`ファイル内の一部の無関係なフィールドは省略されています。Manifestの詳細な形式については、[マニフェストファイルによるプラグイン情報の定義](/plugin_dev_ja/0411-plugin-info-by-manifest.ja)ドキュメントを参照してください。 + +### Agentプロバイダーの定義 + +次に、`agent.yaml`ファイルを新規作成し、基本的なAgentプロバイダー情報を入力する必要があります。 + +```yaml +identity: + author: langgenius + name: agent + label: + en_US: Agent + zh_Hans: Agent + pt_BR: Agent + description: + en_US: Agent + zh_Hans: Agent + pt_BR: Agent + icon: icon.svg +strategies: + - strategies/function_calling.yaml +``` + +主にいくつかの記述的な基本情報を含み、現在のプロバイダーがどの戦略を含むかを指定します。上記のサンプルコードでは、最も基本的な`function_calling.yaml`戦略ファイルのみが指定されています。 + +### Agent戦略の定義と実装 + +#### 定義 + +次に、Agent戦略を実現できるコードを定義する必要があります。`function_calling.yaml`ファイルを新規作成します: + +```yaml +identity: + name: function_calling + author: Dify + label: + en_US: FunctionCalling + zh_Hans: FunctionCalling + pt_BR: FunctionCalling +description: + en_US: Function Calling is a basic strategy for agent, model will use the tools provided to perform the task. + zh_Hans: Function Callingは基本的なAgent戦略であり、モデルは提供されたツールを使用してタスクを実行します。 + pt_BR: Function Calling is a basic strategy for agent, model will use the tools provided to perform the task. +parameters: + - name: model + type: model-selector + scope: tool-call&llm + required: true + label: + en_US: Model + zh_Hans: モデル + pt_BR: Model + - name: tools + type: array[tools] + required: true + label: + en_US: Tools list + zh_Hans: ツールリスト + pt_BR: Tools list + - name: query + type: string + required: true + label: + en_US: Query + zh_Hans: ユーザーの質問 + pt_BR: Query + - name: max_iterations + type: number + required: false + default: 5 + label: + en_US: Max Iterations + zh_Hans: 最大反復回数 + pt_BR: Max Iterations + max: 50 + min: 1 +extra: + python: + source: strategies/function_calling.py +``` + +コード形式は[`Tool`標準形式](tool.md)に似ており、`model`、`tools`、`query`、`max_iterations`など合計4つのパラメータを定義し、最も基本的なAgent戦略の実装を容易にします。このコードの意味は、ユーザーがモデルと使用するツールを選択し、最大反復回数を設定し、最終的にqueryを渡してAgentの実行を開始できるようにすることです。 + +#### 機能実装コードの作成 + +**パラメータの取得** + +上記で定義された4つのパラメータに基づき、`model`タイプのパラメータは`model-selector`であり、`tool`タイプのパラメータは特殊な`array[tools]`です。パラメータで取得された形式は、SDKに組み込まれている`AgentModelConfig`と`list[ToolEntity]`を使用して変換できます。 + +```python +from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity + +class FunctionCallingParams(BaseModel): + query: str + model: AgentModelConfig + tools: list[ToolEntity] | None + maximum_iterations: int = 3 + + class FunctionCallingAgentStrategy(AgentStrategy): + def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]: + """ + Run FunctionCall agent application + """ + fc_params = FunctionCallingParams(**parameters) +``` + +**モデルの呼び出し** + +指定されたモデルの呼び出しは、Agentプラグインに不可欠な機能です。SDK内の`session.model.invoke()`関数を使用してモデルを呼び出します。`model`から必要な入力パラメータを取得できます。 + +`invoke model`のメソッドシグネチャのサンプルコード: + +```python +def invoke( + self, + model_config: LLMModelConfig, + prompt_messages: list[PromptMessage], + tools: list[PromptMessageTool] | None = None, + stop: list[str] | None = None, + stream: bool = True, + ) -> Generator[LLMResultChunk, None, None] | LLMResult: +``` + +モデル情報`model_config`、プロンプト情報`prompt_messages`、およびツール情報`tools`を渡す必要があります。 + +そのうち`prompt_messages`パラメータは以下のサンプルコードを参照して呼び出すことができますが、`tool_messages`は一定の変換が必要です。 + +`invoke model`の使用方法のサンプルコードを参照してください: + +```python +from collections.abc import Generator +from typing import Any + +from pydantic import BaseModel + +from dify_plugin.entities.agent import AgentInvokeMessage +from dify_plugin.entities.model.llm import LLMModelConfig +from dify_plugin.entities.model.message import ( + PromptMessageTool, + SystemPromptMessage, + UserPromptMessage, +) +from dify_plugin.entities.tool import ToolParameter +from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity + +class FunctionCallingParams(BaseModel): + query: str + instruction: str | None + model: AgentModelConfig + tools: list[ToolEntity] | None + maximum_iterations: int = 3 + +class FunctionCallingAgentStrategy(AgentStrategy): + def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]: + """ + Run FunctionCall agent application + """ + # init params + fc_params = FunctionCallingParams(**parameters) + query = fc_params.query + model = fc_params.model + stop = fc_params.model.completion_params.get("stop", []) if fc_params.model.completion_params else [] + prompt_messages = [ + SystemPromptMessage(content="あなたのシステムプロンプトメッセージ"), + UserPromptMessage(content=query), + ] + tools = fc_params.tools + prompt_messages_tools = self._init_prompt_tools(tools) + + # invoke llm + chunks = self.session.model.llm.invoke( + model_config=LLMModelConfig(**model.model_dump(mode="json")), + prompt_messages=prompt_messages, + stream=True, + stop=stop, + tools=prompt_messages_tools, + ) + + def _init_prompt_tools(self, tools: list[ToolEntity] | None) -> list[PromptMessageTool]: + """ + Init tools + """ + + prompt_messages_tools = [] + for tool in tools or []: + try: + prompt_tool = self._convert_tool_to_prompt_message_tool(tool) + except Exception: + # APIツールは削除された可能性があります + continue + + # save prompt tool + prompt_messages_tools.append(prompt_tool) + + return prompt_messages_tools + + def _convert_tool_to_prompt_message_tool(self, tool: ToolEntity) -> PromptMessageTool: + """ + convert tool to prompt message tool + """ + message_tool = PromptMessageTool( + name=tool.identity.name, + description=tool.description.llm if tool.description else "", + parameters={ + "type": "object", + "properties": {}, + "required": [], + }, + ) + + parameters = tool.parameters + for parameter in parameters: + if parameter.form != ToolParameter.ToolParameterForm.LLM: + continue + + parameter_type = parameter.type + if parameter.type in { + ToolParameter.ToolParameterType.FILE, + ToolParameter.ToolParameterType.FILES, + }: + continue + enum = [] + if parameter.type == ToolParameter.ToolParameterType.SELECT: + enum = [option.value for option in parameter.options] if parameter.options else [] + + message_tool.parameters["properties"][parameter.name] = { + "type": parameter_type, + "description": parameter.llm_description or "", + } + + if len(enum) > 0: + message_tool.parameters["properties"][parameter.name]["enum"] = enum + + if parameter.required: + message_tool.parameters["required"].append(parameter.name) + + return message_tool + +``` + +**ツールの呼び出し** + +ツールの呼び出しもAgentプラグインに不可欠な機能です。`self.session.tool.invoke()`を使用して呼び出すことができます。`invoke tool`のメソッドシグネチャのサンプルコード: + +```python +def invoke( + self, + provider_type: ToolProviderType, + provider: str, + tool_name: str, + parameters: dict[str, Any], + ) -> Generator[ToolInvokeMessage, None, None] +``` + +必須パラメータは`provider_type`、`provider`、`tool_name`、`parameters`です。そのうち`tool_name`と`parameters`は、Function CallingではLLMによって生成されることがよくあります。`invoke tool`の使用例コード: + +```python +from dify_plugin.entities.tool import ToolProviderType + +class FunctionCallingAgentStrategy(AgentStrategy): + def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]: + """ + Run FunctionCall agent application + """ + fc_params = FunctionCallingParams(**parameters) + + # tool_call_name and tool_call_args parameter is obtained from the output of LLM + tool_instances = {tool.identity.name: tool for tool in fc_params.tools} if fc_params.tools else {} + tool_instance = tool_instances[tool_call_name] + tool_invoke_responses = self.session.tool.invoke( + provider_type=ToolProviderType.BUILT_IN, + provider=tool_instance.identity.provider, + tool_name=tool_instance.identity.name, + # デフォルト値を追加 + parameters={**tool_instance.runtime_parameters, **tool_call_args}, + ) +``` + +`self.session.tool.invoke()`関数の出力はGeneratorであり、同様にストリーミング解析が必要であることを意味します。 + +解析方法については、以下の関数を参照してください: + +```python +import json +from collections.abc import Generator +from typing import cast + +from dify_plugin.entities.agent import AgentInvokeMessage +from dify_plugin.entities.tool import ToolInvokeMessage + +def parse_invoke_response(tool_invoke_responses: Generator[AgentInvokeMessage]) -> str: + result = "" + for response in tool_invoke_responses: + if response.type == ToolInvokeMessage.MessageType.TEXT: + result += cast(ToolInvokeMessage.TextMessage, response.message).text + elif response.type == ToolInvokeMessage.MessageType.LINK: + result += ( + f"結果リンク: {cast(ToolInvokeMessage.TextMessage, response.message).text}。" + + " ユーザーに確認するよう伝えてください。" + ) + elif response.type in { + ToolInvokeMessage.MessageType.IMAGE_LINK, + ToolInvokeMessage.MessageType.IMAGE, + }: + result += ( + "画像は既に作成されユーザーに送信済みです。" + + "作成する必要はありません。ユーザーに今すぐ確認するよう伝えてください。" + ) + elif response.type == ToolInvokeMessage.MessageType.JSON: + text = json.dumps(cast(ToolInvokeMessage.JsonMessage, response.message).json_object, ensure_ascii=False) + result += f"ツール応答: {text}。" + else: + result += f"ツール応答: {response.message!r}。" + return result +``` + +#### Log + +Agentの思考プロセスを確認したい場合、正常に返されたメッセージを確認する以外に、専用のインターフェースを使用してAgent全体の思考プロセスをツリー構造で表示することもできます。 + +**ログの作成** + +* このインターフェースは`AgentLogMessage`を作成して返します。このMessageはログ内のツリーのノードを表します。 +* `parent`が渡された場合、そのノードは親ノードを持つことを示します。 +* ステータスはデフォルトで"Success"(成功)です。ただし、タスクの実行プロセスをよりよく表示したい場合は、まずステータスを"start"に設定して「実行中」のログを表示し、タスク完了後にそのログのステータスを"Success"に更新することができます。これにより、ユーザーはタスクの開始から完了までの全プロセスを明確に確認できます。 +* `label`は、最終的にユーザーにログのタイトルを表示するために使用されます。 + +```python + def create_log_message( + self, + label: str, + data: Mapping[str, Any], + status: AgentInvokeMessage.LogMessage.LogStatus = AgentInvokeMessage.LogMessage.LogStatus.SUCCESS, + parent: AgentInvokeMessage | None = None, + ) -> AgentInvokeMessage +``` + +**ログの完了** + +前のステップで開始ステータスとして`start`状態を選択した場合、ログ完了インターフェースを使用してステータスを変更できます。 + +```python + def finish_log_message( + self, + log: AgentInvokeMessage, + status: AgentInvokeMessage.LogMessage.LogStatus = AgentInvokeMessage.LogMessage.LogStatus.SUCCESS, + error: Optional[str] = None, + ) -> AgentInvokeMessage +``` + +**インスタンス** + +この例では、単純な2ステップの実行プロセスを示しています。まず「思考中」のステータスログを出力し、次に実際のタスク処理を完了します。 + +```python +class FunctionCallingAgentStrategy(AgentStrategy): + def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]: + thinking_log = self.create_log_message( + data={ + "Query": parameters.get("query"), + }, + label="思考中", + status=AgentInvokeMessage.LogMessage.LogStatus.START, + ) + + yield thinking_log + + llm_response = self.session.model.llm.invoke( + model_config=LLMModelConfig( + provider="openai", + model="gpt-4o-mini", + mode="chat", + completion_params={}, + ), + prompt_messages=[ + SystemPromptMessage(content="あなたは役立つアシスタントです"), + UserPromptMessage(content=parameters.get("query")), + ], + stream=False, + tools=[], + ) + + thinking_log = self.finish_log_message( + log=thinking_log, + ) + + yield thinking_log + + yield self.create_text_message(text=llm_response.message.content) +``` + +## 関連リソース + +- [プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja) - プラグイン開発の全体的なアーキテクチャを理解する +- [Agent戦略プラグイン開発例](/plugin_dev_ja/9433-agent-strategy-plugin.ja) - 実際のAgent戦略プラグイン開発例 +- [マニフェストファイルによるプラグイン情報の定義](/plugin_dev_ja/0411-plugin-info-by-manifest.ja) - Manifestファイルの詳細な形式を理解する +- [モデルの逆呼び出し](/plugin_dev_ja/9242-reverse-invocation-model.ja) - プラットフォーム内のモデル機能を呼び出す方法を理解する +- [ツールの逆呼び出し](/plugin_dev_ja/9242-reverse-invocation-tool.ja) - 他のプラグインを呼び出す方法を理解する \ No newline at end of file diff --git a/plugin_dev_ja/9241-bundle.zh.mdx b/plugin_dev_ja/9241-bundle.zh.mdx new file mode 100644 index 00000000..4c8d84c0 --- /dev/null +++ b/plugin_dev_ja/9241-bundle.zh.mdx @@ -0,0 +1,94 @@ +--- +dimensions: + type: + primary: implementation + detail: advanced + level: beginner +standard_title: Bundle +language: ja +title: Bundle プラグインパッケージ +description: 本ドキュメントでは、Bundle プラグインパッケージの概念および開発方法について解説します。Bundle プラグインパッケージは、複数のプラグインを一つに統合することができ、Marketplace タイプ、GitHub タイプ、Package タイプの3種類をサポートしています。本ドキュメントでは、Bundle プロジェクトの作成、さまざまなタイプの依存関係の追加、および Bundle プロジェクトのパッケージ化に至る全プロセスを詳細に説明します。 +--- + +Bundle プラグインパッケージは、複数のプラグインの集合です。これにより、複数のプラグインを単一のプラグイン内にパッケージ化し、プラグインの一括インストールを実現するとともに、より強力なサービスを提供できます。 + +Dify CLI ツールを使用して、複数のプラグインを Bundle としてパッケージ化できます。Bundle プラグインパッケージには、次の3つのタイプがあります。 + +* `Marketplace` タイプ。プラグインの ID とバージョン情報を保存します。インポート時には Dify Marketplace を通じて具体的なプラグインパッケージがダウンロードされます。 +* `GitHub` タイプ。GitHub のリポジトリURL、リリースバージョン番号、アセットファイル名を保存します。インポート時には Dify が対応する GitHub リポジトリにアクセスしてプラグインパッケージをダウンロードします。 +* `Package` タイプ。プラグインパッケージは Bundle 内に直接保存されます。参照元を保存しないため、Bundle パッケージのサイズが大きくなる可能性があります。 + +### 事前準備 + +* Dify プラグインスキャフォールディングツール +* Python 環境、バージョン ≥ 3.10 + +プラグイン開発用のスキャフォールディングツールの準備方法については、[開発ツールの初期化](initialize-development-tools.md)を参照してください。 + +### Bundle プロジェクトの作成 + +現在のパスで、スキャフォールディングコマンドラインツールを実行して、新しいプラグインパッケージプロジェクトを作成します。 + +```bash +./dify-plugin-darwin-arm64 bundle init +``` + +このバイナリファイルを `dify` にリネームし、`/usr/local/bin` パスにコピーした場合、次のコマンドを実行して新しいプラグインプロジェクトを作成できます。 + +```bash +dify bundle init +``` + +#### 1. プラグイン情報の入力 + +プロンプトに従って、プラグイン名、作成者情報、プラグインの説明を設定します。チームで作業している場合は、作成者として組織名を記入することもできます。 + +> 名称は1~128文字で、使用できる文字は英字、数字、ハイフン、アンダースコアのみです。 + +![Bundle の基本情報](https://assets-docs.dify.ai/2024/12/03a1c4cdc72213f09523eb1b40832279.png) + +情報を入力して Enter キーを押すと、Bundle プラグインプロジェクトディレクトリが自動的に作成されます。 + +![](https://assets-docs.dify.ai/2024/12/356d1a8201fac3759bf01ee64e79a52b.png) + +#### 2. 依存関係の追加 + +* **Marketplace** + +次のコマンドを実行します。 + +```bash +dify-plugin bundle append marketplace . --marketplace_pattern=langgenius/openai:0.0.1 +``` + +ここで、`marketplace_pattern` は Marketplace でのプラグイン参照であり、フォーマットは `組織名/プラグイン名:バージョン番号` です。 + +* **Github** + +次のコマンドを実行します。 + +```bash +dify-plugin bundle append github . --repo_pattern=langgenius/openai:0.0.1/openai.difypkg +``` + +ここで、`repo_pattern` は GitHub でのプラグイン参照であり、フォーマットは `組織名/リポジトリ名:release/アセットファイル名` です。 + +* **Package** + +次のコマンドを実行します。 + +```bash +dify-plugin bundle append package . --package_path=./openai.difypkg +``` + +ここで、`package_path` はプラグインパッケージのパスです。 + +### Bundle プロジェクトのパッケージ化 + +次のコマンドを実行して Bundle プラグインをパッケージ化します。 + +```bash +dify-plugin bundle package ./bundle +``` + +コマンドを実行すると、現在のディレクトリに `bundle.difybndl` ファイルが自動的に作成されます。このファイルが最終的なパッケージ結果です。 \ No newline at end of file diff --git a/plugin_dev_ja/9241-reverse-invocation.zh.mdx b/plugin_dev_ja/9241-reverse-invocation.zh.mdx new file mode 100644 index 00000000..8a3a1abd --- /dev/null +++ b/plugin_dev_ja/9241-reverse-invocation.zh.mdx @@ -0,0 +1,35 @@ +--- +dimensions: + type: + primary: implementation + detail: advanced + level: beginner +standard_title: Reverse Invocation +language: ja +title: Difyサービスのリバース呼び出し +description: このドキュメントでは、Difyプラグインのリバース呼び出し機能について簡単に説明します。この機能は、プラグインがDifyメインプラットフォーム内の指定されたサービスを呼び出すことを可能にします。本文書では、呼び出し可能なモジュールとして、App(アプリデータへのアクセス)、Model(プラットフォーム内のモデル機能の呼び出し)、Tool(プラットフォーム内の他のツールプラグインの呼び出し)、Node(Chatflow/Workflowアプリケーション内のノード呼び出し)の4つのカテゴリをリストアップしています。 +--- + +プラグインは、Difyメインプラットフォーム内の一部のサービスを自由に呼び出し、プラグインの機能を向上させることができます。 + +### 呼び出し可能なDifyモジュール + +* [App](/plugin_dev_ja/9242-reverse-invocation-app.ja) + + プラグインはDifyプラットフォーム内のアプリデータにアクセスできます。 +* [Model](/plugin_dev_ja/9242-reverse-invocation-model.ja) + + プラグインはDifyプラットフォーム内のLLM機能をリバース呼び出しできます。これには、TTS、Rerankなど、プラットフォーム内のすべてのモデルタイプと機能が含まれます。 +* [Tool](/plugin_dev_ja/9242-reverse-invocation-tool.ja) + + プラグインはDifyプラットフォーム内の他のツールタイプのプラグインを呼び出すことができます。 +* [Node](/plugin_dev_ja/9243-reverse-invocation-node.ja) + + プラグインはDifyプラットフォーム内の特定のChatflow/Workflowアプリケーション内のノードを呼び出すことができます。 + +## 関連リソース + +- [Extensionプラグインの開発](/plugin_dev_ja/9231-extension-plugin.ja) - 外部システムと統合するプラグインの開発方法を学びます +- [Slack Botプラグインの開発](/plugin_dev_ja/0432-develop-a-slack-bot-plugin.ja) - リバース呼び出しを使用してSlackプラットフォームとの統合を実現する実装例 +- [バンドルタイプのプラグイン](/plugin_dev_ja/9241-bundle.ja) - リバース呼び出しを使用する複数のプラグインをパッケージ化する方法を学びます +- [永続ストレージの使用](/plugin_dev_ja/0411-persistent-storage-kv.ja) - KVストレージを通じてプラグインの機能を向上させます \ No newline at end of file diff --git a/plugin_dev_ja/9242-reverse-invocation-app.zh.mdx b/plugin_dev_ja/9242-reverse-invocation-app.zh.mdx new file mode 100644 index 00000000..8b046609 --- /dev/null +++ b/plugin_dev_ja/9242-reverse-invocation-app.zh.mdx @@ -0,0 +1,123 @@ +--- +dimensions: + type: + primary: implementation + detail: advanced + level: intermediate +standard_title: Reverse Invocation App +language: ja +title: アプリ +description: このドキュメントでは、プラグインがDifyプラットフォーム内のAppサービスをリバース呼び出しする方法について詳しく説明します。内容には、チャットインターフェース(Chatbot/Agent/Chatflowタイプのアプリケーションに適用)、Workflowインターフェース、Completionインターフェースの3種類のインターフェースが含まれ、各インターフェースのエントリーポイント、呼び出し規約、および実際の呼び出し例のコードが提供されています。 +--- + +アプリのリバース呼び出しとは、プラグインがDify内のアプリデータにアクセスできることを指します。このモジュールは、ストリーミングと非ストリーミングの両方のアプリ呼び出しをサポートしています。リバース呼び出しの基本的な概念にまだ慣れていない場合は、まず[Difyサービスのリバース呼び出し](/plugin_dev_ja/9241-reverse-invocation.ja)をお読みください。 + +**インターフェースタイプ:** + +* `Chatbot/Agent/Chatflow` タイプのアプリケーションはすべてチャットタイプのアプリケーションであり、同じタイプの入力パラメータと出力パラメータを持つため、統一して**チャットインターフェース**と見なすことができます。 +* Workflowアプリケーションの場合、単独で**Workflowインターフェース**を占有します。 +* Completion(テキスト生成アプリケーション)アプリケーションの場合、単独で**Completionインターフェース**を占有します。 + +注意:プラグインは、プラグインが存在するワークスペース内のアプリにのみアクセスできます。 + +### チャットインターフェースの呼び出し + +#### **エントリーポイント** + +```python + self.session.app.chat +``` + +#### **インターフェース規約** + +```python + def invoke( + self, + app_id: str, + inputs: dict, + response_mode: Literal["streaming", "blocking"], + conversation_id: str, + files: list, + ) -> Generator[dict, None, None] | dict: + pass +``` + +`response_mode` が `streaming` の場合、このインターフェースは直接 `Generator[dict]` を返します。そうでない場合は直接 `dict` を返します。具体的なインターフェースフィールドについては、`ServiceApi` の戻り結果を参照してください。 + +#### **使用例** + +`Endpoint`内でチャットタイプのアプリを呼び出し、結果を直接返すことができます。 + +```python +import json +from typing import Mapping +from werkzeug import Request, Response +from dify_plugin import Endpoint + +class Duck(Endpoint): + def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response: + """ + Invokes the endpoint with the given request. + """ + app_id = values["app_id"] + + def generator(): + response = self.session.app.workflow.invoke( + app_id=app_id, inputs={}, response_mode="streaming", files=[] + ) + + for data in response: + yield f"{json.dumps(data)}
" + + return Response(generator(), status=200, content_type="text/html") +``` + +### Workflowインターフェースの呼び出し + +#### **エントリーポイント** + +```python + self.session.app.workflow +``` + +#### **インターフェース規約** + +```python + def invoke( + self, + app_id: str, + inputs: dict, + response_mode: Literal["streaming", "blocking"], + files: list, + ) -> Generator[dict, None, None] | dict: + pass +``` + +### Completionインターフェースの呼び出し + +#### **エントリーポイント** + +```python + self.session.app.completion +``` + +**インターフェース規約** + +```python + def invoke( + self, + app_id: str, + inputs: dict, + response_mode: Literal["streaming", "blocking"], + files: list, + ) -> Generator[dict, None, None] | dict: + pass +``` + +## 関連リソース + +- [Difyサービスのリバース呼び出し](/plugin_dev_ja/9241-reverse-invocation.ja) - リバース呼び出しの基本的な概念を理解する +- [モデルのリバース呼び出し](/plugin_dev_ja/9242-reverse-invocation-model.ja) - プラットフォーム内のモデル機能を呼び出す方法を理解する +- [ツールのリバース呼び出し](/plugin_dev_ja/9242-reverse-invocation-tool.ja) - 他のプラグインを呼び出す方法を理解する +- [Slack Botプラグインの開発](/plugin_dev_ja/0432-develop-a-slack-bot-plugin.ja) - リバース呼び出しを使用した実際の応用例 +- [拡張機能プラグインの開発](/plugin_dev_ja/9231-extension-plugin.ja) - 拡張機能プラグインの開発方法を学ぶ \ No newline at end of file diff --git a/plugin_dev_ja/9242-reverse-invocation-model.zh.mdx b/plugin_dev_ja/9242-reverse-invocation-model.zh.mdx new file mode 100644 index 00000000..57e3ceae --- /dev/null +++ b/plugin_dev_ja/9242-reverse-invocation-model.zh.mdx @@ -0,0 +1,289 @@ +--- +dimensions: + type: + primary: implementation + detail: advanced + level: intermediate +standard_title: Reverse Invocation Model +language: ja +title: モデル +description: このドキュメントでは、プラグインがDifyプラットフォーム内のモデルサービスをリバースコールする方法について詳しく説明します。内容は、LLM、Summary、TextEmbedding、Rerank、TTS、Speech2Text、Moderationなどのモデルをリバースコールする具体的な方法を含み、各モデルの呼び出しには、対応するエントリポイント、インターフェースパラメータの説明、および実際の使用例コードが付随しており、モデル呼び出しのベストプラクティスも提供しています。 +--- + +リバースコールモデルとは、プラグインがDify内のLLM(TTS、Rerankなど、プラットフォーム内のすべてのモデルタイプと機能を含む)をリバースコールする能力を指します。リバースコールの基本概念にまだ慣れていない場合は、まず[Difyサービスのリバースコール](/plugin_dev_ja/9241-reverse-invocation.ja)をお読みください。 + +ただし、モデルの呼び出しには `ModelConfig` 型のパラメータを渡す必要があり、その構造は[一般的な仕様定義](/plugin_dev_ja/0411-general-specifications.ja)を参照できます。また、モデルの種類によって、この構造にはわずかな違いがあることに注意してください。 + +例えば、`LLM` タイプのモデルの場合、`completion_params` と `mode` パラメータも含まれている必要があり、この構造を手動で構築するか、`model-selector` タイプのパラメータまたは設定を使用できます。 + +### LLMの呼び出し + +#### **エントリポイント** + +```python + self.session.model.llm +``` + +#### **エンドポイント** + +```python + def invoke( + self, + model_config: LLMModelConfig, + prompt_messages: list[PromptMessage], + tools: list[PromptMessageTool] | None = None, + stop: list[str] | None = None, + stream: bool = True, + ) -> Generator[LLMResultChunk, None, None] | LLMResult: + pass +``` + +呼び出すモデルに `tool_call` の機能がない場合、ここで渡される `tools` は有効にならないことに注意してください。 + +#### **使用例** + +`Tool` 内で `OpenAI` の `gpt-4o-mini` モデルを呼び出したい場合は、以下のサンプルコードを参照してください。 + +```python +from collections.abc import Generator +from typing import Any + +from dify_plugin import Tool +from dify_plugin.entities.model.llm import LLMModelConfig +from dify_plugin.entities.tool import ToolInvokeMessage +from dify_plugin.entities.model.message import SystemPromptMessage, UserPromptMessage + +class LLMTool(Tool): + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]: + response = self.session.model.llm.invoke( + model_config=LLMModelConfig( + provider='openai', + model='gpt-4o-mini', + mode='chat', + completion_params={} + ), + prompt_messages=[ + SystemPromptMessage( + content='you are a helpful assistant' + ), + UserPromptMessage( + content=tool_parameters.get('query') + ) + ], + stream=True + ) + + for chunk in response: + if chunk.delta.message: + assert isinstance(chunk.delta.message.content, str) + yield self.create_text_message(text=chunk.delta.message.content) +``` + +コード内で `tool_parameters` の `query` パラメータが渡されていることに注意してください。 + +### **ベストプラクティス** + +`LLMModelConfig` を手動で構築することは推奨されません。代わりに、ユーザーがUI上で使用したいモデルを選択できるようにすることを推奨します。この場合、ツールのパラメータリストを次のように変更し、`model` パラメータを追加できます。 + +```yaml +identity: + name: llm + author: Dify + label: + en_US: LLM + zh_Hans: LLM + pt_BR: LLM + ja: LLM +description: + human: + en_US: A tool for invoking a large language model + zh_Hans: 用于调用大型语言模型的工具 + pt_BR: A tool for invoking a large language model + ja: 大規模言語モデルを呼び出すためのツール + llm: A tool for invoking a large language model +parameters: + - name: prompt + type: string + required: true + label: + en_US: Prompt string + zh_Hans: 提示字符串 + pt_BR: Prompt string + ja: プロンプト文字列 + human_description: + en_US: used for searching + zh_Hans: 用于搜索网页内容 + pt_BR: used for searching + ja: ウェブコンテンツの検索に使用 + llm_description: key words for searching + form: llm + - name: model + type: model-selector + scope: llm + required: true + label: + en_US: Model + zh_Hans: 使用的模型 + pt_BR: Model + ja: 使用するモデル + human_description: + en_US: Model + zh_Hans: 使用的模型 + pt_BR: Model + ja: 使用するモデル + llm_description: which Model to invoke + form: form +extra: + python: + source: tools/llm.py +``` + +この例では `model` の `scope` が `llm` に指定されていることに注意してください。この場合、ユーザーは `llm` タイプのパラメータしか選択できず、上記の使用例のコードを次のように変更できます。 + +```python +from collections.abc import Generator +from typing import Any + +from dify_plugin import Tool +from dify_plugin.entities.model.llm import LLMModelConfig +from dify_plugin.entities.tool import ToolInvokeMessage +from dify_plugin.entities.model.message import SystemPromptMessage, UserPromptMessage + +class LLMTool(Tool): + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]: + response = self.session.model.llm.invoke( + model_config=tool_parameters.get('model'), + prompt_messages=[ + SystemPromptMessage( + content='you are a helpful assistant' + ), + UserPromptMessage( + content=tool_parameters.get('query') + ) + ], + stream=True + ) + + for chunk in response: + if chunk.delta.message: + assert isinstance(chunk.delta.message.content, str) + yield self.create_text_message(text=chunk.delta.message.content) +``` + +### Summaryの呼び出し + +このエンドポイントにリクエストしてテキストを要約することができます。現在のワークスペース内のシステムモデルを使用してテキストを要約します。 + +**エントリポイント** + +```python + self.session.model.summary +``` + +**エンドポイント** + +* `text` は要約されるテキストです。 +* `instruction` は追加したい指示であり、これによりテキストを特定のスタイルで要約できます。 + +```python + def invoke( + self, text: str, instruction: str, + ) -> str: +``` + +### TextEmbeddingの呼び出し + +**エントリポイント** + +```python + self.session.model.text_embedding +``` + +**エンドポイント** + +```python + def invoke( + self, model_config: TextEmbeddingResult, texts: list[str] + ) -> TextEmbeddingResult: + pass +``` + +### Rerankの呼び出し + +**エントリポイント** + +```python + self.session.model.rerank +``` + +**エンドポイント** + +```python + def invoke( + self, model_config: RerankModelConfig, docs: list[str], query: str + ) -> RerankResult: + pass +``` + +### TTSの呼び出し + +**エントリポイント** + +```python + self.session.model.tts +``` + +**エンドポイント** + +```python + def invoke( + self, model_config: TTSModelConfig, content_text: str + ) -> Generator[bytes, None, None]: + pass +``` + +`tts` エンドポイントが返す `bytes` ストリームは `mp3` オーディオバイトストリームであり、各イテレーションで返されるのは完全なオーディオであることに注意してください。より高度な処理タスクを実行したい場合は、適切なライブラリを選択してください。 + +### Speech2Textの呼び出し + +**エントリポイント** + +```python + self.session.model.speech2text +``` + +**エンドポイント** + +```python + def invoke( + self, model_config: Speech2TextModelConfig, file: IO[bytes] + ) -> str: + pass +``` + +ここで `file` は `mp3` 形式でエンコードされたオーディオファイルです。 + +### Moderationの呼び出し + +**エントリポイント** + +```python + self.session.model.moderation +``` + +**エンドポイント** + +```python + def invoke(self, model_config: ModerationModelConfig, text: str) -> bool: + pass +``` + +このエンドポイントが `true` を返した場合、`text` に機密コンテンツが含まれていることを示します。 + +## 関連リソース + +- [Difyサービスのリバースコール](/plugin_dev_ja/9241-reverse-invocation.ja) - リバースコールの基本概念を理解する +- [Appのリバースコール](/plugin_dev_ja/9242-reverse-invocation-app.ja) - プラットフォーム内のAppを呼び出す方法を理解する +- [Toolのリバースコール](/plugin_dev_ja/9242-reverse-invocation-tool.ja) - 他のプラグインを呼び出す方法を理解する +- [モデルプラグイン開発ガイド](/plugin_dev_ja/0211-getting-started-new-model.ja) - カスタムモデルプラグインの開発方法を学ぶ +- [モデル設計規則](/plugin_dev_ja/0411-model-designing-rules.ja) - モデルプラグインの設計原則を理解する \ No newline at end of file diff --git a/plugin_dev_ja/9242-reverse-invocation-tool.zh.mdx b/plugin_dev_ja/9242-reverse-invocation-tool.zh.mdx new file mode 100644 index 00000000..f43028b9 --- /dev/null +++ b/plugin_dev_ja/9242-reverse-invocation-tool.zh.mdx @@ -0,0 +1,92 @@ +--- +dimensions: + type: + primary: implementation + detail: advanced + level: intermediate +standard_title: Reverse Invocation Tool +language: ja +title: ツール +description: このドキュメントでは、プラグインがDifyプラットフォーム内のツールサービスをリバース呼び出しする方法について詳しく説明します。内容は、インストール済みのツール(Built-in Tool)の呼び出し、Workflow as Toolの呼び出し、カスタムツール(Custom Tool)の呼び出しという3つの異なるタイプのツール呼び出し方法を網羅しています。各呼び出し方法には、対応するエントリポイントとインターフェースパラメータの説明が付いています。 +--- + +リバース呼び出しツールとは、プラグインがDifyプラットフォーム内の他のツールタイプのプラグインを呼び出すことができることを指します。リバース呼び出しの基本概念にまだ慣れていない場合は、まず[Difyサービスのリバース呼び出し](/plugin_dev_ja/9241-reverse-invocation.ja)をお読みください。 + +次のような要求に遭遇した場合: + +* あるツールタイプのプラグインが既に機能を実装しているが、期待通りの効果が得られず、データの二次加工が必要な場合。 +* あるタスクでクローラーを使用する必要があり、クローラーサービスを自由に選択したい場合。 +* 複数のツールの結果を統合する必要があるが、Workflowアプリケーションでは処理が難しい場合。 + +この場合、プラグイン内で既に実装されている他のツールを呼び出す必要があります。そのツールは、マーケットプレイスのツールプラグイン、独自に構築したWorkflow as a Tool、またはカスタムツールである可能性があります。 + +上記の要求は、プラグインの `self.session.tool` フィールドを呼び出すことで実現できます。 + +### インストール済みのツールの呼び出し + +プラグインが現在のワークスペースにインストールされている各ツール(他のツールタイプのプラグインも含む)を呼び出すことを許可します。 + +**エントリポイント** + +```python + self.session.tool +``` + +**インターフェース** + +```python + def invoke_builtin_tool( + self, provider: str, tool_name: str, parameters: dict[str, Any] + ) -> Generator[ToolInvokeMessage, None, None]: + pass +``` + +ここで、provider はプラグインのIDにツールサプライヤー名を加えたもので、`langgenius/google/google` のような形式です。tool\_name は具体的なツール名で、`parameters` は最終的にそのツールに渡されるパラメータです。 + +### Workflow as Tool の呼び出し + +Workflow as Tool に関する詳細については、[ツールプラグインのドキュメント](/plugin_dev_ja/9223-tool.ja)を参照してください。 + +**エントリポイント** + +```python + self.session.tool +``` + +**インターフェース** + +```python + def invoke_workflow_tool( + self, provider: str, tool_name: str, parameters: dict[str, Any] + ) -> Generator[ToolInvokeMessage, None, None]: + pass +``` + +この場合、provider はそのツールのIDです。tool\_name はそのツールの作成時に記入が求められます。 + +### カスタムツールの呼び出し + +**エントリポイント** + +```python + self.session.tool +``` + +**インターフェース** + +```python + def invoke_api_tool( + self, provider: str, tool_name: str, parameters: dict[str, Any] + ) -> Generator[ToolInvokeMessage, None, None]: + pass +``` + +この場合、`provider` はそのツールのIDです。`tool_name` は OpenAPI の `operation_id` であり、存在しない場合は Dify が自動生成した `tool_name` となります。具体的な名称はツール管理ページで確認できます。 + +## 関連リソース + +- [Difyサービスのリバース呼び出し](/plugin_dev_ja/9241-reverse-invocation.ja) - リバース呼び出しの基本概念を理解する +- [Appのリバース呼び出し](/plugin_dev_ja/9242-reverse-invocation-app.ja) - プラットフォーム内のAppを呼び出す方法を理解する +- [Modelのリバース呼び出し](/plugin_dev_ja/9242-reverse-invocation-model.ja) - プラットフォーム内のモデル機能を呼び出す方法を理解する +- [ツールプラグイン開発ガイド](/plugin_dev_ja/0211-getting-started-dify-tool.ja) - ツールプラグインの開発方法を学ぶ +- [高度なツールプラグイン](/plugin_dev_ja/9223-tool.ja) - Workflow as Tool などの高度な機能を理解する \ No newline at end of file diff --git a/plugin_dev_ja/9243-customizable-model.zh.mdx b/plugin_dev_ja/9243-customizable-model.zh.mdx new file mode 100644 index 00000000..09ab4528 --- /dev/null +++ b/plugin_dev_ja/9243-customizable-model.zh.mdx @@ -0,0 +1,350 @@ +--- +dimensions: + type: + primary: implementation + detail: advanced + level: advanced +standard_title: Customizable Model +language: ja +title: カスタムモデルの統合 +description: このドキュメントでは、Difyでカスタムモデルを統合する方法を、Xinferenceモデルを例に詳しく説明します。ドキュメントには、モデルプロバイダーファイルの作成、モデルタイプに応じたコードの記述、モデル呼び出しロジックと例外処理の実装、デバッグと公開までの完全なプロセスが含まれています。特に、LLM呼び出し、トークン計算、認証情報検証、パラメータ生成などのコアメソッドの実装について詳しく説明しています。 +--- + +カスタムモデルとは、自身でデプロイまたは設定する必要があるLLMを指します。本稿では、[Xinferenceモデル](https://inference.readthedocs.io/en/latest/)を例に、モデルプラグイン内でカスタムモデルを統合する方法を説明します。 + +カスタムモデルはデフォルトでモデルタイプとモデル名の2つのパラメータを含んでおり、プロバイダーのYAMLファイルで定義する必要はありません。 + +プロバイダー設定ファイルでは、`validate_provider_credential`を実装する必要はありません。Runtimeは、ユーザーが選択したモデルタイプまたはモデル名に基づいて、対応するモデルレイヤーの`validate_credentials`メソッドを自動的に呼び出して検証を行います。 + +### カスタムモデルプラグインの統合 + +カスタムモデルの統合は、以下のステップに分かれます: + +1. **モデルプロバイダーファイルの作成** + + カスタムモデルに含まれるモデルタイプを明確にします。 +2. **モデルタイプに基づいたコードファイルの作成** + + モデルのタイプ(例:`llm`または`text_embedding`)に基づいてコードファイルを作成します。各モデルタイプが独立したロジックレイヤーを持つようにし、保守と拡張を容易にします。 +3. **異なるモデルモジュールに基づいたモデル呼び出しコードの記述** + + 対応するモデルタイプモジュールの下に、モデルタイプと同名のPythonファイル(例:llm.py)を作成します。ファイル内で具体的なモデルロジックを実装するクラスを定義し、そのクラスはシステムのモデルインターフェース仕様に準拠する必要があります。 +4. **プラグインのデバッグ** + + 新たに追加されたプロバイダー機能に対して単体テストと結合テストを作成し、すべての機能モジュールが期待通りに動作することを確認します。 + +*** + +### 1. **モデルプロバイダーファイルの作成** + +プラグインプロジェクトの`/provider`パスの下に、新しい`xinference.yaml`ファイルを作成します。 + +`Xinference`ファミリーモデルは`LLM`、`Text Embedding`、`Rerank`モデルタイプをサポートしているため、`xinference.yaml`ファイルにこれらのモデルタイプを含める必要があります。 + +サンプルコード: + +```yaml +provider: xinference # プロバイダー識別子を決定 +label: # プロバイダー表示名。en_US (英語)、zh_Hans (中国語)の2言語を設定可能。zh_Hans を設定しない場合はデフォルトで en_US が使用されます。 + en_US: Xorbits Inference + zh_Hans: Xorbits Inference # 中国語の表示名(例として英語と同じにしていますが、通常は中国語訳) +icon_small: # 小アイコン。他のプロバイダーのアイコンを参考に、対応するプロバイダー実装ディレクトリ下の _assets ディレクトリに保存。中英ポリシーは label と同様。 + en_US: icon_s_en.svg +icon_large: # 大アイコン + en_US: icon_l_en.svg +help: # ヘルプ + title: + en_US: How to deploy Xinference + zh_Hans: Xinferenceのデプロイ方法 + url: + en_US: https://github.com/xorbitsai/inference +supported_model_types: # サポートされるモデルタイプ。XinferenceはLLM/Text Embedding/Rerankを同時にサポートします。 +- llm +- text-embedding +- rerank +configurate_methods: # Xinferenceはローカルデプロイのプロバイダーであり、事前定義されたモデルはありません。どのモデルを使用するかはXinferenceのドキュメントに従ってデプロイする必要があるため、ここのメソッドはカスタムモデルです。 +- customizable-model +provider_credential_schema: + credential_form_schemas: +``` + +次に、`provider_credential_schema`フィールドを定義する必要があります。`Xinference`は`text-generation`、`embeddings`、`reranking`モデルをサポートしています。サンプルコードは以下の通りです: + +```yaml +provider_credential_schema: + credential_form_schemas: + - variable: model_type + type: select + label: + en_US: Model type + zh_Hans: モデルタイプ + required: true + options: + - value: text-generation + label: + en_US: Language Model + zh_Hans: 言語モデル + - value: embeddings + label: + en_US: Text Embedding + zh_Hans: テキスト埋め込み # 中国語の表示名(例) + - value: reranking + label: + en_US: Rerank + zh_Hans: リランク # 中国語の表示名(例) +``` + +Xinferenceの各モデルでは、名前`model_name`を定義する必要があります。 + +```yaml + - variable: model_name + type: text-input + label: + en_US: Model name + zh_Hans: モデル名 + required: true + placeholder: + zh_Hans: モデル名を入力してください + en_US: Input model name +``` + +Xinferenceモデルでは、ユーザーがモデルのローカルデプロイアドレスを入力する必要があります。プラグイン内では、Xinferenceモデルのローカルデプロイアドレス(server\_url)とモデルUIDを入力できる場所を提供する必要があります。サンプルコードは以下の通りです: + +```yaml + - variable: server_url + label: + zh_Hans: サーバーURL + en_US: Server url + type: text-input + required: true + placeholder: + zh_Hans: ここにXinferenceのサーバーアドレスを入力してください(例:https://example.com/xxx) + en_US: Enter the url of your Xinference, for example https://example.com/xxx + - variable: model_uid + label: + zh_Hans: モデルUID + en_US: Model uid + type: text-input + required: true + placeholder: + zh_Hans: ここにあなたのモデルUIDを入力してください + en_US: Enter the model uid +``` + +すべてのパラメータを入力すると、カスタムモデルプロバイダーのYAML設定ファイルの作成が完了します。次に、設定ファイル内で定義されたモデルに具体的な機能コードファイルを追加する必要があります。 + +### 2. モデルコードの記述 + +Xinferenceモデルプロバイダーのモデルタイプには、llm、rerank、speech2text、ttsタイプが含まれるため、`/models`パスの下に各モデルタイプごとに独立したグループを作成し、対応する機能コードファイルを作成する必要があります。 + +以下では、llmタイプを例に、`llm.py`コードファイルの作成方法を説明します。コード作成時には、`XinferenceAILargeLanguageModel`という名前のXinference LLMクラスを作成し、`__base.large_language_model.LargeLanguageModel`ベースクラスを継承し、以下のいくつかのメソッドを実装する必要があります: + +* **LLM呼び出し** + + LLM呼び出しのコアメソッドであり、ストリーミングと同期の両方のレスポンスをサポートします。 + +```python +def _invoke(self, model: str, credentials: dict, + prompt_messages: list[PromptMessage], model_parameters: dict, + tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None, + stream: bool = True, user: Optional[str] = None) \ + -> Union[LLMResult, Generator]: + """ + 大規模言語モデルを呼び出す + + :param model: モデル名 + :param credentials: モデルの認証情報 + :param prompt_messages: プロンプトメッセージ + :param model_parameters: モデルパラメータ + :param tools: ツール呼び出し用のツール + :param stop: ストップワード + :param stream: ストリーム応答であるか + :param user: 一意のユーザーID + :return: 完全な応答またはストリーム応答チャンクジェネレータの結果 + """ +``` + + コードを実装する際には、同期応答とストリーミング応答をそれぞれ処理するために、2つの関数を使用してデータを返す必要があることに注意してください。 + + Pythonは、関数内に`yield`キーワードが含まれる関数をジェネレータ関数として認識し、返されるデータ型は`Generator`に固定されるため、同期応答とストリーミング応答をそれぞれ実装する必要があります。例えば、以下のサンプルコードです: + +> この例では簡略化されたパラメータを使用しています。実際のコード作成時には、上記のパラメータリストを参照してください。 + +```python +def _invoke(self, stream: bool, **kwargs) \ + -> Union[LLMResult, Generator]: + if stream: + return self._handle_stream_response(**kwargs) + return self._handle_sync_response(**kwargs) + +def _handle_stream_response(self, **kwargs) -> Generator: + for chunk in response: + yield chunk +def _handle_sync_response(self, **kwargs) -> LLMResult: + return LLMResult(**response) +``` + +* **入力トークンの事前計算** + + モデルがトークンを事前計算するインターフェースを提供していない場合は、直接0を返すことができます。 + +```python +def get_num_tokens(self, model: str, credentials: dict, prompt_messages: list[PromptMessage], + tools: Optional[list[PromptMessageTool]] = None) -> int: + """ + 指定されたプロンプトメッセージのトークン数を取得する + + :param model: モデル名 + :param credentials: モデルの認証情報 + :param prompt_messages: プロンプトメッセージ + :param tools: ツール呼び出し用のツール + :return: + """ +``` + + 場合によっては、直接0を返したくない場合は、`self._get_num_tokens_by_gpt2(text: str)`メソッドを使用してトークンを計算できます。このメソッドは`AIModel`ベースクラスにあり、GPT-2のTokenizerを使用して計算します。ただし、これは代替案であり、計算結果にはある程度の誤差が生じる可能性があることに注意してください。 + +* **モデル認証情報検証** + + プロバイダーの認証情報検証と同様に、ここでは個別のモデルに対して検証を行います。 + +```python +def validate_credentials(self, model: str, credentials: dict) -> None: + """ + モデルの認証情報を検証する + + :param model: モデル名 + :param credentials: モデルの認証情報 + :return: + """ +``` + +* **モデルパラメータスキーマ** + + [事前定義済みモデルタイプ](integrate-the-predefined-model.md)とは異なり、YAMLファイルでモデルがサポートするパラメータが事前設定されていないため、モデルパラメータのスキーマを動的に生成する必要があります。 + + 例えば、Xinferenceは`max_tokens`、`temperature`、`top_p`の3つのモデルパラメータをサポートしています。しかし、一部のプロバイダー(例えばOpenLLM)は、具体的なモデルによって異なるパラメータをサポートします。 + + 例を挙げると、プロバイダー`OpenLLM`のAモデルは`top_k`パラメータをサポートしていますが、Bモデルは`top_k`をサポートしていません。この場合、各モデルに対応するパラメータスキーマを動的に生成する必要があります。サンプルコードは以下の通りです: + +```python + def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity | None: + """ + カスタマイズ可能なモデルスキーマを定義するために使用されます + """ + rules = [ + ParameterRule( + name='temperature', type=ParameterType.FLOAT, + use_template='temperature', + label=I18nObject( + zh_Hans='温度', en_US='Temperature' + ) + ), + ParameterRule( + name='top_p', type=ParameterType.FLOAT, + use_template='top_p', + label=I18nObject( + zh_Hans='Top P', en_US='Top P' + ) + ), + ParameterRule( + name='max_tokens', type=ParameterType.INT, + use_template='max_tokens', + min=1, + default=512, + label=I18nObject( + zh_Hans='最大生成長', en_US='Max Tokens' + ) + ) + ] + + # モデルがAの場合、top_kをルールに追加 + if model == 'A': + rules.append( + ParameterRule( + name='top_k', type=ParameterType.INT, + use_template='top_k', + min=1, + default=50, + label=I18nObject( + zh_Hans='Top K', en_US='Top K' + ) + ) + ) + + """ + ここには重要でないコードがあります + """ + + entity = AIModelEntity( + model=model, + label=I18nObject( + en_US=model, + zh_Hans=model # 必要に応じて翻訳 + ), + fetch_from=FetchFrom.CUSTOMIZABLE_MODEL, + model_type=model_type, + model_properties={ + ModelPropertyKey.MODE: ModelType.LLM, + }, + parameter_rules=rules + ) + + return entity +``` + +* **呼び出し例外エラーマッピング表** + + モデル呼び出しが例外をスローした場合、Runtimeが指定する`InvokeError`タイプにマッピングする必要があります。これにより、Difyが異なるエラーに対して異なる後続処理を行うのに便利です。 + + Runtimeエラー: + + * `InvokeConnectionError` 呼び出し接続エラー + * `InvokeServerUnavailableError` 呼び出しサービス利用不可 + * `InvokeRateLimitError` 呼び出しレート制限到達 + * `InvokeAuthorizationError` 呼び出し認証失敗 + * `InvokeBadRequestError` 呼び出しパラメータ不正 + +```python +@property +def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]: + """ + モデル呼び出しエラーを統一エラーにマッピングする + キーは呼び出し元にスローされるエラータイプです + 値はモデルによってスローされるエラータイプであり、呼び出し元のために統一されたエラータイプに変換する必要があります。 + + :return: 呼び出しエラーマッピング + """ +``` + + より多くのインターフェースメソッドについては、[インターフェースドキュメント:Model](../../../schema-definition/model/)を参照してください。 + + 本稿で扱った完全なコードファイルを入手するには、[GitHubコードリポジトリ](https://github.com/langgenius/dify-official-plugins/tree/main/models/xinference)にアクセスしてください。 + +### 3. プラグインのデバッグ + +プラグインの開発が完了したら、次にプラグインが正常に動作するかをテストする必要があります。詳細については、以下を参照してください: + +[debug-plugin.md](../../debug-plugin.md) + +### 4. プラグインの公開 + +プラグインをDify Marketplaceに公開したい場合は、以下の内容を参照してください: + +[publish-to-dify-marketplace](../../../publish-plugins/publish-to-dify-marketplace/) + +### **さらに探索** + +**クイックスタート:** + +* [Extensionプラグインの開発](../extension-plugin.md) +* [Toolプラグインの開発](../tool-plugin.md) +* [Bundleプラグイン:複数のプラグインをバンドルする](../bundle.md) + +**プラグインインターフェースドキュメント:** + +* [Manifest構造](../../../schema-definition/manifest.md) +* [Endpoint詳細定義](../../../schema-definition/endpoint.md) +* [Dify機能の逆呼び出し](../../../schema-definition/reverse-invocation-of-the-dify-service/) +* [ツール](../../../schema-definition/tool.md) +* [モデル](../../../schema-definition/model/) \ No newline at end of file diff --git a/plugin_dev_ja/9243-reverse-invocation-node.zh.mdx b/plugin_dev_ja/9243-reverse-invocation-node.zh.mdx new file mode 100644 index 00000000..b17d32c8 --- /dev/null +++ b/plugin_dev_ja/9243-reverse-invocation-node.zh.mdx @@ -0,0 +1,96 @@ +--- +dimensions: + type: + primary: implementation + detail: advanced + level: advanced +standard_title: Reverse Invocation Node +language: ja +title: ノード +description: このドキュメントでは、プラグインがDifyプラットフォーム内のChatflow/Workflowアプリケーションノード機能を逆呼び出しする方法について説明します。主な内容は、パラメータ抽出器ノード(ParameterExtractor)と問題分類器ノード(QuestionClassifier)という2つの特殊なノードの呼び出し方法です。また、これら2つのノードの呼び出しエントリーポイント、インターフェースパラメータ、および使用例コードについて詳しく説明します。 +--- + +逆呼び出しノードとは、プラグインがDify内のChatflow/Workflowアプリケーション内部のノードにアクセスできる機能を指します。 + +`Workflow` 内の `ParameterExtractor(パラメータ抽出器)`と `QuestionClassifier(問題分類器)`ノードは、より複雑なプロンプトとコードロジックをカプセル化しており、LLM を通じて多くのハードコーディングでは解決困難なタスクを完了することができます。プラグインはこれら2つのノードを呼び出すことができます。 + +### パラメータ抽出器ノードの呼び出し; + +#### **エントリーポイント** + +```python + self.session.workflow_node.parameter_extractor +``` + +#### **インターフェース** + +```python + def invoke( + self, + parameters: list[ParameterConfig], + model: ModelConfig, + query: str, + instruction: str = "", + ) -> NodeResponse + pass +``` + +ここで、`parameters` は抽出する必要のあるパラメータのリストであり、`model` は `LLMModelConfig` 仕様に準拠し、`query` はパラメータを抽出する元のテキストであり、`instruction` はLLMに追加で与える必要のある可能性のある指示です。`NodeResponse` の構造については、こちらの[ドキュメント](../general-specifications.md#noderesponse)を参照してください。 + +#### **使用例** + +会話の中から特定の人物名を抽出したい場合は、以下のコードを参照してください。 + +```python +from collections.abc import Generator +from dify_plugin.entities.tool import ToolInvokeMessage +from dify_plugin import Tool +from dify_plugin.entities.workflow_node import ModelConfig, ParameterConfig + +class ParameterExtractorTool(Tool): + def _invoke( + self, tool_parameters: dict + ) -> Generator[ToolInvokeMessage, None, None]: + response = self.session.workflow_node.parameter_extractor.invoke( + parameters=[ + ParameterConfig( + name="name", + description="name of the person", + required=True, + type="string", + ) + ], + model=ModelConfig( + provider="langgenius/openai/openai", + name="gpt-4o-mini", + completion_params={}, + ), + query="My name is John Doe", + instruction="Extract the name of the person", + ) + + yield self.create_text_message(response.outputs["name"]) +``` + +### 問題分類器ノードの呼び出し + +#### **エントリーポイント** + +```python + self.session.workflow_node.question_classifier +``` + +#### **インターフェース** + +```python + def invoke( + self, + classes: list[ClassConfig], + model: ModelConfig, + query: str, + instruction: str = "", + ) -> NodeResponse: + pass +``` + +このインターフェースパラメータは `ParameterExtractor` と同じで、最終的な戻り結果は `NodeResponse.outputs['class_name']` に格納されます。 \ No newline at end of file diff --git a/plugin_dev_ja/9433-agent-strategy-plugin.zh.mdx b/plugin_dev_ja/9433-agent-strategy-plugin.zh.mdx new file mode 100644 index 00000000..270b0ac1 --- /dev/null +++ b/plugin_dev_ja/9433-agent-strategy-plugin.zh.mdx @@ -0,0 +1,1091 @@ +--- +dimensions: + type: + primary: reference + detail: examples + level: advanced +standard_title: Agent Strategy Plugin +language: ja +title: Agent戦略プラグイン +description: このドキュメントでは、Agent戦略プラグインの開発方法について、プラグインテンプレートの初期化から、モデル呼び出し、ツール呼び出し、ログ出力、パッケージ化、公開までの全プロセスを詳細に説明します。LLMが推論や意思決定ロジックを実行するのを支援する自動ツール呼び出し機能の実装方法など、詳細なコード例も提供します。 +--- + +Agent戦略プラグインは、LLMが推論や意思決定ロジックを実行するのを支援し、ツールの選択、呼び出し、結果処理を含め、より自動化された方法で問題を処理できるようにします。 + +この記事では、ツール呼び出し(Function Calling)機能を備え、現在の正確な時刻を自動的に取得するプラグインを作成する方法をデモンストレーションします。 + +### 事前準備 + +* Dify プラグインスケルトンツール +* Python 環境、バージョン番号 ≥ 3.12 + +プラグイン開発用のスケルトンツールの準備方法に関する詳細については、[開発ツールの初期化](/plugin_dev_ja/0221-initialize-development-tools.ja)を参照してください。 + +**Tips**:ターミナルで `dify version` コマンドを実行し、バージョン番号が表示されるかどうかを確認して、スケルトンツールが正常にインストールされたことを確認してください。 + +### 1. プラグインテンプレートの初期化 + +次のコマンドを実行して、Agent プラグイン開発テンプレートを初期化します。 + +``` +dify plugin init +``` + +ページの指示に従って、対応する情報を入力します。以下のコードのコメント情報を参照して設定してください。 + +``` +➜ Dify Plugins Developing dify plugin init +Edit profile of the plugin +Plugin name (press Enter to next step): # プラグイン名を入力 +Author (press Enter to next step): Author name # プラグインの作者を入力 +Description (press Enter to next step): Description # プラグインの説明を入力 +--- +Select the language you want to use for plugin development, and press Enter to con +BTW, you need Python 3.12+ to develop the Plugin if you choose Python. +-> python # Python 環境を選択 + go (not supported yet) +--- +Based on the ability you want to extend, we have divided the Plugin into four type + +- Tool: It's a tool provider, but not only limited to tools, you can implement an +- Model: Just a model provider, extending others is not allowed. +- Extension: Other times, you may only need a simple http service to extend the fu +- Agent Strategy: Implement your own logics here, just by focusing on Agent itself + +What's more, we have provided the template for you, you can choose one of them b + tool +-> agent-strategy # Agent 戦略テンプレートを選択 + llm + text-embedding +--- +Configure the permissions of the plugin, use up and down to navigate, tab to sel +Backwards Invocation: +Tools: + Enabled: [✔] You can invoke tools inside Dify if it's enabled # デフォルトで有効 +Models: + Enabled: [✔] You can invoke models inside Dify if it's enabled # デフォルトで有効 + LLM: [✔] You can invoke LLM models inside Dify if it's enabled # デフォルトで有効 + Text Embedding: [✘] You can invoke text embedding models inside Dify if it' + Rerank: [✘] You can invoke rerank models inside Dify if it's enabled +... +``` + +プラグインテンプレートを初期化すると、プラグイン開発プロセスに必要な完全なリソースを含むコードフォルダが生成されます。Agent 戦略プラグインの全体的なコード構造を理解することは、プラグインの開発プロセスに役立ちます。 + +``` +├── GUIDE.md # ユーザーガイドとドキュメント +├── PRIVACY.md # プライバシーポリシーとデータ処理ガイドライン +├── README.md # プロジェクト概要とセットアップ手順 +├── _assets/ # 静的アセットディレクトリ +│ └── icon.svg # Agent 戦略プロバイダーのアイコン/ロゴ +├── main.py # メインアプリケーションエントリーポイント +├── manifest.yaml # 基本プラグイン設定 +├── provider/ # プロバイダー設定ディレクトリ +│ └── basic_agent.yaml # あなたの Agent プロバイダー設定 +├── requirements.txt # Python 依存関係リスト +└── strategies/ # 戦略実装ディレクトリ + ├── basic_agent.py # 基本 Agent 戦略実装 + └── basic_agent.yaml # 基本 Agent 戦略設定 +``` + +プラグインの機能コードは `strategies/` ディレクトリに集約されています。 + +### 2. プラグイン機能の開発 + +Agent 戦略プラグインの開発は、主に以下の2つのファイルを中心に行われます。 + +* プラグイン宣言ファイル:`strategies/basic_agent.yaml` +* プラグイン機能コード:`strategies/basic_agent.py` + +#### 2.1 パラメータの定義 + +Agent プラグインを作成するには、まず `strategies/basic_agent.yaml` ファイルでプラグインに必要なパラメータを定義する必要があります。これらのパラメータは、LLM モデルの呼び出しやツールの使用能力など、プラグインのコア機能を決定します。 + +以下の4つの基本パラメータを優先的に設定することをお勧めします。 + +1. **model**:呼び出す大規模言語モデル(LLM)を指定します(例:GPT-4、GPT-4o-mini など)。 +2. **tools**:プラグインが使用できるツールリストを定義し、プラグイン機能を強化します。 +3. **query**:モデルと対話するためのプロンプトまたは入力内容を設定します。 +4. **maximum_iterations**:プラグイン実行の最大反復回数を制限し、過剰な計算を回避します。 + +コード例: + +```yaml +identity: + name: basic_agent # agent_strategy の名前 + author: novice # agent_strategy の作成者 + label: + en_US: BasicAgent # agent_strategy の英語ラベル +description: + en_US: BasicAgent # agent_strategy の英語の説明 +parameters: + - name: model # model パラメータの名前 + type: model-selector # モデルタイプ + scope: tool-call&llm # パラメータのスコープ + required: true + label: + en_US: Model + zh_Hans: 模型 + pt_BR: Model + - name: tools # tools パラメータの名前 + type: array[tools] # tool パラメータのタイプ + required: true + label: + en_US: Tools list + zh_Hans: 工具列表 + pt_BR: Tools list + - name: query # query パラメータの名前 + type: string # query パラメータのタイプ + required: true + label: + en_US: Query + zh_Hans: 查询 + pt_BR: Query + - name: maximum_iterations + type: number + required: false + default: 5 + label: + en_US: Maxium Iterations + zh_Hans: 最大迭代次数 + pt_BR: Maxium Iterations + max: 50 # 最大値と最小値を設定すると、パラメータの表示はスライダーになります + min: 1 +extra: + python: + source: strategies/basic_agent.py + +``` + +パラメータ設定が完了すると、プラグインは対応する設定の使用ページを自動生成し、直感的で便利な調整と使用が可能になります。 + +![Agent戦略プラグインの使用ページ](https://assets-docs.dify.ai/2025/01/d011e2eba4c37f07a9564067ba787df8.png) + +#### 2.2 パラメータの取得と実行 + +ユーザーがプラグインの使用ページで基本情報を入力した後、プラグインは入力されたパラメータを処理する必要があります。そのため、まず `strategies/basic_agent.py` ファイル内で後で使用するための Agent パラメータクラスを定義する必要があります。 + +入力パラメータの検証: + +```python +from dify_plugin.entities.agent import AgentInvokeMessage +from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity +from pydantic import BaseModel + +class BasicParams(BaseModel): + maximum_iterations: int + model: AgentModelConfig + tools: list[ToolEntity] + query: str + +``` + +パラメータを取得した後、具体的なビジネスロジックを実行します。 + +```python +class BasicAgentAgentStrategy(AgentStrategy): + def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]: + params = BasicParams(**parameters) +``` + +### 3. モデルの呼び出し + +Agent 戦略プラグインにおいて、**モデルの呼び出し**はコア実行ロジックの1つです。SDK が提供する `session.model.llm.invoke()` メソッドを使用して、LLM モデルを効率的に呼び出し、テキスト生成、対話処理などの機能を実現できます。 + +モデルに**ツール呼び出し**機能を持たせたい場合は、まずモデルがツール呼び出し形式に準拠した入力パラメータを出力できることを確認する必要があります。つまり、モデルはユーザーの指示に基づいて、ツールインターフェースの要件に適合するパラメータを生成する必要があります。 + +以下のパラメータを構築します。 + +* model:モデル情報 +* prompt_messages:プロンプトメッセージ +* tools:ツール情報(Function Calling 関連) +* stop:停止トークン +* stream:ストリーミング出力をサポートするかどうか + +メソッド定義のコード例: + +```python +def invoke( + self, + model_config: LLMModelConfig, + prompt_messages: list[PromptMessage], + tools: list[PromptMessageTool] | None = None, + stop: list[str] | None = None, + stream: bool = True, + ) -> Generator[LLMResultChunk, None, None] | LLMResult:... +``` + +完全な機能実装を確認するには、モデル呼び出しの[コード例](agent-strategy-plugin.md#diao-yong-gong-ju-1)を参照してください。 + +このコードは以下の機能を実現します。ユーザーが指示を入力すると、Agent 戦略プラグインは自動的に LLM を呼び出し、生成結果に基づいてツール呼び出しに必要なパラメータを構築・送信し、モデルが接続済みのツールを柔軟にスケジュールして、複雑なタスクを効率的に完了できるようにします。 + +![ツールリクエストパラメータの生成](https://assets-docs.dify.ai/2025/01/01e32c2d77150213c7c929b3cceb4dae.png) + +### 4. ツールの呼び出し + +ツールパラメータを入力した後、Agent 戦略プラグインに実際にツールを呼び出す能力を与える必要があります。SDK の `session.tool.invoke()` 関数を使用してツールを呼び出すことができます。 + +以下のパラメータを構築します。 + +* provider:ツールプロバイダー +* tool_name:ツール名 +* parameters:入力パラメータ + +メソッド定義のコード例: + +```python + def invoke( + self, + provider_type: ToolProviderType, + provider: str, + tool_name: str, + parameters: dict[str, Any], + ) -> Generator[ToolInvokeMessage, None, None]:... +``` + +LLM を介して直接パラメータを生成し、ツール呼び出しを完了したい場合は、以下のツール呼び出しのコード例を参照してください。 + +```python +tool_instances = ( + {tool.identity.name: tool for tool in params.tools} if params.tools else {} +) +for tool_call_id, tool_call_name, tool_call_args in tool_calls: + tool_instance = tool_instances[tool_call_name] + self.session.tool.invoke( + provider_type=ToolProviderType.BUILT_IN, + provider=tool_instance.identity.provider, + tool_name=tool_instance.identity.name, + parameters={**tool_instance.runtime_parameters, **tool_call_args}, + ) +``` + +完全な機能コードを確認するには、ツール呼び出しの[コード例](agent-strategy-plugin.md#diao-yong-gong-ju-1)をお読みください。 + +この部分の機能コードを実装すると、Agent 戦略プラグインは自動 Function Calling 機能を持つようになります。例えば、現在の時刻を自動的に取得するなどです。 + +![ツール呼び出し](https://assets-docs.dify.ai/2025/01/80e5de8acc2b0ed00524e490fd611ff5.png) + +### 5. ログ作成 + +**Agent 戦略プラグイン**では、複雑なタスクを完了するために通常、複数回の操作を実行する必要があります。各操作の実行結果を記録することは、開発者にとって非常に重要であり、Agent の実行プロセスを追跡し、各ステップの決定根拠を分析し、戦略効果をより良く評価・最適化するのに役立ちます。 + +この機能を実現するために、SDK の `create_log_message` および `finish_log_message` メソッドを使用してログを記録できます。この方法では、モデル呼び出しの前後で操作状態をリアルタイムに記録できるだけでなく、開発者が問題を迅速に特定するのにも役立ちます。 + +シナリオ例: + +* モデル呼び出しの前に、「モデル呼び出し開始」というログを記録し、開発者がタスクの実行進捗を明確に把握できるようにします。 +* モデル呼び出しが成功した後、「呼び出し成功」というログを記録し、モデル応答の完全性を追跡しやすくします。 + +```python +model_log = self.create_log_message( + label=f"{params.model.model} Thought", + data={}, + metadata={"start_at": model_started_at, "provider": params.model.provider}, + status=ToolInvokeMessage.LogMessage.LogStatus.START, + ) +yield model_log +self.session.model.llm.invoke(...) +yield self.finish_log_message( + log=model_log, + data={ + "output": response, + "tool_name": tool_call_names, + "tool_input": tool_call_inputs, + }, + metadata={ + "started_at": model_started_at, + "finished_at": time.perf_counter(), + "elapsed_time": time.perf_counter() - model_started_at, + "provider": params.model.provider, + }, +) +``` + +設定完了後、ワークフローログに実行結果が出力されます。 + +![Agent 実行結果の出力](https://assets-docs.dify.ai/2025/01/96516388a4fb1da9cea85fc1804ff377.png) + +Agent の実行過程では、複数ラウンドのログが生成される可能性があります。ログが階層構造を持つと、開発者にとって確認しやすくなります。ログ記録時に parent パラメータを渡すことで、異なるラウンドのログが親子関係を形成し、ログ表示がより明確で追跡しやすくなります。 + +**参照方法:** + +```python +function_call_round_log = self.create_log_message( + label="Function Call Round1 ", + data={}, + metadata={}, +) +yield function_call_round_log + +model_log = self.create_log_message( + label=f"{params.model.model} Thought", + data={}, + metadata={"start_at": model_started_at, "provider": params.model.provider}, + status=ToolInvokeMessage.LogMessage.LogStatus.START, + # add parent log + parent=function_call_round_log, +) +yield model_log +``` + +#### プラグイン機能のコード例 + + +#### モデルの呼び出し + +以下のコードは、Agent 戦略プラグインにモデル呼び出し機能を与える方法を示します。 + +```python +import json +from collections.abc import Generator +from typing import Any, cast + +from dify_plugin.entities.agent import AgentInvokeMessage +from dify_plugin.entities.model.llm import LLMModelConfig, LLMResult, LLMResultChunk +from dify_plugin.entities.model.message import ( + PromptMessageTool, + UserPromptMessage, +) +from dify_plugin.entities.tool import ToolInvokeMessage, ToolParameter, ToolProviderType +from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity +from pydantic import BaseModel + +class BasicParams(BaseModel): + maximum_iterations: int + model: AgentModelConfig + tools: list[ToolEntity] + query: str + +class BasicAgentAgentStrategy(AgentStrategy): + def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]: + params = BasicParams(**parameters) + chunks: Generator[LLMResultChunk, None, None] | LLMResult = ( + self.session.model.llm.invoke( + model_config=LLMModelConfig(**params.model.model_dump(mode="json")), + prompt_messages=[UserPromptMessage(content=params.query)], + tools=[ + self._convert_tool_to_prompt_message_tool(tool) + for tool in params.tools + ], + stop=params.model.completion_params.get("stop", []) + if params.model.completion_params + else [], + stream=True, + ) + ) + response = "" + tool_calls = [] + tool_instances = ( + {tool.identity.name: tool for tool in params.tools} if params.tools else {} + ) + + for chunk in chunks: + # check if there is any tool call + if self.check_tool_calls(chunk): + tool_calls = self.extract_tool_calls(chunk) + tool_call_names = ";".join([tool_call[1] for tool_call in tool_calls]) + try: + tool_call_inputs = json.dumps( + {tool_call[1]: tool_call[2] for tool_call in tool_calls}, + ensure_ascii=False, + ) + except json.JSONDecodeError: + # ensure ascii to avoid encoding error + tool_call_inputs = json.dumps( + {tool_call[1]: tool_call[2] for tool_call in tool_calls} + ) + print(tool_call_names, tool_call_inputs) + if chunk.delta.message and chunk.delta.message.content: + if isinstance(chunk.delta.message.content, list): + for content in chunk.delta.message.content: + response += content.data + print(content.data, end="", flush=True) + else: + response += str(chunk.delta.message.content) + print(str(chunk.delta.message.content), end="", flush=True) + + if chunk.delta.usage: + # usage of the model + usage = chunk.delta.usage + + yield self.create_text_message( + text=f"{response or json.dumps(tool_calls, ensure_ascii=False)}\n" + ) + result = "" + for tool_call_id, tool_call_name, tool_call_args in tool_calls: + tool_instance = tool_instances[tool_call_name] + tool_invoke_responses = self.session.tool.invoke( + provider_type=ToolProviderType.BUILT_IN, + provider=tool_instance.identity.provider, + tool_name=tool_instance.identity.name, + parameters={**tool_instance.runtime_parameters, **tool_call_args}, + ) + if not tool_instance: + tool_invoke_responses = { + "tool_call_id": tool_call_id, + "tool_call_name": tool_call_name, + "tool_response": f"there is not a tool named {tool_call_name}", + } + else: + # invoke tool + tool_invoke_responses = self.session.tool.invoke( + provider_type=ToolProviderType.BUILT_IN, + provider=tool_instance.identity.provider, + tool_name=tool_instance.identity.name, + parameters={**tool_instance.runtime_parameters, **tool_call_args}, + ) + result = "" + for tool_invoke_response in tool_invoke_responses: + if tool_invoke_response.type == ToolInvokeMessage.MessageType.TEXT: + result += cast( + ToolInvokeMessage.TextMessage, tool_invoke_response.message + ).text + elif ( + tool_invoke_response.type == ToolInvokeMessage.MessageType.LINK + ): + result += ( + f"result link: {cast(ToolInvokeMessage.TextMessage, tool_invoke_response.message).text}." + + " please tell user to check it." + ) + elif tool_invoke_response.type in { + ToolInvokeMessage.MessageType.IMAGE_LINK, + ToolInvokeMessage.MessageType.IMAGE, + }: + result += ( + "image has been created and sent to user already, " + + "you do not need to create it, just tell the user to check it now." + ) + elif ( + tool_invoke_response.type == ToolInvokeMessage.MessageType.JSON + ): + text = json.dumps( + cast( + ToolInvokeMessage.JsonMessage, + tool_invoke_response.message, + ).json_object, + ensure_ascii=False, + ) + result += f"tool response: {text}." + else: + result += f"tool response: {tool_invoke_response.message!r}." + + tool_response = { + "tool_call_id": tool_call_id, + "tool_call_name": tool_call_name, + "tool_response": result, + } + yield self.create_text_message(result) + + def _convert_tool_to_prompt_message_tool( + self, tool: ToolEntity + ) -> PromptMessageTool: + """ + convert tool to prompt message tool + """ + message_tool = PromptMessageTool( + name=tool.identity.name, + description=tool.description.llm if tool.description else "", + parameters={ + "type": "object", + "properties": {}, + "required": [], + }, + ) + + parameters = tool.parameters + for parameter in parameters: + if parameter.form != ToolParameter.ToolParameterForm.LLM: + continue + + parameter_type = parameter.type + if parameter.type in { + ToolParameter.ToolParameterType.FILE, + ToolParameter.ToolParameterType.FILES, + }: + continue + enum = [] + if parameter.type == ToolParameter.ToolParameterType.SELECT: + enum = ( + [option.value for option in parameter.options] + if parameter.options + else [] + ) + + message_tool.parameters["properties"][parameter.name] = { + "type": parameter_type, + "description": parameter.llm_description or "", + } + + if len(enum) > 0: + message_tool.parameters["properties"][parameter.name]["enum"] = enum + + if parameter.required: + message_tool.parameters["required"].append(parameter.name) + + return message_tool + + def check_tool_calls(self, llm_result_chunk: LLMResultChunk) -> bool: + """ + Check if there is any tool call in llm result chunk + """ + return bool(llm_result_chunk.delta.message.tool_calls) + + def extract_tool_calls( + self, llm_result_chunk: LLMResultChunk + ) -> list[tuple[str, str, dict[str, Any]]]: + """ + Extract tool calls from llm result chunk + + Returns: + List[Tuple[str, str, Dict[str, Any]]]: [(tool_call_id, tool_call_name, tool_call_args)] + """ + tool_calls = [] + for prompt_message in llm_result_chunk.delta.message.tool_calls: + args = {} + if prompt_message.function.arguments != "": + args = json.loads(prompt_message.function.arguments) + + tool_calls.append( + ( + prompt_message.id, + prompt_message.function.name, + args, + ) + ) + + return tool_calls +``` + + +#### ツールの呼び出し + +以下のコードは、Agent 戦略プラグインにモデル呼び出しを実装し、ツールに正規化されたリクエストを送信する方法を示します。 + +```python +import json +from collections.abc import Generator +from typing import Any, cast + +from dify_plugin.entities.agent import AgentInvokeMessage +from dify_plugin.entities.model.llm import LLMModelConfig, LLMResult, LLMResultChunk +from dify_plugin.entities.model.message import ( + PromptMessageTool, + UserPromptMessage, +) +from dify_plugin.entities.tool import ToolInvokeMessage, ToolParameter, ToolProviderType +from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity +from pydantic import BaseModel + +class BasicParams(BaseModel): + maximum_iterations: int + model: AgentModelConfig + tools: list[ToolEntity] + query: str + +class BasicAgentAgentStrategy(AgentStrategy): + def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]: + params = BasicParams(**parameters) + chunks: Generator[LLMResultChunk, None, None] | LLMResult = ( + self.session.model.llm.invoke( + model_config=LLMModelConfig(**params.model.model_dump(mode="json")), + prompt_messages=[UserPromptMessage(content=params.query)], + tools=[ + self._convert_tool_to_prompt_message_tool(tool) + for tool in params.tools + ], + stop=params.model.completion_params.get("stop", []) + if params.model.completion_params + else [], + stream=True, + ) + ) + response = "" + tool_calls = [] + tool_instances = ( + {tool.identity.name: tool for tool in params.tools} if params.tools else {} + ) + + for chunk in chunks: + # check if there is any tool call + if self.check_tool_calls(chunk): + tool_calls = self.extract_tool_calls(chunk) + tool_call_names = ";".join([tool_call[1] for tool_call in tool_calls]) + try: + tool_call_inputs = json.dumps( + {tool_call[1]: tool_call[2] for tool_call in tool_calls}, + ensure_ascii=False, + ) + except json.JSONDecodeError: + # ensure ascii to avoid encoding error + tool_call_inputs = json.dumps( + {tool_call[1]: tool_call[2] for tool_call in tool_calls} + ) + print(tool_call_names, tool_call_inputs) + if chunk.delta.message and chunk.delta.message.content: + if isinstance(chunk.delta.message.content, list): + for content in chunk.delta.message.content: + response += content.data + print(content.data, end="", flush=True) + else: + response += str(chunk.delta.message.content) + print(str(chunk.delta.message.content), end="", flush=True) + + if chunk.delta.usage: + # usage of the model + usage = chunk.delta.usage + + yield self.create_text_message( + text=f"{response or json.dumps(tool_calls, ensure_ascii=False)}\n" + ) + result = "" + for tool_call_id, tool_call_name, tool_call_args in tool_calls: + tool_instance = tool_instances[tool_call_name] + tool_invoke_responses = self.session.tool.invoke( + provider_type=ToolProviderType.BUILT_IN, + provider=tool_instance.identity.provider, + tool_name=tool_instance.identity.name, + parameters={**tool_instance.runtime_parameters, **tool_call_args}, + ) + if not tool_instance: + tool_invoke_responses = { + "tool_call_id": tool_call_id, + "tool_call_name": tool_call_name, + "tool_response": f"there is not a tool named {tool_call_name}", + } + else: + # invoke tool + tool_invoke_responses = self.session.tool.invoke( + provider_type=ToolProviderType.BUILT_IN, + provider=tool_instance.identity.provider, + tool_name=tool_instance.identity.name, + parameters={**tool_instance.runtime_parameters, **tool_call_args}, + ) + result = "" + for tool_invoke_response in tool_invoke_responses: + if tool_invoke_response.type == ToolInvokeMessage.MessageType.TEXT: + result += cast( + ToolInvokeMessage.TextMessage, tool_invoke_response.message + ).text + elif ( + tool_invoke_response.type == ToolInvokeMessage.MessageType.LINK + ): + result += ( + f"result link: {cast(ToolInvokeMessage.TextMessage, tool_invoke_response.message).text}." + + " please tell user to check it." + ) + elif tool_invoke_response.type in { + ToolInvokeMessage.MessageType.IMAGE_LINK, + ToolInvokeMessage.MessageType.IMAGE, + }: + result += ( + "image has been created and sent to user already, " + + "you do not need to create it, just tell the user to check it now." + ) + elif ( + tool_invoke_response.type == ToolInvokeMessage.MessageType.JSON + ): + text = json.dumps( + cast( + ToolInvokeMessage.JsonMessage, + tool_invoke_response.message, + ).json_object, + ensure_ascii=False, + ) + result += f"tool response: {text}." + else: + result += f"tool response: {tool_invoke_response.message!r}." + + tool_response = { + "tool_call_id": tool_call_id, + "tool_call_name": tool_call_name, + "tool_response": result, + } + yield self.create_text_message(result) + + def _convert_tool_to_prompt_message_tool( + self, tool: ToolEntity + ) -> PromptMessageTool: + """ + convert tool to prompt message tool + """ + message_tool = PromptMessageTool( + name=tool.identity.name, + description=tool.description.llm if tool.description else "", + parameters={ + "type": "object", + "properties": {}, + "required": [], + }, + ) + + parameters = tool.parameters + for parameter in parameters: + if parameter.form != ToolParameter.ToolParameterForm.LLM: + continue + + parameter_type = parameter.type + if parameter.type in { + ToolParameter.ToolParameterType.FILE, + ToolParameter.ToolParameterType.FILES, + }: + continue + enum = [] + if parameter.type == ToolParameter.ToolParameterType.SELECT: + enum = ( + [option.value for option in parameter.options] + if parameter.options + else [] + ) + + message_tool.parameters["properties"][parameter.name] = { + "type": parameter_type, + "description": parameter.llm_description or "", + } + + if len(enum) > 0: + message_tool.parameters["properties"][parameter.name]["enum"] = enum + + if parameter.required: + message_tool.parameters["required"].append(parameter.name) + + return message_tool + + def check_tool_calls(self, llm_result_chunk: LLMResultChunk) -> bool: + """ + Check if there is any tool call in llm result chunk + """ + return bool(llm_result_chunk.delta.message.tool_calls) + + def extract_tool_calls( + self, llm_result_chunk: LLMResultChunk + ) -> list[tuple[str, str, dict[str, Any]]]: + """ + Extract tool calls from llm result chunk + + Returns: + List[Tuple[str, str, Dict[str, Any]]]: [(tool_call_id, tool_call_name, tool_call_args)] + """ + tool_calls = [] + for prompt_message in llm_result_chunk.delta.message.tool_calls: + args = {} + if prompt_message.function.arguments != "": + args = json.loads(prompt_message.function.arguments) + + tool_calls.append( + ( + prompt_message.id, + prompt_message.function.name, + args, + ) + ) + + return tool_calls +``` + + +#### 完全な機能コード例 + +**モデル呼び出し、ツール呼び出し**、および**複数ラウンドログ出力機能**を含む完全なプラグインコード例: + +```python +import json +import time +from collections.abc import Generator +from typing import Any, cast + +from dify_plugin.entities.agent import AgentInvokeMessage +from dify_plugin.entities.model.llm import LLMModelConfig, LLMResult, LLMResultChunk +from dify_plugin.entities.model.message import ( + PromptMessageTool, + UserPromptMessage, +) +from dify_plugin.entities.tool import ToolInvokeMessage, ToolParameter, ToolProviderType +from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity +from pydantic import BaseModel + +class BasicParams(BaseModel): + maximum_iterations: int + model: AgentModelConfig + tools: list[ToolEntity] + query: str + +class BasicAgentAgentStrategy(AgentStrategy): + def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]: + params = BasicParams(**parameters) + function_call_round_log = self.create_log_message( + label="Function Call Round1 ", + data={}, + metadata={}, + ) + yield function_call_round_log + model_started_at = time.perf_counter() + model_log = self.create_log_message( + label=f"{params.model.model} Thought", + data={}, + metadata={"start_at": model_started_at, "provider": params.model.provider}, + status=ToolInvokeMessage.LogMessage.LogStatus.START, + parent=function_call_round_log, + ) + yield model_log + chunks: Generator[LLMResultChunk, None, None] | LLMResult = ( + self.session.model.llm.invoke( + model_config=LLMModelConfig(**params.model.model_dump(mode="json")), + prompt_messages=[UserPromptMessage(content=params.query)], + tools=[ + self._convert_tool_to_prompt_message_tool(tool) + for tool in params.tools + ], + stop=params.model.completion_params.get("stop", []) + if params.model.completion_params + else [], + stream=True, + ) + ) + response = "" + tool_calls = [] + tool_instances = ( + {tool.identity.name: tool for tool in params.tools} if params.tools else {} + ) + tool_call_names = "" + tool_call_inputs = "" + for chunk in chunks: + # check if there is any tool call + if self.check_tool_calls(chunk): + tool_calls = self.extract_tool_calls(chunk) + tool_call_names = ";".join([tool_call[1] for tool_call in tool_calls]) + try: + tool_call_inputs = json.dumps( + {tool_call[1]: tool_call[2] for tool_call in tool_calls}, + ensure_ascii=False, + ) + except json.JSONDecodeError: + # ensure ascii to avoid encoding error + tool_call_inputs = json.dumps( + {tool_call[1]: tool_call[2] for tool_call in tool_calls} + ) + print(tool_call_names, tool_call_inputs) + if chunk.delta.message and chunk.delta.message.content: + if isinstance(chunk.delta.message.content, list): + for content in chunk.delta.message.content: + response += content.data + print(content.data, end="", flush=True) + else: + response += str(chunk.delta.message.content) + print(str(chunk.delta.message.content), end="", flush=True) + + if chunk.delta.usage: + # usage of the model + usage = chunk.delta.usage + + yield self.finish_log_message( + log=model_log, + data={ + "output": response, + "tool_name": tool_call_names, + "tool_input": tool_call_inputs, + }, + metadata={ + "started_at": model_started_at, + "finished_at": time.perf_counter(), + "elapsed_time": time.perf_counter() - model_started_at, + "provider": params.model.provider, + }, + ) + yield self.create_text_message( + text=f"{response or json.dumps(tool_calls, ensure_ascii=False)}\n" + ) + result = "" + for tool_call_id, tool_call_name, tool_call_args in tool_calls: + tool_instance = tool_instances[tool_call_name] + tool_invoke_responses = self.session.tool.invoke( + provider_type=ToolProviderType.BUILT_IN, + provider=tool_instance.identity.provider, + tool_name=tool_instance.identity.name, + parameters={**tool_instance.runtime_parameters, **tool_call_args}, + ) + if not tool_instance: + tool_invoke_responses = { + "tool_call_id": tool_call_id, + "tool_call_name": tool_call_name, + "tool_response": f"there is not a tool named {tool_call_name}", + } + else: + # invoke tool + tool_invoke_responses = self.session.tool.invoke( + provider_type=ToolProviderType.BUILT_IN, + provider=tool_instance.identity.provider, + tool_name=tool_instance.identity.name, + parameters={**tool_instance.runtime_parameters, **tool_call_args}, + ) + result = "" + for tool_invoke_response in tool_invoke_responses: + if tool_invoke_response.type == ToolInvokeMessage.MessageType.TEXT: + result += cast( + ToolInvokeMessage.TextMessage, tool_invoke_response.message + ).text + elif ( + tool_invoke_response.type == ToolInvokeMessage.MessageType.LINK + ): + result += ( + f"result link: {cast(ToolInvokeMessage.TextMessage, tool_invoke_response.message).text}." + + " please tell user to check it." + ) + elif tool_invoke_response.type in { + ToolInvokeMessage.MessageType.IMAGE_LINK, + ToolInvokeMessage.MessageType.IMAGE, + }: + result += ( + "image has been created and sent to user already, " + + "you do not need to create it, just tell the user to check it now." + ) + elif ( + tool_invoke_response.type == ToolInvokeMessage.MessageType.JSON + ): + text = json.dumps( + cast( + ToolInvokeMessage.JsonMessage, + tool_invoke_response.message, + ).json_object, + ensure_ascii=False, + ) + result += f"tool response: {text}." + else: + result += f"tool response: {tool_invoke_response.message!r}." + + tool_response = { + "tool_call_id": tool_call_id, + "tool_call_name": tool_call_name, + "tool_response": result, + } + yield self.create_text_message(result) + + def _convert_tool_to_prompt_message_tool( + self, tool: ToolEntity + ) -> PromptMessageTool: + """ + convert tool to prompt message tool + """ + message_tool = PromptMessageTool( + name=tool.identity.name, + description=tool.description.llm if tool.description else "", + parameters={ + "type": "object", + "properties": {}, + "required": [], + }, + ) + + parameters = tool.parameters + for parameter in parameters: + if parameter.form != ToolParameter.ToolParameterForm.LLM: + continue + + parameter_type = parameter.type + if parameter.type in { + ToolParameter.ToolParameterType.FILE, + ToolParameter.ToolParameterType.FILES, + }: + continue + enum = [] + if parameter.type == ToolParameter.ToolParameterType.SELECT: + enum = ( + [option.value for option in parameter.options] + if parameter.options + else [] + ) + + message_tool.parameters["properties"][parameter.name] = { + "type": parameter_type, + "description": parameter.llm_description or "", + } + + if len(enum) > 0: + message_tool.parameters["properties"][parameter.name]["enum"] = enum + + if parameter.required: + message_tool.parameters["required"].append(parameter.name) + + return message_tool + + def check_tool_calls(self, llm_result_chunk: LLMResultChunk) -> bool: + """ + Check if there is any tool call in llm result chunk + """ + return bool(llm_result_chunk.delta.message.tool_calls) + + def extract_tool_calls( + self, llm_result_chunk: LLMResultChunk + ) -> list[tuple[str, str, dict[str, Any]]]: + """ + Extract tool calls from llm result chunk + + Returns: + List[Tuple[str, str, Dict[str, Any]]]: [(tool_call_id, tool_call_name, tool_call_args)] + """ + tool_calls = [] + for prompt_message in llm_result_chunk.delta.message.tool_calls: + args = {} + if prompt_message.function.arguments != "": + args = json.loads(prompt_message.function.arguments) + + tool_calls.append( + ( + prompt_message.id, + prompt_message.function.name, + args, + ) + ) + + return tool_calls +``` + + + +### 3. プラグインのデバッグ + +プラグインの宣言ファイルと機能コードを設定した後、プラグインのディレクトリ内で `python -m main` コマンドを実行してプラグインを再起動します。次に、プラグインが正常に動作するかをテストする必要があります。Dify はリモートデバッグ方法を提供しており、[「プラグイン管理」](https://console-plugin.dify.dev/plugins)にアクセスしてデバッグキーとリモートサーバーアドレスを取得します。 + +![リモートデバッグプラグイン](https://assets-docs.dify.ai/2024/12/053415ef127f1f4d6dd85dd3ae79626a.png) + +プラグインプロジェクトに戻り、`.env.example` ファイルをコピーして `.env` に名前変更し、取得したリモートサーバーアドレスとデバッグキーなどの情報を `REMOTE_INSTALL_HOST` および `REMOTE_INSTALL_KEY` パラメータに入力します。 + +```bash +INSTALL_METHOD=remote +REMOTE_INSTALL_HOST=remote +REMOTE_INSTALL_PORT=5003 +REMOTE_INSTALL_KEY=****-****-****-****-**** +``` + +`python -m main` コマンドを実行してプラグインを起動します。プラグインページで、このプラグインが Workspace にインストールされていることを確認できます。他のチームメンバーもこのプラグインにアクセスできます。 + +![プラグインへのアクセス](https://assets-docs.dify.ai/2025/01/c82ec0202e5bf914b36e06c796398dd6.png) + +### プラグインのパッケージ化(オプション) + +プラグインが正常に動作することを確認した後、以下のコマンドラインツールを使用してプラグインをパッケージ化し、名前を付けることができます。実行後、現在のフォルダに `google.difypkg` ファイルが見つかります。これが最終的なプラグインパッケージです。 + +```bash +# ./basic_agent をプラグインプロジェクトの実際のパスに置き換えてください +dify plugin package ./basic_agent/ +``` + +おめでとうございます!これでツールタイププラグインの完全な開発、デバッグ、パッケージ化プロセスが完了しました。 + +### プラグインの公開(オプション) + +これで、[Dify Plugins コードリポジトリ](https://github.com/langgenius/dify-plugins)にアップロードしてプラグインを公開できます。アップロードする前に、プラグインが[プラグイン公開規範](https://docs.dify.ai/ja/plugins/publish-plugins/publish-to-dify-marketplace)に従っていることを確認してください。審査に合格すると、コードはメインブランチにマージされ、自動的に [Dify Marketplace](https://marketplace.dify.ai/) に公開されます。 + +### さらに探求する + +複雑なタスクは、多くの場合、複数回の思考と複数回のツール呼び出しを必要とします。よりインテリジェントなタスク処理を実現するために、通常、**モデル呼び出し → ツール呼び出し**というループ実行戦略が採用され、タスクが完了するか、設定された最大反復回数に達するまで続けられます。 + +このプロセスでは、プロンプト管理が特に重要になります。モデル入力を効率的に整理し、動的に調整するために、プラグイン内の Function Calling 機能の[完全な実装コード](https://github.com/langgenius/dify-official-plugins/blob/main/agent-strategies/cot_agent/strategies/function_calling.py)を参照し、標準化された方法でモデルに外部ツールを呼び出させ、返された結果を処理する方法を理解することをお勧めします。 + +## 関連リソース + +- [Agent プラグイン開発の基礎](/plugin_dev_ja/9232-agent.ja) - Agent 戦略プラグインの基本概念を理解する +- [プラグイン開発の基本概念](/plugin_dev_ja/0111-getting-started-dify-plugin.ja) - プラグイン開発の全体的なアーキテクチャを理解する +- [開発ツールの初期化](/plugin_dev_ja/0221-initialize-development-tools.ja) - 開発環境の構築方法を学ぶ +- [モデルの逆呼び出し](/plugin_dev_ja/9242-reverse-invocation-model.ja) - プラットフォーム内のモデル機能を呼び出す方法を理解する +- [ツールの逆呼び出し](/plugin_dev_ja/9242-reverse-invocation-tool.ja) - 他のプラグインを呼び出す方法を理解する +- [プラグイン公開の概要](/plugin_dev_ja/0321-release-overview.ja) - プラグイン公開プロセスを学ぶ \ No newline at end of file