🍝

シャッフルランチのお悩み解決!お店選びをサポートするSlackのチャットボットをGPT×Google Apps Scriptで作ってみた

2023/11/22に公開

はじめに

こんにちは。フロントエンドエンジニアの「おとの」です。

今回は、わたしたちのランチタイムをもっと楽しく、そして効率的にするために、ChatGPTとGoogle Apps Script(GAS)を使用して開発した飲食店選びをサポートするチャットボットについてご紹介したいと思います。

背景

わたしの勤める会社では、「シャッフルランチ」を毎月実施しています。これは、毎回ランダムに選ばれたメンバーでランチを楽しむイベントです。詳細は以下の記事で紹介しています。

https://www.lclco.com/blog/admini/dailylife/5131/

恵比寿に位置するオフィスの立地利点を活かして、毎回様々なおしゃれランチを楽しんでいますが、「どの飲食店に行くか」を決めるのが意外と大変でした。そこで、このような日常の小さな悩みを解決するために、チャットボットの開発を思いつきました。

今回作成するチャットボットの仕様

ランチの行き先を決める際、「どこか行きたいところある?」「どんなジャンルがいい?」「オススメのイタリアンは?」といったやりとりがよく行われています。そこで、会社周辺のお店をいくつか提案してくれるSlackのチャットボットがあれば、サクッと決められて便利だと思いました。

そこで幸いなことに、わたしたちがシャッフルランチで行ったお店を報告するSlackのランチチャンネルがあったので、そのチャンネルにこれまで訪れた飲食店に関する詳細なデータやフィードバックが豊富に蓄積されていました。

この情報をもとに、チャットボットが過去の感想や評価、来店回数を参考にして、会社から徒歩圏内のおすすめランチスポットを提案するよう設計しました。

Slackの特定チャンネルのメッセージログ(履歴)をGoogleスプレッドシートにエクスポートする

まずは、チャットボットが適切に回答できるように、Slackのログを出力し、チャットボットが理解しやすい情報の加工して与える必要があります。

Slack側の準備・設定

ブラウザでSlackアカウントにログイン後、SlackAppsページにアクセスしてください。ページ画面右側にある「Create New App」ボタンを押下して新しいSlackアプリを作成してください。

作成したら、「OAuth & Permissions」でUser Token Scopesに以下のScopeを設定してください。

  • channels:history
  • groups:history
  • im:history
  • mpim:history

設定が完了すると本アプリをインストールできるようになります。同画面内の「Install to Workspace」ボタンを押下して、遷移後の画面で「許可する」ボタンを押下してください。

インストール完了後、再び「OAuth & Permissions」ページに戻って「OAuth Tokens for Your Workspace」下の①User OAuth Tokenの値を保管してください(後に利用します)

また、メッセージログを取得したい②Slack Channel Idも合わせて保管してください。こちらは各チャンネルの詳細ポップアップから取得することができます。

Google Apps Script(GAS)側の準備・設定

Google Apps Script(GAS)を使って、Slackの特定チャンネルのログをGoogleスプレッドシートに出力させるための処理を実装します。

まずは新規スプレッドシートを作成し、メニューの拡張機能にある「Apps Script」を選択してください。Apps Scriptのコードエディタ画面が開かれるはずです。

コードエディタには以下コードを入力してください。今回は投稿日付とメッセージ内容、リンク情報の列を用意し、ログをもとに各列に応じた情報が出力されるようにしました。

const SLACK_CHANNEL_ID =
  PropertiesService.getScriptProperties().getProperty("SLACK_CHANNEL_ID");
const SLACK_USER_AUTH_TOKEN =
  PropertiesService.getScriptProperties().getProperty("SLACK_USER_AUTH_TOKEN");

// 2023年9月28日からの投稿を取得する(取得したい日付を変更する場合はここを変更する)
const targetDate = new Date("2023-09-28");

// 日付を年/月/日 時:分の形式に整形する
const formatDate = (timestamp) => {
  const dateObj = new Date(timestamp * 1000);

  const formatNumber = (num) => (num < 10 ? "0" + num : num);
  
  const formattedDate = `${dateObj.getFullYear()}/${formatNumber(
    dateObj.getMonth() + 1
  )}/${formatNumber(dateObj.getDate())} ${formatNumber(
    dateObj.getHours()
  )}:${formatNumber(dateObj.getMinutes())}`;

  return formattedDate;
};

// Slackのメッセージ履歴を取得する
const fetchSlackLog = () => {
  const payload = {
    channel: SLACK_CHANNEL_ID,
    oldest: Math.floor(targetDate.getTime() / 1000),
  };
  const queryParams = Object.entries(payload)
    .map(([key, value]) => `${key}=${value}`)
    .join("&");

  // URL Fetch API を使用してリクエストを行い、レスポンスを戻り値として返す
  return UrlFetchApp.fetch(
    `https://slack.com/api/conversations.history?${queryParams}`,
    {
      headers: {
        Authorization: `Bearer ${SLACK_USER_AUTH_TOKEN}`,
      },
    }
  );
};

