⚠️ 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.md Step 1, 3, 4, 5
  • 退役対応表: langgraph_migration/migration_overview.md

Dify 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 上にあるもの

名前種別役割状態
TriageChatflowADR 要否 / Mode 判定プロンプト v0.2 動作中
ScoringWorkflow50 点満点採点プロンプト 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 では不要。

観点WorkflowChatflow
実行モデルワンショットマルチターン会話
履歴なし自動保持
API 実行構造化入出力で扱いやすいチャット履歴前提
Phase 2a 適合度✅ 最適× オーバースペック
Phase 2b(Socratic 問診)別 Chatflow を作って前段に挟む方針

2. 事前準備

項目必要な値取得タイミング
Cloudflare Worker URLhttps://decision-pipeline.bizlp.workers.devmain スペースが Worker 実装後
HMAC Secretopenssl 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 作成

  1. Dify Studio を開く(https://cloud.dify.ai/apps)
  2. 右上「Create App」→ 「Workflow」を選択
  3. App 名: Decision Pipeline v2a
  4. アイコン: 任意(区別しやすいもの)
  5. 「Create」

→ 空の Workflow キャンバスが開く


5. Step 2: 環境変数登録(Workspace Settings)

Workspace 単位の環境変数(v2a 以外の App でも参照可)を登録。

  1. 左下のアバターアイコン → 「Settings」 → 「Environment Variables
  2. 以下を追加:
NameValueType
CLOUDFLARE_WORKER_URL(Worker 実装後に確定。一旦 https://placeholder.example.com で OK)String
DIFY_WEBHOOK_SECRETopenssl rand -hex 32 の出力値Secret(マスクされる)

Secret 型を選ぶと Workflow 実行ログにも値が表示されない。


6. Step 3: Start ノード設定

キャンバスにデフォルトで配置されている Start ノード:

  1. ノードクリック → 右パネル「Variables
  2. 「+ Add」で以下を追加:
Variable NameTypeRequiredMax length
user_inputParagraphYes5000

Paragraph 型は複数行入力可。Markdown ドラフトをそのまま受け取れる。


7. Step 4: N1 — Triage ノード追加

  1. Start ノード右の「+」→ 「LLM」を選択
  2. ノード名(左上): N1: Triage
  3. Model: Claude Sonnet 4.6(または GPT-4o)
  4. Model Parameters → Temperature: 0.0、Max Tokens: 500
  5. System Prompt: 既存 Triage Chatflow からプロンプト v0.2 をコピペ(prompts/01_triage.md §システムプロンプト本体)
    • プロンプト末尾の {{user_input}}{{#start.user_input#}} に書き換え
  6. 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 要否分岐

  1. N1 の右「+」→ 「If/Else
  2. ノード名: N2: ADR 要否分岐
  3. Conditions:
    IF  N1.structured_output.is_adr_worthy  is  true
    
  4. True 分岐 → 後続(N4 へ進む。一旦未接続でも可)
  5. False 分岐 → N3(次に作る End ノードへ)

9. Step 6: N3 — 対象外 End ノード

  1. N2 の False 側「+」→ 「End
  2. ノード名: N3: 対象外終了
  3. Output Variables:
    • result_md (String) = 下記テンプレ
## 🛑 ADR 対象外と判定されました

**判定理由:**
{{#N1.structured_output.reason#}}

通常の PR 概要欄に記載してください。設計判断を伴う場合は、より具体的に
「何が問題で、どの選択肢を比較したか」を書き、再度 Decision Pipeline に
投入することを推奨します。

10. Step 7: N4 — Scoring ノード追加

N2 の True 側に LLM ノード:

  1. 「+」→ 「LLM」
  2. ノード名: N4: Scoring
  3. Model: Claude Sonnet 4.6
  4. Temperature: 0.0、Max Tokens: 2000
  5. System Prompt: 既存 Scoring Workflow からプロンプト v0.1 をコピペ(prompts/02_scoring.md
    • 入力部分を以下のように書き換え:
      [起案者の入力]
      {{#start.user_input#}}
      
      [モード]
      {{#N1.structured_output.mode#}}
      
  6. 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 分岐

  1. N4 後ろに「If/Else」
  2. ノード名: N5: pass 分岐
  3. Conditions: N4.structured_output.pass is true
  4. True → N7(次のステップ)、False → N6

12. Step 9: N6 — 差戻し End ノード

N5 の False 側に End ノード:

  1. ノード名: N6: 差戻し終了
  2. Output Variablesresult_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 ノード:

  1. ノード名: N7: ADR 本文生成
  2. Model: Claude Sonnet 4.6
  3. Temperature: 0.2、Max Tokens: 2000
  4. 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 部分のみ使用)
  1. Output Variable: adr_body_md (String)
    • Structured Output は OFF(プレーン Markdown を返す)

14. Step 11: N8 — Slug 生成 LLM

  1. N7 の後ろに LLM ノード
  2. ノード名: N8: Slug 生成
  3. Model: Claude Haiku 4.5
  4. Temperature: 0.0、Max Tokens: 50
  5. System Prompt:
ADR タイトルから snake_case のスラッグを生成せよ。

[ルール]
- 小文字英数字とアンダースコアのみ
- 20 文字以内
- 日本語は意味を保ったまま英訳
- 動詞は省略可("add_xxx" より "xxx" 推奨)
- 一般的な略語は使用可(cap, fn, ref, ddl など)

[出力フォーマット]
スラッグのみを返す。説明・コードブロック禁止。

[入力タイトル]
{{#N1.structured_output.suggested_title#}}
  1. Output Variable: adr_slug (String)

15. Step 12: N9 — Payload 構築 + HMAC 署名

  1. N8 の後ろに「Code」ノードを追加
  2. ノード名: N9: Payload 構築
  3. Language: Python 3
  4. 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,
    }
  1. 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#}}
  1. Output Variables:
    • payload_json (String)
    • signature (String)

重要: json.dumpsseparators=(",", ":")空白を含めない 標準形式。Worker 側も同じ形式でハッシュ計算するため食い違いが起きない。


16. Step 13: N10 — HTTP リクエスト

  1. N9 の後ろに「HTTP Request」ノード
  2. ノード名: N10: GitHub 通知
  3. Method: POST
  4. URL: {{#env.CLOUDFLARE_WORKER_URL#}}/dispatch
  5. Headers (キー・値で追加):
    Content-Type: application/json
    X-Dify-Signature: {{#N9.signature#}}
    
  6. Body:
    • Type: Raw
    • Content-Type: application/json
    • Body content: {{#N9.payload_json#}}
  7. Timeout: 30 秒
  8. Retry: 1 回(5xx のみ)

17. Step 14: N11 — HTTP 成功分岐

  1. If/Else ノード
  2. ノード名: N11: HTTP 成功分岐
  3. Conditions:
    IF  N10.status_code  ==  200
    
  4. 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 側で意図的に rejectN12 で 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 subscriptableN4 が pass=true でも一部フィールド欠落の可能性。Scoring プロンプトの JSON Schema 必須フィールドを再確認
N10 が常に 401 INVALID_SIGNATUREHMAC Secret が Worker と Dify で完全一致しているか / payload_jsonseparators=(",", ":") で生成しているか / Worker 側も同じ separator を使っているか
N10 が常に 400 SCHEMA_INVALIDWorker のログで欠落フィールドを確認、N9 の payload 構築漏れ
{{#sys.user_name#}} が空Dify SSO 設定で表示名が登録されているか / 開発時は固定値で代替
Run History に payload_json が表示されないN9 の Output Variables に payload_json を含めているか確認

23. Publish と URL 共有

  1. キャンバス右上「Publish」→ バージョン名を入力(例: v0.1
  2. 「Publish to API」を選択(業務委託者には API 経由で叩かせる場合)
  3. または「Embedded」「Web App」モードで URL を発行
  4. 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 新規構築手順)