🛎️

ローカルでDIfyを使ってAIエージェントを実装する方法

2024/10/25に公開

はじめに

AIエージェントというものを利用して、複雑なワークフローを作成することに興味があったので、勉強しました。

宗教的にWebUIを使えない私ですが、Difyは非常に簡単に使えるとのことでしたので、今回ばかりは解禁して、環境構築など含めて忘備録として記載します。
(もちろん、LangGraphなどWebUIを使わない方法も試しているので、そちらも記事にします。そのうち)

環境構築から少し複雑なAIエージェントの作り方まで、この記事内で紹介しています。
スクリーンショットなどを多用して、事前知識などなしで見れるようにしておりますので、皆様に参考になれば嬉しいです。

色々書く内容が多く、かなりの分量(18000文字くらい)になってしまいましたが、初心者の方が「環境構築」から「AIエージェントの構築」「AIエージェントの他システムからのAPI呼び出し」まで一通り試せる記事になっていると思います!

もちろん、クラウド版を払えるお金はないので、ローカル版(+LLMのAPI呼び出し)を使います。
(今回はAzureのOpenAI API gpt-4oモデルを使いますが、GoogleのGemini 1.5 Flashなど個人規模のリクエストなら、ほぼ無料で使えるようなモデルもあります。もちろんLocal LLMを利用しても無料です。)

環境構築

前提

M2 Macを利用した環境構築になります。

リポジトリをクローンする

下記コマンドでリポジトリをクローンします。

git clone https://github.com/langgenius/dify.git

Docker Desktopを導入する

Linux系ディストリビューションではないので、Linux仮想環境であるDocker Desktopを導入します。
導入方法は下記の記事をご覧ください。
https://zenn.dev/asap/articles/101379d8951b08#dockerのインストール

UbuntuなどのLinux系ディストリビューションをご利用の場合は、Docker Engineだけで十分です。
が、ここでは導入方法は記載しませんので、他の記事等でお調べください

DockerでDifyをセルフホスティングする

下記コマンドを実行するだけでセルフホスティングが可能です。
(Docker Desktopは立ち上げている状態で実行してください)

cd dify/docker
cp .env.example .env
docker compose up -d

下記のようなコンソール画面になれば、成功です

ブラウザからDifyに接続する

下記URLでブラウザからDifyに接続してください。

http://localhost/install

下記のような画面が表示されれば、成功です。

管理者アカウントを登録して、ログインする

上記の画面にて、自由なアカウント名とメールアドレス、パスワードの管理者アカウントを作成してください。
作成後、下記のような画面が表示されるので、作成したアカウントを利用してログインしてください。

ログインすると、下記のようなダッシュボードが表示されます。これがDifyのホーム画面です。

利用するモデルのAPIを登録する

今回利用するDifyはローカルでのセルフホストのため、クラウド版のように月額料金を支払う必要はありません。
代わりに、Dify内部で生成AIを利用するためには、APIで利用する必要があるためそれを登録します。

ホーム画面右上のアイコンをクリックし、下の画面の「設定」をクリックしてください。

すると、下記のような画面になりますので、左の「モデルプロバイダー」をクリックしてください。

すると、下記のように様々な生成AIプロバイダが表示され、好きなプロバイダを選択して、好きなモデルを利用することができます。

今回私は、Azure OpenAI Serviceのgpt-4oを利用するため、それを登録します。
マウスをアイコンの上にのせて「モデル追加」をクリックします。

すると、下記のようなポップアップが表示されるので、必要な項目を入力してください。
(スクロールするとさらに下にも記入箇所がありますので注意してください)

全部入力したら、「保存」ボタンがクリックできるようになります。
保存できれば、下記のように登録できるので、設定を「x」ボタンで消して、ホーム画面に戻ってください。

まずは単純なワークフローを作ってみる

ではまず、はじめに簡単なワークフローを作ってみましょう。
どんなワークフローを作るかというと、完成版を見た方が早いので下の画像をご覧ください

ここでやっていることは下記です。

  • ユーザは「今日の天気」か「今日の日付」に関して質問してくる
  • LLMがユーザの質問を確認して、「天気に関する質問」か「今日の日付に関する質問」かを判定して、枝分かれする
  • 分類後、それぞれの先には専門のLLMを置き、後段のLLMがプロンプトに応じて回答する