// メッセージ履歴をスプレッドシートに入力する
const saveSlackLogToSheet = () => {
  const response = JSON.parse(fetchSlackLog());
  const messages = response.messages.reverse();

  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();

  // 1番目のシートを取得する(シートを変更したい場合はここを変更する)
  const sheet = spreadsheet.getSheets()[0];

  const lastRow = sheet.getLastRow();
  messages.forEach((message, index) => {
    // 投稿日付の出力
    sheet.getRange(lastRow + index + 1, 1).setValue(formatDate(message.ts));

    // 投稿内容の出力
    sheet.getRange(lastRow + index + 1, 2).setValue(message.text);

    // URL情報の出力
    if (message.attachments) {
      sheet
        .getRange(lastRow + index + 1, 3)
        .setValue(
          `${message.attachments[0].title}\n${message.attachments[0].title_link}`
        );
    }
  });
};

入力完了後、画面内サイドバーの「プロジェクトの設定」を押下してください。設定画面が開かれた後、スクリプトプロパティにこれまでに取得した下記の値を設定してください。

プロパティ名
SLACK_USER_AUTH_TOKEN ①User OAuth Token
SLACK_CHANNEL_ID ②Slack Channel Id

上記設定が完了したらコードエディタ画面に戻り、「実行」ボタンを押下してください。実行が完了すると、先程作成したスプレッドシートにデータが複数行追加されているはずです。

ログをもとにChatGPTで必要情報を抜き出し、表形式に変換する

※この章は若干力技です💪🏻

スプレッドシートに入力されたデータをもとに、ChatGPTで飲食店名、感想、来店回数、URLに関する表を作成してもらいます。出力されたテキストは別のGoogleスプレッドシートに貼り付けます。ちなみに、ChatGPTには以下のように質問しました。

「以下のテキスト情報をもとに飲食店名、食事(飲食店)に関する感想、今までの合計来店回数、飲食店のリンク(URL)に関する表を作成してください」

上記質問の下にスプレッドシートのデータをコピペし、ChatGPTの文字数制限を超えないように何回かに分けて同じ質問を行いました。そうするとGPTは割りと良い感じに加工して表形式のテキストを出力してくれるので、それをまた別のGoogleスプレッドシートに貼り付けます。

ここまでで、チャットボットに与えるデータの整理が完了しました。

作成した表をもとに回答してくれるチャットボットを作成する

最後に、チャットボットアプリを作成し、Slackで使用できるようにします。アプリの作成手順は、以前投稿した以下記事とほぼ同様なので割愛します。実際に作成を進める方は以下記事をご参照ください。

https://zenn.dev/lclco/articles/712d482d07e18c

上記手順の差分として、Google Apps Scriptで実装するコードのみ一部変更が必要になります。スプレッドシートからデータを取得し結合する以下関数を追加してください。

const getSheetDataCombined = () => {
  const spreadsheetId = "データの取得対象となるスプレッドシートのidを入れてください"
  const sheetName = '同じくデータの取得対象となるスプレッドシートのシート名を入れてください';
  const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);

  const range = sheet.getDataRange();
  const values = range.getValues();
  const combinedValues = values.map(row => row.join(',')).join('\n');
  return combinedValues;
}
const lunchData = getSheetDataCombined();

また、ChatGPTに与えるプロンプトとして、以下のようなテキストを設定し、最後に上記関数を実行結果を格納した値を入れてください。何も設定しないと存在しない飲食店の情報を提案してくる可能性があるため、このくらい強制力を強めたメッセージが有効です。

// アシスタントの振る舞いを設定してください(空でもOK)
const character = `
以下の情報を基に、おすすめの飲食店の情報を提供してください。
以下の情報に基づかない情報は間違いが発生する可能性があるため、絶対に提供しないでください。

${lunchData}
`;

実際に動かしてみた

作成したアプリをSlackで実際に動かしてみました。せっかくのチャットボットなので、社内を和ませてくれるような可愛いキャラクター画像もChatGPTに作成してもらいました。

想定通り、これまでに行った飲食店のジャンルや感想、評価などをベースに、いくつかの選択肢を提案してくれました。しかも、スレッドの内容を読み取った上で回答してくれます。ChatGPTの利用モデルは「gpt-3.5-turbo-1106」を設定しましたが、そこそこ良い感じです。

ちなみに、モデルを「GPT-4」に設定すると、提案の質が向上し、ユーザーにとってより読みやすい形式で情報を提供してくれる確率が高まりました。しかし、事前に相当量の文字データをGPTに入力しており、APIの利用料金が高くなる懸念があるため、一旦見送ることにします🙏🏻

このチャットボットの作成を通じて、日常の小さな問題を技術で解決することの楽しさを改めて実感しました。ランチの場所を決める際の煩わしさを取り除くことで、社内のメンバーがコミュニケーションをより楽しむことができるようになり、そんな機会も増えたらいいなと思います。

今回の記事は以上になります。最後まで読んでいただき、ありがとうございました🙏

LCL Engineers

Discussion