🐿️

difyとmakeによる問い合わせワークフロー作成体験

に公開

目次


はじめに

本記事は問い合わせワークフローの作成体験を記載したものです。

社内の問い合わせやナレッジ探索を、チャットから自然に始められるようにしたい——そんな動機から、Slack でメンションされた質問を Make で受け、Dify のワークフロー(LLM)で回答し、Google スプレッドシートに質問と回答を追記する仕組みを構築しました。

本構成では、スプレッドシートへの書き込みは Make ではなく、Dify ワークフロー内(ノードの連携)で行います。 Make の役割は、Slack から受け取った内容を Dify API に渡すところまでが中心です。

本稿では、完成したワークフローの全体像と、実装・運用で実際に踏んだつまずきと対処を整理します。ノーコード寄りの連携(Make)と、プロンプトやツール連携を柔軟に組める Dify を組み合わせる構成は、社内 PoC や小規模運用にも向きやすいと感じています。

検証の過程では MAKE(旧 Integromat)を使ってノーコードで GPT Slack bot を作る(令和トラベル Tech Blog / Zenn) を主に参考にしました。

difyとmakeの簡単な説明

◼︎difyについて

Dify は、LLM アプリを構築・公開するためのプラットフォームです。チャットボットや ワークフロー(ノードをつないで LLM・知識ベース・ツール連携を定義)を GUI で組み、REST API や Webhook により 外部システムから呼び出せます。本記事では、質問文を受け取って 回答を生成し、あわせて Google スプレッドシートへの追記までを Dify ワークフロー内でまとめる想定です。

◼︎makeについて

Make(旧 Integromat)は、ブラウザ上で シナリオ(モジュールを線でつないだフロー)を組み、Webhook の受信HTTP で外部 API を呼び出すといった処理を、コードをほとんど書かずに自動化できるサービスです。本記事では、Slack から届いたイベントを受け取り、**Dify の API にリクエストを渡す「オーケストレーション」**の役に Make を使っています。


成果物・動作のイメージ(スクリーンショット用)

概要

slackへ問い合わせ内容を投稿してその内容をmakeが受け取り、difyへ連携するといった流れになります。
問い合わせ内容とLLMの回答はスプレッドシートへ蓄積されます。

<成果物のワークフロー>

全体ワークフロー図

要点:スプレッドシートの更新は、Make ではなく Dify ワークフロー内のノード(例: コード実行、Sheets 用の HTTP / 公式ツール等)で行います。