正直、使い道は全くないワークフローです。(想像力がなくて申し訳ございません)
ですが、上記のようなことができれば、ユーザの入力に応じて、別の振る舞いをさせることが可能になります。

また、同様に工夫すれば、ユーザからの悪意のあるプロンプト攻撃を前段のLLMが分類して、以降の処理を破棄するなどの処理が簡単に実装できます。

実際には、今回作ったワークフローを微調整して、皆様の業務などにお役立ていただければ幸いです。

作り方

ワークフロー画面へ遷移する

まずは「最初から作成」をクリックします。

続いて、ポップアップが表示されるので、下記のように「ワークフロー」を選択後、「test-01」という名前を入力後、「作成する」ボタンをクリックしてください。

すると、ワークフローを作成できる画面に遷移します。

分類器LLMを配置する

ここで必要なパーツをまずはじめに配置していきます。
まずは、ユーザからの質問を「天気の質問」か「日付の質問」を分類するLLMが必要です。

したがって、「開始」のマークにマウスカーソルを合わせると、表示される「+」ボタンをクリックし、「LLM」を選択します。

すると、下記のようにLLMを置くことができました。

「LLM」ブロックを置くと、上の写真のように、モデル選択やSYSTEMプロンプトなどを入力できるウインドウが現れました。
では、LLMの設定をしていきます。

モデルは自分でAPIを登録したモデルになっていることを確認してください。
なっていない場合や、別のモデルを利用したい場合は、モデル名のプルダウンをクリックすると、別のポップアップが表示されるので、そこから変更できます。
また、temperatureなどのパラメータもそこから変更できます

今回は、正確に判断して欲しいので、「Temperature」を0に設定しましょう。

続いて、LLMのSystemプロンプトには下記を入力してください。

あなたは分類器です。TrueかFalseで回答してください。

あなたは、ユーザからの質問が「今日の日付に関する質問」か「今日の天気に関する質問か」を分類する必要があります。

今日の日付に関する質問ならTrue
今日の天気に関する質問ならFalse

で回答してください

分類器LLMの動作確認をしてみる

では、一旦LLMの動作確認をしてみましょう

入力を「開始」ブロックで設定する

「開始」ブロックをクリックすると、したのような画面になります。

ここで、入力フィールドの右にある「+」ボタンを押すと、ポップアップが表示されるので、下記のように入力してください。

「保存」を押してホーム画面に戻ります。

「開始」ブロックで設定した入力をLLMが参照できるようにする

続いて、今「開始」ブロックで設定した入力を、分類器LLMが受け付けるようにしたいです。
もう一度「LLM」ブロックをクリックします。

続いて、「メッセージを追加」のボタンをクリックしてください。
すると、もう一つの入力ボックスが表示されると思います。

この新しい「USER」のボックスの中の{x}ボタンをクリックすると、先ほどラベル付けした「開始」ブロックの入力が変数として利用できるようになっています。

{x}inputsを設定してください。すると、下記のような状態になっているはずです。

では、ワークフロー編集画面に戻り、右上の「実行」ボタンを押してみましょう

すると、先ほど設定した入力を受け付けるフォームが表示されるので、「今日の天気は晴れですか?」と入力して「実行を開始」を押してみましょう。

少し待つと、下記のような画面が表示されます。
あれ?出力が表示されていないですね?

これは、ワークフローには最後に必ず「終了」ブロックを置く必要があり、それがないため最終出力が表示されていないということです。
ですが、LLM単体の出力は、先ほどのウインドウの右にある「トレース」タブから見ることができます。

トレースタブをクリックし、「LLM」を展開すると下の画像のようになります。

「出力」欄を見ると、ちゃんと天気の質問なので「False」が回答されていることがわかると思います。
では、ここまででLLMの動作確認は完了です。

IF/ELSEブロックの接続

ここからは、一気に行きます。
LLMには「IF/ELSE」ブロックを繋げてください。

すると「IF/ELSE」ブロックのポップアップが表示されます。
「条件を追加」ボタンを押して、下記の画像のように設定してください。

続いて、「IF/ELSE」ブロックの2出力にそれぞれ、LLMを接続してください。下記の画像のような画面になると思います。

LLM2(日付質問回答専門家)の設定

LLM2(IFに繋がっている方)には下記のプロンプトをSYSTEMプロンプトとして入力してください。

