VSCodeとGASでChatGPT(gpt-3.5-turbo)をより安全快適に使う

OpenAI社が2023年3月1日に公開した「[ChatGPT API](https://platform.openai.com/docs/models/gpt-3-5)」についての初心者向け記事です。
VisualStudio CodeとGoogle Apps ScriptでChatGPT(gpt-3.5-turbo)をより安全快適に使いましょう。

GASはともかくスプレッドシートでGPTを関数化すると、ものすごい勢いでトークンを失う＆安定に動かない問題を解決しています。


# 「gpt-3.5-turbo」そもそも何が嬉しいの？

公式資料

https://openai.com/blog/introducing-chatgpt-and-whisper-apis

まず何といってもモデルの使用料金がインパクトあります。
ChatGPTモデルファミリー「gpt-3.5-turbo」は、ChatGPT製品で使用しているものと同じモデルであり、既存の「GPT-3.5」よりもGPT-3.5モデルより10倍安く、価格は1,000トークンあたり0.002ドルです。
また、チャット以外の多くのユースケースに最適なモデルです。プロンプトにわずかな調整を加えるだけで、text-davinci-003 から gpt-3.5-turbo に移行することができます。

- [OpenAI社のモデル一覧](https://platform.openai.com/docs/models/gpt-3-5)

https://twitter.com/o_ob/status/1632060077116108800

その他の違いとしては、
- 従来はオプトアウト(申請することで利用されない)だった→ChatGPI APIはオプトイン形式(申請しない限り利用されない)でモデルの改善に利用されないことになった(法的監視で30日は保管される)
- 企業など専用インスタンスはAzureに置くことができる
- 従来方式に代わりメタデータと一緒にメッセージのシーケンスを消費。
  - 内部では入力はモデルが消費するトークンのつながりとしてモデルにレンダリングされる。チャットマークアップ言語「ChatML」という新しいフォーマットが使用されている。
    - もうちょっと利用者向けに解説すると、質問者とAIアシスタントの今までのやり取りを渡すことで、再現性の高い会話の生成や、結果の検証がしやすくなります。

あと、体感で「ものすごく速い」です。


# VisualStudio Code で試す

REST APIを使ったことない人でも試せるように説明してみます。
WindowsでもMacでも動くはずです。
OpenAIのAPIを[こちら](https://platform.openai.com/account/api-keys)から取得しておいてください。

1. まずVisual Studio Codeの拡張機能「[REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client)」をインストールします。
2. 適当なフォルダで、hogehoge.rest もしくは hogehoge.http というファイル名のテキストファイルを作成
3. 以下のようなリクエストを作ります。``{{OpenAI}}``のところはAPIのSECRET KEY（ベアラートークン）です。取扱注意です。

```
# この下の[Send Request]をクリックするとリクエストが飛びます
curl https://api.openai.com/v1/chat/completions \
 -H 'Content-Type: application/json' \
 -H 'Authorization: Bearer {{OpenAI}}' \
 -d '{
  "model": "gpt-3.5-turbo",
  "messages": [
    {
      "role": "user",
      "content": "JSONのエスケープ処理をGoogle Apps Scriptで："
    }
  ]
 }'
```

こんな感じの結果が返ってきます

![こんな感じの結果が返ってきます](https://res.cloudinary.com/zenn/image/fetch/s--kGkG9ffD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_1200/https://storage.googleapis.com/zenn-user-upload/deployed-images/5f09c6c9cb83a1ef5b207dfd.png%3Fsha%3D0d7e7e6bd3e77b4b313582d39f14e9aa4ec7881b)

`200 OK` が返ってこない人は、以下の確認をお願いします。

- ネットワークの接続
- OpenAIアカウントの確認（トークンを使い切っていないか）
- ベアラートークンの誤り
- エンドポイントなどの誤り
- role, content 等のJSON書式誤り

ベアラートークンをソースコードに書いたまま、GitHubなどに置かないように気をつけてください。
Ctrl+Shift+P でコマンドパレットを開いて、「Settings.json」と打って、ユーザの設定を開きましょう。

![](https://res.cloudinary.com/zenn/image/fetch/s--wtLufIPF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_1200/https://storage.googleapis.com/zenn-user-upload/deployed-images/3e06b107b9cec603c2a5459f.png%3Fsha%3De6b2b40338165592d9ad93499dd93b228e2ad591)

`"rest-client.environmentVariables":` というセクションを作り、`dev`環境の変数`{{OpenAI}}`を書いていきましょう。

![](https://res.cloudinary.com/zenn/image/fetch/s--y0Lkh2SU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_1200/https://storage.googleapis.com/zenn-user-upload/deployed-images/ac85b90699521c2a87d0ec31.png%3Fsha%3Dfca5c128b532b0b04e8ae9814c596bcd7433842a)


こうすることで、GitHubのソースコード管理下にベアラートークンを書くのではなく、VisualStudio Codeのユーザの環境変数に保存することができます。
この方法を使えば、GitHubアカウントなどで複数のPCを使っていても同期できますし、Ctrl + Alt + E で環境を切り替えることも簡単にできるようになります（右下に表示されています）。

さて、もうひとつだけ ChatGPT APIらしい実験をして、GASの関数に入りましょう。

```
curl https://api.openai.com/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer {{OpenAI}}' \
  -d '{
  "model": "gpt-3.5-turbo",
  "messages": [
    { "role": "user",
      "content": "父親の役割を箇条書きで3つ教えてください。"
    },
    {
      "role": "assistant",
      "content": "\n\n1. 子供たちの育成や教育のサポート\n2. 家庭と社会との仲介役として、家族や地域社会の問題を解決する役割\n3. 家計管理や家族の健康管理に関する責任の一部を担う。"
    },
    {
      "role": "user",
      "content": "上記の色を選んだ理由を教えてください。"
    }
  ]
}'
```
このようなコンテキストを考慮した問合せを行うことができます。
message配列の2つ目のcontentは実際にChatGPTが出力してきたAIアシスタントの回答としての文字列です。3つめの要素はあえて回答を無視したユーザの質問です。
このように、ロールを設定することで、なぜそのような回答をしたのかも検証しやすくなりますし、ルールなども書きやすくなります。その先の回答の精度も上げることができます。


# Google Apps Script - Spreadsheet関数での利用

以下は、Google Apps Scriptでのコードです。
スプレッドシート上で「=ChatGPT(B1)」といった形式で利用できるようになります。
Try, Catchを使ってより安全に問合せをしています。

```
function ChatGPT(text){
  let ret = "";
  let prop = PropertiesService.getScriptProperties();
  let apikey = prop.getProperty("OpenAI-APIKEY"); //スクリプトプロパティにこの名前で保存しておいてください
  var url = "https://api.openai.com/v1/chat/completions";
  let header = {
    "Authorization":"Bearer "+ apikey,
    "Content-type": "application/json",
  }
  let payload = {
    "model": "gpt-3.5-turbo",
    "max_tokens" : 2048,  //turboの最大値は4096まで、他は2048。消費するtokenは入力した文章のtokenも含むので、大富豪であったとしても控えめにしておいたほうがいい。
    "temperature" : 1.0,
    "messages": [{"role": "user", "content": escapeAll(text)}] //この関数については後ほど解説します
  }
  let options = {
    "muteHttpExceptions" : true, //エラーを制御したい場合はこちらをfalse
    "headers": header, 
    "method": "POST",
    "payload": JSON.stringify(payload)
  }
  try { // Try, Catchを使ってより安全に
    const response = UrlFetchApp.fetch(url, options);
    // Logger.log(response.getContentText());
    const res = JSON.parse(response);
    ret += res.choices[0].message.content.trim();
    //Logger.log(ret);
  } catch (e) {
    ret += "[Error] "+ e.message; // エラー文字列が不要な場合はこちらをコメントアウト
    Logger.log(ret);
  }
  return ret;
}
```

GAS編集画面の左から、 `OpenAI-APIKEY`という名前の スクリプトプロパティを作成してAPI Secretを安全に管理しましょう。
（とはいえ、このスプレッドシートの取扱いによっては非常に危険です）
OpenAIのダッシュボード [Billing > Usage Limits](https://platform.openai.com/account/billing/limits) から上限を設定することができます。


さて、この関数の下に、さらに以下の文字列をエスケープする関数を追加してください。

```
function escapeAll(jsonString="JSONのエスケープ処理をGoogle Apps Scriptで:") {
  return jsonString.replace(/[\"\\\n\r\t\b\f\']/g, function (match) {
    switch (match) {
      case '"': return '\\"';
      case '\\': return '\\\\';
      case '\n': return '\\n';
      case '\r': return '\\r';
      case '\t': return '\\t';
      case '\b': return '\\b';
      case '\f': return '\\f';
      case '\'': return '\\\'';
    }
  });
}
```

スプレッドシートとGASにおけるJSONでは引用符や「:(コロン)」や改行コードは誤動作の原因になりますので、危ないものは明示的に回避しています。
`JSON.stringfy(text)`を使うことで回避できるはずなのですが、それでもチャットの中でどんな文字列が危険な動作をするかはわからないので、より丁寧に書いたほうがいいかもしれません（このコードはChatGPTが生成しています。いろんな書き方があって勉強になります）。


## GAS で使ってみた考察

このコードを大きなスプレッドシートで使うと`[Error]`が表示されることもあります。
`=If(Left(ChatGPT(A1),7)="[Error]",...)` といった形で回避することをお勧めします。

理由の多くは(表示されるエラーを観測するとわかりますが)

- Google → OpenAI へのリクエストが多すぎる
- 問合せのためのトークンが足りない
- 解釈できない文字列が混入した
 
…といったケースであることがわかっています。

リクエストが多すぎるのは、このようなGAS関数での実装で、短期間に同一IPアドレスから大量のリクエストが発せられるときに起きます。
実際にはリロードや再計算、再描画の際にコールされますので、自分の財布も痛いので、大量にこの関数を使うことはおすすめできません。
（他の方のブログなどでSpreadsheetでこのようなGPTのAPIを叩く例を見かけるのですが）
開発中はセルから直接使って実験し、安定してきたらセルから呼び出すのではなく、処理の中で呼ぶぐらいがよいかもしれません。

Google Sheetsに大量のGPT利用関数を配置すると、「単一時間内の同一IPアドレスからの制限エラー」という内容のエラーで止まります。
「止まってくれてありがたい」なのですが、有料トークンを無駄に消費しないためのセルの組み方などは重要かもしれないですね。
例えば、上のようにIF文で区切るとか、1行で何度もGPT関数をコールしないようにするとか、処理が終わったセルは文字列に置きなおしていくとかですね。


なお問合せ時のトークンは最大2048となっていますが、turboの最大は4096に拡張されています。

- [公式資料](https://platform.openai.com/docs/api-reference/completions/create?lang=curl)
- [Tokenizer](https://platform.openai.com/tokenizer)
- [公式サイトのディスカッション](https://community.openai.com/t/can-i-set-max-tokens-for-chatgpt-turbo/81207/11)

completionsでの問いあわせは、問合せにつかうプロンプトとしての文字列と、回答として出力される文字列の両方で合計4096となるように納めないと、エラーになるようです。

# まとめ

- ChatGPT APIは安くて早くて便利
- Visual Studio Code + REST Client を使って簡単に安全に実験できます
- Try-Catchを使った安全な問合せ
- Google Spreadsheet のセルで利用できる関数はコールされすぎてあぶない
- とはいえセルやスクリプトの組み方でいろんな使い方ができそう

業務だけでなく、TwitterBotや AI VTuber(AITuber)にも使えそうですね！

この記事は[Zenn](https://zenn.dev/o_ob/articles/gas-chatgpt-api)で原稿を書いています。