Open10

コントリビューション・フェス参加のためGraphAIを学んでたらDifyとの比較になった

はらたけはらたけ

@snakajima さんが、AI使ったアイデアソンとGraphAIを使ったプロダクト制作のフェスを開催してた。優秀賞のMac mini に釣られ、初めて他の方のリポジトリにIssueを投稿するくらいのやる気はある。

Life is beautiful コントリビューション・フェス
https://github.com/snakajima/life-is-beautiful

GraphAIは知らなかったけど、「非同期型のAI Agent」「Difyで作ったワークフローをGraphAIに移植」というキーワードに興味を持ったので見てみる

はらたけはらたけ

どこから見るかと悩んだけどZennを調べれば先人の知恵がまとまっててありがたい

AI Agentを簡単に効率よく開発/実行するGraphAIの紹介
https://zenn.dev/singularity/articles/graphai-tutorial_1

元のGithubリポジトリに書いてあった通りだけど、確かに非同期に AI + Agent でデータのやり取りをして、最終的な成果物を出すためのライブラリらしい。

朝の支度の事例が分かりやすかった。

起きたらすぐするタスク
(1) コーヒーのお湯をわかす (4分)
(2) 子供を起こす
3分おきの5回呼ぶと起きる。ただし、3分以内の呼んだり、5分以上間隔が開くと、最初からやり直し
(3) 朝食をつくる (15分)
(4) いつも遅れる新聞をとりにいく
5分以上間隔をあけて4回見に行くと、新聞はきている。ただし5分未満で見に行くと 最初からやりなおし

→ 並行したりフロー化されてたり、依存したり中断したり。Difyのワークフロー書式とは設計思想が全然違う。こっちのほうが自分が考えていたAgentワークフローに近いかも。

はらたけはらたけ

私はDifyer

自分は非エンジニア属性なのでコード書いたりは良く分かってない人間だけど、Difyが出たときは「これはすごい!」と思って自分でレンタルサーバー借りて個人で Dify & Firecrawl をセットアップして使い倒してたくらいには、ノードベースのAIツールに未来と魅力を感じた。

https://dify.ai/jp

https://github.com/langgenius/dify

でも今はあまり使ってない。

Difyをあまり使わなくなった理由

  1. 「ノード繋ぐだけ!」→ノードを繋いだだけでは出来ないことのほうが多い
  2. エラー訂正のワークフローを自前で組む必要がある
  3. (基本的には)同期型のワークフローを組むことになる
  4. 結果をリフレクションできない (Iterationで擬似的に組めるが厳密には違う)

「Difyめっちゃいいじゃん!」って思って色々やっていた頃の思い出をキレイなままにしたかったので自分のお気持ちを表明したことが無かったけど、書き出してみるとけっこう文句があったわ。せっかくなのでまとめてみる。

はらたけはらたけ

「ノード繋ぐだけ」では何もできない

シンプルなワークフローであれば「LLMの出力」をそのまま無加工で出力すれば終了。でも、ちょっとでも複雑なことをしたいと思ったらプログラミング基礎知識が無いと何もできない。

例えば、RAGの検索結果とWeb検索で入手したデータを元にしてLLMに回答を作らせる場合。

  1. Web検索で取得したいくつかのページURLをIterationに渡して
  2. Iterationで Firecrawl → ページデータを取得して
  3. LLMに渡して要約して
  4. 結果をRAG用のVDBに書き込んで
  5. 並行して取得していたRAGからデータを受け取って
  6. 全部変数ノードにまとめてからコードノート(またはテンプレートノードで)String Textにして
  7. 調査結果をLLMにコンテキストとして渡して、最終回答を生成させる

→ これを実現するために何回 JSON Parseさせるねん!と、チャットボットフローを作ってる当時はブチ切れてた記憶がある。

何らかの処理に失敗してもPython側のエラーコードしか表示されないので、自分のParse処理が間違ってるのかLLMの出力のせいなのかDifyの仕様の問題なのか切り分けができず、けっきょくPythonコードのほうが早かった…みたいなことが何回もあった。

これ解決策は?

(1) 自分が賢くなる

OR

(2) 「ノードを繋ぐだけで、処理元のOutput → 次のInputの受け取り書式をAgentが自動検知して訂正してくれたらいいのにな〜」

って当時は思っていた記憶がある。
Agentの訂正結果がキャッシュされて2回目以降は自動的に修正されるか、または処理元・先の書式をあらかじめ自動訂正しといてくれるか。→ これが実現されたらノードをポチポチ繋ぐだけで全てのアプリが完成するので、真のノードベースAIエージェントと言えるかもしれない。

そして自分は (1) を選んだ。

はらたけはらたけ

ノードとノードの間に「ユニットテストノード」が常に挟まれて、成功すると消失するようなイメージ。

はらたけはらたけ

エラー訂正のワークフローを自前で組む必要がある

初期のLLMはJSON書式を固定化させるにも工夫が必要で、それでも1〜2%くらいはすり抜けてエラーになる出力が混じるなんてザラだった。(それでも1年前の話だったけど…)

