Dify Workflow Phase 2a セットアップ手順
⚠️ Status: Retired (2026-05-08, ADR-0019)
本セットアップ手順は ADR-0019 により退役。Dify Cloud 上でのワークフロー構築は不要となり、Cloudflare Workers + GCP Cloud Run (LiteLLM Gateway) 構成への移行手順を新規参照すること。
- 退役理由:
docs/adr/0019-drp-migration.md- 後継デプロイ手順:
langgraph_migration/main_workspace_checklist.mdStep 1, 3, 4, 5- 退役対応表:
langgraph_migration/migration_overview.mdDify Cloud 上の既存 Workflow は Archive(YAML エクスポートで保存、削除はしない)として ADR-0019「撤退条件」に備える。
Status: Retired (2026-05-08)
対象: Phase 2a で Dify ワークフローを実装するオーナー — 退役のため新規実行不要
前提読み物: dify_workflow_spec.md(仕様書本体, 同じく Retired)
1. 前提・現状
1.1 Phase 1 で Dify 上にあるもの
| 名前 | 種別 | 役割 | 状態 |
|---|---|---|---|
| Triage | Chatflow | ADR 要否 / Mode 判定 | プロンプト v0.2 動作中 |
| Scoring | Workflow | 50 点満点採点 | プロンプト v0.1 動作中 |
両 App は 未連結。それぞれ単独で TC-01〜08 を流して検証した状態。
1.2 v2a で作るもの
新規 Workflow App: Decision Pipeline v2a
- 既存 Triage Chatflow / Scoring Workflow は そのまま残す(リファレンスとして)
- v2a App は両者からプロンプトをコピペで流用して新規構築
- v2a 完成後、Phase 1 の 2 App はアーカイブせず保持(プロンプト改訂時の単体検証用)
1.3 なぜ Chatflow ではなく Workflow か
Phase 2a の処理は「起案ドラフトを 1 回投げる → 判定・採点・ADR 生成 → PR 作成 → 結果表示」の ワンショット完結型。Chatflow は対話履歴の永続化・マルチターン会話に強みがあるが、Phase 2a では不要。
| 観点 | Workflow | Chatflow |
|---|---|---|
| 実行モデル | ワンショット | マルチターン会話 |
| 履歴 | なし | 自動保持 |
| API 実行 | 構造化入出力で扱いやすい | チャット履歴前提 |
| Phase 2a 適合度 | ✅ 最適 | × オーバースペック |
| Phase 2b(Socratic 問診) | 別 Chatflow を作って前段に挟む方針 | – |
2. 事前準備
| 項目 | 必要な値 | 取得タイミング |
|---|---|---|
| Cloudflare Worker URL | https://decision-pipeline.bizlp.workers.dev 等 | main スペースが Worker 実装後 |
| HMAC Secret | openssl rand -hex 32 で生成(32 バイト=64 hex 文字) | v2a 着手前にオーナーが生成 |
Dify と Cloudflare Worker で 同一の HMAC Secret を共有する。1Password 等に保管。
3. 用語整理
Dify Workspace(テナント / 契約単位 / チーム)
└─ Studio(アプリ一覧画面・以前「ダッシュボード」と表記)
├─ Triage (Chatflow) ← Phase 1 で作成済み
├─ Scoring (Workflow) ← Phase 1 で作成済み
└─ Decision Pipeline v2a (Workflow) ← 今回新規作成
「Dify ダッシュボード」「Dify Studio」「アプリ一覧画面」は全て同じものを指す(Dify 公式名称は Studio)。
4. Step 1: 新規 Workflow App 作成
- Dify Studio を開く(https://cloud.dify.ai/apps)
- 右上「Create App」→ 「Workflow」を選択
- App 名:
Decision Pipeline v2a - アイコン: 任意(区別しやすいもの)
- 「Create」
→ 空の Workflow キャンバスが開く
5. Step 2: 環境変数登録(Workspace Settings)
Workspace 単位の環境変数(v2a 以外の App でも参照可)を登録。
- 左下のアバターアイコン → 「Settings」 → 「Environment Variables」
- 以下を追加:
| Name | Value | Type |
|---|---|---|
CLOUDFLARE_WORKER_URL | (Worker 実装後に確定。一旦 https://placeholder.example.com で OK) | String |
DIFY_WEBHOOK_SECRET | openssl rand -hex 32 の出力値 | Secret(マスクされる) |
Secret 型を選ぶと Workflow 実行ログにも値が表示されない。
6. Step 3: Start ノード設定
キャンバスにデフォルトで配置されている Start ノード:
- ノードクリック → 右パネル「Variables」
- 「+ Add」で以下を追加:
| Variable Name | Type | Required | Max length |
|---|---|---|---|
user_input | Paragraph | Yes | 5000 |
Paragraph 型は複数行入力可。Markdown ドラフトをそのまま受け取れる。
7. Step 4: N1 — Triage ノード追加
- Start ノード右の「+」→ 「LLM」を選択
- ノード名(左上):
N1: Triage - Model: Claude Sonnet 4.6(または GPT-4o)
- Model Parameters → Temperature:
0.0、Max Tokens:500 - System Prompt: 既存 Triage Chatflow からプロンプト v0.2 をコピペ(
prompts/01_triage.md§システムプロンプト本体)- プロンプト末尾の
{{user_input}}を{{#start.user_input#}}に書き換え
- プロンプト末尾の
- Output Variables:
- 「STRUCTURED OUTPUT」を ON
- JSON Schema として下記を設定:
{
"type": "object",
"properties": {
"is_adr_worthy": { "type": "boolean" },
"mode": { "type": "string", "enum": ["Light", "Standard", "Critical"] },
"reason": { "type": "string" },
"suggested_title": { "type": "string" }
},
"required": ["is_adr_worthy", "reason"]
}
Structured Output が有効な Dify バージョンを使用すること。利用不可の場合は通常の text 出力にして N9 (Code Node) で
json.loadsする。
8. Step 5: N2 — ADR 要否分岐
- N1 の右「+」→ 「If/Else」
- ノード名:
N2: ADR 要否分岐 - Conditions:
IF N1.structured_output.is_adr_worthy is true - True 分岐 → 後続(N4 へ進む。一旦未接続でも可)
- False 分岐 → N3(次に作る End ノードへ)
9. Step 6: N3 — 対象外 End ノード
- N2 の False 側「+」→ 「End」
- ノード名:
N3: 対象外終了 - Output Variables:
result_md(String) = 下記テンプレ
## 🛑 ADR 対象外と判定されました
**判定理由:**
{{#N1.structured_output.reason#}}
通常の PR 概要欄に記載してください。設計判断を伴う場合は、より具体的に
「何が問題で、どの選択肢を比較したか」を書き、再度 Decision Pipeline に
投入することを推奨します。
10. Step 7: N4 — Scoring ノード追加
N2 の True 側に LLM ノード:
- 「+」→ 「LLM」
- ノード名:
N4: Scoring - Model: Claude Sonnet 4.6
- Temperature: 0.0、Max Tokens: 2000
- System Prompt: 既存 Scoring Workflow からプロンプト v0.1 をコピペ(
prompts/02_scoring.md)- 入力部分を以下のように書き換え:
[起案者の入力] {{#start.user_input#}} [モード] {{#N1.structured_output.mode#}}
- 入力部分を以下のように書き換え:
- Output Variables → Structured Output ON、JSON Schema:
{
"type": "object",
"properties": {
"scores": { "type": "object" },
"total": { "type": "integer" },
"mode": { "type": "string" },
"threshold": { "type": "integer" },
"pass": { "type": "boolean" },
"weakest_items": { "type": "array", "items": { "type": "string" } },
"feedback_for_user": { "type": "string" },
"score_summary_md": { "type": "string" }
},
"required": ["total", "pass", "threshold", "score_summary_md"]
}
11. Step 8: N5 — pass 分岐
- N4 後ろに「If/Else」
- ノード名:
N5: pass 分岐 - Conditions:
N4.structured_output.pass is true - True → N7(次のステップ)、False → N6
12. Step 9: N6 — 差戻し End ノード
N5 の False 側に End ノード:
- ノード名:
N6: 差戻し終了 - Output Variables →
result_md(String):
## 📉 品質スコア {{#N4.structured_output.total#}}/50(閾値 {{#N4.structured_output.threshold#}} / Mode: {{#N4.structured_output.mode#}})
スコアが Mode の閾値に届かなかったため、PR は作成されませんでした。
### 改善ポイント
{{#N4.structured_output.feedback_for_user#}}
### スコア内訳
{{#N4.structured_output.score_summary_md#}}
### 弱点項目
{{#N4.structured_output.weakest_items#}}
このフィードバックを参考に起案ドラフトを書き直し、再度投入してください。
13. Step 10: N7 — ADR 本文生成 LLM
N5 の True 側に LLM ノード:
- ノード名:
N7: ADR 本文生成 - Model: Claude Sonnet 4.6
- Temperature: 0.2、Max Tokens: 2000
- System Prompt:
あなたは ADR 起草アシスタントである。起案者の入力(user_input)と Triage / Scoring 結果を読み、
ADR テンプレートに準拠した Markdown を生成せよ。
[ルール]
1. テンプレートのセクション順を厳守: コンテキスト → 決定 → 検討した代替案 → 影響 → 撤退条件 → 参照
2. ヘッダ部分は以下の形式で開始(ADR 番号は XXX のまま、Actions 側で置換される):
# ADR-XXX: {triage.suggested_title}
- **Status**: Proposed
- **Mode**: {triage.mode}
- **起案者**: {submitter.name}
- **起案日**: {today}
- **承認日**: -
3. 起案者の言葉を尊重し、要約しすぎない。論理的な接続詞のみ追加してよい。
4. 起案者が書いていない情報を推測で補わない。「(記載なし)」と明示する。
5. Light Mode のみ「検討した代替案」「撤退条件」を省略可。Standard / Critical では空でも見出しを残す。
6. 影響セクションは正/負/リスクの 3 つのサブ箇条書きに整理する。
7. Markdown のみを出力。前置き・解説禁止。
[入力]
- user_input:
{{#start.user_input#}}
- triage.suggested_title: {{#N1.structured_output.suggested_title#}}
- triage.mode: {{#N1.structured_output.mode#}}
- triage.reason: {{#N1.structured_output.reason#}}
- submitter.name: {{#sys.user_name#}}
- today: {{#sys.current_time#}}(YYYY-MM-DD 部分のみ使用)
- Output Variable:
adr_body_md(String)- Structured Output は OFF(プレーン Markdown を返す)
14. Step 11: N8 — Slug 生成 LLM
- N7 の後ろに LLM ノード
- ノード名:
N8: Slug 生成 - Model: Claude Haiku 4.5
- Temperature: 0.0、Max Tokens: 50
- System Prompt:
ADR タイトルから snake_case のスラッグを生成せよ。
[ルール]
- 小文字英数字とアンダースコアのみ
- 20 文字以内
- 日本語は意味を保ったまま英訳
- 動詞は省略可("add_xxx" より "xxx" 推奨)
- 一般的な略語は使用可(cap, fn, ref, ddl など)
[出力フォーマット]
スラッグのみを返す。説明・コードブロック禁止。
[入力タイトル]
{{#N1.structured_output.suggested_title#}}
- Output Variable:
adr_slug(String)
15. Step 12: N9 — Payload 構築 + HMAC 署名
- N8 の後ろに「Code」ノードを追加
- ノード名:
N9: Payload 構築 - Language: Python 3
- Code:
import json
import hmac
import hashlib
from datetime import datetime, timezone
def main(
user_input: str,
submitter_name: str,
submitter_id: str,
triage_is_adr_worthy: bool,
triage_mode: str,
triage_title: str,
triage_reason: str,
scoring_scores: dict,
scoring_total: int,
scoring_pass: bool,
scoring_summary_md: str,
adr_body_md: str,
adr_slug: str,
secret: str,
) -> dict:
payload = {
"schema_version": "1.0",
"submitter": {
"name": submitter_name,
"dify_user_id": submitter_id,
},
"user_input": user_input,
"triage": {
"is_adr_worthy": triage_is_adr_worthy,
"mode": triage_mode,
"title_summary": triage_title,
"reason": triage_reason,
},
"scoring": {
"scores": scoring_scores,
"total": scoring_total,
"pass": scoring_pass,
"score_summary_md": scoring_summary_md,
},
"adr_draft": {
"slug": adr_slug,
"body_md": adr_body_md,
},
"submitted_at": datetime.now(timezone.utc).isoformat(),
}
payload_json = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
signature = hmac.new(
secret.encode("utf-8"),
payload_json.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return {
"payload_json": payload_json,
"signature": signature,
}
- Input Variables マッピング:
| 関数引数 | Dify 側ソース |
|---|---|
user_input | {{#start.user_input#}} |
submitter_name | {{#sys.user_name#}} |
submitter_id | {{#sys.user_id#}} |
triage_is_adr_worthy | {{#N1.structured_output.is_adr_worthy#}} |
triage_mode | {{#N1.structured_output.mode#}} |
triage_title | {{#N1.structured_output.suggested_title#}} |
triage_reason | {{#N1.structured_output.reason#}} |
scoring_scores | {{#N4.structured_output.scores#}} |
scoring_total | {{#N4.structured_output.total#}} |
scoring_pass | {{#N4.structured_output.pass#}} |
scoring_summary_md | {{#N4.structured_output.score_summary_md#}} |
adr_body_md | {{#N7.text#}} |
adr_slug | {{#N8.text#}} |
secret | {{#env.DIFY_WEBHOOK_SECRET#}} |
- Output Variables:
payload_json(String)signature(String)
重要:
json.dumpsのseparators=(",", ":")は 空白を含めない 標準形式。Worker 側も同じ形式でハッシュ計算するため食い違いが起きない。
16. Step 13: N10 — HTTP リクエスト
- N9 の後ろに「HTTP Request」ノード
- ノード名:
N10: GitHub 通知 - Method: POST
- URL:
{{#env.CLOUDFLARE_WORKER_URL#}}/dispatch - Headers (キー・値で追加):
Content-Type: application/json X-Dify-Signature: {{#N9.signature#}} - Body:
- Type: Raw
- Content-Type:
application/json - Body content:
{{#N9.payload_json#}}
- Timeout: 30 秒
- Retry: 1 回(5xx のみ)
17. Step 14: N11 — HTTP 成功分岐
- If/Else ノード
- ノード名:
N11: HTTP 成功分岐 - Conditions:
IF N10.status_code == 200 - True → N13、False → N12
18. Step 15: N12 — HTTP エラー End
## ⚠️ PR 作成に失敗しました
GitHub への送信時にエラーが発生しました。
- **HTTP ステータス**: {{#N10.status_code#}}
- **エラー詳細**: {{#N10.body#}}
時間をおいて再度投入してください。問題が継続する場合はオーナーに連絡してください。
19. Step 16: N13 — 成功 End
## ✅ PR が作成されました
- **PR URL**: {{#N10.body.pr_url#}}
- **ADR 番号**: {{#N10.body.adr_number#}}
- **ブランチ**: {{#N10.body.branch#}}
- **Mode**: {{#N1.structured_output.mode#}}
- **スコア**: {{#N4.structured_output.total#}}/50
オーナーがレビューします。コメントは GitHub または Slack で対応してください。
### スコア内訳
{{#N4.structured_output.score_summary_md#}}
N10 のレスポンスボディを構造化フィールドとして参照したい場合、Dify の HTTP ノードの「Response → Parse JSON」を ON にする必要がある。OFF の場合は文字列として
{{#N10.body#}}を表示する形にフォールバック。
20. ノード接続全体図
[Start]
↓
[N1 Triage]
↓
[N2 If/Else: is_adr_worthy?]
├ false → [N3 End: 対象外]
└ true ↓
[N4 Scoring]
↓
[N5 If/Else: pass?]
├ false → [N6 End: 差戻し]
└ true ↓
[N7 ADR 本文生成]
↓
[N8 Slug 生成]
↓
[N9 Payload 構築]
↓
[N10 HTTP リクエスト]
↓
[N11 If/Else: HTTP 成功?]
├ false → [N12 End: HTTP エラー]
└ true → [N13 End: 成功]
接続忘れがないかは「Publish」ボタン押下時の Validation で検出される。
21. 動作確認テスト
main スペースが Cloudflare Worker と GitHub Actions を実装してから:
| テスト | 入力 | 期待 |
|---|---|---|
| TC-01 | 「ボタンの色を青→緑」 | N3 で対象外表示。N4 が走らない |
| TC-06 | 「ADR ストアに何か入れたい」(曖昧) | N6 で差戻し。スコア < 40 で N10 が走らない |
| TC-07 | 「領収書 OCR を Gemini→Claude へ」(高品質) | N13 で PR URL 表示 |
| TC-08 | 境界線スコア(40 ぴったり) | N5 で True、PR 作成される |
| エラー: Worker 停止 | TC-07 を Worker 停止状態で | N12 でタイムアウト表示 |
| エラー: 署名不一致 | Worker 側で意図的に reject | N12 で 401 表示 |
各テストで Dify の「Run History」を開き、「想定外の枝に進んでいないか」を確認。
22. トラブルシュート
| 症状 | 確認ポイント |
|---|---|
| N1/N4 で Structured Output が機能しない | Dify バージョンが対応しているか / フォールバックとして JSON 文字列出力 + N9 で json.loads |
N7/N8 の出力に余計な前置き(Here is...)が入る | プロンプトで「Markdown のみ」「説明禁止」を強調、Few-shot を増やす |
N9 が 'NoneType' object is not subscriptable | N4 が pass=true でも一部フィールド欠落の可能性。Scoring プロンプトの JSON Schema 必須フィールドを再確認 |
| N10 が常に 401 INVALID_SIGNATURE | HMAC Secret が Worker と Dify で完全一致しているか / payload_json を separators=(",", ":") で生成しているか / Worker 側も同じ separator を使っているか |
| N10 が常に 400 SCHEMA_INVALID | Worker のログで欠落フィールドを確認、N9 の payload 構築漏れ |
{{#sys.user_name#}} が空 | Dify SSO 設定で表示名が登録されているか / 開発時は固定値で代替 |
| Run History に payload_json が表示されない | N9 の Output Variables に payload_json を含めているか確認 |
23. Publish と URL 共有
- キャンバス右上「Publish」→ バージョン名を入力(例:
v0.1) - 「Publish to API」を選択(業務委託者には API 経由で叩かせる場合)
- または「Embedded」「Web App」モードで URL を発行
- URL を業務委託者の操作マニュアル (
operator_guide.md) に貼る
Phase 2a では業務委託者の人数が限定的なので、まずは Web App URL 共有で十分。API 経由は Phase 2b 以降で検討。
24. Phase 1 App との関係整理
| App 名 | 用途 | 削除タイミング |
|---|---|---|
| Triage (Chatflow) | プロンプト改訂時の単体検証 | プロンプト v1.0 確定後に判断 |
| Scoring (Workflow) | 同上 | 同上 |
| Decision Pipeline v2a | 本番運用 | – |
Phase 1 App でプロンプト改善 → 動作確認 → v2a の N1/N4 にコピペで反映、というフローで運用する。
変更履歴
| 日時 | 変更内容 |
|---|---|
| 2026-05-07 | 初版作成(Phase 1 実態を踏まえた v2a 新規構築手順) |