お手軽にChatGPTをSlackから使う（gpt-3.5-turbo）

# 概要
2023年3月1日に[一般公開](https://openai.com/blog/introducing-chatgpt-and-whisper-apis)されたChatGPT API（gpt-3.5-turbo）をSlackから使ってみました。
今まで最も性能の高いモデルだったGPT3（text-davinci-003) と比較すると、
API経由で入力されたデータはデフォルトで学習しないので情報漏洩のリスクが軽減し、
（ただし、データ自体はOpenAI社が30日間保存している）
利用価格は10分の1（英語の場合で約4000文字または750語あたり約0.27円）に削減されたようです。
![F096A612-01D4-4089-B241-16249331D626.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/1b177e84-8e81-e4d0-1ce6-579c480e5d6f.png)

# システム構成
![image_123986672.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/abdfb82f-f1bc-37a3-85af-b0d0a3c52130.jpeg)
`Slack/AWS Lambda/ChatGPT API` の3つです。
SlackからのリクエストをAWS Lambdaに送信する場合、API GatewayとAWS Lambdaを組み合わせてAPIエンドポイントを作成し、API Gatewayに認証/承認を設定することで安全なアーキテクチャを実現できそうでしたが、
今回はお手軽にChatGPT APIを利用したいので割愛します。

## Slack（Slack App）
テキストをAWS Lambdaに送信する

## AWS Lambda
Slackからテキストを受信し、ChatGPT APIにテキストを送信し、その回答をSlackに投稿する

## ChatGPT API（gpt-3.5-turbo）
AWS Lambdaからテキストを受信し、回答を返却する

# つくりかた
## 1. Slack AppのTokenを発行
### 1-1. Slack Appを作成
1. https://api.slack.com/apps へアクセスする
2. `Create New App → From scratch`を押下する
3. アプリ名とワークスペースを入力して`Create App`を押下する
アプリ名はなんでもOKですが私は「Chat GPT Service」にしました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/d346ad42-7b42-0c3e-f7c5-98d4639a6100.jpeg" width=50%>

### 1-2. Slack botを設定
`App Home`を押下し、Slack botの名前を設定する。
名前はなんでもOKですが私は「Chat GPT」にしました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/d6359ae9-1f30-76cf-90bf-153714ae61b5.jpeg" width=50%>

### 1-3. Slack（Slack App）のTokenを取得
1\. `OAuth & Permissions`を押下し、以下のScopesを設定する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/000465fa-bc19-3f37-6264-b5b0adb09316.png" width=50%>

2\. `Install to Workspace`を押下し、ワークスペースにインストールする
3\. Tokenが発行されるので`Bot User OAuth Token`のTokenを保持する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/3c867167-5e00-6315-6928-3a1aeefd86b7.png" width=50%>

