この記事はDifyアドベントカレンダー2024用に書いた記事です。
ローカルのdifyに独自の組み込みツールを作成する
difyには組み込みツールという各種SaaSなどを呼び出すようなツールが準備されています。
difyは元々SaaSでのサービスをメインに作られている(と思われる)ため、例えば基本的にファイルへの出力がサポートされていないとか、(まだ)動画や音声を扱うようなものがあまりなかったりします。
しかしローカルで動かしてているdifyでは、生成されたテキストをファイルに書き出したいとか、動画をffmpeg的なもので編集したいとかが結構あります。
そこで、ファイルへのテキスト書き込みだけをする非常に簡単な組み込みツールを作ることを題材に、ローカルのdifyに独自の組み込みツールを作成して運用するための手順を説明します。
公式では下記ページにビルトインツールの作り方について説明があります。この記事と合わせて参照してみてください。
クイック統合ツール | Dify https://docs.dify.ai/ja-jp/guides/tools/quick-tool-integration
組み込みツールの場所と構成
組み込みツールのおいてある場所は、ソース内の以下のディレクトリにあります。
dify/api/core/tools/provider/builtin/
この中に、組織名(サービス名)でディレクトリを作ります。
今回は公開するものではなく自分用のツールを作るという体で「mytools」というディレクトリにしました。つまりこの場合
dify/api/core/tools/provider/builtin/mytools/
というフォルダに自前のツールを作っていくことになります。
この組織名(サービス名)は、ディレクトリ名と同一になるので、当然ユニークである必要があります。
difyは頻繁にアップデートされていきますが、多くの場合gitでpullしてアップデートしていると思います。
その際にgitでのコンフリクトが起きないように、組み込みツールのフォルダのみを追加すれば動くように設計されています。
.gitignoreの設定
それでもgitで変更が出てくるのが気持ち悪いと思うので、builtinフォルダの中にgitignoreを設定しておきます。
dify/api/core/tools/provider/builtin/.gitignore
.gitignore mytools
組み込みツールの中身
このディレクトリ内には
- _assets/
- tools/
- mytools.yaml
- mytools.py
を準備します。
ここでフォルダ名と同じ名前のついている「mytools.yaml」や「mytools.py」は、全体についての設定と基本のクラス設定のみです。
tools/ ディレクトリ内に、具体的に機能ごとのスクリプトとその設定のYAMLファイルを作っていきます。
このとき、このツールごとのファイル名は他のツールのファイル名ともユニークである必要がある、ように思います。たぶん…
_assets/ ディレクトリ以下には、アイコンやスクリプトから使われるファイルが置かれます。アイコンが不要ならばこのディレクトリ自体がなくても大丈夫です。
ツールの作成方法
具体的にファイルを作成していきます。
新しく作るときには、関連しそうなツールをコピーして、その中身を変更するのが楽だと思います。
mytools.yaml
組織名(サービス名)自体の設定を書きます。
名前と作者の設定、どの種別のツールかだけです。
identity: author: stealthinu name: mytools label: en_US: My Tools description: en_US: My Tools icon: icon.svg tags: - utilities
mytools.py
ほぼ決め打ちで、class名をファイル名と同じ名前でつけます。
なので名前は他と被らないように注意します。
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController class MyToolsProvider(BuiltinToolProviderController): def _validate_credentials(self, credentials: dict) -> None: pass
tools/file_writer.yaml
ツールの名前や作者、説明、パラメータの説明を書きます。
パラメータの説明には、どんなパラメータが必要で、それが何か、データの型や必須条件などを記述します。
ただしここに設定した内容は、あくまで説明のためのもので、プログラム的には後述のget_runtime_parametersの中で指定するようになっています。
identity: name: file_writer author: stealthinu label: en_US: File Writer description: human: en_US: Write content to a file. llm: Write content to a file. parameters: - name: content type: string required: true label: en_US: Content ja_JP: 内容 human_description: en_US: The content to write to the file. ja_JP: ファイルに書き込む内容。 llm_description: The content to write to the file. form: llm
tools/file_writer.py
ここに実際の処理を書きます。絶対に必要なものは以下の2関数です。
- get_runtime_parameters : 取得するパラメータとその型や説明を設定します。
- _invoke : ノードで処理する内容。実際にやりたい処理を書きます。
パラメータの渡し方だけ注意すればあとは普通にpythonを書けばよいです。
ただし、ファイルの出力についてはdify側にデータを渡して処理してもらうような書き方が正しいようで、そのあたりが注意が必要です。
ここではcreate_blob_messageを使って、組み込みツール用のディレクトリにテキストファイルを出力するようにしています。
tools/file_writer.py
from typing import Any from core.tools.entities.common_entities import I18nObject from core.tools.entities.tool_entities import ToolInvokeMessage, ToolParameter from core.tools.tool.builtin_tool import BuiltinTool class FileWriterTool(BuiltinTool): def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> list[ToolInvokeMessage]: content = tool_parameters.get("content") if not content: return [self.create_text_message("No content provided")] try: # コンテンツをUTF-8でエンコード binary_content = content.encode('utf-8') # Difyシステムにファイルを返す return [ self.create_text_message("Successfully prepared content"), self.create_blob_message( blob=binary_content, meta={"mime_type": "text/plain"}, save_as=self.VariableKey.CUSTOM.value, ), ] except Exception as e: return [self.create_text_message(f"Failed to process file: {str(e)}")] def get_runtime_parameters(self) -> list[ToolParameter]: parameters = [] # コンテンツパラメータ parameters.append( ToolParameter( name="content", label=I18nObject( en_US="Content", ja_JP="内容" ), human_description=I18nObject( en_US="The content to write to the file.", ja_JP="ファイルに書き込む内容。" ), type=ToolParameter.ToolParameterType.STRING, form=ToolParameter.ToolParameterForm.LLM, required=True ) ) return parameters
dockerの運用方法
difyの運用はdockerで行われていることが多いのではないかと思います。
組み込みツールを入れて docker compose up しても、組み込みツールが入った状況で dify が動くことはありません。これは docker-compose で動いている dify-api が、dokcer hubから落としてきたdockerイメージを利用するためです。
そこで、ローカルで dify-api のdockerイメージを作成して、docker-compose のオーバーライド設定で、その docker-image を使うように設定してやります。
dify-apiは下記のようにしてローカルに「dify-api」というdockerイメージで作成します。
$ cd dify/api $ docker build . -t dify-api
そのままdocker-composeを使うと、ローカルのdify-api dockerイメージが利用されないため、以下のようなdocker-compose.override.yamlを作成します。
dify/docker/docker-compose.override.yaml
services: api: image: dify-api:latest worker: image: dify-api:latest
この状況で
$ docker compose up -d
すると組み込みツールが入った状況でdifyが起動します。
動作確認
difyが起動したら、「ツール」を確認して「My Tools」が存在しているか確認してください。
さらに「My Tools」を選択すると「File Writer」があること、パラメータがContent文字列が必須であることが確認できます。
スタジオでワークフローを作成し、「開始」の入力フィールドに「query」という文字列入力のパラメータを追加します。
開始 -> + -> ツール -> My Tools/File Writer を選択し、「内容」に「開始/(x)query」を選択します。
File Writer -> + -> 「終了」で、出力変数に「FIle Writer/(x)files」を選択します。

組み込みツールがファイルを出力する場所
ツールがファイルを出力する場所は、difyをdockerで動かしている場合、以下のdocker用のディレクトリの中に、さらにUIDでディレクトリが作られて、その中に出力されます。
dify/docker/volumes/app/storage/tools/
例えば以下のようなファイル名でテキストファイルが作成されます。
dify/docker/volumes/app/storage/tools/de4a84eb-ac6e-45d3-a8b6-49f776dfc900/cf4ff00b019a4f9c81cce53cc0137aa3.txt