ocomojiのナレッジ

初心者が【ChatGPT + GAS】でSlackBotアプリを作ってみた

自己紹介

はじめまして、ocomojiのエンジニア、Kです
最近流行のChatGPTのAPIを利用してGASでSlackBotアプリを作ってみたので紹介します

はじめに

仕事では連絡ツールとしてSlackを使用しています
そのためSlackは常時起動状態ですが、chatGPTを使用するには毎回ブラウザを起動する手間がありました
そこで「chatGPT使うのに毎回ブラウザが面倒だなぁ・・・そうだ、SlackでchatGPTのBOTアプリを作成すれば解消できるのでは!?」
という発想に至りました(Slackに外部のアプリを導入したり自作することが可能)
ちなみに私はSlackアプリ開発、GAS開発が初めてのため、一から調べながらの開発となりました
(8割は他者様記事のおかげ^^;)

※GASのwebhookを使用する方法もあるみたいですがこの記事では使用しません

やりたかったこと

作成したSlackBotが参加しているチャンネルで投稿するとchatGPTから回答を返すBotの作成

事前準備

Slack、GAS(GoogleAppsScript)、OpenAI(chatGPT)のアカウントが用意されてあり使用できる状態であること(権限も含めて)

開発目次

  1. GASでSlackからの投稿イベント受信用URLを作成する
  2. SlackBotアプリを作成
  3. SlackBotアプリを使用するためのトークンを取得
  4. GASにSlackライブラリを追加
  5. SlackBotアプリを使用するためのメンバーIDを取得
  6. chatGPTを使用するためのAPIキーを取得
  7. GASへSlackBotアプリとchatGPTを連携~設定編~
  8. SlackチャンネルにSlackBotアプリを追加
  9. GASへSlackBotアプリとchatGPTを連携
  10. 【トラブルシューティング】動作確認~通信編~【GAS + SlackBot】
  11. 【トラブルシューティング】動作確認~通信編~【GAS + chatGPT】
  12. 【トラブルシューティング】奥の手

1.GASでSlackからの投稿イベント受信用URLを作成する