## 2. OpenAI APIのキーを発行
1. [OpenAIのAPI keys](https://platform.openai.com/account/api-keys)にアクセスする
2. `+ Create new sercret key`を押下する
3. `API key generated`にAPIキーが発行されるので保持する

### ※有料版を利用する場合の注意点
私は無料版を利用していますが、有料版を利用する場合、
想定外の高額請求は避けたいので利用料制限を設定しておくことをお勧めします。
左側メニュー`Billing`の`Usage limits`内の`Hard limit`（利用料が一定を超えたらAPIの利用停止）で設定できます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/4461239c-14f0-98ed-37c7-1ed685387edb.png" width=50%>

## 3. 疎通用のAWS Lambdaを作成
### 3-1. 関数を作成
1\. [AWS Lambda](https://ap-northeast-1.console.aws.amazon.com/lambda/home)で関数を作成する
- ランタイム設定：Node.js 18.x
- アーキテクチャ：x86_64

2\. タイムアウトを設定する
- タイムアウト：とりあえず3分
実運用では状況に応じて調整する必要があります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/0721f23a-7eb0-8fc9-24eb-f70186498491.png" width=50%>

### 3-2. 関数URLを設定
今回、認証タイプはNONEにする。
実運用には向かないです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/c9007d3c-5226-d571-a712-d881dee69b86.png" width=50%>

### 3-3. 疎通用のコードを作成
Slack Appと疎通を行うためのコードを作成する
```javascript
export const handler = async (event) => {
    let json = JSON.stringify(event.body);
    return { statusCode: 200, body: json };
};
```

## 4. Slack Appのイベントを設定
### 4-1. Event Subscriptionsを設定
Slack Appの`Event Subscriptions`を押下してEnable EventsをONにし、
前項「3-2. 関数URLを設定」で作成したAWS Lambdaの関数URLを設定する。
`Verified`になれば疎通成功です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/6442c712-9e28-2231-bea0-06cbeb08a49e.png" width=50%>

### 4-2. Subscribe to bot eventsを設定
Subscribe to bot eventsを以下のように設定し、`Save Changes`を押下する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/222ce3bb-ebbf-0def-b7c9-4e8ba95944fc.png" width=50%>

## 5. AWS Lambdaのレイヤーを作成
### 5-1. nvmを導入
ローカルのターミナルで以下のコマンドを実行する。
nodejsはLambdaで利用するバージョンに合わせました。
```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
. ~/.nvm/nvm.sh

nvm install v18.13.0
```

### 5-2. パッケージを生成
ローカルのターミナルで以下のコマンドを実行する。
パッケージのバージョンは2023年3月1日時点で最新のものにしました。
```bash
mkdir nodejs
cd nodejs
npm install @slack/web-api
npm install openai

npm list @slack/web-api
# @slack/web-api@6.8.1

npm list openai
# openai@3.2.1

zip -r nodejs.zip .
```

### 5-3. レイヤーを作成
先ほどローカルで生成した`nodejs.zip`をレイヤーにアップロードし、作成する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/925af812-db0e-3fe7-d503-407580b38eeb.png" width=50%>

## 6. ChatGPT用のAWS Lambdaを作成
### 6-1. 環境変数を設定
前項「3. 疎通用のAWS Lambdaを作成」で作成済みのLambdaに、
「1. Slack AppのTokenを発行」と「2. OpenAI APIのキーを発行」で保持したTokenとキーを
`SLACK_BOT_TOKEN`と`OPENAI_API_KEY`にそれぞれ設定する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/dbcbb7b2-d926-1c8d-131f-60c0755fa524.png" width=50%>

### 6-2. レイヤーを追加
前項「5-3. レイヤーを作成」で作成したレイヤーを関数に追加する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/1d45b959-c8d3-de52-50d0-6231c30ba556.png" width=50%>

### 6-3. コードを変更
前項「3-3. 疎通用のコードを作成」で作成したコードを以下に変更する
```javascript
import { WebClient } from '@slack/web-api';
import { Configuration, OpenAIApi } from 'openai';

const slackClient  = new WebClient(process.env.SLACK_BOT_TOKEN);
const openaiConfig = new Configuration({ apiKey: process.env.OPENAI_API_KEY });
const openaiClient = new OpenAIApi(openaiConfig);

export const handler = async (event, context) => {
    if (event.headers['x-slack-retry-num']) {
        return { statusCode: 200, body: JSON.stringify({ message: 'No need to resend' }) };
    }

    const body = JSON.parse(event.body);
    const text = body.event.text.replace(/<@.*>/g, '');

    const openaiResponse = await createCompletion(text);

    const thread_ts = body.event.thread_ts || body.event.ts;
    await postMessage(body.event.channel, thread_ts, openaiResponse);

    return { statusCode: 200, body: JSON.stringify({ success: true }) };
};

async function createCompletion(text) {
    try {
        const response = await openaiClient.createChatCompletion({
          model: 'gpt-3.5-turbo',
          messages: [{role: 'user', content: text}],
        });
        return response.data.choices[0].message?.content;
    } catch(err) {
        console.error(err);
    }
}

async function postMessage(channel, thread_ts, text) {
    try {
        let payload = {
            channel: channel,
            thread_ts: thread_ts,
            text: text,
            as_user: true,
        };
        await slackClient.chat.postMessage(payload);
    } catch(err) {
        console.error(err);
    }
}
```

# つかいかた
1. Slackのチャンネルに、作成したSlack Appを追加する
2. botへのメンション付きでテキストを送信する
3. botからスレッドに回答が届く

実行ログはAWSのCloudWatchで確認できます。
![884F0903-C120-47BE-95FF-ABD084BD0F2D.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/7f73d8f7-fd35-26cb-262f-dfce16167d48.png)

# 参考
- [Slack | Web API](https://slack.dev/node-slack-sdk/web-api
)
- [OpenAI API](https://platform.openai.com/docs/api-reference/introduction?lang=node.js)