あなたはユーザの質問内容を繰り返し発言した後、それに対して回答してください。

ただし今日は10/23です
ちなみに「知識習得」を利用する場合

ちなみに、今回の記事では実施しませんが、
知識習得を利用する場合は、LLMの前に知識習得ブロックをつなげることで、実現できます。

知識習得に必要なナレッジファイルは知識習得ブロック内で登録できます。
また、該当のナレッジファイルを何のクエリで検索するのかも設定してください。
(今回ならば、ユーザ入力ですね)

そしてLLMの設定も下記のようになります。
コンテキスト欄に、知識習得の出力を用意し、SYSTEMプロンプト内に、「コンテキスト」という変数を入れることで、記事と同様に知識をSYSTEMプロンプト内に書き込むことができます。

また、最初の分類器LLMと同様に、「メッセージを追加」からUSERプロンプトを追加し、{x}ボタンから、ユーザの入力である{x}inputsを受け取れるようにしてください。
おそらく下記のようになっているはずです。

LLM3(天気質問回答専門家)の設定

LLM2と同様に、プロンプトの設定をしていきます。
SYSTEMプロンプトは下記を設定してください。

あなたはユーザからの質問を繰り返してください。
その後、質問に回答してください。ただし今日は雨です

さらに、USERプロンプト欄はLLM2と同様に、ユーザ入力変数を設定してください。
最終的に下記のようになるはずです。

「終了」ブロックの設置

最後に「終了」ブロックを設定します。

今回のフローでは終了箇所は2箇所になるので、2つの「終了ブロック」を接続します。

そして、このワークフローの出力変数を設定しますが、欲しいのは直前のLLMの出力になることに注意して設定してください。
最終的に下記のように設定されているはずです。

test-01ワークフローの動作確認

では、動作確認をします。前述した通り、ホーム画面の「実行」ボタンを押すと、ユーザ入力欄が現れるので、質問してみてください。

私は、「今日は雨が降りますか?」と質問しました。
すると、下記の画像のように結果が表示されます。

この通り、(実際に雨かどうかは別として)プロンプトの通りに回答させることができています。

また、トレースタブを見るとわかるように、ちゃんと分類器は適切に分類ができていることがわかります。

複雑なAIエージェントを作る

ここまでで、一個のワークフローを作成し、ユーザの質問に対して、適切に分類を行い、適切なエスカレーション先が回答を行う処理を実現できました。

続いては、このワークフロー自体を複数用意し、その上のエージェントが、ユーザの質問に応じて、どのワークフローが適切かを選択し、ワークフローを走らせ、その結果をユーザに返すような複雑なAIエージェントを実装してみようと思います。

作るAIエージェント

せっかく、今日の天気と今日の日付を回答してくれるワークフローができているので、これを拡張させましょう。

今日の日付天気を回答するワークフロー「test-01」をベースに、
明日の日付天気を回答するワークフロー「test-02」と
明後日の日付天気を回答するワークフロー「test-03」を作成します。

作成方法は同様に作成すれば良いですが、より簡単に作成する方法を後述します。

そして、それを束ねるエージェントはユーザの質問が
「今日に関する質問」なら「test-01」ワークフローを
「明日に関する質問」なら「test-02」ワークフローを
「明後日に関する質問」なら「test-03」ワークフローを起動して、
質問内容を起動したワークフローに流し込んで回答を取得し、その結果をユーザに回答するようなAIエージェントを実装します。

イメージとしては下記のような感じです。

(それぞれのtoolがワークフローになります)

ちょっと複雑になってきましたね。
これ自体には何の価値もないですが、これを実装できるようになれば、かなり色々なことができるようになりそうという感覚はありますでしょうか。

では実際に作ってみましょう

ワークフローを複製する

では画面左上の「Dify」アイコンをクリックして、ホーム画面に戻りましょう。

続いて、「test-01」のワークフローの右下にある3点リーダから「DSLをエクスポート」をクリックします。

すると、「ダウンロード」フォルダにyamlファイルが保存されます。

続いて、「DSLファイルをインポート」をクリックして、先ほどダウンロードした「test-01.yaml」を選択します。

「作成する」をクリックします。

一度ホーム画面に戻ると「test-01」が二つ存在していると思うので、一つを「情報を編集する」から名前を変更します。

下記のように「test-02」として「保存」を押してください

