見出し画像

【Dify×GAS】スプレットシートからDifyを呼び出してアンケート分析するアプリを作ってみた【DifyAPI呼び出し解説】

割引あり

Difyとは、ノーコードでAIアプリケーションを開発できるオープンソースのプラットフォームです。

今回はスプレットシートからDifyを呼び出してアンケート分析するアプリを作ってみました。

Difyでは作成したアプリをAPIとして呼び出せます。この機能を使えばGASからDifyで作ったアプリを呼び出せます。

ただ、API呼び出しの方法には以下の2種類があり、公式が推奨している方法(ストリーミングモード)は、やや複雑です。

ストリーミングモード:ストリーミング モード (推奨) は、SSE ( Server-Sent Events )を介してタイプライターのような出力を実装します。
 
ブロッキングモード:実行完了後に結果を返します。(処理が長い場合はリクエストが中断される可能性があります) Cloudflare の制限により、100 秒後にリクエストは返されずに中断されます。

Dify API呼び出しマニュアルから引用

この記事では、GASからDifyのアプリをAPI呼び出しする方法として、ストリーミングモード、ブロッキングモード2パターンの方法を紹介します。

Pythonなどの他の言語にも応用できる方法なので、Difyで作ったアプリをAPI呼び出ししたいと考えている人はぜひ参考にしてみてください。

また、記事の最後には自身のDify環境にインポートするだけで使えるDSLファイルと、GASコード全文を添付しています。

Difyって何?どうやって始めるの?という方は、以下の記事で解説しているので合わせて参考にしてみてください。


アプリケーションの概要

まずは今回作成したアプリケーションの概要を説明します。

今回は「スプレットシートに記載したアンケート内容とアンケート結果をDifyに送信してアンケート分析を行い、分析結果をスプレットシート記入する」というアプリを作りました。

全体的な流れは以下のとおりです。

  1. スプレットシートにアンケート内容とアンケート内容を記入する

  2. アンケート分析を開始する(GASからDifyをAPI呼び出しする)

  3. アンケート分析結果がスプレットシートに出力される

それぞれの流れについて簡単に紹介します。

①スプレットシートにはアンケート内容とアンケート結果を記入しておきます。

アンケート内容


アンケート結果

②スプレットシートのメニューを開いて「分析を実行」を押すと、Difyのワークフローが呼び出されます。

分析を実行

③分析実行後、アンケート分析結果が出力されます。

アンケート分析結果

アプリ作成の流れ

今回は以下の流れでアプリケーションを作成します。

  1. Difyでのワークフロー構築

  2. GASの実装

まずはGASから呼び出すDifyのアプリを構築します。その後、GASの処理を構築するという流れです。

それでは1つずつ作り方を説明します。

Difyでのワークフロー構築

まずは、Difyのワークフローを作成します。

今回は作成するワークフローはかなりシンプルです。

今回作成するワークフロー

以下ではワークフローの作成方法を説明します。

①開始ブロックでアンケート内容とアンケート結果を受け付ける

開始ブロックでは、アンケート内容とアンケート結果を受け取る入力フィールドを用意します。

開始ブロック

入力フィールドの中身は以下のとおりです。

入力フィールド

フィールドタイプは「段落」、最大長は空欄にしておくことで、文字数の制限なく長文を入力できます。
必須オプションは自由ですが今回はオンにしておきました。

②LLMでアンケート分析を行う

次にLLMの処理でアンケート分析を行います。

LLMブロック

今回設定したプロンプトは以下のとおりです。

SYSTEMプロンプト

あなたは優秀な市場分析者です。
あなたの役割はアンケートの結果を定量分析することです。

USERプロンプト

#指示
以下のアンケートデータに基づいて、各質問に対する平均スコアを計算してください。
異なる質問項目間の相関関係や回答者属性と回答内容のクロス集計も行い、定量的に分析してください。
自由記述の設問がある場合は、回答内容の主要なテーマ、傾向、感情を特定してください。回答者の意見や提案を要約し、サービス改善のための具体的な洞察を提供してください。
最後にすべての質問項目と回答結果を踏まえた上でのアンケート分析結果を出力してください。

#アンケート内容
[アンケート内容の入力フィールド]
#アンケート結果
[アンケート結果の入力フィールド]
 #出力 各質問に対する平均スコア
分析結果の総評

[アンケート内容の入力フィールド]、[アンケート結果の入力フィールド]にはそれぞれ開始ブロックで設定した入力フィールドを設定してください。

USERプロンプト

③終了ブロックで分析結果を返却する

最後に分析結果を返却すればワークフローは完成です。

終了ブロック

