この記事は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/ ディレクト リ以下には、アイコンやスクリプト から使われるファイルが置かれます。アイコンが不要ならばこのディレクト リ自体がなくても大丈夫です。
ツールの作成方法
具体的にファイルを作成していきます。
新しく作るときには、関連しそうなツールをコピーして、その中身を変更するのが楽だと思います。
組織名(サービス名)自体の設定を書きます。
名前と作者の設定、どの種別のツールかだけです。
identity :
author : stealthinu
name : mytools
label :
en_US : My Tools
description :
en_US : My Tools
icon : icon.svg
tags :
- utilities
ほぼ決め打ちで、class名をファイル名と同じ名前でつけます。
なので名前は他と被らないように注意します。
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
class MyToolsProvider (BuiltinToolProviderController):
def _validate_credentials (self, credentials: dict ) -> None :
pass
ツールの名前や作者、説明、パラメータの説明を書きます。
パラメータの説明には、どんなパラメータが必要で、それが何か、データの型や必須条件などを記述します。
ただしここに設定した内容は、あくまで説明のためのもので、プログラム的には後述の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
ここに実際の処理を書きます。絶対に必要なものは以下の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 :
binary_content = content.encode('utf-8' )
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-filewriter
組み込みツールがファイルを出力する場所
ツールがファイルを出力する場所は、difyをdockerで動かしている場合、以下のdocker用のディレクト リの中に、さらにUIDでディレクト リが作られて、その中に出力されます。
dify/docker/volumes/app/storage/tools/
例えば以下のようなファイル名でテキストファイルが作成されます。
dify/docker/volumes/app/storage/tools/de4a84eb-ac6e-45d3-a8b6-49f776dfc900/cf4ff00b019a4f9c81cce53cc0137aa3.txt