🐪

Difyのワークフローエディタの実装をみてみよう

に公開

はじめに

DifyはオープンソースのLLMアプリケーション開発プラットフォームです。
機能のひとつとしてワークフローがあり、ユーザーがAIワークフローを視覚的に構築できるようになっています。これがどのように実装されているのか、個人的に気になっているポイントを中心に調査しました。
https://github.com/langgenius/dify

全体構造

ワークフローエディタはReactFlowをコアとして以下の主要コンポーネントで構成されている。

  1. WorkflowContainer - メインコンテナコンポーネント。ReactFlowProviderでラップされており、ワークフロー全体のコンテキストを提供している。
  2. Header - ワークフローの操作ヘッダー。タイトル編集、実行ボタン、履歴表示など。
  3. Nodes - 様々なタイプのブロック(ノード)を実装したコンポーネント群。
  4. Edges - ノード間の接続を表すエッジコンポーネント。
  5. Panel - 設定パネルやプロパティエディタ。選択されたノードの詳細設定ができる。
  6. Operator - ズームや操作モード切替などの操作UI。

https://github.com/langgenius/dify/blob/34cba83ac4d56011794a3cd6a18b95fa0f0603d1/web/app/components/workflow/index.tsx#L119-L123

状態管理戦略

ストア

Zustandを使用して状態管理を行なっている。WorkflowStore内でNodes、Edges含め各種状態を管理しており、useRefを用いてシングルトンパターンで提供されているのが特徴的。

https://github.com/langgenius/dify/blob/34cba83ac4d56011794a3cd6a18b95fa0f0603d/web/app/components/workflow/context.tsx

イベントエミッターによる状態同期

ahooksライブラリによるイベントエミッターを使い、コンポーネント間を疎結合にしている。Workflowコンポーネントでは、このイベントエミッターを購読して状態更新を検知している。

https://github.com/langgenius/dify/blob/34cba83ac4d56011794a3cd6a18b95fa0f0603d/web/app/components/workflow/index.tsx#L153-L156

ノードとエッジの構造

ワークフローエディタの中核となるノードとエッジのデータ構造はReactFlowのそれらを拡張して作られている。
https://github.com/langgenius/dify/blob/34cba83ac4d56011794a3cd6a18b95fa0f0603d/web/app/components/workflow/types.ts#L109

ノードのデータ構造

Edgeとの接続、UI表示関連などの各種プロパティを持っている。

https://github.com/langgenius/dify/blob/34cba83ac4d56011794a3cd6a18b95fa0f0603d/web/app/components/workflow/types.ts#L57-L66

上記は全てのノードに共通するもので、各ノードに固有のプロパティは別に存在する。
https://github.com/langgenius/dify/blob/34cba83ac4d56011794a3cd6a18b95fa0f0603d1/web/app/components/workflow/nodes/llm/types.ts#L3-L5

ノードのタイプ

様々なタイプのノード(Start、End、LLM、KnowledgeRetrievalなど)はBlockEnumで定義されており、それぞれ異なる機能を持っている。これらのノードタイプはconstant.tsで一元管理されている。

https://github.com/langgenius/dify/blob/34cba83ac4d56011794a3cd6a18b95fa0f0603d1/web/app/components/workflow/constants.ts#L34-L44

この設計により各ノードタイプごとに接続可能なノードの制約やバリデーションロジックを定義でき、拡張性が高く保たれている。
各ノードの実装の詳細は/nodesで整理されている。

エッジのデータ構造

インタラクション、構造などに関する各種プロパティを持っている。

https://github.com/langgenius/dify/blob/34cba83ac4d56011794a3cd6a18b95fa0f0603d1/web/app/components/workflow/types.ts#L93-L99

ReactFlowとの相互変換

DifyのワークフローをReactFlowで表現できるように変換を行う必要がある。

複雑だが、この変換プロセスでは大まかに以下の処理が行われている

  1. 一部の特殊なノードに対する前処理
  2. ノードの位置やサイズの初期化
  3. 接続関係の計算・付加
  4. ノードタイプごとの処理

https://github.com/langgenius/dify/blob/34cba83ac4d56011794a3cd6a18b95fa0f0603d1/web/app/components/workflow/utils.ts#L301-L302

エッジの変換と循環参照の処理

エッジも同様に変換処理が必要であり、以下のように行う。

  1. 循環参照の除去
  2. デフォルトのプロパティを追加

https://github.com/langgenius/dify/blob/34cba83ac4d56011794a3cd6a18b95fa0f0603d1/web/app/components/workflow/utils.ts#L395-L396

UX的な取り組み

自動保存

定期的に自動保存される。
storeで実装されていて、ユーザーの編集作業が大変なワークフローエディタでは比較的一般的な機能。
https://github.com/langgenius/dify/blob/main/web/app/components/workflow/store.ts#L234-L236

ノードのスナップ機能

操作したいノードを他のノードと水平・垂直に揃えやすくするために、位置の目安となるガイドラインの表示と、実際にノードをそこにスナップさせる機能がある。
https://github.com/langgenius/dify/blob/main/web/app/components/workflow/hooks/use-nodes-interactions.ts#L125-L136

自動整列機能

elkjsという整列アルゴリズムを提供するライブラリを使っている。
https://github.com/langgenius/dify/blob/main/web/app/components/workflow/hooks/use-nodes-layout.ts#L42-L56

まとめ

Difyのワークフローの実装を見ていきました。
ReactFlowを使う上で課題となりがちなのがバックエンドで必要な構造をReactFlowで利用できる形にするためのViewの変換周りなのですが、参考になりました。

Discussion