今回のメインはGASからDifyをAPI呼び出しする処理なので、Dify側のワークフローはシンプルなものになっています。

記事の最後にDifyにインポートするだけで使えるDSLファイルも添付しているので、ぜひ参考にしてみてください。

Google Apps Script (GAS) の実装

それではいよいよ本題です。

先ほど作成したDifyのワークフローをGASからAPI呼び出しする処理を実装していきます。

Difyで作成したアプリをAPI呼び出しするには以下2つの方法があります。

  • ストリーミングモード

  • ブロッキングモード

ストリーミングモードの方が長文に対応していて安定しているというメリットがありますが呼び出し処理は複雑になります。

今回は両方のやり方を紹介するので、利用シーンに応じて呼び出し方を使い分けられるようになりましょう。

①共通処理

まずは共通処理です。

いずれもDifyのAPI呼び出しには関係がない処理ですが、今回のアプリを実装する上では必要な処理となります。

同じアプリを再現しようとしている人は、コピペで自身のGASに貼り付けて使用してください。

共通処理としては以下4つの関数を定義します。

  • スプレットシートを開いたときに「分析を実行」のカスタムメニューを作成する(onOpen関数

  • 「アンケート内容」シートと「アンケート結果」シートの内容を読み込んでDifyの処理を呼び出す(runSurveyAnalysis関数

  • スプレットシートのデータをすべて読み込む。runSurveyAnalysis関数から呼び出される。(getDataFromSheet関数

  • Difyで分析した結果をスプレットシートに出力する(writeDataToSheet関数

なお、runSurveyAnalysis関数では、「callDifyWorkflow」という名前の関数を呼び出してDifyをAPI呼び出ししています。

コピペで流用する場合、このあと紹介する「②-1.ストリーミングモードでDifyをAPI呼び出しする方法」もしくは「②-2. ブロッキングモードでDifyをAPI呼び出しする方法」のいずれかの関数名に置き換えて使用してください。

コードの全文は以下のとおりです。

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('アンケート分析')
      .addItem('分析を実行', 'runSurveyAnalysis')
      .addToUi();
}

/**
 * アンケート分析を行う
 */
function runSurveyAnalysis() {

  // スプレッドシートのIDとシート名を指定
  var quetion_sheetName = 'アンケート内容'

  // スプレッドシートを開く
  var quetion_sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(quetion_sheetName);
  stringifiedQuetionData = getDataFromSheet(quetion_sheet)


  // スプレッドシートのIDとシート名を指定

  var result_sheetName = 'アンケート結果';  

  // スプレッドシートを開く
  var result_sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(result_sheetName);
  stringifiedSurveyData = getDataFromSheet(result_sheet)
  
  // Difyのワークフローを呼び出す
  // ★ストリーミングモードもしくはブロッキングモードどちらかの処理を呼び出してください
  var difyResponse = callDifyWorkflow(stringifiedQuetionData, stringifiedSurveyData);
  
  // 結果をシートに出力
  writeDataToSheet(difyResponse)
}

/**
 * スプレットシートのデータを取得してJSON形式で返却する
 */
function getDataFromSheet(sheet) {
  // データの範囲を取得
  var dataRange = sheet.getDataRange();
  var data = dataRange.getValues();
  
  // ヘッダーを取得
  var headers = data[0];
  
  // データをオブジェクトの配列に変換
  var surveyData = [];
  for (var i = 1; i < data.length; i++) {
    var row = data[i];
    var entry = {};
    for (var j = 0; j < row.length; j++) {
      entry[headers[j]] = row[j];
    }
    surveyData.push(entry);
  }
  return JSON.stringify(surveyData);
}

/**
 * アンケート分析結果を出力する
 */
function writeDataToSheet(result) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("分析結果");
  var lastRow = sheet.getLastRow();

  // 1列目は現在日時
  var date_range = sheet.getRange(lastRow + 1, 1);
  date_range.setValue(new Date())
  date_range.setNumberFormat("yyyy/MM/dd HH:mm:ss");

  // 2列名はデータ
  var result_range = sheet.getRange(lastRow + 1, 2);
  result_range.setValue(result);
}

GASからDifyを呼び出す処理はこのあと説明します。

②-1. ストリーミングモードでDifyをAPI呼び出しする方法

ここではストリーミングモードでGASからDifyをAPI呼び出しする方法について説明します。

ストリーミングモードとは、Difyのワークフローの実行結果を段階的に受け取る方法です。これは、長い処理や複雑な処理を行う際に特に有用です。

このあと紹介するブロッキングモードでは、処理が完全に終わるまで待つ必要がありますが、ストリーミングモードでは処理の進行状況をリアルタイムで確認できます。

ストリーミングモードの処理の流れは、以下のようになります:

  1. ワークフロー開始:「workflow_started」イベントが送られ、処理が始まったことを知らせます。

  2. 各ノードの処理:ワークフロー内の各ステップ(ノード)が実行されるたびに、「node_finished」イベントが送られます。これにより、どの段階まで処理が進んでいるかを把握できます。

  3. ワークフロー終了:すべてのノードの処理が終わると、「workflow_finished」イベントが送られ、処理の完了を示します。

  4. 最終結果の取得:最後のノード(この例では「終了」というタイトルのノード)の処理が終わったときに、最後のノードの出力を「answer」変数に格納し、それを最終的な結果として返しています。

このように、ストリーミングモードを使うことで、長い処理でもユーザーに途中経過を示せます。ただ、使いたいのは最終結果のみはなずです。

今回は最終結果の値のみ取得し、それ以外は経過をログに出力するコードを作成しました。

ストリーミングモードでどのような値が返ってくるかの流れも分かりやすいので参考にしてみてください。

コードの全文は以下のとおりです。

なお、DifyをAPI呼び出しするにはDifyのAPIキーが必要です。

APIキーを取得するには、Difyのワークフロー編集画面で「APIリファレンスにアクセス」をクリックします。

APIリファレンスにアクセス

別タブで開いたAPIリファレンス画面右上の「APIキー」をクリックするとAPIキーの作成や取得ができます。

APIキーを取得

取得したAPIキーを以下のコードの「DifyのAPIキー」欄に貼り付けて使用しましょう。

/**
 * Difyのワークフローをストリーミングモードで呼び出す
 */
function callDifyWorkflow(quetsionData, surveyData) {
  // Dify APIのエンドポイントとAPIキーを設定
  var url = 'https://api.dify.ai/v1/workflows/run';
  var apiKey = 'DifyのAPIキー';
  
  // リクエストのヘッダー情報
  var headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + apiKey
  };

  var payload = {
    "inputs": {'question_data': quetsionData, 'survey_data': surveyData},
    "response_mode": "streaming",
    "user": "abc-123"
  };
  
  var options = {
    'method': 'post',
    'headers': headers,
    'payload': JSON.stringify(payload),
    'muteHttpExceptions': true
  };
  try {
    var response = UrlFetchApp.fetch(url, options);
    var responseCode = response.getResponseCode();
    
    if (responseCode === 200) {
      Logger.log('API call successful');

      var content = response.getContentText();
      var chunks = content.split('\n\n');
      var answer = '';

      for (var i = 0; i < chunks.length; i++) {
        var chunk = chunks[i].trim();
        if (chunk.startsWith('data: ')) {
          try {
            var json = JSON.parse(chunk.substring(6));
            switch (json.event) {
              case 'workflow_started':
                Logger.log('workflow_started')
              case 'workflow_finished':
                Logger.log('workflow_finished');
              case 'node_finished':
                Logger.log('node_finished');
                if (json.data.title == '終了'){
                  answer = json.data.outputs.text;
                }
              default:
                Logger.log('Event: ' + json.event + ', title: ' + json.data.title);
            }
          } catch (e) {
            Logger.log('Error parsing JSON: ' + e.toString());
          }
        }
      }
    } else {
      Logger.log('API call failed with response code: ' + responseCode);
      Logger.log('Error message: ' + responseBody);
    }
  } catch (error) {
    Logger.log('Error calling Dify API: ' + error.toString());
  }
  return answer;
}

実行結果は以下になります。このようにノード実行の途中経過を随時出力する仕様になっています。

2024/09/21 6:19:44	情報	API call successful
2024/09/21 6:19:44	情報	workflow_started
2024/09/21 6:19:44	情報	workflow_finished
2024/09/21 6:19:44	情報	node_finished
2024/09/21 6:19:44	情報	Event: workflow_started, title: undefined
2024/09/21 6:19:44	情報	Event: node_started, title: 開始
2024/09/21 6:19:44	情報	node_finished
2024/09/21 6:19:44	情報	Event: node_finished, title: 開始
2024/09/21 6:19:44	情報	Event: node_started, title: LLM
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	Event: text_chunk, title: undefined
2024/09/21 6:19:44	情報	node_finished
2024/09/21 6:19:44	情報	Event: node_finished, title: LLM
2024/09/21 6:19:44	情報	Event: node_started, title: 終了
2024/09/21 6:19:44	情報	node_finished
2024/09/21 6:19:44	情報	Event: node_finished, title: 終了
2024/09/21 6:19:44	情報	workflow_finished
2024/09/21 6:19:44	情報	node_finished
2024/09/21 6:19:44	情報	Event: workflow_finished, title: undefined

②-2. ブロッキングモードで呼び出す場合

次にブロッキングモードでGASからDifyをAPI呼び出しする方法について説明します。

Difyのブロッキングモードは、リクエストを送信した後、Difyの処理がすべて終わるまで待機し、その結果を一括で返す応答モードです。

Difyの処理がすべて完了してから値が返却されるため、シンプルで使いやすいです。

ただ、処理時間が長くなる可能性があり、その間は他の操作ができないため、リアルタイム性が求められるアプリケーションには不向きというデメリットもあります。

個人的には、処理時間を気にしない場合は、ブロッキングモードでのAPI呼び出しの方が使いやすいという印象です。

コードの全文は以下のとおりです。

function callDifyCreateAPIBlocking(quetsionData, surveyData) {
  var url = 'https://api.dify.ai/v1/workflows/run';
  var apiKey = 'DifyのAPIキー';

  // リクエストのヘッダー情報
  var headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + apiKey
  };

  var payload = {
    "inputs": {'question_data': quetsionData, 'survey_data': surveyData},
    "response_mode": "blocking",
    "user": "abc-123"
  };
  
  var options = {
    'method': 'post',
    'headers': headers,
    'payload': JSON.stringify(payload),
    'muteHttpExceptions': true
  };
  
  try {
    var response = UrlFetchApp.fetch(url, options);
    var responseCode = response.getResponseCode();
    var responseBody = response.getContentText();
    
    if (responseCode === 200) {
      Logger.log('API call successful');

      var jsonResponse = response.getContentText()
      // JSONをオブジェクトに変換
      var responseObject = JSON.parse(jsonResponse);

      // 必要なデータを取得
      var taskId = responseObject.task_id;
      var workflowRunId = responseObject.workflow_run_id;
      var answer = responseObject.data.outputs.text;

      // ログに出力
      Logger.log('Task ID: ' + taskId);
      Logger.log('Workflow Run ID: ' + workflowRunId);
      Logger.log('Outputs Text: ' + answer);

    } else {
      Logger.log('API call failed with response code: ' + responseCode);
      Logger.log('Error message: ' + responseBody);
    }
  } catch (error) {
    Logger.log('Error calling Dify API: ' + error.toString());
  }
  return answer;
}


