💬

SlackとOpenAIを連携する - FastAPIとAzureを使って

2024/06/04に公開

はじめに

この記事では、SlackのチャットからOpenAIにメッセージを送信するための環境構築と手順をまとめます。

構成

  • Slack App: Slackでのメッセージを受け付け、Pythonアプリにメッセージを送信します。
  • Pythonアプリ: Slack Appからのメッセージを受け取り、OpenAIにリクエストを投げます。
    • アプリの実装にはFastAPIを使います。Slack関連の実装にはSlack Boltを使います。
    • アプリそのものは、AzureのApp Serviceにデプロイします。

Slack Boltの概要や基本的な使い方については公式ガイドをご覧ください。
https://slack.dev/bolt-python/ja-jp/tutorial/getting-started-http

Slack Appの作成

Slack Appの基本設定

  1. Slack APIにアクセスし、「Your Apps」から新しいアプリを作成します。
  2. アプリ名とワークスペースを指定して、Slack Appを新規作成します。
  3. 「Basic Information」の「Install your app」から、ワークスペースにSlack Appをインストールします。

Bot Userの追加と設定

  1. アプリの設定ページで「OAuth & Permissions」に移動します。
  2. 「Scopes」セクションで下記のPermissionを追加します。
chat:write

Pythonアプリの作成

Slack AppからOpenAIへ直接リクエストを投げることはせず、間にPythonアプリを挟みます。
このアプリはFastAPIを利用したAPIアプリであり、Slack AppからのリクエストはSlack Boltを利用して受け取ります。

エンドポイントの実装

まず、必要なライブラリをインストールします。

pip install fastapi gunicorn slack_bolt openai

次に、Pythonコードを実装します。

app.py
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機能を利用します。
https://code.visualstudio.com/docs/editor/port-forwarding
VSCodeでPort forwardingを実行したら、「Visibility」をPublicにするのをお忘れなく。

Slack App Event Subscriptionの設定

  1. Slack Appのページにて、「Event Subscriptions」セクションで「Enable Events」をオンにします。
  2. Request URLにVSCodeの「PORTS」タブに表示されている「Forwarded Address」の末尾に/slack/eventsを付したURL(例:https://your-app-url/slack/events)を「Request URL」に設定します。
  3. 「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そのもののデプロイをお忘れなく。
https://learn.microsoft.com/ja-jp/azure/app-service/quickstart-python?tabs=flask%2Cwindows%2Cazure-cli%2Cazure-cli-deploy%2Cdeploy-instructions-azportal%2Cterminal-bash%2Cdeploy-instructions-zip-azcli#2---create-a-web-app-in-azure

App Serviceへのデプロイには、 VSCodeの拡張機能であるAzure App Serviceを利用します。
https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureappservice

拡張機能をインストール後、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にメッセージを送り、返信があれば成功です!

GitHubで編集を提案
Accenture Japan (有志)

Discussion