ざっくりしたデータの流れは次のとおりです。

  • Slack → Make: Event Subscriptions の Request URL に Make の Custom Webhook を指定し、app_mention(※本件では@verification_dify_and_make) などのイベントを受け取る。
  • Make → Dify: REST API(例: https://api.dify.ai/v1/workflows/run)を HTTP モジュールから呼び出し、入力変数にメンション文・チャンネル情報などを渡す。
  • Dify 内 → Google スプレッドシート: 本稿の構成では、BATCHGET で行数を取得 → 次行の範囲を計算 → 書き込みといった処理を Dify 上のノードで完結させる。Make から Sheets API を直接叩いていない点が、従来の「オーケストレーター全部入り Make」案と違います。

◼︎makeのワークフロー

◼︎difyのワークフロー(※1枚で撮りきれなかったので2枚に分けています)


<問い合わせ動作>

◼︎基本的な質問

①slackへメンション(@verification_dify_and_make)をつけて問い合わせ内容を投稿する

②slackのbotから回答が返信される

dify側のLLMで【回答】、【ナレッジの名称】、【判別】のフォーマットで回答するようにと指定しています。
※【回答】→単純なLLMからの回答内容
【ナレッジの名称】→difyが回答をする際に参照したナレッジ(Rag)の名称
【判別】→本番の問い合わせ運用を想定して回答に問題がなければAIの方で「通常」と記載してもらう。
ナレッジ等が不十分で回答精度が良くないと判断した場合は「要エスカレーション」と記載する。

※補足

この時dify側では
下記の「質問分類器」ノードでどういった類の質問なのかが分類されています。

今回の質問である「債権管理システムの目的を教えてください。」はクラス1の「仕様質問」に分類されます。

クラス1からナレッジノード(SPEC_FAQ)へ進みRAGを参照します。

「SPEC_FAQ」ノードには下記のRAG(テキストファイル)を参照するようにと設定してあります。

債権管理システム 仕様・FAQ

対象範囲
- 債権管理システムの目的・位置づけの説明
- 利用できる主な機能の一覧と概要
- 請求・入金・消込・残高・検索に関するよくある質問
- 公式マニュアルや運用ルールの代替にはならない一般的な説明

債権管理システムとは
債権管理システムは、請求情報・入金情報・消込情報を管理し、債権残高を把握するためのシステムです。

債権管理システムでできること
債権管理システムでは主に以下の機能を利用できます。

- 請求情報の登録・管理
- 入金情報の登録・管理
- 請求と入金の消込
- 未消込データの確認
- 債権残高の確認
- 消込結果の確認
- 債権情報の検索・一覧確認

主な機能一覧

1. 請求情報管理
請求先、請求金額、請求日、支払期日などの請求情報を登録・管理します。

2. 入金情報管理
入金日、入金額、入金元などの入金情報を登録・管理します。

3. 入金消込
請求情報と入金情報を紐付け、どの請求に対してどの入金が対応するかを管理します。

4. 未消込管理
まだ消込されていない請求や入金を確認できます。

5. 債権残高管理
請求・入金・消込結果をもとに、現在の債権残高を確認できます。

6. 検索・照会
取引先、請求日、入金日、ステータスなどの条件でデータを検索・照会できます。

よくある質問

Q. 債権管理システムの主な機能は何ですか?
A. 請求情報管理、入金情報管理、入金消込、未消込管理、債権残高管理、検索・照会です。

Q. 入金消込とは何ですか?
A. 入金消込とは、請求情報と入金情報を紐付ける処理です。

Q. 未消込データとは何ですか?
A. 未消込データとは、まだ請求と入金の紐付けが完了していないデータです。

Q. 債権残高は確認できますか?
A. はい、請求・入金・消込結果をもとに債権残高を確認できます。

「SPEC_FAQ」ノードから「LLM1」ノードへ進みます。
LLM1は下記のように設定してあります。

③質問と回答内容をスプレッドシートに蓄積


※以降よりこのスプレッドシートのことを「回答蓄積スプレッドシート」と呼称します。


実装のポイント

<Slack と Make:検証と通常イベントの分岐>

Slackに特定のメンションを指定するとMakeへ接続する為にSlackのEventSubscriptionsという機能を使用しています。

◼︎EventSubscriptionsのページ

EventSubscriptionsは Request URL 検証時に type:url_verificationchallengeキー の返却が必要となります。

◼︎サンプルJSON

{
    "token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
    "challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
    "type": "url_verification"
}

通常のメンションでは event_callback が届きます。
Makeの設定ではRouter の手前で片方だけ通すフィルタにすると、もう一方のイベントがすべて落ちるため、Router で先に分岐し、ルートごとにフィルタを付ける形が安全でした。
検証ルートでは Webhook 応答で challenge をそのまま返す、本処理ルートでは event_callback(必要なら app_mention)に限定する、というパターンです。

◼︎MakeのRouterの分岐

◼︎検証時の分岐設定(type=url_verificationであれば検証用ルート(4番のWebhook responseが実行されます。)

◼︎本処理ルートの分岐設定(type=event_callbackであれば本処理ルート(13番のWebhook response以降の処理が実行されます。))

<Dify:外部から叩くときは「公開」状態>

Make から Dify の API を呼ぶ場合、ワークフロー(アプリ)が未公開だと 404 や「トリガーが見つからない」といったエラーになることがあります。エディタ上のテストと、外部(Make)から見える公開状態は別なので、連携確認時は 公開済みかを必ず確認した方が良いです。

<Dify: コード実行ノード>

今回、コード実行ノードは以下の2箇所で使用しました。

  • コード実行/セルの取得
    • スプレッドシートの最終行を判別する為に使用
  • コード実行/回答を整形
    • LLMからの回答を整形してOUTPUTノードへ結果を渡す

各ノードはJavaScriptで実行されます。

①「コード実行/セルの取得」について

<なぜコード実行をするのか>

◼︎「コード実行/セル取得」のノード画像

「コード実行/セル取得」ノードでは回答蓄積スプレッドシートの最終行の次の行を取得します。

例:A2:B2まで記載されていれば実行結果は「A3:B3」が返却されます。

◼︎スプレッドシート(A2:B2まで記載済)

◼︎difyの詳細ログ

ログの「出力」結果よりA3:B3が返却されていることが確認できます。
この結果をもってして回答結果を最終行の次の行へ結果を記載させることができます。

◼︎ノードの内容

※補足
ssh_bodyは「BATACH GET SPREADSHEET」ノードの実行結果になります。
sheet_nameは「シート1」という文言が格納されている環境変数になります。
回答蓄積スプレッドシートがシート1を使用しているためこの文字列が入ります。

◼︎環境変数画面

◼︎JavaScriptソース

function main({ ssh_body, sheet_name }) {
  const values = ssh_body?.[0]?.valueRanges?.[0]?.values ?? [];
  const nextRow = values.length + 1;
  const rangeOnly = `A${nextRow}:B${nextRow}`;
  return { result: rangeOnly };
}

②「コード実行/回答を整形」について

<なぜコード実行をするのか>

◼︎「コード実行/回答を整形」のノード画像

◼︎ノードの内容

LLMからの回答をそのままスプレッドシートへ書き込み(BATACH UPDATE SPREDSHEETノードの実行)をしようとするとjsonのフォーマットになっていないとエラーになることがありました。

◼︎エラー内容

Output result must be a string, got list

原因としてはLLMの回答結果が配列になっていたのでjsonの文字列に変換する必要がありました。

そこで下記のようなJavaScriptを実行して必ずjson文字列に変換させるようにしました。

function main({ query, text, cell }) {
  const sheet = "シート1"; // 実際のシート名に変更
  const range = `${sheet}!${cell}`;

  /** Slack のユーザ/ボットメンション `<@U0123ABC>` を除去 */
  const stripSlackMentions = (s) =>
    (s ?? "")
      .toString()
      .replace(/<@[^>]+>/g, "") // `<@...>` 全体(将来の形式にも多少強い)
      .trim();

  const q = stripSlackMentions(query);
  const t = (text ?? "").toString().trim();

  const data = [
    {
      range,
      values: [[q, t]],
    },
  ];

  return {
    result: JSON.stringify(data),
  };
}

まとめ

Slack × Make × Dify とスプレッドシートを組み合わせると、チャット起点の問い合わせ構造化されたログとして残しやすくなります。Sheets 更新を Dify 内に閉じると、Make は「Slack から Dify へ橋渡し」に専念でき、LLM 周りのロジックを一箇所でメンテしやすい、という利点もあります。

少なくとも Slack のイベント型の違いDify の公開状態Sheets の「次行」計算と JSON の形は、早い段階で押さえておくと安心です。

同様の構成を検討されている方の参考になれば幸いです。


参考リンク


91works Tech Blog

Discussion