アプリの実行と結果

冒頭でも説明しましたが、今回作成したアプリを実行した結果を紹介します。

以下のようにアンケート内容とアンケート結果をそれぞれスプレットシートに記載しておきます。

アンケート内容
アンケート結果

スプレットシートを開くとGASの「onOpen」関数によりカスタムメニューとして「アンケート分析」が生成されます。

カスタムメニュー

「分析を実行」をクリックすると、Difyの処理が呼び出されてアンケート分析がされます。

分析結果は「分析結果」シートに日付とともに出力されます

分析結果

ちなみにGeminiで分析した結果、分析結果には全体の平均値や男女ごとの平均値も記載されていますが、値は間違っている場合もありました。

ただ、自由回答の記述や全体の傾向からの総評はおおむね正しく、アンケート内容とアンケート結果を入力するだけのシンプルな作りでもある程度の効果は確認できました。

まとめ

今回はスプレットシートからDifyを呼び出してアンケート分析するアプリを作成しました。

今回作成したアプリのポイントはGASからDifyをAPI呼び出しする処理です。

Difyで作成したアプリをAPI呼び出しするには以下2つの方法があります。

  • ストリーミングモード

  • ブロッキングモード

ストリーミングモードとは、Difyのワークフローの実行結果を段階的に受け取る方法です。

ブロッキングモードとは、リクエストを送信した後、Difyの処理がすべて終わるまで待機し、その結果を一括で返す方法です。

ストリーミングモードの方が長文に対応していて安定しているというメリットがありますが呼び出し処理は複雑になります。

個人的には処理時間を気にしないのであればブロッキングモードの方が使いやすくておすすめです。

今回紹介したコードを参考にDifyのAPI呼び出しにもチャレンジしてみましょう。

ちなみに今回紹介したDifyのDSLファイル、GASコード全文、アンケート内容とアンケート結果を記載したExcelファイルはこのあとの有料部分で配布します。

有料価格を設定していますが記事紹介で無料になる割引も設定しているので、ぜひ参考にして貰えると嬉しいです。

Xもやっているので分からないことや、ご指摘などあればお気軽にご連絡ください。

ここから先は

5,770字 / 1画像 / 2ファイル

この記事が参加している募集

この記事が気に入ったらサポートをしてみませんか?