【Dify】ループ処理が追加されるまで ワークフロー再帰 で耐える
はじめに
最近生成AI周りを触っていてDifyやBedrockのインパクトに恐れ慄いている。世の founder や early adopters はずっと前からこれらを認知し、すぐさま使いなしていることに感心するとともに、Difyで絶妙にできないことなどに憤死しそうになる日々である。今回ご紹介させていただく件にしても、おそらくもう直ぐリリースされると思われるし、そもそも別のやり方もありそうではあるが、似たようなことをしたくて断念した人がいれば参考になるかもしれません。
え?Difyってループ処理ないん?
イテレーション処理はあります。配列などをいい感じに操作するアレです。
しかし、以下のようなループ処理はめちゃくちゃ探したんですが、なさそうでした。(2024/07/23現在。あったらすまん)
- Difyのチャットボットのapiを叩く
- 返答された内容をLLMに考えさせて、再度apiを叩く
- これの繰り返し
まず何やねんそのケース
と思われるのですが、例えば、アンケートのチャットボットを作成したとして、それのテストデータ(ログ)を自分で作成するケースなどです。色々なペルソナを自分で考えて、テスト用のアンケートを作るのは億劫だし、時間もかかります。そこでペルソナだけ適当に入力して、あとはボタンをポチれば、テストデータ(ログ)ができるというのはなかなか魅力的ではないでしょうか?
言ってる意味がわからんから図で書けや
これまで通りチャットボットを手入力すると、上記のようになります。これを以下のようにペルソナだけ設定して自動でやりとりして欲しいとします。
用途は限られてくると思うのですが、これを自動化するにあたって、パッと2つの案が浮上しました。
もうそれ「コード」で書けばええやんけ。
Difyでは、直接コーディングすることができます。このJSでHTTPリクエストを実行し、返答してきた回答を判断して、打ち止めとなる回答(例えば、「ほなまたな!」)以外はループでHTTPリクエストを実行し続ければよさそうです。しかし、超残念なお知らせがあります。
この制限のため、コードでHTTPリクエストはタイムアウトになり実行できません。
そのために、Difyは専用のHTTPリクエストノードを用意してくれています。でもこれだと、やりたいループ処理ができそうになかったので一旦諦めました。
雛形のワークフロー作成して、必要分だけコピーすれば?
いや、これは超負けた気がします。明確な敗北です。
やりたいこととしては、以下の繰り返しです。
- 「HTTP」ノードでDify Chatbot api にリクエストを送り、レスポンスを受け取る
- 「IF/ELSE」ノードで打ち止めあるか判断する
- なければ、レスポンスを見て、LLMに次の回答を作成させる
- あれば終了
- 1に戻る
つまり、打ち止めとなる量を推測して、そのワークフロー分だけ作っておく。みたいな感じです。
ざっくりですが、イメージは以下です。
上のフローが一連の繰り返し単位だと仮定して、それを別々に作成し、繋げていきます。確かにこれだとできますが、おわってますね。かつてボコボコにされた上司からだけでなく、方々から「本当にセンスねぇな」ってやじが飛んで来そうです。
ワークフローを再帰的に使う
現状自分の脳みそで捻り出したのは、ワークフローを無理やり再帰的に呼び出して使うことでした。
まず、先ほどのワイボットの例で全体像をご紹介します。
- 登場人物
- ワイボット (chatbot)
- ドッペルゲンガーワイ(workflow)
- やりたいこと
ドッペルゲンガーワイ(workflow)からワイボット(chatbot)のAPIを叩いて、打ち止めの返答(ほなまたな!)以外であれば、LLMが回答を生成し、再度リクエストを送る。ほなまたな!が返答されるまでこれを繰り返す。
全体像
ざっくり流れとしては以下のような構成です。いくつか注意が必要なので詳細で解説します。
- 開始ノード
- HTTPリクエストノード
- IF/ELSEノード
4. IF: 打ち止めワード(ほなまたな!) or 200以外 → 終了
5. ELSE: body から 回答とconversation_idを抽出
6. IF/ELSEノード
7. IF: Jsonの解析失敗→ 終了
8. ELSE: LLMに回答を作成させる
9. Escape処理
10. 再帰呼び出し(ドッペルゲンガーワイ)
1. 開始ノード
- chat
- conversation_id
chatは必須にしています。これはどちらでもいいですが、会話の最初はワイ(ドッペルゲンガー)からスタートさせる設定です。どっちでもいです。
conversation_idは最初はオプショナルです。というか最初は不要です。理由としては、DifyのChatbotはチャットが開始されて初めてconversation_idが割り振られます。なので最初のHTTPリクエスト時はいらないのです。しかし、再帰処理では必要です。理由はご想像の通りで、どの会話やねん? ってなりますもんね。
2. HTTPリクエストノード
- query
- conversation_id
ここあまり言うことないので飛ばします。
3. IF/ELSEノード
body注意です。デコード処理挟む必要があります。面倒だったんで \u307b\u306a\u307e\u305f\u306a\uff01(ほなまたな!) が含まれているかどうかで分岐させています。デコードしないと無限に実行されるので注意です。
4. 終了ノード
飛ばします。
5. body → answer,conversation_id抽出ノード
HTTPリクエストノードのレスポンスはbodyです。このbodyからanswer,conversation_idを抽出するためにjs描いてます。もちろんこれは、パラメータ抽出ノード使っても良いです。ただ、無駄にgpt回す必要がないなと思ったのと、遅いのでコードの方がいいと思い、このようにしてます。
6. IF/ELSEノード
デバッグも兼ねて、念のため追加した感じです。
7. Jsonの解析失敗→ 終了
飛ばします。
8. ELSE: LLMに回答を作成させる
良識忘れてました。
9. Escape処理
これ結構重要です。ここでエスケープ処理をしないと、次リクエストを送った時400になります。
次紹介するんですが、このエスケープした結果を次の再帰処理のchatに入れます。これがリクエスト時の "query":"{{🏠開始.{x}chat}}",
に代入されるんですが、ここにエスケープされていない文字が入りBad Requestとなるわけです。
10. 再帰呼び出し(ドッペルゲンガーワイ)
- 抽出しておいたconversation_idを入れて、会話の続きを楽しみます。
- chatにエスケープ処理した結果を入れ、400を防ぎます。
出来上がり
要注意事項
-
その1
散々再帰でLoop処理できるって書いてきたのですが、1点伝えてなかったことがあります。Cloud.DifyではWorkflowの呼び出し回数が5回までと制限されています。(WORKFLOW_CALL_MAX_DEPTH=5
)
理由は、無限に呼び出してしまったらOpenAIやClaudeの課金額が莫大になる可能性があるからです。(Limitは設定していると思いますが。)
なのでデフォルトは5回と設定されている上Cloud版ではこの環境変数の更新方法が載ってなさそうな感じがします(載ってたら教えてくれそん)。Difyを自前でホストしている方は、この環境変数を変えて、再起動すればOKです。 -
その2
この再帰処理がベストではない気がしてます。
なんかいい方法あったらコメント欄で教えていただけたら幸いです。 -
その3
超肌感ですが、すぐにでもLoop実装されると思ってます。
Discussion