見出し画像

蒙古タンメン中本について答えるスプレッドシートの関数をGoogle Apps Script(GAS)とOpenAI Assistants APIで作ってみた

こんにちは。
penguinmanと申します。

2023年11月6日(火)にOpen AIから大きな発表がありましたね。
あまりにも大きな発表が多すぎて私もまだ噛み砕き切れておりませんが、今からもっと面白いことができるかもとワクワクしています!

今日は先日発表されたAssistants APIとGoogle Apps Script(以降GASと略記します)を使って蒙古タンメン中本(以降中本と略記します)について答えてくれる関数を作りましたのでレシピを共有したいと思います🍜


何を作ったか?

Wikipediaの中本の記事を取り込んで、中本について答えてくれるようなスプレッドシートの関数(post_assistant関数とよびます)をGoogle Apps Script(GAS)とAssistants APIを使って作成しました。今回は中本の記事を取り込ませましたが、ここで会社の社内規定やプロダクトのFAQを取り込めば、それについて答える関数ができるので汎用性は◎だと思っています。

post_assistant関数に中本に関する質問文を入れると回答してくれる。
本当はGIFでお見せしたかったのですがnoteでアップロードが失敗するのでご容赦ください。

どうやって作ったか?

基本的に以下の4つの関数をGAS上で組んでいます。
各関数はAssistants APIのエンドポイントにHTTPリクエストを投げています。

処理の流れ

メインで動くコードはmain.gsであり、上記4つの関数を実行しています。
GASで実行する際には以下のコードをコピペした上で、スクリプトプロパティにOpenAIのAPI keyを設定し(後述)、main.gsの2行目のところにassistant_idを貼り付ける(後述)必要があります。GASがわかる方はこれで大丈夫です。よくわからない方も安心してください😊 この後説明していきます💪

main.gs

function post_assistant(message){
  // プロパティからAPIキーを取得 (要設定)
  const OPENAI_API_KEY = PropertiesService.getScriptProperties().getProperty('API_KEY'); # スクリプトプロパティにOpenAI API keyを貼り付ける(後述)
  // 実行するアシスタントid (コード書き換え必要)
  let assistant_id = "asst_*****************" # assistant_idを貼り付ける(後述)

  // httpリクエストのベースの情報
  let options = {
    'contentType': 'application/json',
    'headers': {
      'Authorization': 'Bearer ' + OPENAI_API_KEY,
      'OpenAI-Beta': 'assistants=v1'
    },
    'muteHttpExceptions': true // HTTP例外をミュートにする
  };
  // threadを新規作成
  let thread_id = create_thread(options);
  Logger.log(thread_id);
  // メッセージを送る
  send_message(options, thread_id, message);
  // アシスタントを実行する
  run_assistant(options, thread_id, assistant_id);
  // アシスタントの実行終了まで20秒待機
  Utilities.sleep(20000);
  // メッセージを取得する
  let res_message = get_message(options, thread_id);
  Logger.log(res_message)
  return res_message
}

create_thread.gs

function create_thread(options) {
  let options_c = JSON.parse(JSON.stringify(options));
  let url = 'https://api.openai.com/v1/threads';
  options_c['method'] = 'post';

  try {
    var response = UrlFetchApp.fetch(url, options_c);
    var responseCode = response.getResponseCode();
    var content = response.getContentText();
    
    if (responseCode == 200) {
      Logger.log('Success: ' + content);
      return JSON.parse(content)["id"];
    } else {
      Logger.log('Error: ' + responseCode + '\n' + content);
    }
  } catch (e) {
    Logger.log('Exception: ' + e.toString());
  }
}

send_message.gs

function send_message(options, thread_id, message) {
  let options_c = JSON.parse(JSON.stringify(options));
  let url = 'https://api.openai.com/v1/threads/'+thread_id+'/messages';
  options_c['method'] = 'post';
  // threadに送るメッセージの定義
  let data = {
    "role": "user",
    "content": message
  };
  options_c['payload'] = JSON.stringify(data);

  try {
    var response = UrlFetchApp.fetch(url, options_c);
    var responseCode = response.getResponseCode();
    var content = response.getContentText();
    
    if (responseCode == 200) {
      Logger.log('Success: ' + content);
    } else {
      Logger.log('Error: ' + responseCode + '\n' + content);
    }
  } catch (e) {
    Logger.log('Exception: ' + e.toString());
  }
  
}

run_assistant.gs

function run_assistant(options, thread_id, assistant_id) {
  let options_c = JSON.parse(JSON.stringify(options));
  let url = 'https://api.openai.com/v1/threads/' + thread_id + '/runs';
  options['method'] = 'post';
  // 起動させるアシスタントの定義
  var data = {
    "assistant_id": assistant_id
  }
  options_c['payload'] = JSON.stringify(data);

  try {
    var response = UrlFetchApp.fetch(url, options_c);
    var responseCode = response.getResponseCode();
    var content = response.getContentText();
    
    if (responseCode == 200) {
      Logger.log('Success: ' + content);
    } else {
      Logger.log('Error: ' + responseCode + '\n' + content);
    }
  } catch (e) {
    Logger.log('Exception: ' + e.toString());
  }
  
}

get_message.gs

