SlackとOpenAIを連携する - FastAPIとAzureを使って
はじめに
この記事では、SlackのチャットからOpenAIにメッセージを送信するための環境構築と手順をまとめます。
構成
- Slack App: Slackでのメッセージを受け付け、Pythonアプリにメッセージを送信します。
-
Pythonアプリ: Slack Appからのメッセージを受け取り、OpenAIにリクエストを投げます。
- アプリの実装にはFastAPIを使います。Slack関連の実装にはSlack Boltを使います。
- アプリそのものは、AzureのApp Serviceにデプロイします。
Slack Boltの概要や基本的な使い方については公式ガイドをご覧ください。
Slack Appの作成
Slack Appの基本設定
- Slack APIにアクセスし、「Your Apps」から新しいアプリを作成します。
- アプリ名とワークスペースを指定して、Slack Appを新規作成します。
- 「Basic Information」の「Install your app」から、ワークスペースにSlack Appをインストールします。
Bot Userの追加と設定
- アプリの設定ページで「OAuth & Permissions」に移動します。
- 「Scopes」セクションで下記のPermissionを追加します。
chat:write
Pythonアプリの作成
Slack AppからOpenAIへ直接リクエストを投げることはせず、間にPythonアプリを挟みます。
このアプリはFastAPIを利用したAPIアプリであり、Slack AppからのリクエストはSlack Boltを利用して受け取ります。
エンドポイントの実装
まず、必要なライブラリをインストールします。
pip install fastapi gunicorn slack_bolt openai
次に、Pythonコードを実装します。
import os
import logging
from fastapi import FastAPI, Request, Response
from openai import OpenAI
from slack_bolt import App
from slack_bolt.adapter.fastapi import SlackRequestHandler
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
logging.basicConfig(level=logging.INFO)
app = App(
token=os.getenv("YOUR_SLACK_BOT_TOKEN"),
signing_secret=os.getenv("YOUR_SLACK_SIGNING_SECRET")
)
handler = SlackRequestHandler(app=app)
client = WebClient(token=os.getenv("YOUR_SLACK_BOT_TOKEN"))
@app.event("message")
def handle_message_events(body, say):
'''Slack Appがメッセージを検知した際にリクエストが飛んでくるエンドポイント'''
# Slack Appが受け取ったメッセージの内容を取り出す
event = body['event']
channel = event['channel']
thread_ts = event.get('thread_ts', event['ts'])
messages = get_thread_history(channel=channel, thread_ts=thread_ts)
response = call_openai(messages=messages)
# Slackにメッセージを返す
say(text=response, channel=channel, thread_ts=thread_ts)
def get_thread_history(channel, thread_ts):
'''メッセージが投稿されたスレッドの会話履歴を取得する'''
try:
result = client.conversations_replies(channel=channel, ts=thread_ts, inclusive=True, limit=50)
messages = result['messages']
return messages
except SlackApiError as e:
logging.error(f"Error fetching conversation history: {e.response['error']}")
return []
# OpenAIのクライアント
openai = OpenAI(
api_key=os.getenv("YOUR_OPENAI_API_KEY")
)
def call_openai(messages: list) -> str:
'''OpenAIに送信するメッセージを生成する'''
conversation = []
for msg in messages:
# OpenAIへのメッセージに含まれるロールについて、Slack Appからのメッセージは `assistant` とする
role = "assistant" if msg['user'] == client.auth_test()['user_id'] else "user"
conversation.append({"role": role, "content": msg['text']})
completion = openai.chat.completions.create(
model=os.getenv("CHAT_MODEL_GPT4o"),
messages=[
{
"role": "system",
"content": f"""
あなたは親切で丁寧かつ、有効的な対話型アシスタントです。
ユーザーの質問や要求に対して、もし曖昧な点や複数の解釈が可能な場合は、ユーザーの意図を明確にするための質問を返してください。
ユーザーの意図を理解した上で、的確な応答を心がけてください。
"""
}
] + conversation
)
return completion.choices[0].message.content
api = FastAPI()
@api.post("/slack/events", name="slack events")
async def events(request: Request) -> Response:
'''Slackからの疎通用リクエストを受け取るためのエンドポイント'''
return await handler.handle(request)
@api.get("/slack/test")
async def test_endpoint():
'''疎通確認用のエンドポイント'''
return {"message": "Working!"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(api, host="0.0.0.0", port=3000)
ローカル起動とテスト
以下のコマンドを実行して、アプリをローカルで起動します。
# 環境変数
export YOUR_SLACK_SIGNING_SECRET=xxxxxx
export YOUR_SLACK_BOT_TOKEN=xxxxxx
export YOUR_OPENAI_API_KEY=xxxxxx
export CHAT_MODEL_GPT4o=gpt-4o
# 起動
python app.py
http://localhost:3000/slack/test
にアクセスして、{"message": "Working!"}
が返ってくれば疎通成功です。
ローカルで起動したアプリとSlack Appの疎通
Slack Appにリクエスト先のPythonアプリに登録には、Event Subscriptionを利用します。
Event Subscription先にはパブリックなエンドポイントを登録する必要があります。
ここでは、ローカルでのデバッグに、Visual Studio CodeのPort forwarding機能を利用します。
VSCodeでPort forwardingを実行したら、「Visibility」をPublicにするのをお忘れなく。
Slack App Event Subscriptionの設定
- Slack Appのページにて、「Event Subscriptions」セクションで「Enable Events」をオンにします。
- Request URLにVSCodeの「PORTS」タブに表示されている「Forwarded Address」の末尾に/slack/eventsを付したURL(例:
https://your-app-url/slack/events
)を「Request URL」に設定します。 - 「Subscribe to Bot Events」に下記のイベントを追加します。ちなみに、下記のイベントを追加すると、それぞれに必要なScopeが勝手に追加されます。
message.channels
message.groups
message.im
message.mpim
Slack Appをチャネルに招待する
Slackのチャネル(例えばgeneral)にSlack Appを追加します。
その後、チャネルにメッセージを送信して、Slack Appがメッセージに返信してくれたら成功です。
App Service にデプロイする
デプロイの前にApp Serviceそのもののデプロイをお忘れなく。
App Serviceへのデプロイには、 VSCodeの拡張機能であるAzure App Serviceを利用します。
拡張機能をインストール後、VSCodeのサイドバーにある「Azure」を選択した後、「RESOURCES」からデプロイ先のApp Serviceを右クリックして、「Deploy to Web App...」を選択します。
すると、デプロイに伴う各種設定を聞かれるので、それぞれ選択していくとデプロイが始まります。
また、デプロイの途中経過は「AZURE」タブに表示されます。
環境情報の設定
無事にデプロイが成功したら、環境情報を設定します。
環境情報はVSCodeもしくはAzure Portalから設定します。
Azure Portalを使う場合は、デプロイ先であるApp Serviceのページに行き、「Settings」>「Environment Variables」からも設定できます。設定する内容は下記の通り。
# Pythonアプリ用の環境変数
YOUR_SLACK_SIGNING_SECRET=xxxxxx
YOUR_SLACK_BOT_TOKEN=xxxxxx
YOUR_OPENAI_API_KEY=xxxxxx
# OpenAIのモデルはお好みで
CHAT_MODEL_GPT4o=gpt-4o
# App Service用の環境変数
# コンテナ側で利用するポート番号
WEBSITES_PORT=8000
# コンテナ起動のタイムアウト時間。デフォルト(230秒)では短すぎる場合に設定する
WEBSITES_CONTAINER_START_TIME_LIMIT=600
起動コマンドの設定
続いて、gunicornを利用したアプリケーションの起動コマンドを設定します。こちらはAzure Portalから、App Serviceのページに行き、「Settings」>「Configuration」ページで設定します。
「Configuration」ページ内の「General settings」>「Startup Command」に下記コマンドを設定します。
gunicorn app:api -w 4 -k uvicorn.workers.UvicornWorker
これによって、gunicornを使ったPythonアプリの起動ができます。
Slack App のEvent Subscriptionを更新
App Service側の設定が完了したら、Slack AppのEvent Subscriptionを更新します。
ローカル起動時に登録したURLを、App Serviceのパブリックエンドポイントの末尾に/slack/events/
を付したURLに更新します。
設定変更後、再度、Slack Appにメッセージを送り、返信があれば成功です!
Discussion