これで「test-01」を複製した「test-02」ができました。(中身はまだ同一)

同様の手順で、「test-03」も複製して作成してください。

非推奨

作った「test-01」のワークフローの右下にある3点リーダから「複製」をクリックしてください。

すると、新しい名前で複製できるので、「test-02」と「test-03」を複製して用意しましょう。

test-02のプロンプトを変更する

下記のように変更しましょう

質問分類器(LLM)

あなたは分類器です。TrueかFalseで回答してください。

あなたは、ユーザからの質問が「明日の日付に関する質問」か「明日の天気に関する質問か」を分類する必要があります。

明日の日付に関する質問ならTrue
明日の天気に関する質問ならFalse

で回答してください

日付質問回答LLM(LLM2)

あなたはユーザの質問内容を繰り返し発言した後、それに対して回答してください。

ただし明日は10/24です

天気質問回答LLM(LLM3)

あなたはユーザからの質問を繰り返してください。
その後、質問に回答してください。ただし明日は曇りです

test-03のプロンプトを変更する

下記のように変更しましょう

質問分類器(LLM)

あなたは分類器です。TrueかFalseで回答してください。

あなたは、ユーザからの質問が「明後日の日付に関する質問」か「明後日の天気に関する質問か」を分類する必要があります。

明後日の日付に関する質問ならTrue
明後日の天気に関する質問ならFalse

で回答してください

日付質問回答LLM(LLM2)

あなたはユーザの質問内容を繰り返し発言した後、それに対して回答してください。

ただし明後日は10/25です

天気質問回答LLM(LLM3)

あなたはユーザからの質問を繰り返してください。
その後、質問に回答してください。ただし明後日は晴れです

3つのワークフローを公開

AIエージェントから3つのワークフローを参照できるようにするためには、ワークフローの公開設定を変更する必要があります。

ここでは「test-01」ワークフローに対して実施しますが、同様の処理を3つのワークフローにも実施してください。

下記の画面の右上の「公開する」をクリックしてください。

「公開する」ボタンをクリックします。

クリック後、一番下の「ツールとしてのワークフロー」がクリックできるようになりますので、クリックしてください。

すると、入力画面が表示されるので、下記のように記載して、保存をクリックしてください。

(ラベルやプライバシーポリシーは記載する必要はありません)

以上の処理を、「test-02」「test-03」ワークフローにも実施してください。

エージェントを作成する

ホーム画面から開始します。

左上の「最初から作成」をクリックしてください。
下記のように設定してください。
(アプリのタイプは「エージェント」、アプリの名前は「AI-agent-test」)

「作成する」ボタンをクリックすると、下記のような画面になり、エージェントを作成できます。

ツールの設定

下記から、先ほど設定したワークフローをAIエージェントに設定します。
「+追加」をクリックしてください。

左の列からワークフローを選んで、3つ全て「追加」ボタンをクリックして追加します。

こうなっていたら成功です。

SYSTEMプロンプトの設定

続いて、AIエージェントのSYSTEMプロンプトを構築します。

「手順」ボックスの枠の中にSYSTEMプロンプトをそのまま記載しても良いですが、Difyにはプロンプトのブラッシュアップ機能があるため、それを利用します。

「手順」ボックス右上の「自動」ボタンを押します。
下記の画像のように「指示」枠に、叩きのプロンプトを入力してください。

叩きのプロンプトは下記です。

あなたは質問回答botです。
以下の手順に従ってユーザの質問に合わせて適切なツールを呼んでください。

・ユーザの質問を受け取ります
・ユーザの質問内容に合わせて適切なツールを選択します。
 ・今日に関する質問の場合は「test01」
 ・明日に関する質問の場合は「test02」
 ・明後日に関する質問の場合は「test03」
・選択したツールの名前を回答に含めてください。
・ユーザの質問をそのままツールの入力として使用してください。
・ツールの出力を回答してください。

続いて、右下の「生成」ボタンをクリックしてください。
すると、右側にブラッシュアップされたプロンプトと必要な変数が設定されるため、右下の「適用」ボタンをクリックするだけで、反映されます。

下記のようになっていれば成功です。

あとは、現状変数が「必須」になっていますが、ユーザの質問やツールの選択などは事前にユーザが入力するものではないので、「オプション」をONにして、使わなくても良いようにします。
下記にように「オプション」がONになっていれば大丈夫です。

