SlackからChatGPTを使う

# 概要
ChatGPT 面白いですよね。
いつ仕事奪われるかとドキドキします。
「そんなChatGPTがSlackから使えたら便利かも？」と思い、2023-03-01 に公開されたChatGPTのAPIを利用して作ってみました。

※ご注意
ChatGPTのAPIは従量課金なのでご注意ください。
無料枠があるので一旦は使えます。

# アーキテクチャ
Slash Commandで質問 > API Gateway > AWS Lambda(一次受け) > AWS Lambda(本処理) > ChatGPTに問い合わせ > Slackチャンネルに回答

ポイント
Slash Commandは3秒以内にレスポンスを返す必要がある一方、OpenAIのAPIレスポンスは数秒かかるため1プロセスでの実現はできません。
そのため AWS Lambda を2段階に分けて、非同期起動した AWS Lambda で ChatGPT を利用してSlackチャンネルに投げ込む形としました。

# 手順
1. OpenAIアカウントでAPI_KEY取得
2. AWS Lambda(本処理)作成
3. AWS Lambda(一次受け)作成
4. API Gatewayでルート設定し、バックエンドにAWS Lambda(一次受け)を設定
5. SlackアカウントでSlash Command作成

順に説明していきます。

# 1.OpenAIアカウントでAPI_KEY取得
こちらから発行します。

Account API Keys - OpenAI API
https://platform.openai.com/account/api-keys

# 2.AWS Lambda(本処理)作成
```python

import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    logger.info(event)
    logger.info(context)

    # application/x-www-form-urlencoded で受信するのでデコードする
    logger.info(event["body"])
    import base64
    kv = base64.b64decode(event["body"]).decode("utf-8")
    logger.info(kv)
    import urllib
    parsed = urllib.parse.parse_qs(kv)
    # logger.info(parsed)
    name = parsed["user_name"][0]
    prompt = parsed["text"][0]
    response_url = parsed["response_url"][0]

    import os
    model_url = os.environ["MODEL_URL"]
    model = os.environ["MODEL"]

    import requests
    response = requests.post(model_url,
        headers={
            "Content-Type": "application/json",
            "Authorization": "Bearer %s" % os.environ["OPEN_API_KEY"]
        },
        json={
            "model": model,
            "messages": [
                {"role": "user", "content": prompt,},
            ]
        }
    )

    import json
    values = json.loads(response.text)
    logger.info(values)
    payload = {"response_type": "in_channel","text": prompt + "\n\n".join([choice["message"]["content"] for choice in values["choices"]])}
    requests.post(response_url,data=json.dumps(payload),headers={"content-type": "application/json"})

    return {
        "statusCode": 200,
        "body": ""
    }
```

| 環境変数名        | 値 |
|:-------------|:---------------|
| OPEN_API_KEY | 「1.」で取得したもの |
| MODEL_URL    | https://api.openai.com/v1/chat/completions |
| MODEL        | gpt-3.5-turbo |

requestsモジュールを利用しているので同梱するなり、AWS Lambda Layersで読み込むなりしてください。

API仕様はこちらです。
Chat completion - OpenAI API
https://platform.openai.com/docs/guides/chat

ポイント
・呼び出し元チャンネルに回答するためにリクエストに含まれる response_url を利用しています。
・チャンネル内のユーザ全員に見えるようレスポンスに response_type=in_channel を設定しています。
・Slash Commandは他ユーザには見えないのでレスポンスに追加するよう調整しています。

一次受けから本関数を呼び出すためにLambda関数名を控えておきます。

# 3.AWS Lambda(一次受け)作成
```python

import logging
import os
import json

logger = logging.getLogger()
logger.setLevel(logging.INFO)

import boto3
client = boto3.client("lambda")

def lambda_handler(event, context):
    logger.info(event)

    res = client.invoke(
      FunctionName=os.environ["INVOKE_FUNCTION_NAME"],
      InvocationType="Event", # 非同期
      Payload=json.dumps(event)
    )
    logger.info(res)

    return {
        "statusCode": 200,
        "body": "しばし待たれよ"
    }

```

| 環境変数名        | 値 |
|:-------------|:---------------|
| INVOKE_FUNCTION_NAME | 「3.」で控えたもの |

起動時間優先でboto3のインスタンス作成をトップレベルで行うようにしました。

# 4.API Gatewayでルート設定し、バックエンドにAWS Lambda(一次受け)を設定
今回は慣れている HTTP API で実装しました。
AWS Lambda Function URLでも良いです。

エンドポイントを控えておきます。

# 5.SlackアカウントでSlash Command作成
こちらの「Slack APIの作成」の項目を参考にさせていただきました。
Request URLに「4.」で控えたエンドポイントを設定します。

【小ネタ】SlackのスラッシュコマンドからLambdaを実行させる方法 | DevelopersIO
https://dev.classmethod.jp/articles/lambda-api-slack-command/

# 結果
![00e85350.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309394/d32746fe-6f0b-abb3-48e5-d9f158a924ec.png)

# 課題
・このアーキテクチャでもコールドスタートだとタイムアウトするかもしれません。安定化させたい場合はメモリ割り当てを強化してください。なお費用。


# 参考URL
API Reference - OpenAI API
https://platform.openai.com/docs/api-reference/introduction

Introducing ChatGPT and Whisper APIs
https://openai.com/blog/introducing-chatgpt-and-whisper-apis

Slack Bot をサーバーレスで運用する時の、タイムアウト対策【小技】 - Qiita
https://qiita.com/saken649/items/b70e462ae41614b72f77

AWS LambdaからLambdaを非同期で呼び出す(Python) - Qiita
https://qiita.com/ume1126/items/8170a10fad6b21f0f54a

「ChatGPTのAPI」と「GPT-3のAPI」は同じものなの？違うものなの？について
https://did2memo.net/2023/02/23/chatgpt-chatgpt-api-vs-gpt-3-api/#chatgptapigpt-3api

# 余談
これを作るにあたって ChatGPT にも聞いてみました。
ヒントになりました。ありがとう！