GPT-4 モデルを使用した Slack Bot を 月1ドル以下で運用する
はじめに
GPT-4を使いたくてChatGPT Plus($20)を契約してみたものの、元を取るほどには使わなかったので、安く使うためにAWS LambdaにGPT-4 Turbo(preview)を使ったSlack Botを作ってみました。
ポイントは以下のとおりです。
- FaaS (AWS Lambda) を使い、コンピューティング費用を抑える
- Slack Botにすることで、対話のための UI を作らなくてすむ(Slack無料プランでも利用可)
- OpenAIのAPIを使う
今回使用したソースコードは以下に公開しています。
必要なもの
- AWSのアカウント
- Slackワークスペース
- OpenAIのアカウント
利用技術について
Slack Bolt
SlackのSDKであるSlack Boltには、以下の2つのモードがあります。
- HTTPモード
- ソケットモード
HTTPモードは以前から存在する方法で、Slackで何らかのイベントが発生した際に、HTTPエンドポイントを呼び出して処理する方法です。インターネット経由でアクセス可能なHTTPエンドポイントを公開しておく必要があります。
ソケットモードはエンドポイントを公開する必要がないかわりに、何らかのインスタンスを常に起動しておきSlackと接続しておく必要があります。
WebSocket のコネクションを常時クラウド上のインスタンスで立ち上げると費用がかかるので、今回はHTTPモードを使用します。
具体的にはHTTPエンドポイントを作成するために、API GatewayとLambdaを使用します。
Lazy Listeners
ここで課題となるのが Lambda の手前に置くAPI Gateway や Slack の制約です。
API GatewayとSlack(HTTPモード)には以下の制約があります。
- API Gatewayのタイムアウト時間は 最大30秒
- Slack Botのタイムアウト時間は 最大3秒
GPT-4のような時間がかかる処理は、上記の時間内に終了することができません。
そこで利用するのが Slack Boltの機能である 「Lazy Listeners」 です。
簡単に言えば、Slackのタイムアウト発生前に一旦レスポンスを返し、その後に自分自身(Lambda)を非同期に呼び出して回答を生成して返すという機能です。
Lazy Listenersについては以下の記事を参考にさせていただきました。
仕様
仕様はおおむね以下の通りです。
-
@chatbot
など、botアプリケーションへのメンションに対してスレッドで返信する - 開始したスレッドにユーザが投稿した際は、メンションがなくてもさらに返信する
- スレッドの会話内容を保存して返信に利用する
会話内容はDynamoDBに保存しますが、トークン数の節約のため24時間以上、または20個以上前の会話内容は忘れます。
実装
中心となるコードは以下のようになります。
メンションおよびメッセージ(スレッドのリプライ)に対して返答します。
3秒以内に respond_to_slack_within_3_seconds
でレスポンスを返し、その後それぞれの非同期処理を実行します。
import logging
import os
import time
import boto3
import openai
from slack_bolt import App
# (中略)
MODEL = os.environ["OPENAI_MODEL"]
SYSTEM_CONTENT = "You are an excellent assistant."
MAX_HISTORY = 20
OPENAI_API_TIMEOUT = 50
# OPENAIのAPI KEY
api_key = os.environ["OPENAI_API_KEY"]
app = App(
# リクエストの検証に必要な値
# Settings > Basic Information > App Credentials > Signing Secret で取得可能な値
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
# 上でインストールしたときに発行されたアクセストークン
# Settings > Install App で取得可能な値
token=os.environ["SLACK_BOT_TOKEN"],
# AWS Lamdba では、必ずこの設定を true にしておく
process_before_response=True,
)
def respond_to_slack_within_3_seconds(body, ack):
# リスナーの処理を 3 秒以内に完了
logging.debug(body)
ack()
# メンションに反応するように設定
app.event("app_mention")(ack=respond_to_slack_within_3_seconds, lazy=[process_mention])
# メッセージに反応するように設定
app.message()(ack=respond_to_slack_within_3_seconds, lazy=[process_message])
if __name__ == "__main__":
# python app.py のように実行すると開発用 Web サーバーで起動します
app.start()
# これより以降は AWS Lambda 環境で実行したときのみ実行されます
from slack_bolt.adapter.aws_lambda import SlackRequestHandler
# ロギングを AWS Lambda 向けに初期化します
SlackRequestHandler.clear_all_log_handlers()
logging.basicConfig(format="%(asctime)s %(message)s", level=logging.DEBUG)
# AWS Lambda 環境で実行される関数
def handler(event, context):
# AWS Lambda 環境のリクエスト情報を app が処理できるよう変換してくれるアダプター
slack_handler = SlackRequestHandler(app=app)
# 応答はそのまま AWS Lambda の戻り値として返せます
return slack_handler.handle(event, context)
遅延実行されるイベントリスナーの詳細はGithubにあるソースコードを参照してください。
デプロイ
以下の手順でアプリケーションのデプロイを行います
- Slackアプリの作成
- アプリケーションのデプロイ
- Slackアプリの設定変更
Slackアプリの作成
まずSlackアプリを作成します。以下に手順を示します。
-
以下のリンクから「Create New App」を選択します。
https://api.slack.com/apps -
「From an App manifest」を選択します
-
インストールするワークスペースを選択します
-
マニフェストに以下の内容を記載してアプリケーションを作成します(request_urlは後で変更します)
display_information:
name: lambda-chatbot-app
features:
bot_user:
display_name: chatbot
always_online: true
oauth_config:
scopes:
bot:
- chat:write
- chat:write.public
- app_mentions:read
- channels:history
settings:
event_subscriptions:
request_url: https://example.execute-api.us-east-1.amazonaws.com/slack/events
bot_events:
- app_mention
- message.channels
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false
-
アプリケーションの一覧から作成したアプリケーションを選択し、
Signing Secret
とBot User OAuth Token
をメモします。-
Signing Secret
は左ペインのBasic Informationを選択すると表示されます
-
Bot User OAuth Token
は OAuth & Permissions を選択すると表示されます
-
これでSlackアプリができました。
OpenAIのアクセスキーを取得
OpenAIのAPIを使うには、アクセスキーが必要です。
アカウントがない場合は、まず以下のリンクの「Sign Up」からアカウントを登録してください。
アカウントの登録には、メールアドレスと携帯電話番号が必要です。
-
ログインしたら「API」を選びます
-
左側のメニューから「API Keys」を選び、新しいキーを作成します
キーを発行したらメモしておきます。悪用されないように管理には充分注意してください。
前払いのチャージ
なお、2023年11月時点では、作成したばかりのアカウントでGPT-4 Turboのモデル(gpt-4-1106-preview)を利用するには、あらかじめ前払いでいくらかチャージしておく必要がありました。
クレジットのチャージは以下から実施できます。
-
左側メニューの「Setting」→「Billing」を選択する
-
「Payment methods」でクレジットカードを登録
-
「Buy credits」でいくらかチャージ(私は$10チャージしました)
クレジットカードを登録したら、Usage Limitsで上限を低めに設定しておくと安心です。
アプリケーションのデプロイ
次にAWS Lambdaをデプロイします。Lambdaを呼び出すためのAPI Gatewayもあわせて作ります。
Serverless Frameworkのインストール
デプロイにはNode.jsのパッケージマネージャnpmとServerless Frameworkを使用します。
また、必要なpythonパッケージを同梱するためのプラグインをあらかじめインストールします。
$ sls plugin install -n serverless-python-requirements
必要なパッケージはrequrements.txtに記述します。
slack-bolt
python-lambda
python-dotenv
openai >= 1.0
boto3
アクセスキーの設定
.env
という名前のファイルを作成し、SlackのSigning Secret
とBot User OAuth Token
、OpenAIのAPI Keys
を記載します。
SLACK_SIGNING_SECRET=xxxxx
SLACK_BOT_TOKEN=xxxxx
OPENAI_API_KEY=xxxxx
ソースコードを構成管理する場合、.env
ファイルをリポジトリに含めないように気をつけましょう。とくにGitHubなどの公開リポジトリで管理する場合は注意しましょう。
.env
Lambdaのデプロイ
また、serverless.yml
ファイルにデプロイのための情報を記述します。
ここではGPT-4 Turboのモデル gpt-4-1106-preview
を使用するように記述していますが、GPT-3.5 (gpt-3.5-turbo-1106
) なども利用可能です。
frameworkVersion: '3'
useDotenv: true
service: lambda-chatbot-app
provider:
name: aws
runtime: python3.10
region: us-east-1
iam:
role:
statements:
- Effect: Allow
Action:
- lambda:InvokeFunction
- lambda:InvokeAsync
Resource: "*"
- Effect: 'Allow'
Action:
- 'dynamodb:*'
Resource: "*"
environment:
SERVERLESS_STAGE: ${opt:stage, 'prod'}
OPENAI_MODEL: 'gpt-4-1106-preview'
SLACK_SIGNING_SECRET: ${env:SLACK_SIGNING_SECRET}
SLACK_BOT_TOKEN: ${env:SLACK_BOT_TOKEN}
OPENAI_API_KEY: ${env:OPENAI_API_KEY}
functions:
app:
handler: app.handler
timeout: 180
events:
- httpApi:
path: /slack/events
method: post
resources:
Resources:
ExampleTable:
Type: "AWS::DynamoDB::Table"
Properties:
TableName: "lambda-chatbot-app-history"
AttributeDefinitions:
- AttributeName: "id"
AttributeType: "S"
- AttributeName: "timestamp"
AttributeType: "N"
KeySchema:
- AttributeName: "id"
KeyType: "HASH"
- AttributeName: "timestamp"
KeyType: "RANGE"
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
package:
patterns:
- "!.env"
- "!.venv/**"
- "!.secret/**"
- "!node_modules/**"
plugins:
- serverless-python-requirements
custom:
pythonRequirements:
#zip: true
#slim: true
準備ができたらアプリケーションをデプロイします。
$ sls deploy
非同期に実行されるときのためにlambdaのタイムアウト時間を長めに設定しているため、API Gatewayのタイムアウト時間(30秒)より長いと警告が出ますが無視してください。
作成されたendpointのURLをメモしておきます。
Slackアプリの設定変更
SlackアプリのApp ManifestのURLに設定し、アプリをインストールしなおします。
-
以下のリンクから先ほど作成したアプリケーションを選択します。
https://api.slack.com/apps -
左メニューの「App Manifest」を選択し、request_url を先ほどメモしたendpointのURLに書き換えて保存します。
動作確認
Slackのワークスペースから適当なチャンネルでchatbotに話しかけてみます。
しばらく待って返信があれば成功です。
費用
-
週に数回会話するくらいだと、OpenAIの使用料は1か月で$1以下でした。
-
AWS Lambda/API Gatewayは無料枠の範囲に収まっています
どなたかの参考になれば幸いです。
追記(2023/12/26)
OpenAI SDK 1.x向けに修正しました。
Discussion