Difyのワークフローエディタの実装をみてみよう
はじめに
DifyはオープンソースのLLMアプリケーション開発プラットフォームです。
機能のひとつとしてワークフローがあり、ユーザーがAIワークフローを視覚的に構築できるようになっています。これがどのように実装されているのか、個人的に気になっているポイントを中心に調査しました。
全体構造
ワークフローエディタはReactFlowをコアとして以下の主要コンポーネントで構成されている。
- WorkflowContainer - メインコンテナコンポーネント。ReactFlowProviderでラップされており、ワークフロー全体のコンテキストを提供している。
- Header - ワークフローの操作ヘッダー。タイトル編集、実行ボタン、履歴表示など。
- Nodes - 様々なタイプのブロック(ノード)を実装したコンポーネント群。
- Edges - ノード間の接続を表すエッジコンポーネント。
- Panel - 設定パネルやプロパティエディタ。選択されたノードの詳細設定ができる。
- Operator - ズームや操作モード切替などの操作UI。
状態管理戦略
ストア
Zustandを使用して状態管理を行なっている。WorkflowStore内でNodes、Edges含め各種状態を管理しており、useRefを用いてシングルトンパターンで提供されているのが特徴的。
イベントエミッターによる状態同期
ahooksライブラリによるイベントエミッターを使い、コンポーネント間を疎結合にしている。Workflowコンポーネントでは、このイベントエミッターを購読して状態更新を検知している。
ノードとエッジの構造
ワークフローエディタの中核となるノードとエッジのデータ構造はReactFlowのそれらを拡張して作られている。
ノードのデータ構造
Edgeとの接続、UI表示関連などの各種プロパティを持っている。
上記は全てのノードに共通するもので、各ノードに固有のプロパティは別に存在する。
ノードのタイプ
様々なタイプのノード(Start、End、LLM、KnowledgeRetrievalなど)はBlockEnum
で定義されており、それぞれ異なる機能を持っている。これらのノードタイプはconstant.ts
で一元管理されている。
この設計により各ノードタイプごとに接続可能なノードの制約やバリデーションロジックを定義でき、拡張性が高く保たれている。
各ノードの実装の詳細は/nodesで整理されている。
エッジのデータ構造
インタラクション、構造などに関する各種プロパティを持っている。
ReactFlowとの相互変換
DifyのワークフローをReactFlowで表現できるように変換を行う必要がある。
複雑だが、この変換プロセスでは大まかに以下の処理が行われている
- 一部の特殊なノードに対する前処理
- ノードの位置やサイズの初期化
- 接続関係の計算・付加
- ノードタイプごとの処理
エッジの変換と循環参照の処理
エッジも同様に変換処理が必要であり、以下のように行う。
- 循環参照の除去
- デフォルトのプロパティを追加
UX的な取り組み
自動保存
定期的に自動保存される。
storeで実装されていて、ユーザーの編集作業が大変なワークフローエディタでは比較的一般的な機能。
ノードのスナップ機能
操作したいノードを他のノードと水平・垂直に揃えやすくするために、位置の目安となるガイドラインの表示と、実際にノードをそこにスナップさせる機能がある。
自動整列機能
elkjsという整列アルゴリズムを提供するライブラリを使っている。
まとめ
Difyのワークフローの実装を見ていきました。
ReactFlowを使う上で課題となりがちなのがバックエンドで必要な構造をReactFlowで利用できる形にするためのViewの変換周りなのですが、参考になりました。
Discussion