Workflow ノード設計哲学:コンポーザビリティ、Human-in-the-Loop、確定的ノードと確率的ノードの共存
はじめに
Dify の Workflow は、単なる「LLM を呼び出すフローチャート」ではない。その設計哲学の核心は、AI が自律的に処理できる領域と、人間が判断すべき領域を明示的に分離し、一つのフローの中で共存させることにある。
本稿では、Dify Workflow のノード設計を支える4つの設計原則 – コンポーザビリティ、Human-in-the-Loop(HITL)、確定的ノードと確率的ノードの使い分け、エラーハンドリングパターン – を詳細に解説する。
1. Workflow の基本アーキテクチャ
1.1 ノードとエッジの構成
Dify Workflow は、有向非巡回グラフ(DAG)として表現される:
graph LR
START((Start)) --> A[LLM Node]
A --> B{IF/ELSE}
B -->|条件A| C[Knowledge Base]
B -->|条件B| D[HTTP Request]
C --> E[LLM Node 2]
D --> E
E --> F[Human Review]
F -->|承認| G[HTTP Request<br/>外部API送信]
F -->|差し戻し| A
G --> END((End))
1.2 ノードの分類体系
Dify の Workflow ノードは、その性質によって以下のように分類できる:
| カテゴリ | ノードタイプ | 出力の性質 | 実行時間 |
|---|---|---|---|
| 確率的ノード | LLM, Question Classifier | 非決定的(同じ入力でも異なる出力) | 中〜長 |
| 確定的ノード | IF/ELSE, Code, Template, Variable | 決定的(同じ入力なら同じ出力) | 短 |
| 外部連携ノード | HTTP Request, Tool | 外部依存 | 可変 |
| データノード | Knowledge Retrieval, Variable Aggregator | 検索結果依存 | 中 |
| 人間介在ノード | Human Input (待機型) | 人間の判断依存 | 不定 |
| 制御ノード | Start, End, Iteration, Parameter Extractor | フロー制御 | 短 |
2. コンポーザビリティ:ノードの組み合わせ可能性
2.1 設計原則
Dify Workflow のコンポーザビリティは、以下の原則に基づいている:
- 各ノードは独立した入出力インターフェースを持つ: ノード間の依存は変数の受け渡しのみ
- ノードの型に関わらず同一の接続プロトコル: LLM ノードも HTTP ノードも同じ方法で接続可能
- ネストと参照の分離: 変数の参照は明示的で、暗黙のグローバル状態を持たない
graph TB
subgraph "コンポーザビリティの原則"
direction LR
subgraph "ノード A"
A_IN[入力変数] --> A_PROC[処理ロジック]
A_PROC --> A_OUT[出力変数]
end
subgraph "ノード B"
B_IN[入力変数] --> B_PROC[処理ロジック]
B_PROC --> B_OUT[出力変数]
end
A_OUT -->|"変数参照"| B_IN
end
2.2 変数スコーピングモデル
Workflow 内の変数は、以下のスコープルールに従う:
| スコープ | 説明 | アクセス方法 |
|---|---|---|
| ノードローカル | ノード内部の処理で使用 | ノード内のみ |
| ノード出力 | ノードの処理結果 | 下流ノードから {{node_id.output}} で参照 |
| Workflow 入力 | Start ノードで定義される入力パラメータ | 全ノードから {{start.input_name}} で参照 |
| 環境変数 | Workflow レベルの定数 | 全ノードから参照可能 |
| 会話変数 | Chat 型 Workflow で会話をまたいで保持 | 全ノードから参照・更新可能 |
この設計により、ノードは「自分の入力と参照先のノード出力」だけを知っていればよく、Workflow 全体の状態を把握する必要がない。これがコンポーザビリティの基盤である。
2.3 Iteration ノード:繰り返し処理のコンポーザビリティ
Iteration ノードは、配列データに対してサブフローを繰り返し実行する。これにより、単一ノードでは表現できない複雑な処理を、コンポーザブルな形で実現できる:
graph TB
START((Start)) --> SPLIT[Code Node<br/>文書を章ごとに分割]
SPLIT --> ITER[Iteration Node]
subgraph ITER[Iteration: 各章に対して]
I_LLM[LLM Node<br/>要約生成]
I_LLM --> I_QA[LLM Node<br/>QA 生成]
end
ITER --> AGG[Variable Aggregator<br/>結果統合]
AGG --> END((End))
3. Human-in-the-Loop(HITL):人間介在の設計哲学
3.1 なぜ Human-in-the-Loop が必要なのか
AI の自律性が高まるほど、人間の制御が重要になる。これは矛盾ではなく、リスク管理の基本原則である:
graph TB
subgraph "AI 自律性と人間制御のマトリクス"
direction TB
A["低リスク x 高自動化<br/>完全自動処理<br/>例: FAQ 自動回答"]
B["高リスク x 高自動化<br/>AI ドラフト + 人間承認<br/>例: 契約書レビュー"]
C["低リスク x 低自動化<br/>手動 + AI アシスト<br/>例: 創造的文書作成"]
D["高リスク x 低自動化<br/>人間主導 + AI 補助<br/>例: 法務判断"]
end
3.2 HITL の3つのモード
Dify の Workflow における Human-in-the-Loop は、公開資料に基づくと以下の3つのモードで運用される:
Approval(承認)モード
AI が生成した結果を人間が確認し、承認または却下する:
graph LR
A[LLM Node<br/>回答ドラフト生成] --> B[Human Input<br/>承認/却下]
B -->|承認| C[HTTP Request<br/>顧客に送信]
B -->|却下| D[LLM Node<br/>再生成]
D --> B
適用シナリオ:
- 顧客向けメール自動生成後の送信前確認
- 契約書条項の AI レビュー結果の法務確認
- 稟議書ドラフトの上長承認
Correction(修正)モード
AI の生成結果を人間が直接編集できる:
graph LR
A[LLM Node<br/>レポート生成] --> B[Human Input<br/>内容修正可能]
B --> C[Template Node<br/>フォーマット適用]
C --> D[HTTP Request<br/>レポート保存]
適用シナリオ:
- AI 生成レポートの数値・表現の修正
- 翻訳結果のニュアンス調整
- 技術文書の専門用語の修正
Escalation(エスカレーション)モード
AI の確信度が低い場合、自動的に人間にエスカレートする:
graph TB
A[LLM Node<br/>問い合わせ分類] --> B{IF/ELSE<br/>確信度チェック}
B -->|確信度 > 0.8| C[自動回答]
B -->|確信度 <= 0.8| D[Human Input<br/>担当者対応]
D --> E[回答送信]
C --> E
適用シナリオ:
- カスタマーサポートの自動応答 / エスカレーション判定
- OCR 認識結果の信頼度に応じた人間確認
- クレーム対応の自動 / 手動振り分け
3.3 HITL の設計上の考慮事項
| 考慮事項 | 説明 | 推奨対応 |
|---|---|---|
| タイムアウト | 人間の応答が得られない場合の処理 | デフォルト動作の設定(エスカレーション or 保留) |
| 並行処理 | 複数の承認待ちが同時に発生する場合 | 承認キューの管理・優先度設定 |
| 監査証跡 | 誰がいつ何を承認/修正したか | 操作ログの永続化 |
| 権限管理 | 承認権限を持つユーザーの制御 | Workspace のロール設定と連携 |
| SLA | 承認待ち時間の管理 | アラート通知の設定 |
4. 確定的ノードと確率的ノードの共存
4.1 なぜ区別が重要なのか
Workflow を設計する上で最も重要な認識は、ノードによって出力の予測可能性が根本的に異なるということだ:
| ノードタイプ | 出力の性質 | テスト方法 | エラー対応 |
|---|---|---|---|
| 確定的 (Code, IF/ELSE) | 同じ入力で同じ出力 | ユニットテスト可能 | バグとして修正 |
| 確率的 (LLM) | 同じ入力で異なる出力の可能性 | 統計的評価が必要 | プロンプト / パラメータ調整 |
この区別を意識しないと、以下の問題が発生する:
- LLM ノードの出力を後続処理が正確にパースできない(形式の不安定性)
- テスト時は動作したが本番で異なる出力が発生(再現性の欠如)
- エラーの原因が LLM の出力なのかロジックなのか切り分けできない
4.2 確定的ノードの役割
確定的ノードは、Workflow に予測可能性と制御可能性を提供する:
# Code ノードの例:LLM 出力の正規化
def main(llm_output: str) -> dict:
"""
LLM の確率的な出力を、確定的な構造に変換する。
これにより、後続ノードは安定したデータ形式に依存できる。
"""
import json
import re
# JSON ブロックの抽出(LLM が余分なテキストを付加する場合への対応)
json_match = re.search(r'\{[\s\S]*\}', llm_output)
if json_match:
try:
data = json.loads(json_match.group())
return {
"status": "success",
"category": data.get("category", "unknown"),
"confidence": float(data.get("confidence", 0)),
"summary": data.get("summary", "")
}
except (json.JSONDecodeError, ValueError):
pass
return {
"status": "parse_error",
"category": "unknown",
"confidence": 0.0,
"summary": llm_output[:200]
}
4.3 確率的ノードと確定的ノードの協調パターン
graph TB
subgraph "推奨パターン: 確率 then 確定 then 分岐"
A[LLM Node<br/>確率的: テキスト生成] --> B[Code Node<br/>確定的: 出力パース]
B --> C{IF/ELSE<br/>確定的: 条件分岐}
C -->|正常| D[次の処理]
C -->|パースエラー| E[エラー処理]
end
subgraph "アンチパターン: 確率の連鎖"
X[LLM Node 1] --> Y[LLM Node 2<br/>前段の出力形式に依存]
Y --> Z[LLM Node 3<br/>さらに不安定な入力]
end
style A fill:#f96,stroke:#333,color:#fff
style B fill:#69b,stroke:#333,color:#fff
style C fill:#69b,stroke:#333,color:#fff
style X fill:#f96,stroke:#333,color:#fff
style Y fill:#f96,stroke:#333,color:#fff
style Z fill:#f96,stroke:#333,color:#fff
設計原則: LLM(確率的)ノードの直後には、必ず Code(確定的)ノードを配置して出力を正規化する。これにより、確率的な出力の不安定性が Workflow 全体に伝播することを防ぐ。
5. エラーハンドリングパターン
5.1 Workflow におけるエラーの種類
| エラー種別 | 発生源 | 例 | 対応パターン |
|---|---|---|---|
| モデルエラー | LLM ノード | レート制限、タイムアウト、不正な出力 | リトライ / フォールバック |
| 外部 API エラー | HTTP Request | 接続タイムアウト、5xx エラー | リトライ / 代替エンドポイント |
| データエラー | Knowledge Retrieval | 検索結果0件、不正なデータ形式 | デフォルト値 / エスカレーション |
| ロジックエラー | Code / IF/ELSE | 型不一致、NULL 参照 | バグ修正(設計時に対応) |
| 人間介在タイムアウト | Human Input | 承認者が応答しない | エスカレーション / 自動承認 |
5.2 リトライパターン
graph TB
A[LLM Node] -->|成功| B[次のノード]
A -->|エラー| C{リトライ回数<br/>< 上限?}
C -->|Yes| D[待機<br/>指数バックオフ]
D --> A
C -->|No| E{フォールバック<br/>モデルあり?}
E -->|Yes| F[代替 LLM Node<br/>別モデルで実行]
F --> B
E -->|No| G[エラー終了<br/>/ エスカレーション]
5.3 フォールバックパターン
Provider 抽象層と組み合わせることで、モデルレベルのフォールバックが可能:
graph LR
subgraph "フォールバックチェーン"
P1[GPT-4o<br/>Primary] -->|エラー| P2[Claude Sonnet<br/>Secondary]
P2 -->|エラー| P3[GPT-4o-mini<br/>Tertiary]
P3 -->|エラー| ERR[エラー処理]
end
5.4 グレースフルデグラデーション
完全な失敗ではなく、品質を段階的に低下させながらもサービスを継続するパターン:
| 段階 | 状態 | 対応 |
|---|---|---|
| 正常 | 全ノード正常動作 | 最高品質の回答 |
| 軽度劣化 | Rerank 不可 | Vector Search のみで回答(精度やや低下) |
| 中度劣化 | Knowledge Base 検索不可 | LLM の内部知識のみで回答(注意書き付き) |
| 重度劣化 | Primary LLM 不可 | フォールバックモデルで回答 |
| サービス停止 | 全モデル不可 | 人間にエスカレーション |
6. Workflow 設計のベストプラクティス
6.1 ノード設計の原則
| 原則 | 説明 | 具体例 |
|---|---|---|
| 単一責任 | 1ノード = 1つの責任 | 「分類 + 要約」を1つの LLM に詰め込まない |
| 確率的出力の即時正規化 | LLM 直後に Code ノードを配置 | JSON パース・バリデーション |
| フェイルセーフ | すべてのパスにエラー処理を配置 | IF/ELSE でエラー分岐を設計 |
| 可観測性 | 中間結果をログ出力 | 重要なノードの出力を変数として保持 |
| 冪等性 | 同じ入力で再実行しても副作用がない | 外部 API 呼び出しの冪等性を確保 |
6.2 実践的な Workflow テンプレート
顧客問い合わせ自動応答
graph TB
START((Start<br/>問い合わせ受信)) --> CLASS[LLM Node<br/>問い合わせ分類]
CLASS --> PARSE[Code Node<br/>分類結果パース]
PARSE --> ROUTE{IF/ELSE<br/>カテゴリ判定}
ROUTE -->|FAQ| KB[Knowledge Retrieval<br/>FAQ検索]
ROUTE -->|技術問題| TECH[Knowledge Retrieval<br/>技術文書検索]
ROUTE -->|クレーム| HUMAN[Human Input<br/>担当者対応]
KB --> ANS[LLM Node<br/>回答生成]
TECH --> ANS
ANS --> CONF{IF/ELSE<br/>確信度チェック}
CONF -->|高| SEND[HTTP Request<br/>回答送信]
CONF -->|低| REVIEW[Human Input<br/>回答確認]
REVIEW -->|承認| SEND
REVIEW -->|修正| ANS
HUMAN --> SEND
SEND --> END((End))
6.3 アンチパターン
| アンチパターン | 問題 | 改善案 |
|---|---|---|
| LLM チェーン | 確率的ノードの連鎖で出力が不安定化 | 間に Code ノードを挟んで正規化 |
| モノリシック LLM | 1つの LLM に複数の責任を持たせる | タスクを分割して別ノードに |
| エラー無視 | エラーパスを設計しない | すべての分岐にエラー処理を配置 |
| 過剰自動化 | リスクの高い処理も自動実行 | HITL ノードでチェックポイントを設置 |
| ハードコーディング | プロンプトに固定値を埋め込む | 変数・環境変数を活用 |
7. 日本企業における Workflow 設計の考慮事項
7.1 稟議プロセスとの統合
日本企業特有の稟議制度は、HITL ノードと自然に対応する:
graph TB
A[AI ドラフト生成] --> B[Human Input<br/>起案者確認]
B --> C[Human Input<br/>課長承認]
C --> D{IF/ELSE<br/>金額判定}
D -->|100万円以上| E[Human Input<br/>部長承認]
D -->|100万円未満| F[処理実行]
E --> F
7.2 監査証跡の要件
日本の企業コンプライアンスでは、AI が生成した内容と人間が承認した記録の両方を保持する必要がある:
| 記録項目 | 保持すべきデータ | 保存期間の目安 |
|---|---|---|
| AI 生成ログ | プロンプト、モデル名、生成結果、タイムスタンプ | 5-10年 |
| 人間判断ログ | 承認者 ID、判断内容、タイムスタンプ | 5-10年 |
| 外部 API 呼び出し | リクエスト/レスポンス、エンドポイント | 3-5年 |
| エラーログ | エラー種別、発生時刻、影響範囲 | 1-3年 |
7.3 段階的導入戦略
| フェーズ | 自動化レベル | HITL 配置 | リスク管理 |
|---|---|---|---|
| Phase 1 | AI ドラフト + 全件人間確認 | 全ノードに HITL | 最小リスク・信頼構築 |
| Phase 2 | 高確信度は自動 + 低確信度は人間確認 | 条件付き HITL | 効率と安全のバランス |
| Phase 3 | 大部分自動 + 例外のみ人間対応 | エスカレーション型 HITL | 高効率・監視体制が前提 |
8. Workflow ノード設計の将来展望
8.1 Agent ノードとの融合
Dify は Workflow ノードと Agent 機能の融合を進めており、以下のような進化が見込まれる:
- Agent as Node: Workflow の一ノードとして Agent を埋め込み、自律的なタスク実行を可能にする
- 動的ルーティング: LLM が Workflow の分岐先を動的に決定する(従来の IF/ELSE を超えた柔軟性)
- マルチ Agent 協調: 複数の Agent が Workflow 内で協調して問題を解決
8.2 より高度なエラーハンドリング
- 自動リカバリ: エラー発生時に LLM がエラー原因を分析し、自動的に修復を試みる
- 学習型フォールバック: 過去のエラーパターンから最適なフォールバック戦略を学習
- 予防的チェック: ノード実行前に入力データの妥当性を AI が事前検証
まとめ
Dify Workflow のノード設計哲学は、以下の4つの柱に集約される:
-
コンポーザビリティ: 各ノードが独立した入出力インターフェースを持ち、自由に組み合わせ可能。変数スコーピングにより、ノード間の依存関係が明示的かつ最小限に保たれる。
-
Human-in-the-Loop: AI の自律性と人間の制御を共存させる。Approval、Correction、Escalation の3モードにより、リスクレベルに応じた人間介在を設計できる。
-
確定的ノードと確率的ノードの使い分け: LLM(確率的)の出力を Code(確定的)ノードで即座に正規化し、Workflow 全体の予測可能性を確保する。
-
エラーハンドリング: リトライ、フォールバック、グレースフルデグラデーション、エスカレーションの多層的なエラー対応により、本番環境での堅牢性を実現する。
成熟した Workflow とは、自動化比率が高いものではなく、「どこを自動化し、どこに人間を配置し、どこにエラー処理を置くか」の境界設計が明確なものである。Dify のノード設計哲学は、その境界設計を GUI 上で視覚的に行えるフレームワークを提供している。