初期のDifyは IF ELSE も貧弱で、LLM側のエラーに巻き込まれると訂正できずに処理が強制終了したり、ひどいときはシレッとエラーを含めたままチャットが継続して次の会話でエラーを巻き込んでさらに回答が酷くなるなどがザラだった。
※こういう事があるから Langchain から袂を分かつ事になったのかもしれない…

v0.10 を超えた頃にはLLM性能も飛躍的に向上して判定ノードも増えたので、「ここで失敗したらこっちに飛ばして……」という感じで分岐できて、エラーに悩むことは無くなった。
いやしかしですよ、 そもそもLLMのエラー時に「訂正して再開する」が選択肢に無い今の設計にもだいぶ不満ある

LLMが正しい出力をすることが前提な設計思想はちょっと受け入れづらい。

そういう事もあって、Dify自体は「失敗しても痛くも痒くもないこと」にしか使ってない。社内向けのRAG付きチャットボットとか、自分用のリサーチBotとか、最終結果がおかしくても良いことはOK。
そうなると、だんだん「Google NotebookLM とかでよくね?」っていう感じになってくる。

どうなってたら良かった?

「期待している結果」ではなかったときに元のノードに戻って処理をやり直せるリフレクションノード が存在してたら、だいぶ違っていたかも。

リフレクションノードの話は後でまとめて書く

はらたけはらたけ

同期型のワークフローしかないよね〜 →ちょっと緩和された

初期のDifyは同期型しかなかったので、(Iterationも作業が完了するまで何もできなかった) バージョンアップで並列処理 ができるようになってだいぶ緩和した。

でも けっきょく全ての処理が完了するまで次の処理が開始できないので同期型だよね? という印象は拭えない。

自分がやりたかったこと

  • チャットボットで会話を投げるとLLMが処理しつつ並列IterationでWeb検索を実行して、あとで検索結果を教えてくれる (非同期型コミュニケーション) → 検索には時間がかかるので結果が出るまでチャットを継続したかった

  • 全ての処理が終わって結果が出たあとに「記録して」と言ったら外部ツールを呼び出して記録中にも続きの会話がすぐにできる (非同期記録) → VDBに結果を記録したり外部ツールの呼び出しAPIの結果待ちに時間がかかる

いずれも「LLMからのフィードバックの速さを優先して、後処理で結果を出したほうが人間の体験がいいよね」という自分自身の経験則で作ってみたかったワークフローだけどDifyはこれが出来ない。これもDifyを toC 向けの顧客サポート向けチャットに組み込みづらかった理由のひとつ。

どうなってたら満足だった?

Difyには外部ワークフローを呼び出す機能があるので、それの結果を待たずに次のチャットが継続できる機能があればよかったかも。

外部ワークフローが処理 → VDBに記録 → チャット中にVDBに結果が増えてることをLLMが検知 → 会話に反映

こういうのもエージェント感ある。

はらたけはらたけ

結果をリフレクションできない

「期待している結果」ではなかったときに元のノードに戻って処理をやり直せるリフレクションノード が存在してたら、だいぶ違っていたかも。

Difyの限界を感じて自分でツールを作るようになったのはこれが大半の理由かもしれない。
期待値とは「当初の指示」であり、これを達成できるまでどこまで元の処理を繰り返し実行できるようにするかがエージェント的な挙動なのでは?

どういう風になっていたら満足できそう?

細かい設計とかは想像出来ないけど、リフレクションノードという物を仮に作るとしたら…

  • 各ノードにマイルストーンを設置して、その出力を記録しておく
  • リフレクションノードはそこに至るまでに生成された各マイルストーンの出力(の概要)を全て受け取れる
  • リフレクションによって「ここまでの結果にエラーや問題が存在しないか?」を検証して、NGの可能性がある場合は検証結果をマイルストーンに送信してやり直す / マイルストーンのノードを修正してから再実行する
  • 最終結果のリフレクションノードは全ての結果を受け取り、当初の指示を達成できたかどうかを判断する
リフレクションノードの判断基準
 └エラー訂正
 └人間からの当初の指示
 └人間からのノードに直接書き込まれた指示
 └ここまでの処理にかかった経過時間
 └ここまでに使われた計算資源(Token数なども含める)
 └マイルストーンに戻る際の制限 (指示の書き換えを認める・処理の書き換えを認める etc...)

→ この処理自体がものすごく難しいけど、非同期型で自立型のエージェントを作ろうって思ったら「処理を任せる=任せるための終了判断を用意する」という手順があればいいので、これについて深堀りすると良さそう。

処理にかかった時間 / 計算資源 この2つを判断基準に入れたのは、これが機械と人間の共通言語だと思ったので。時間経過で出来ないことをいつまでもウダウダ続けず、体力というリソース不足で処理を停止するのは大事。

はらたけはらたけ

ノードベースで話し合うDifyのことを忘れて GraphAI を見る

ここまで書いたDifyの不満は、概ねGraphAIでは問題にすらなっていないという印象。
Difyと同じYamlで記述されていたので、もっとシンプルなノードベース・ワークフローのようなものかと思っていたけど、全く別物のように見える。

今日はここまで。

というかこれは非エンジニアの自分だと理解してる間に3ヶ月経過してしまいそうで、何か作るスタートラインに立つのは厳しそう。