Slackからの投稿をGAS側で受信できるようにします

  1. GAS(https://script.google.com/home)を起動
  2. 新しいプロジェクト押下(プロジェクト名やファイル名は任意)
  3. 既存コードを以下のコードで上書き(プロジェクトを保存ボタンで上書き)
    	//Slackからの投稿を受け取る
    	function doPost(e) {
    		//受信データをパース(解析してデータを抽出)
    		const postParam = JSON.parse(e.postData.getDataAsString());
    
    		//チャレンジ認証
    		//①この認証処理はNG
    		/*
    		if (postParam.type === 'url_verification') {
    			return ContentService.createTextOutput(postParam.challenge);
    		}
    		*/
    		
    		//②この認証処理だとOK
    		if('challenge' in postParam){
    			return ContentService.createTextOutput(postParam.challenge);
    		}
    
    		return;
    	}
    									
    ※function名の"doPost"はイベント(予約語)のため任意に変更しないこと(非予約語だと思ってた)
    ※チャレンジ認証に苦戦した
     最初に参考にさせて頂いた記事では1で認証されていたが上手く動かなかった
     他記事を調べていくと2の認証も紹介されていたため実装してみると上手く動いた
     GASの仕様が変わった?

  4. デプロイ実行
    (デプロイ→新しいデプロイ→種類の選択:ウェブアプリ→アクセスできるユーザー:全員→デプロイボタン押下)
  5. デプロイ完了画面でウェブアプリのURLをコピーしてメモ
    完了ボタン押下

    SlackからGASへの認証が通るかは後述する順2で確認するため、とりあえず順1の通り作成してみてください


2.SlackBotアプリを作成

SlackBotアプリのページが英語のため見づらいです^^;

  1. SlackAPI(https://api.slack.com/apps)を起動
  2. Create New Appを押下
    From scratchを選択
    App NameとPick a workspace to develop your app inを選択
    ※今回私の作成したアプリ名は『chatGPT2』としています
    Create Appを押下
  3. サイドバーでEvent Subscriptionsを押下
    Enable EventsのトグルをOn
    Request URLにSlackからの投稿イベント受信用URL(順1で作成したURL)をペースト
    ※Verifiedが表示されたら認証成功!
    Subscribe to bot events → Add Bot User Eventで以下のイベントを追加
     app_mention:メンション指定
     message.channels:チャンネルへのメッセージ投稿
    Save Changesを押下



  4. サイドバーでOAuth & Permissionsを押下
    Scopes → Bot Token Scopes → Add an OAuth Scopeで以下のスコープを追加
     chat:write:メッセージ投稿
     ※app_mentions:read、channels:historyが既に追加されてる場合はそのままでOK
    OAuth Tokens for Your Workspace
     Install to Workspaceを押下
     許可するを押下



3.SlackBotアプリを使用するためのトークンを取得

SlackBotアプリのトークンを取得します(IDのようなもの)

  1. SlackAPI(https://api.slack.com/apps)を起動
  2. サイドバーでOAuth & Permissionsを押下
  3. OAuth Tokens for Your Workspace
     Bot User OAuth Tokenをコピーしてメモ



4.GASにSlackライブラリを追加

GASにSlackAPIライブラリを追加します

  1. GAS(https://script.google.com/home)を起動
  2. ライブラリの+を押下
  3. ライブラリの追加
  4. スクリプトIDに以下のIDをコピペして検索を押下
    1on93YOYfSmV92R5q59NpKmsyWIQD8qnoLYk-gkQBI92C58SPyA2x1-bqbr
  5. 追加ボタン押下(バージョンやIDは初期値のままでOK)


5.SlackへSlackBotアプリを追加してメンバーIDを取得

Slackへ作成したBotアプリを追加してメンバーIDを取得します

  1. Slackを起動
  2. サイドバー→アプリを追加する→chatGPT2で検索→該当アプリを押下
  3. サイドバーにchatGPT2が追加されるため、chatGPT2を押下してプロフを起動


  4. メンバーIDをコピーしてメモ



6.chatGPTを使用するためのAPIキーを取得

  1. OpenAI(https://platform.openai.com/overview)を起動
  2. ログイン
  3. 右上の自身のアカウントアイコンを押下
  4. View API keysを押下
  5. Create new secret keyを押下
  6. 表示されたキーがAPIキーとなる。コピーしてメモしておく



7.GASへSlackBotアプリとchatGPTを連携~設定編~

GASへSlackBotアプリとchatGPTの連携設定を行う

  1. GAS(https://script.google.com/home)を起動
  2. サイドバーの歯車マーク(設定)を押下
  3. スクリプトプロパティを追加を押下して下記画像の通り3つ追加
     openApiKey:chatGPTを使用するためのOpenAIのAPIキー。OpenAIアカウントから取得(順6)
     slackBotId:SlackBotアプリのメンバーID。Slackアプリから取得(順5)
     slackBotToken:SlackBotアプリのトークン。ブラウザから取得。Bot User OAuth Token(順3)
  4. スクリプトプロパティを保存を押下


8.SlackチャンネルにSlackBotアプリを追加

  1. Slackを起動
  2. 任意のチャンネルを押下してプロパティ画面を起動
  3. 上タブのインテグレーションを押下
  4. アプリを追加するを押下
  5. 作成したSlackBot(chatGPT2)が一覧に出現するので、追加を押下



9.GASへSlackBotアプリとchatGPTを連携~コード編~

GASへSlackBotとchatGPTの連携を実装

  1. GAS(https://script.google.com/home)を起動
  2. GASで既存コードを以下のコードで上書き(プロジェクトを保存ボタンで上書き)
    	//Slackからの投稿を受け取る
    	//**********  START Main **********
    	function doPost(e) {
    	  //受信データをパース(解析してデータを抽出)
    	  const postParam = JSON.parse(e.postData.getDataAsString());
    	  
    	  //Challenge認証用
    
    	  //1.この認証処理はNG
    	  /*
    	  if (postParam.type === 'url_verification') {
    	    return ContentService.createTextOutput(postParam.challenge);
    	  }
    	  */
    
    	  //2.この認証処理だとOK
    	  if('challenge' in postParam){
    	    return ContentService.createTextOutput(postParam.challenge);
    	  }
    	  
    	  //3.Slackで投稿したチャンネルへ文を返す
    	  //sendSlack(postParam.event.channel, 'Slackから投稿キャッチ!GASから返信するよ!');
    
    	  //イベント再送回避
    	  //キャッシュに積まれていれば処理済
    	  //未処理の場合はキャッシュに積む(有効期間5m)
    	  const event_id = postParam.event_id;
    	  const cache = CacheService.getScriptCache();
    	  const isProcessed = cache.get(event_id);
    
    	  if (isProcessed) return;
    
    	  cache.put(event_id, true, 601);
    
    	  //サブタイプが設定されたイベント
    	  if('subtype' in postParam.event) return;
    
    	  //ChatGPTBotが送信した場合
    	  //ChatGPTで応答メッセージを作成し、Slackに送信する
    	  const botId = PropertiesService.getScriptProperties().getProperty('slackBotId');
    	  if (postParam.event && postParam.event.user !== botId) {
    	    const channel = postParam.event.channel;
    	    const text = postParam.event.text;
    	    var message = requestChatGPT(text);
    
    	    sendSlack(channel, message);
    	  }
    
    	  return;
    	}
    	//**********  END MAIN **********
    
    
    	//4. **********  START 【トラブルシューティング】動作確認~通信編~【GAS + chatGPT】 **********
    	function testGAStoChatGPT() {
    	  console.log(requestChatGPT("chatGPTとは?"));
    	}
    	//**********  END 【トラブルシューティング】動作確認~通信編~【GAS + chatGPT】 **********
    
    
    	//**********  START SlackBotsを通してメッセージを送信する **********
    	//20230301 これ単体では動作することを確認済
    	function sendSlack(channel, message) {
    	  const slackToken = PropertiesService.getScriptProperties().getProperty('slackBotToken');
    	  const slackApp = SlackApp.create(slackToken);
    	  slackApp.postMessage(channel, message);
    	}
    	//**********  END SlackBotsを通してメッセージを送信する **********
    
    
    	//**********  START ChatGPTにテキストを送信し、応答メッセージを取得する **********
    	//20230301 これ単体では動作することを確認済
    	function requestChatGPT(message) {
    
    	  const apiKey = PropertiesService.getScriptProperties().getProperty('openApiKey');
    	  const apiUrl = 'https://api.openai.com/v1/chat/completions';
    	  
    	  //リクエストデータの設定
    	  const headers = {
    	    'Authorization':'Bearer '+ apiKey,
    	    'Content-type': 'application/json'
    	  };
    	  const options = {
    	    'headers': headers, 
    	    'method': 'POST',
    	    'payload': JSON.stringify({
    	      //モデルを指定 gpt-3.5-turbo:chatGPT text-davinci-003:GPT-3
    	      'model': 'gpt-3.5-turbo',
    	      //生成される文章の最大トークン数を指定。単語数みたいな
    	      'max_tokens' : 1500,
    	      //0.5と指定すると、生成される文章は入力となる文章に似たものが多くなる傾向にある
    	      //1.0と指定すると、生成される文章はより多様なものになる傾向にある
    	      'temperature': 0.5,
    	      //クエリとなる文字列を指定
    	      'messages': [{ role: "user", content: message }]
    	    })
    	  };
    
    	  //リクエストを送信し、結果取得
    	  const response = JSON.parse(UrlFetchApp.fetch(apiUrl, options).getContentText());
    	  const resMessage = response.choices[0].message.content;
    
    	  return resMessage;
    	}
    	//********** END ChatGPTにテキストを送信し、応答メッセージを取得する **********
    
    									
  3. 以下の手順で再デプロイを行う
    1. デプロイ → デプロイを管理 → 鉛筆マーク(編集)
    2. バージョンを押下して【新バージョン】を選択 ※1
    3. デプロイを押下
    4. アクセスを承認を押下
    5. 以降、アクセスについての許可確認が何度か出現するが全て許可をしていく

    ※再デプロイの手順に誤りがある場合は、
     SlackBotsを作成した時のウェブアプリURLと別のURLになり、動かなくなるので注意
    ※上記の順5は初回の再デプロイのみ出現


  4. SlackBotアプリを追加した任意のチャンネルでBotをメンションして質問を送信
  5. SlackBotアプリから回答が返ってくる
  6. 連携成功!お疲れ様でした!


10.【トラブルシューティング】通信確認【GAS → SlackBot】

SlackBotから返信が無い場合、GASとSlackBotで連携ができているか通信テストを行う
通信の流れ:Slack → GAS → chatGPT → GAS → Slack

  1. GAS(https://script.google.com/home)を起動
  2. 既存コード中(順9)の3の箇所のコメントを解除(スラッシュスラッシュを削除)
    //sendSlack~~ → sendSlack~~
  3. 再デプロイ(順9-3)
  4. SlackBotアプリを追加した任意のチャンネルでBotをメンションして適当なメッセージを送信
  5. SlackBotアプリから『Slackから投稿キャッチOK!GASから返信するよ!』という返信が届いたら通信に成功
  6. 通信テスト成功:次の順で GAS → chatGPT → GASの通信を確認
    通信テスト失敗:GASとSlack間の設定を見直すこと
    (とりあえずchatGPTを無視してGAS + Slackでシンプルな処理を実装してテストでも良き)
  7. 既存コード中(順9)で3の箇所のコメントを戻す(スラッシュスラッシュを復元)
    sendSlack~~ → //sendSlack~~
  8. 再デプロイして元の状態に戻しておく(順9-3)


11.【トラブルシューティング】通信確認【GAS → chatGPT → GAS】

GASからSlackBotへ返信はあったがchatGPTからの回答が届か無い場合(順10は成功した状態)、GASとchatGPTで連携ができているか通信テストを行う
通信の流れ:Slack → GAS → chatGPT → GAS → Slack

  1. GAS(https://script.google.com/home)を起動
  2. 上メニューのデバッグの右から関数:testGAStoChatGPTを選択
    ※既存コード中(順9)に用意した関数を実行
  3. 上メニューの実行を押下
  4. 下部の実行ログにchtatGPTからの回答が届いたら通信に成功
  5. 通信テスト成功:Slack → GASの通信を確認
     ※方法については今記事では割愛します
     ※この方法をスキップして順13に進んでも良き
    通信テスト失敗:GASとchatGPT間の設定を見直すこと
    (とりあえずSlackを無視してGAS + chatGPTでシンプルな処理を実装してテストでも良き)


12.【トラブルシューティング】奥の手

ここまでやっても上手くいかない場合は、作成したGAS・SlackBotアプリを削除して1から作成してみてください
私は『順10、順11、Slack → GASの通信、など部分テストでは正常に動作するのに結合テストになると動作しない><』という状態に陥りました
原因が分からなかったので急がば回れの考えで、再度順1から作成しなおしところ、正常に動作しました

苦戦したこと

  1. チャレンジ認証(順1)

  2. 部品レベルでは動作するが結合すると動作しなかった(順12で対応)
    ※Slack → GASの通信が怪しいと思いつつも、それを確認する手段に時間を割かなかったことに反省
     GASでログ出力する方法があるようなのでそれができれば効率よく開発できたかもしれません泣
     
  3. GASでのJavaScriptに苦戦
    コード中に(順9)『function doPost(e) {』とありますが、私はそれを『Slackからの投稿を受け取る関数でdoPostと命名』したものだと認識していました(この部分は他者様の記事を参考にしたため)
    後で判明しましたがdoPostは、『関数ではなくSlackからの投稿を受け取るGAS専用イベント』でした
    ※正確にはHTTP POSTを受け付けるイベント

    私はそれに気づかずに、Slackからの投稿イベントはどこにあるの?と混乱してコードの理解に苦戦しました泣
    doPostだと分かりにくい関数名だから変えちゃおう!と行動に移したことで今まで動いていた箇所が動かなくなり更に混乱・・・『GAS doPost』で検索すると説明記事がずらり、あっさり解決
    ※このような認識違いをしていた原因は次順の感想に後述

  4. Slack、GAS、chatGPT_APIの3ツールを用いるし、手順が長いしで心が折れそうだった!
    辛抱強さが必要です

作ってみての感想

本件を実現するためにはGAS、Slackアプリ、chatGPT_AIの3つのツールについて理解する必要があり、敷居がとても高い気がした上に手順も長くて最初の段階で心が折れそうでした
しかし実際に作成してみると、苦戦はしましたが意外とあっさり実現できました(他者様記事の賜物)

GASでのJavaScriptのコード理解に苦戦した原因ですが、私は長年jQueryを扱っていたため
「doPostはfunctionがついているので関数だろう。しかしこの関数を実行するイベントがどこにも無い!」と経験と勘でそう思い込んでいたことです
JavaScriptではなくGASで扱うJavaScriptという認識をしていればよかった・・・

【自身の経験や勘に頼る】ということが近道に繋がる場合は多々ありますが、それが全てではなく時には【自身の経験や勘を疑う】ことも大切だと気付かされるいい経験となりました
初めて使うものなので、GASとは、Slackアプリとは、など事前にもっと調べて着手すれば変なところで苦戦せずに開発できたと思います

今回は
【作成したSlackBotが参加しているチャンネルで投稿するとchatGPTから回答を返すBotの作成】
がやりたきことでした
今後の課題は下記に後述しますが、まだまだ成長できるBotのため随時アップデートできればと考えてます

今後の課題

  1. GASのログ出力機能を習得
  2. GASでjQueryライブラリを使用
  3. Botからスレッドへ返信するように
  4. Botからの返信速度UP
  5. キャラ設定(ocomoji君!)

おまけ

chatGPTの弱点である【嘘】をchatGPT自身にチェックさせてみた

既存コード(順9)の一部を、以下のように変更及び再デプロイ後にBotへ質問を投げてみた

                        	
	  //ChatGPTBotが送信した場合
	  //ChatGPTで応答メッセージを作成し、Slackに送信する
	  const botId = PropertiesService.getScriptProperties().getProperty('slackBotId');
	  if (postParam.event && postParam.event.user !== botId) {
	    const channel = postParam.event.channel;
	    const text = postParam.event.text;
	    var message = requestChatGPT(text);

	     //ファクトチェックを自身に行わせる
	    sendSlack(channel, "【ファクトチェック前】\n" + message); 
	    message = "以下の内容をファクトチェックしてから再度回答して\n\n" + message;
	    message = requestChatGPT(message);
	    sendSlack(channel, "【ファクトチェック後】\n" + message);
	  }

							


・・・鹿島市には鹿島東高等学校も鹿島南高等学校も存在しない泣
ファクトチェックを何重にもしたらどういう回答が返ってくるんだろう?