ChatGPTとLINEbotをGASで連携させる

# 背景
ChatGPTに質問する際、プロンプトを上手く与えると回答の精度が上がる。一方で、毎回の質問のたびにプロンプトを設定するのは面倒。そもそもChatGPTの画面起動も面倒。ログイン必要だし。

巷にはLINEbotがあふれているが、質問数の制限があったりプロンプトが自由に設定できなかったり、質問内容をbot作成者が見ていない保証もないので、自前で作るのが安くて便利で安心。

今回はGASを用いて1質問1回答形式のLINEbotを作成してみた。

# 準備
必要になるのは以下の3つ
- OpenAIのアカウント(API Key)
- LINE公式アカウント(チャンネルアクセストークン)
- Googleアカウント(Drive、GAS)

## OpenAI API

OpenAIのアカウントがない人は右上のSign upから登録します。すでに持っている人はLog inしましょう。

https://platform.openai.com/overview

Usageを見ると無料クレジットが$18あります。既にChatGPT Plus等に課金していると、無料クレジットが無いかもしれません（要確認）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/8cb0abe3-2ebc-a687-e72e-2f90999481ac.png)

無料クレジットが無い場合は、支払情報を登録する必要があります。ChatGPT PlusとOpenAI APIの利用は別金額です。
Billing > Overview > Set up paid accountで登録しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/25b027cd-7429-a781-5583-e2346fc88dbe.png)

OpenAI APIは従量課金です。使い過ぎを防止するため、Usage limitsを設定しておきましょう。
Hard limitは支払い上限、Soft limitは通知の閾値です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/cc183a97-fcd3-5514-3caf-9c92a1879bb2.png)

API Keys > Create new secret keyでAPI Keyを作成しましょう。画面表示されるのは作成時の一度きりなので忘れないようメモしておく必要があります。忘れた場合は再度作成すればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/2b399dec-e8df-cb90-6079-d7aee36eaeb4.png)

## LINE公式アカウント

LINE公式アカウントがない人は「LINE公式アカウントを始める」から登録します。

https://www.linebiz.com/jp/service/line-official-account/

今回はLINE Businness IDの登録からスタートします。bot用に色々検証するので、個人のLINEアカウントに紐づけるより、メールアドレスからアカウントを作成したほうが都合よいでしょう。

https://account.line.biz/signup

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/04608e0d-c031-9943-6a4f-290558f75d84.png)

メールアドレスを入力すると登録用のリンクが送られてくるので、どんどん入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/58521340-073f-cc9d-daf1-5630af096126.png)

アカウント名に「〇〇GPT」などの表記は使えません。「〇〇 powered by GPT-3」などにする必要があります。[詳しくはOpneAIのブランドガイドラインで。](https://openai.com/brand)ただし、アカウント名（Bot名）は20文字以内なのでpowered by表記を付けるゆとりがありません。アカウントの説明文などに書くことにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/866e83b3-c567-5a2f-1ed4-bb702e827474.png)

作成したアカウントの詳細を設定します。画面上部のタブからチャットを選択し応答設定画面を開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/5da1d06c-1bc5-7f3b-05d3-8a8084713ec3.png)

Webhookを有効化しMessaging APIの設定画面を開くをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/2c97fd66-d396-a47d-6033-76485f88f81b.png)

下部のLINE Developersへのリンクをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/12be4b8a-1efc-5476-20c0-91e5b327875c.png)

プロバイダーとチャンネルを選択しMessaging API設定を開きます。
画面下部にあるチャンネルアクセストークン(長期)を発行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/3a7ac784-cab7-e284-46c9-f4104fb65ec8.png)

## GAS
Google Driveの空きスペースで右クリックし、その他 > Google Apps Script からファイル作成可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/dc0b3249-4b46-5536-0f3c-3c69c6fcaebf.png)

GASのコードは以下の通りです。チャンネルアクセストークン、API KEY、プロンプトは自分の環境に合わせて設定してください。
```javascript:GAS
const LINE_CHANNEL_ACCESS_TOKEN = 'your access token';

const OPENAI_API_KEY = "your api key";
const OPENAI_MODEL = "gpt-3.5-turbo";

const OPENAI_SYSTEM_PROMPT = "your prompt";

// Google Apps Script で OpenAI API を利用するための関数
function callOpenAIAPI(prompt) {
  const apiUrl = "https://api.openai.com/v1/chat/completions";
  const headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer " + OPENAI_API_KEY
  };
  
  const payload = {
    "model": OPENAI_MODEL,
    "messages": [
      {"role": "system", "content":OPENAI_SYSTEM_PROMPT},
      {"role": "user", "content": prompt}],
  };
  
  const options = {
    "method": "post",
    "headers": headers,
    "payload": JSON.stringify(payload),
    "muteHttpExceptions": true
  };
  
  const response = UrlFetchApp.fetch(apiUrl, options);
  const responseJson = JSON.parse(response.getContentText());
  
  if (response.getResponseCode() == 200) {
    return responseJson.choices[0].message.content;
  } else {
    throw new Error("Error calling OpenAI API: " + response.getContentText());
  }
}

// openai api動作確認用
function testOpenAIAPI() {
  const prompt = "hello world";
  const result = callOpenAIAPI(prompt);
  Logger.log(result);
}

// webhook で POST された json を処理して、レスポンスを作成し、api経由で返信する
function doPost(e) {
  const event = JSON.parse(e.postData.contents).events[0];

  // このBotはメッセージイベントのみに対応
  if ( event.type != 'message' ) {
    return;
  }

  // openai apiの回答を取得
  const result = callOpenAIAPI(event.message.text);

  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': event.replyToken,
      'messages': [{'type': 'text', 'text': result}],
    })
  });
}
```
種類はウェブアプリ、実行ユーザは自分、アクセスは全員でデプロイします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/ef588d02-f594-d809-81af-65f3afd3d144.png)

デプロイ後に表示されるエンドポイントをコピーして、LINE Messaging API設定のWebhook URLに指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/18bb489f-f822-a3e1-3905-f4a90021cfbc.png)

# 成果
今回はプロンプトに以下を設定して献立メーカーを作成しました。
「You are a professional home cook. Utilizing the ingredients shown, you will be presented with 5 appropriate dishes depending on the conditions. It is possible to use ingredients other than those shown. Please think step by step and answer only the names of the dishes in Japanese.」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/25ddf4ea-1bd9-9fc5-d0b5-a64960b24f29.png)

こちらのリクエストに柔軟に対応してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/2e460c68-9525-f7c3-17cf-afbb923ea34c.png)

# まとめ
LINE公式アカウント、OpenAI APIをGASで連携させて一問一答のbotを作成しました。実際にチャット形式で利用するためには、過去の会話内容を保存しておく必要があります。

また今回はGASを使いましたが、単純にbotを作りたいだけであればMake(旧称：Integromat)を使えばもっと簡単に連携ができそうなので試してみたいです。