一応、今回生成されたSYSTEMプロンプトを下記に記載しておきます。

<instruction>
あなたは質問回答botです。
以下の手順に従ってユーザの質問に合わせて適切なツールを呼んでください。

1. ユーザの質問を受け取ります。
2. ユーザの質問内容に合わせて適切なツールを選択します。
   - 今日に関する質問の場合は「test01」
   - 明日に関する質問の場合は「test02」
   - 明後日に関する質問の場合は「test03」
3. 選択したツールの名前を回答に含めてください。
4. ユーザの質問をそのままツールの入力として使用してください。
5. ツールの出力を回答してください。

出力にはxmlタグを含めないでください。
</instruction>

<input>
{{user_question}}
</input>

<output>
<tool_name>{{selected_tool}}</tool_name>
<tool_output>{{tool_response}}</tool_output>
</output>

<example>
<input>
今日の天気はどうですか?
</input>
<output>
<tool_name>test01</tool_name>
<tool_output>今日の天気は晴れです。</tool_output>
</output>

<input>
明日の予定を教えてください。
</input>
<output>
<tool_name>test02</tool_name>
<tool_output>明日の予定は会議があります。</tool_output>
</output>

<input>
明後日のイベントは何ですか?
</input>
<output>
<tool_name>test03</tool_name>
<tool_output>明後日のイベントはスポーツ大会です。</tool_output>
</output>
</example>

作成したAIエージェントを使う

AIエージェント作成直後の画面は下記のようになっていると思います。

ユーザ入力フィールドが邪魔なので、右上のマークを押して、消しておきましょう。
(使わないので)

続いて、エージェントに下記の質問をしてみましょう。

明後日は傘を持って行った方がいいですか?

下記のように、質問を入力して、三角の「送信」ボタンを押してください。

下記の通り、明後日の質問なので、適切に「test-03」のワークフローが呼び出され、プロンプトに指定した「明後日は晴れ」という情報をもとに回答できていることがわかりました。

作成したAIエージェントを公開する

画面右上の「公開する」ボタンを押してください。

「更新」を押してから、「アプリを実行」をクリックしてください。

すると新しいウインドウで、今作ったAIエージェントが搭載されたchatアプリが起動されます。

今回はローカルサーバで起動しているため、ローカルホストで繋いでいますが、ちゃんとクラウドにサーバを立てて実行すれば、公開も可能なはずです。

同様に実行できるはずなので、質問してみてください。

作成したAIエージェントにAPIで接続する

作成したAIエージェントはAPIで他システムから接続することができます。
「公開する」ボタンから「APIリファレンスにアクセス」をクリックしてください。

するとリファレンスページが新しいウインドウで開きます。
右上の「APIキー」ボタンをクリックしてください。

「新しいシークレットキーを作成」をクリックして、表示されたAPIキーをメモ帳などに保存しておいてください。
このAPIキーを利用して接続します。

続いて、リファレンスに従って、APIの形式を作っておきます。
私の場合は、下記のような形です。API KEYだけは自分のキーに変更してください。

curl -X POST 'http://localhost/v1/chat-messages' \
--header 'Authorization: Bearer {api_key}' \
--header 'Content-Type: application/json' \
--data-raw '{
    "inputs": {},
    "query": "明後日は傘は必要ですか?",
    "response_mode": "streaming",
    "conversation_id": "",
    "user": "abc-123",
    "files": [
      {
        "type": "",
        "transfer_method": "",
        "url": ""
      }
    ]
}'

上記のコマンドをターミナルにコピーして実行すると、APIが呼ばれて、Difyサーバから返答があることを確認できると思います。
下記のような形で、長々と出力が帰ってきています。日本語はdecodeされずに帰ってくるため、みづらいですが、ちゃんとdecodeすれば、正しい返答が返ってきていることがわかります。(例えばchatGPTとかにデコードしてとお願いしたら、デコードしてくれます)

まとめ

ここまで読んでくださりありがとうございました!
使ってみると、かなり簡単にAIエージェントが組めて驚きました!

どんどんAIエージェントを構築するのが楽になっていきますね!

同様の内容をLangGraphでゴリゴリにコードで実装した場合はどうなるのかは、また後日記事にしようと思いますので、そちらも是非ご覧ください。

Discussion