見出し画像

Difyで「MyAssistant」を改善してみた件

★本記事の対象者
・GPTs開発してみたい!と思われている方
・GPTsの機能をいろんな人に届けたい!と思われている方
・Dify興味あるよ!と思われている方
・チーム間でうまく情報連携できる方法はないか・・・と探されている方



はじめに

みなさまお仕事お疲れ様です!光森です。
標題の件、情報共有用の「MyAssistant」というアプリをこれまでにも作っていたのですが、この度Difyを使って改善してみましたので共有させていただきます!
最後にymlファイルもダウンロードできるようにしておりますので、ぜひぜひ最後までご一読いただければ幸いです!

そもそも「MyAssistant」って何?

こちらの疑問につきましては、こちらの記事を参照ください。
@MarinaShimoda(https://x.com/marinashimoda?s=21&t=dPrh0uwpY0IDSxM4gtbNTAさんと共同開発し、東京AI祭(https://www.aisai.tokyo/)にてファイナルまで出場させていただいたGPTsがMy Assistantになります。

簡単に説明をすると、以下の図で説明をしているように、個人・チーム間での情報共有・活用を促進することを目的として作られたGPTsが初代の「MyAssistant」になります。

みなさんも情報共有・活用はうまくいってますか?

そしてこちらが初めてDifyを使って「MyAssistant」を再現してみたバージョンのものになります。
今回はこちらの内容よりもちょっと機能を追加してみました。


DifyでのMyAssistantの全体像

2024/07/07現在ですが、全体像はこんな感じにしております。
現時点では「入力用」と「確認用」の2つのアプリで構築しています(理由は後述)。入力用のアプリでスプレッドシートに情報を登録し、確認用のアプリで登録した情報を元に、LLMとチャットしながら内容の深掘りや新しい気づきを得ることができます。それぞれのアプリは以下で詳細に説明させていただきます。

入力用と確認用の2つのアプリに分けてみました


入力用アプリのワークフロー

入力用アプリのワークフローは以下の通りです。

こちら入力用です

・あまりチャットしながらの作業ではないので「ワークフロー」で構築しました。ワークフローなので、アプリの画面はこんな感じです。

入力用アプリのUI

・入力用アプリは大きく3つのフローで構築されています。
 ① 作業状況を登録するためのフロー
 - 日報のように使うことを想定
 ②-1 情報収集したメモを登録するためのフロー
 
- 気づきなど個人のメモを登録
 ②-2 URL先の情報を整理した上で登録するためのフロー
 
- JinaReaderを使ってスクレイピング→LLMで整理→登録

・特に、今までのMyAssistantでは②の機能がなかったのですが、個人で収集した情報(メモやURL先の内容)をチーム間で共有したい!という想いがあったので、フローを追加してみました。

・また、情報を保存するスプレッドシートは今回は以下のような内容にしています。ここはみなさんの好きな形に改良いただきたいです。

作業状況も情報共有も同じスプレッドシートで保存するようにしてみました
(分けた方がいいかもとも思ったのですが、一旦実験的にやってみました)


確認用アプリのチャットフロー

確認用アプリは以下のとおりです。

こちら確認用です

・こちらについてはLLMとチャットしながら新しい気づきや深掘りをしたかったので、「チャットフロー」で構築しています。ですので、入力用とは違ってアプリはみなさんお馴染みにチャット画面になっています。

みなさんお馴染みのチャット画面

・確認用アプリは大きく3つのフローで構築されています。
 ① キーワードを元に情報をスプレッドシートから収集して、LLMとチャットができるフロー
 - メインのフロー
 ②チャットの内容を元にメンバーにDiscordでチャットを送信するためのフロー
 - ①で確認した内容をすぐに共有できるように構築
 ③作業状況を修正するためのフロー
 
- 確認した内容が誤っていた時に、入力用アプリを立ち上げずに修正したいと思い、確認アプリからちょっと修正するだけ用のフローを構築

・②について補足をすると、Discordのデベロッパーサイト↓

でボットを作り、そこにHTTPノードを使ってチャットの内容を送信することで、チームのサーバーにチャットを投稿する仕組みを作っています。自分も初めてでしたがChatGPTに聞きながら1時間もかからずにすぐ作れましたので、頑張って構築してみてください!(MARUNAGE☆)

こんな感じでボットは作りますという雰囲気だけ紹介😇

GAS

スプレッドシートとの連携に必要なGASのコードは以下の通りです。
「スプレッドシートID」と記載している部分については、ご自身のスプレッドシートIDを記載するようにしてください!

※わからない場合は以下の記事を参照いただければ幸いです。

function doPost(e) {
  // リクエストボディからJSONデータを解析
  var requestData = JSON.parse(e.postData.contents);

  // スプレッドシートの準備
  var sheet = SpreadsheetApp.openById('スプレッドシートID').getSheetByName('シート1');

  // 最終行の"No"を取得して1を加える
  var lastRow = sheet.getLastRow();
  var lastNo = sheet.getRange(lastRow, 1).getValue(); // "No"は1列目にあると仮定
  var newNo = lastNo + 1;

  // `requestData.All`の冒頭にフォーマットされた`newNo`を追加
  requestData.All = `###No\nNo.${newNo}\n${requestData.All}`;

  // 新しい行にデータを追加
  var newRow = lastRow + 1;
  sheet.getRange(newRow, 1).setValue(newNo); // 新しい"No"を1列目に設定
  sheet.getRange(newRow, 2).setValue(requestData.Date); // 2列目にDate
  sheet.getRange(newRow, 3).setValue(requestData.Name); // 3列目にName
  sheet.getRange(newRow, 4).setValue(requestData.Mode); // 4列目にMode
  sheet.getRange(newRow, 5).setValue(requestData.Project); // 5列目にProject
  sheet.getRange(newRow, 6).setValue(requestData.Summary); // 6列目にSummary
  sheet.getRange(newRow, 7).setValue(requestData.Memo); // 7列目にMemo
  sheet.getRange(newRow, 8).setValue(requestData.URL); // 8列目にURL
  sheet.getRange(newRow, 9).setValue(requestData.All); // 9列目にAll

  // 処理完了のメッセージを返す
  return ContentService.createTextOutput(
    JSON.stringify({"result": "Success", "message": "Data added to the spreadsheet."})
  ).setMimeType(ContentService.MimeType.JSON);
}

function doGet(e) {
  // リクエストオブジェクトの存在を確認
  if (!e || !e.parameters) {
    console.error('The request object or parameters are undefined.');
    return ContentService.createTextOutput(
      JSON.stringify({ "error": "No parameters provided in the request." })
    ).setMimeType(ContentService.MimeType.JSON);
  }

  console.log("start GPTs Custom Actions API test");
  console.log("e: " + JSON.stringify(e));
  var searchQuery = e.parameters.keyword; // これは配列になる可能性があります
console.log("e.parameter.keyword: " + e.parameters.keyword); // 同上

// searchQueryが配列である場合、最初の要素を取得
if (Array.isArray(searchQuery)) {
    searchQuery = searchQuery[0];
}

// keywordが含まれているかどうかをチェック
if (!searchQuery) {
    console.log("keyword is missing: " + searchQuery);
    return ContentService.createTextOutput(
      JSON.stringify({ "error": "Invalid request. Please provide a keyword." })
    ).setMimeType(ContentService.MimeType.JSON);
}

  // スプレッドシートの準備
  var sheet = SpreadsheetApp.openById('スプレッドシートID').getSheetByName('シート1');
  var data = sheet.getDataRange().getValues();

  // スプレッドシートを検索
  var results = []; // 検索結果を格納する配列
  for (var i = 1; i < data.length; i++) {
    // 9列目のデータでキーワードが含まれているかチェック
    if (data[i][8].toString().toLowerCase().indexOf(searchQuery.toLowerCase()) !== -1) {
      // 9列目にkeywordが含まれている場合、該当行の1列目と9列目の情報を追加
      results.push({
        //"No": data[i][0], // 1列目のデータ(一旦コメントアウト)
        "All": data[i][8] //9列目のデータ
      });
    }
  }

  if (results.length > 0) {
    // 見つかった場合、見つかったデータをJSONとして返す
    return ContentService.createTextOutput(
      JSON.stringify(results)
    ).setMimeType(ContentService.MimeType.JSON);
  } else {
    console.log("not found");
    // 一致するデータが見つからない場合
    return ContentService.createTextOutput(
      JSON.stringify({ "error": "No data found for the provided keyword." })
    ).setMimeType(ContentService.MimeType.JSON);
  }
}


カスタムツール

カスタムツールのスキーマは以下の通りです。
「ここにデプロイしたURLの /macros以下 のアドレスを設定」
の部分のみ、デプロイしたGASのURLを記載するようにしてください!

※わからない場合は以下の記事を参照いただければ幸いです。

{
  "openapi": "3.1.0",
  "info": {
    "title": "Search and Add Posts",
    "description": "Search posts by keyword and add new posts.",
    "version": "v1.0.0"
  },
  "servers": [
    {
      "url": "https://script.google.com"
    }
  ],
  "paths": {
    "ここにデプロイしたURLの /macros以下 のアドレスを設定": {
      "get": {
        "description": "Search posts by keyword and return details.",
        "operationId": "searchPostsByKeyword",
        "parameters": [
          {
            "name": "keyword",
            "in": "query",
            "description": "Keyword to search in the content.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A list of posts containing the keyword.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/GetResponse"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, such as missing keyword."
          }
        }
      },
      "post": {
        "description": "Add a new post to the spreadsheet.",
        "operationId": "addPost",
        "requestBody": {
          "description": "Data for the new post",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NewPostRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Post successfully added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "string",
                      "description": "Result of the operation"
                    },
                    "message": {
                      "type": "string",
                      "description": "Operation message"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, such as missing required fields."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "GetResponse": {
        "type": "object",
        "properties": {
          "No": {
            "type": "integer",
            "description": "Unique identifier of the post."
          },
          "All": {
            "type": "string",
            "description": "All contents."
          }
        }
      },
      "NewPostRequest": {
        "type": "object",
        "required": ["Date", "Name", "Mode", "Project", "Summary", "Memo", "URL", "All"],
        "properties": {
          "Date": {
            "type": "string",
            "format": "date",
            "description": "Date when the post was made."
          },
          "Name": {
            "type": "string",
            "description": "Name of the person who made the post."
          },
          "Mode": {
            "type": "string",
            "description": "Mode."
          },
          "Project": {
            "type": "string",
            "description": "Project."
          },
          "Summary": {
            "type": "string",
            "description": "Summary."
          },
          "Memo": {
            "type": "string",
            "description": "Memo content of the post."
          },
          "URL": {
            "type": "string",
            "description": "URL."
          },
          "All": {
            "type": "string",
            "description": "All contents."
          }
        }
      }
    }
  }
}

ご参考まで|ymlファイル

今回作成した以下のファイルをGoogle Driveで共有させていただきます。HTTPノードなどご自身の設定に変えていただかないと動かないものもありますが、ご参考まで!

・Input_MyAssistant_ver1.1_send.yml
 - 入力用アプリのymlファイル
・Output_MyAssistant_ver2.0_send.yml
 - 確認用アプリのymlファイル


さいごに

Difyもユーザーが増えて優良記事も増えているので、だいぶ説明を端折ってしまったったんですけど・・・もしわからないよ!って方がいらっしゃれば、私の以下の過去の記事を読んでいただければ理解が深まるかと思いますので、ぜひご一読いただければ幸いです。

今回紹介させていただいたMyAssistantですが、ほんと色々なバリエーションが考えられるかと思いますので、みなさんもぜひ作ってどんどん改良してみてくださいね!!



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