function get_message(options, thread_id) {
  let options_c = JSON.parse(JSON.stringify(options));
  let url = 'https://api.openai.com/v1/threads/'+thread_id+'/messages';
  options_c['method'] = 'get';

  try {
    var response = UrlFetchApp.fetch(url, options_c);
    var responseCode = response.getResponseCode();
    var content = response.getContentText();
    
    if (responseCode == 200) {
      Logger.log('Success: ' + content);
      return JSON.parse(content)["data"][0]["content"][0]["text"]["value"];
    } else {
      Logger.log('Error: ' + responseCode + '\n' + content);
    }
  } catch (e) {
    Logger.log('Exception: ' + e.toString());
  }
}

作成手順

それでは早速作成手順を詳しくみていきたいと思います✨
概略を先にお示しするとOpenAI側の作業が2ステップ、GAS側の作業が2ステップで合計4ステップになります。

作業の概略図

Playgroundでの実験

まずはOpenAIのAsssitants APIのPlaygroundで中本の情報を読み込ませたChatGPT(以降中本GPTと略記します)の精度を確認しましょう。

まずはAsssitantsにアクセスします。

Assistantにアクセスした時の画面

最初は上記のように何もない画面が表示されると思いますので、真ん中の「+Create」ボタンを押します。

Assistantの設定方法

すると今度は上記ような画面が出るので画像のように設定していきます。Retrieval機能(Chatgptにあらかじめ資料を読み込ませる機能)の場合はModelの部分はgpt-4-1106-previewかgpt-3.5-turbo-1106のどちらかが選べるようですが、gpt-4-1106-previewだと1回のやり取りで大体10円程度の費用が発生するので最初は費用が少額で収まるgpt-3.5-turbo-1106を設定することをお勧めします。設定を終えて「save」ボタンを押すとAssistantが追加されます。

Assistant追加後の画面


Assistantを再度選択した時の画面

追加されたAssistantを再度クリックすると上記の画像のような設定画面がもう一度出てくるので右上の「Test」ボタンをクリックしてください。

PalaygroundのTest画面

上記のような画面に遷移するので、テキストボックス内に文章を入れて「Add and run」ボタンを押すと先ほどアップロードしたテキストをもとに回答していることがわかります😀
この画面の左側の部分で資料を追加でアップロードしたり、削除したりできるので資料を変えつつ、自分が望む回答精度になるように調整してみてください。お金の余裕がある方はModelでgpt-4-1106-previewを選んでみるのも良いかもです。中本GPTでは精度がかなり上がりました。

何度がチャットをしたら必ずこちらから費用を確認し、思いがけず多額の請求がこないようにしましょう。

必要情報の取得

Playgroundで十分な精度が出ることを確認したら以下の2つの情報をメモしておいてください。

(1) assistant_id
Playgroudの下記の画像の位置にある「asst_*********」というidです。

assistant_idの位置

(2) OpenAI API key
こちらの画面から発行できます。流出すると他の人に使われてしまうため流出しないように注意してください⚠️

GASの事前設定

適当なスプレッドシートを開いて以下の「Apps Script」部分からGASのエディタに入ってください。

GASエディタの入り方
スクリプトプロパティ画面への入り方

エディタに入ったら左側のメニューの一番したの歯車ボタンをクリックしてスクリプトプロパティの設定画面に行ってください。

スクリプトプロパティの設定

スクリプトプロパティを設定する画面を見つけたら、上記の画像のようにプロパティの欄には「API_KEY」、値の欄には必要情報の取得の(2) OpenAI API keyを入力してください。

入力が終わったら後少しで完成です💪

コードをコピペ

さて、最後のステップです。
下記の画像のように「エディタ」をクリックしてエディタ画面に移動します。

エディタ画面への移動

一番最初にお示ししたmain.gsのコードの2行目の部分に必要情報の取得の(1) assistant_idを貼り付けます。

main.gs

// 実行するアシスタントid (コード書き換え必要)
let assistant_id = "asst_*****************" # assistant_idを貼り付ける(後述)

後はmain.gs~get_message.gsまでの5つの関数をひたすらエディタにコピペし、最後に右上のデプロイボタンを押して完成です✨
仕組みとしてはHTTPリクエストを送っているだけなのですがそれぞれの関数のAssistants APIとの対応関係を載せておきます。

create_thread関数 - Create thread
send_message関数 - Create message
run_assistant関数 - Create run
get_message関数 - List messages

注意点を2点挙げておきます。

(1) main.gsでrun_assistant関数を実行した後に20秒の待ち時間を設けています。これはすぐにget_message関数を実行してしまうと言語モデルからの応答がない状態でメッセージを取得してしまうため、言語モデルのアウトプットが出るまで20秒待たせています。こちらページを見るとおそらくrunの状態(実行中か終了したか)を識別する方法があるのでそれをもとに組むともっと良いかもです。

(2) get_message関数はcreate_thread関数で作成したスレッドにあるメッセージを全て取得し、その最新のメッセージのみを抜き出してくる関数になっています。そのため、もし言語モデルのアウトプットがない状態で実行するとsend_messageで投げたメッセージが返ってくる可能性があります。

Done

後は「何を作ったか?」で共有した通り、スプレッドシート上で中本に関する質問をpost_assistant関数に通すだけです。

この仕組みを使って是非自分オリジナルのGPT関数を作ってみてください!この記事が皆様のお役に立てれば幸いでございます🙇

それではまた!!

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

#AIとやってみた

27,268件

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