SlackボットをAzure App Serviceで動かそう #slack #ai #langgraph #azure #openai #llm #python
はじめに
前回の「Slackでスレッドに返信するボットをローカルPCで動かそう」において、SlackのチャットボットをローカルPC上で動かし、Azure OpenAIと会話できるようにしました。しかしローカルPC上の動作なので、当然ながらローカルPCが起動しているときしかボットが動作しないという大きな欠点があります。
そこで本稿では、チャットボットを安定運用するためにサーバーレスアプリケーション環境である Azure App Service で動かしてみます。加えて、会話履歴はAzure Cosmos DB for PostgreSQL に記録するようにしてみます。
おさらい:ローカルPCで動かしたSlackチャットボット
実際のコードについては前回の「Slackでスレッドに返信するボットをローカルPCで動かそう」を参照してほしいのですが、次の特徴があります。
- Slackアプリとして作成。
- ソケットモードで常時起動し、アプリからSlackへ通信。
- Bolt for Pythonをフレームワークとして使用。
- LangGraphを使用してAzure OpenAIと接続。
- SQLiteに会話履歴を保存。
これらの特徴のいくつかは、サーバーレスアプリケーションで動かす際の障害となるため、作り変える必要があります。
HTTP Request URLs
ローカルPCで動作させていた際に採用していたソケットモードはステートフルで動作するため、サーバーレスアプリケーションとして動かすには不適です。そこでステートレスであるHTTP Request URLsを使用するモードに作り変える必要があります。ソケットモードとHTTPモードの詳しい比較については公式ドキュメント「Exploring HTTP vs Socket Mode」をご覧ください。
HTTPモードで動作させるには、Slackからのリクエストを受け付けるHTTPサーバーを起動する必要があります。とはいえ、HTTPサーバーと言ってもたくさんの種類があるため、どれを選べばよいのでしょうか? Bolt for Pythonのドキュメントを見ると、AdaptersでFlaskという軽量なフレームワークが紹介されているので、これを利用することにしましょう。examplesにも簡単なコードがあります。
HTTPモードに変更した、スレッドで返信するボット
ソケットモードから、Flaskを使用したHTTPモードに変更したコードです。
さらに、会話履歴を Azure Cosmos DB for PostgreSQL に記録するようにしています。ここでは Azure Cosmos DB for PostgreSQL は最安の開発用プランで作成しています。なお、Azure Cosmos DB for PostgreSQL の作成手順などの詳細は省略していますので、公式ドキュメントを参照してください。
from slack_bolt import App from slack_bolt.adapter.flask import SlackRequestHandler from flask import Flask, request import re from typing import Annotated from typing_extensions import TypedDict from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langchain_core.messages import BaseMessage, AIMessage, HumanMessage from langchain_openai import AzureChatOpenAI from psycopg import Connection from langgraph.checkpoint.postgres import PostgresSaver import os from dotenv import load_dotenv load_dotenv() # os.getenv("AZURE_OPENAI_API_KEY") # os.getenv("AZURE_OPENAI_ENDPOINT") llm = AzureChatOpenAI( azure_deployment = os.getenv("AZURE_OPENAI_MODEL_NAME"), api_version = os.getenv("AZURE_OPENAI_API_VERSION"), temperature = 0.95, max_tokens = 1024, ) class State(TypedDict): messages: Annotated[list, add_messages] def chatbot(state: State): res = llm.invoke(state["messages"]) return {"messages": res} graph_builder = StateGraph(State) graph_builder.add_node("chatbot", chatbot) graph_builder.set_entry_point("chatbot") graph_builder.set_finish_point("chatbot") app = App( signing_secret=os.environ.get("SLACK_SIGNING_SECRET"), token=os.getenv("SLACK_BOT_TOKEN") ) slack_bot_id = os.getenv("SLACK_BOT_ID") @app.event("app_mention") def mention_reply(event, say): with PostgresSaver.from_conn_string( os.getenv("DB_URI") ) as checkpointer: checkpointer.setup() graph = graph_builder.compile(checkpointer=checkpointer) user = event['user'] text = event['text'] if 'thread_ts' in event: thread_ts = event['thread_ts'] else: thread_ts = event['ts'] msg = re.sub(f'<@{slack_bot_id}>', '', text) events = graph.stream( {"messages": [("user", msg)]}, {"configurable": {"thread_id": thread_ts}}, stream_mode="values" ) for event in events: message = event["messages"][-1] if type(message) == AIMessage: say( text=message.content, thread_ts=thread_ts ) flask_app = Flask(__name__) handler = SlackRequestHandler(app) @flask_app.route("/slack/events", methods=["POST"]) def slack_events(): return handler.handle(request)
注目点は次の通りです。
app = App( signing_secret=os.environ.get("SLACK_SIGNING_SECRET"), token=os.getenv("SLACK_BOT_TOKEN") )
SLACK_APP_TOKEN
を使わず、代わりに SLACK_SIGNING_SECRET
を使うようになっています。Slack API: Applicationsから、作成した chatbot-test
を開きます。こちらの「Basic Information」から、「App Credentials」の「Signing Secret」を取得してください。これを以降の SLACK_SIGNING_SECRET
として使用します。
with PostgresSaver.from_conn_string( os.getenv("DB_URI") ) as checkpointer: checkpointer.setup() graph = graph_builder.compile(checkpointer=checkpointer)
PostgresSaverを使ってPostgreSQLに接続しています。詳細は How to use Postgres checkpointer for persistence をご覧ください。また DB_URI
にはPostgreSQLに接続するための情報を入れます。ここでは Azure Cosmos DB for PostgreSQL の接続文字列「PostgreSQL connection URL」を指定しています。
ローカルPCでチャットボットを起動
サーバーレスアプリケーションとしてデプロイする前に、ローカルPCで確認してみましょう。しかし、LAN内にいるローカルPCでHTTPモードのチャットボットを起動しても、インターネット上のSlackからのリクエストを受け取ることはできません。そこで ngrok を使って、簡単にトンネリングしてみましょう。
Quickstart | ngrok の手順に従い、アプリケーションバイナリをインストールし、サインアップします。認証トークンを取得・設定したら、次のようにHTTPで8000ポートを公開するように起動します。
% ngrok http 8000
「Forwarding」で示されたURLが外部からリクエストを受け付けるURLとなります。これをメモしておきましょう。
そしてボットアプリを起動します。
% FLASK_APP=app.py flask run -p 8000
FLASK_APP
で指定するのはボットアプリのファイル名とします。 -p 8000
は8000番ポートで待ち受けするという意味になります。
試しに curl コマンドでアクセスしてみましょう。URLは先程メモしたもので、パスを /slack/events
としてみてください。
% curl -X POST https://★-★-★-★-★.ngrok-free.app/slack/events {"error": "invalid request"}
エラーとなっていますが、アプリを起動したターミナルでもアクセスを受け付けたログが表示されているはずです。
127.0.0.1 - - [25/Nov/2024 18:17:43] "POST /slack/events HTTP/1.1" 401 -
これでボットアプリをローカルPCで起動し、インターネット側からアクセスする準備ができました。
Slack側でアプリをソケットモードからHTTPモードに切り替え
Slack側のアプリ設定を行いましょう。「Slackでスレッドに返信するボットをローカルPCで動かそう」で有効化したソケットモードを無効化することで、HTTPモードを有効化します。
Slack API: Applicationsから、作成した chatbot-test
を開きます。次に左のナビゲーションエリアにある「Socket Mode」を「Enable Socket Mode」トグルスイッチをオフにします。
Slack側でアプリのリクエストURLを設定
SlackからボットアプリのHTTPサーバーにアクセスするURLを設定します。
Slack API: Applicationsから、作成した chatbot-test
を開きます。次に左のナビゲーションエリアにある「Event Subscriptions」を開き、「Request URL」に前項でcurlでアクセスしてみた「 https://★-★-★-★-★.ngrok-free.app/slack/events
」を入力してエンターを押します。しばらく待つと Verified ✔ と表示されるので、右下の「Save Changes」ボタンを押します。
もし Verified ✔ が表示されずに「 Your URL didn't respond with the value of the challenge parameter
」のようなエラーが出る場合は入力したURLが間違っていないか、アプリが起動しているか、ngrokが起動しているか、確認してください。
Slackで確認
ではSlackでチャットボットに話しかけてみましょう。
スレッド内で返信がありました! HTTPモードでボットが動いていることが確認できました。
Azure App Service の準備
ボットアプリをローカルPCで動かすことと同じように、Azure Virtual Machines上で動かすという手もあるかもしれません。一緒にPostgreSQLも仮想マシン内にまとめてしまえば、もっと安上がるでしょう。反面、仮想マシンのメンテナンスはやっぱり大変です。そこで、サーバーレスアプリケーション環境である Azure App Service で動かしてみましょう。こちらなら仮想マシンのメンテナンスは必要ありません。
Azure App Service のデプロイ
Azure App Serviceの作成手順を見ていきます。特に重要な点のみをピックアップしていきます。詳細は Azure App Service公式ドキュメントを参照してください。
Azure マーケットプレイスから「Webアプリ」を選択します。まぎらわしいですがAzure App Serviceではありません。次の設定で作成します。なお、要点のみ記載しており、開発用の最低限の設定であることに注意してください。
- 基本
- 公開: コード
- ランタイムスタック: Python 3.12
- オペレーティングシステム: Linux
- データベース
- なし
- デプロイ
- なし
- ネットワーク
- パブリックアクセスを有効にする: オン
- ネットワークインジェクションを有効にする: オフ
- 監視とセキュリティ保護
- なし
アプリの設定
作成したWebアプリの「設定」→「環境変数」で、ローカルPCではpython-dotenvで設定していた環境変数を設定します。
AZURE_OPENAI_API_KEY
AZURE_OPENAI_API_VERSION
AZURE_OPENAI_ENDPOINT
AZURE_OPENAI_MODEL_NAME
SLACK_BOT_ID
SLACK_BOT_TOKEN
SLACK_SIGNING_SECRET
DB_URI
「設定」→「構成」の「全般設定」タブで、「スタックの設定」の「スタートアップコマンド」に次を指定し、「保存」します。
FLASK_APP=app.py flask run -p 8000 -h 0.0.0.0
アプリのプッシュ設定
本稿ではローカルPC上に Git レポジトリを作り、それを Azure にプッシュするという形を取ります。Azure App Service へのローカル Git デプロイ も参照してください。なお、App Service デプロイで基本認証を無効にする によれば、基本認証の利用は推奨されていないようです。しかしローカル Git は基本認証が無効では利用できません。基本認証を利用しないデプロイ方法は今後の検討課題とします。
「設定」→「構成」の「全般設定」タブで、「プラットフォームの設定」で「SCM基本認証の発行資格情報」と「FTP基本認証の発行資格情報」を両方オンにし、「保存」します。
「デプロイ」→「デプロイセンター」で「設定」タブを開きます。「ソース」のプルダウンメニューから「 ローカル Git 」を選択し、一旦「保存」します。
Git プッシュ先の「Git Clone URI」が出てくるので、これをメモしておきます。
「ローカルGitまたはFTPSの資格情報」タブを開きます。「アプリケーションスコープ」の「ローカルGitユーザー名」と「パスワード」を用いて、先の「Git Clone URI」に対してレポジトリがプッシュできるようになります。
アプリのプッシュ
本稿の最初で作成したPythonスクリプトを app.py
という名前でローカルのGitレポジトリに格納しましょう。ここでは webapp-slackbot
ディレクトリの中に入れていきます。また、次の requirements.txt
も一緒に含めます。
langgraph python-dotenv openai langchain_openai psycopg psycopg-pool langgraph-checkpoint-postgres slack_bolt Flask
% mkdir webapp-slackbot % cd webapp-slackbot % git init % git branch -M main master % cp .../app.py .../requirements.txt . % git add app.py requirements.txt % git commit -m "initial import." . % git remote add origin 【Git Clone URI】 % git push origin master
【Git Clone URI】は先にメモした「Git Clone URI」を入力してください。ここで求められるユーザー名とパスワードは先の「ローカルGitユーザー名」と「パスワード」を入力してください。
正しくプッシュされれば、後は自動的にビルド・デプロイしてくれます。これでAzure App Serviceでサーバーレスアプリケーションとしてチャットボットが動き始めました。
Slack側でアプリのリクエストURLを再設定
SlackからボットアプリのHTTPサーバーにアクセスするURLを、ngrokからAzure App Serviceに切り替えます。作成したWebアプリの「概要」の「規定のドメイン」をコピーしておきましょう。
Slack API: Applicationsから chatbot-test
を開きます。次に左のナビゲーションエリアにある「Event Subscriptions」を開き、「Request URL」に先にコピーしたホスト名を入力します。「 https://★.★.azurewebsites.net/slack/events
」のようにパスは補います。入力してエンターを押し、しばらく待つと Verified ✔ と表示されるので、右下の「Save Changes」ボタンを押します。
Slackで確認
では、Slackのほうで @chatbot-test
にメンションしてみましょう。
スレッド内で会話が成立しました。
Azure Cosmos DB for PostgreSQL の接続文字列には psql コマンドもあるので SELECT thread_id, checkpoints FROM checkpoints;
など発行してみてください。会話履歴が保存されていることがわかると思います。
まとめ
本稿では次の特徴を持つSlackチャットボットアプリを作成しました。
- Slackアプリとして作成。
- HTTP Request URLsモードで起動し、Slackからアプリへ通信。
- Bolt for Pythonをフレームワークとして使用。
- LangGraphを使用してAzure OpenAIと接続。
- Azure Cosmos DB for PostgreSQLに会話履歴を保存。
- Azure App Serviceでボットを起動。
これでサーバーレスアプリケーションとなり、ローカルPCで実行する必要がなくなり、常時安定稼働が期待できます。文中でも述べた通り、起動に必要な最低限の設定しか行っていないため監視などはなく、料金節約のため開発用の最低スペックなので、高アクセス時には問題が発生するかもしれません。しかしながら逆に言うと、最低限の土台ができたので、ここから本番向けにさまざまな実際の要望を組み込んでいくことができるようになったとも言えるでしょう。この先さらに改善・改良を進めたいと思います。