はじめに
技術チャレンジ部のとも(Tomo)です。チームTPACで自動運転AIチャレンジ2024に参加中です。
こちらの記事で作ってみたDiscordのボットを、Slackに移植してみました。
素人なので、変なことを言っていたらすいません!
設計方針
Discordで作ったボットのアーキテクチャをなるべく流用するために、以下の方針としました。
- PythonのライブラリからSlack APIを使用する
- Pythonスクリプトがクライアントになる
Slackのボット
フレームワーク
Slackのボットには様々な実装方法があるようです。Slackの開発者ページでは、Boltという公式フレームワークがトップで紹介されています。
Boltには、Python版であるBolt for Pythonがあるので、これを使用してみます。
Bolt入門ガイドでは、ソケットモードが使用されています。ソケットモードでは、HTTPでは必要なRequest URLのエンドポイントを用意しないで済みそうなので、設計方針にも合いそうです。
また、入門ガイドには、
ソケットモードは、Slack アプリ開発をとりあえず始めてみるときやあなたのチームだけのためのアプリをつくるときにおすすめのやり方です。
とあり、お試しにはちょうどよさそう。この方法では、LegacyやClassicな仕組みもうまく避けられそうです。
Slackアプリの作成
Bolt入門ガイドに沿って作ってみます。
- アプリの作成
- Create New App -> From scrachでボット名とワークスペースを設定
- ボットトークンへのスコープの追加
- OAuth & Permissions -> Bot Token Scopes -> Add an Oauth Scopeで以下を追加
-
chat:write
…メッセージの投稿に必要 -
app_mentions:read
…メンション内容の取得に必要
- アプリレベルトークンの取得
- Basic Information -> App-Level Tokens -> Generate Token and Scopesで以下を実施
- Token Nameにトークンの名前を入力
- Add Scopeで
connections:write
を追加 …ソケットモードに必要 - Generateボタンでアプリレベルトークンを生成し、メモ
- Basic Information -> App-Level Tokens -> Generate Token and Scopesで以下を実施
- ソケットモードの有効化
- Socket Mode -> Connect using Socket Mode -> Enable Socket Modeをオン
- イベント取得の設定
- Event Subscriptions -> Enable Eventsをオンにする
- Subscribe -> Add Bot User Eventで、
app_mention
を追加 …メンションイベントの取得に必要
- アプリのワークスペースへのインストールとボットトークンの取得
- OAuth & Permissions -> Install App to Workspaceでアプリをインストール
- Bot User OAuth Access Tokenをメモ
- OAuth & Permissions -> Install App to Workspaceでアプリをインストール
- 必要に応じてアイコンや名前の見直し
- Basic Information -> Display Informationなどの見直しと変更を行い、Save Changesで保存
作ってみて分かったのですが、メンションイベント取得時に、メッセージ内容も取得できるようです。
Pythonスクリプト
Boltパッケージのインストール
pip install slack_bolt
Discord用に使ったスクリプトの修正
Discordの記事で使ったPythonスクリプトを修正します。
主な修正点は以下の通りです。
- DiscordのAPIを呼び出す部分をBolt for Slackに置き換える
- Bolt for Slack化の大枠は、Bolt入門ガイドの「メッセージをリッスンして応答する」をベースにする
- イベント取得部分は、Bolt for Pythonの「イベントのリスニング」を参考にする
- Discordと同じく非同期で扱えるように、Bolt for Pythonの「ソケットモードの利用」を参考に、importやmainを修正する
素人が一番とまどったのは、非同期の部分です。Bolt for Pythonの「ソケットモードの利用」の「Async (asyncio) の利用」には、
aiohttp のような asyncio をベースとしたアダプターを使う場合、アプリケーション全体が asyncio の async/await プログラミングモデルで実装されている必要があります。AsyncApp を動作させるためには AsyncSocketModeHandler とその async なミドルウェアやリスナーを利用します。
とあり、Discordの時もこのようになっていたのですね。今回はAsyncSocketModeHandlerが使用できそうです。
また、Slack APIでは、ソケットモードでも3秒以内の応答が必要とのことで、LLMの長い生成時間を考えると、まずはack()で応答を返す必要がありそうです。(ということに記事を書きながら気づいた…書いてよかった…)
これは非同期でも対応できるとのことで、こちらの記事が大変参考になりました。
これらの情報とChatGPTの支援を受けて修正したPythonスクリプトがこちらです。
import asyncio
import os
from slack_bolt.async_app import AsyncApp
from slack_bolt.adapter.socket_mode.aiohttp import AsyncSocketModeHandler
from dotenv import load_dotenv
import aiohttp
load_dotenv()
DIFY_KEY = os.getenv("DIFY_API_KEY")
app = AsyncApp(token=os.environ.get("SLACK_BOT_TOKEN"))
@app.event("app_mention")
async def handle_app_mention_events(event, say, ack):
await ack()
user = event['user']
query = event['text']
url = 'http://localhost/v1/chat-messages'
headers = {
'Authorization': f'Bearer {DIFY_KEY}',
'Content-Type': 'application/json'
}
data = {
'query': query,
'response_mode': 'blocking',
'user': user,
'conversation_id': '',
'inputs': {}
}
async with aiohttp.ClientSession() as session:
async with session.post(url, headers=headers, json=data) as response:
if response.status == 200:
json_response = await response.json()
answer = json_response.get('answer', 'No answer provided.')
await say(answer)
else:
error_message = await response.text()
await say(f'APIエラー: {response.status}, メッセージ: {error_message}')
async def main():
handler = AsyncSocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
await handler.start_async()
if __name__ == "__main__":
asyncio.run(main())
ファイル.envには、SlackのボットトークンをSLACK_BOT_TOKEN
に、SlackのアプリレベルトークンをSLACK_APP_TOKEN
に、DifyのAPIキーをDIFY_API_KEY
に、それぞれ設定しておきます。
DifyのSystemプロンプト
みなさまをサポートできるように、少しだけ、こんなふうに変えてみました。
実行
ボットをテキストチャンネルに招待する必要があるようです。とりあえずメンションすると招待できたので、これで招待しました。
そして、聞いてみると…
アツい!
今後に向けて
Slackの表記は、Markdownとは異なるmrkdwn
だそうなので、これに対応させてみたいですね。
今回はOpenAI API以外はオンプレ前提で、実行環境も一般のご家庭のため、SLAは一般のご家庭品質です。FaaS化すると、アーキテクチャが大幅に変わりそうですが、勉強になりそうです。
あとは、先ほどのtpac_gorillaくんのコメントにもありましたが、大会を通じて貴重な経験を楽しめればと思います!