【Slack APIタイムアウト対策】Cloud FunctionsでSlackへの返答とCloud Pub/Subへの追加を実装
2024.4.3
はじめに
こんにちは、フクナガです。
先日SlackからVertex AIをCloud Functions経由で呼び出す生成AIチャットボットを実装しました。その際、Slackには返答に対するタイムアウトがあり、何かしらの方法でそれを回避する必要があることを知りました。
スラッシュコマンド は、メッセージ投稿フォームからアプリの機能を呼び出すことができる機能です。
スラッシュコマンドの実行への応答は、とてもよくあるユースケースです。Bolt アプリは Slack API サーバーからのリクエストに対して 3 秒以内に ack() メソッドで応答する必要があります。3 秒以内に応答しなかった場合、コマンドを実行したユーザーに対して Slack 上でタイムアウトした旨が通知されます。
出典:スラッシュコマンド
正確には、「タイムアウトがあるって聞いたことがあるけど、初回レスポンスと処理の受け渡しってどうすればよいんだろう」と悩みました。
今回の記事では、Slack APIからCloud Functionsへリクエストを飛ばす際、どのようにして初回レスポンスを返すか、またその後どうやって処理を進めるか、についてご紹介できればと思います。
利用サービス
今回の想定アーキテクチャは下記です。
本アーキテクチャで利用する各サービスについてそれぞれご説明します。
知ってるよ!という方は「実装」から読み始めていただけるとよいと思います。
(1) Cloud Functions
Google Cloud Functions は、クラウド サービスの構築と接続に使用するサーバーレスのランタイム環境です。Cloud Functions を使用すると、クラウドのインフラストラクチャやサービスで生じたイベントに関連する、シンプルで一義的な関数を作成できます。対象のイベントが発生すると、Cloud Functions がトリガーされ、コードがフルマネージドの環境で実行されます。インフラストラクチャをプロビジョニングする必要はなく、サーバーの管理に悩まされることもありません。
出典:Cloud Functions の概要
Cloud Functionsでは様々なランタイムを利用できますが、今回はPython(3.11)を利用します。
今回は、下記二つの関数を実装します。
・Slackからメッセージを受け取り初回レスポンスを実施する
・Cloud Pub/Subに渡されたリクエストを受け取り、Slackへメッセージを送信
(2) Cloud Pub/Sub
Pub/Sub は、メッセージを生成するサービスを、それらのメッセージを処理するサービスと切り離す、非同期のスケーラブルなメッセージング サービスです。
Pub/Sub を使用すると、サービスが 100 ミリ秒程度のレイテンシで非同期に通信できます。
出典:Pub/Sub とは
今回は、1つ目のCloud Functionsからメッセージを受け取り、2つ目のCloud Functionsへ渡す役割を担います。
(3) Slack apps
Slackでは、スラッシュコマンドを利用したAPI実行や、Webhooksの利用が可能です。
今回は、Cloud Functionsへのリクエスト実施とCloud Functionsからのメッセージ送信の役割を担います。
実装
(1) Cloud Pub/Subの構築
本アーキテクチャでは、SlackからのリクエストをCloud Functions経由でCloud Pub/Subへ送り、それをトリガーとしてSlackへ応答するCloud Functionsを実行します。
それに用いるCloud Pub/Subトピックを作っていきます。
1. Cloud Pub/Subコンソールへ遷移し、「トピック」>「トピックを作成」を押下
2. 「トピックID」を入力し、その他はデフォルトのままで「作成」を押下
3. 作成が完了したことを確認する
(2) 【パブリッシャー】Cloud Functionsの構築
Cloud Pub/Subへのメッセージングを担当しているもののことをパブリッシャーと表現しております。
なんか、「パニッシャー」みたいで強そうですね。
そんなことはさておき、Slackからリクエストを受け取り、初期レスポンスを返す、そしてCloud Pub/SubへのメッセージングをするCloud Functionsを作成していきます。
1. Cloud Functionsコンソールへ遷移し、「ファンクションを作成」を押下
2. 下記パラメータを入力・変更し、画面下部の「次へ」を押下
- 関数名
任意の値を入力 -
リージョン
好きなリージョンでよいが、今回は「asia-northeast1(東京)」を選択 -
トリガー > トリガーのタイプ
「HTTPS」 -
トリガー > 認証
「未認証の呼び出しを許可」 -
ランタイム > インスタンスの最大数
デフォルトで「100」となっているので、極端にスケールしすぎないよう「3」に設定
※必須の設定項目ではありませんが、事故を予防するため、最小限にしておくことを推奨します。
3. 「ランタイム」を「Python3.11」に設定し、下記コードをそれぞれ入力する
①main.py
import functions_framework from google.cloud import pubsub_v1 import requests import json @functions_framework.http def hello_http(request): # Google CloudプロジェクトIDを記載する project_id = "" # (1)で作成したトピックIDを記載する topic_id = "fukunaga-slack-topic" question = request.form.get('text') user_id = request.form.get('user_id') publisher = pubsub_v1.PublisherClient() # The `topic_path` method creates a fully qualified identifier # in the form `projects/{project_id}/topics/{topic_id}` topic_path = publisher.topic_path(project_id, topic_id) data = question.encode("utf-8") future = publisher.publish(topic_path, data) print(future.result()) msg = { "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": f"<@{user_id}> さん、質問を受け付けました。" } } ] } return msg
②requirements.txt
functions-framework==3.* google-cloud-pubsub requests==2.25.1
4. 画面下部「デプロイ」を押下する
※本コードはSlackからのAPI実行を前提としているため、この段階での動作確認はできません。
(3) Slackアプリケーションの作成
1. 下記URLへアクセス
2. Create New Appを押下
3. 「From Scratch」を押下
4. 下記を入力して「Create App」を押下
- App Name
任意の値を入力 -
Pick a workspace to develop your app in:
本アプリを追加するSlackワークスペース
(4) Slash Commandsの作成
今回のボットは「/」コマンドを利用して実装します。
Slack上で「/sendmassage」と打ってから投稿したい文を記載し、Cloud Functionsに送るSlash Commandsを作成します。
1. 左側メニュー「Slash Commands」を選択し、「Create New Command」を押下
2. 下記を入力し、「Save」を押下
- Command
メッセージを送る際に文頭に記載するコマンドを指定 -
Request URL
パブリッシャー用Cloud Functionsのトリガーとして設定されているHTTPS URLを記載
※Cloud Functionsで対象関数を選択し、「トリガー」タブを押下すると確認できる -
Short Description
このコマンドの役割や用途を記載
※場合によっては、作成したコマンドを有効化するために再インストールが必要である旨が画面上部に表示されますので、指示どおり対応してください。
(5) Incoming Webhooksの作成
Cloud Functionsからメッセージを受け取るために、Incoming Webhooksを作成します。
1. 左側メニュー「Incoming Webhooks」を押下し、Activate Incoming Webhooksの右側にあるOffと書かれたボタンを押下
2. 画面下部の「Add New Webhook to Workspace」を押下
3. 送信先のチャンネルを指定し、「許可する」を押下
4. Webhook URLが発行されていることを確認し、手元にコピーする
(6) 【サブスクライバー】Cloud Functionsの構築
Cloud Pub/Subからのメッセージを受け取って処理を行う関数を「サブスクライバー」と表現しています。
2. Cloud Functionsコンソールへ遷移し、「ファンクションを作成」を押下
3. 下記パラメータを入力・変更し、画面下部の「次へ」を押下
※下記画像のようにサービスアカウントに権限を付与するか確認する警告が出るので、「すべて付与」を押下
- 関数名
任意の値を入力 -
リージョン
好きなリージョンでよいが、今回は「asia-northeast1(東京)」を選択 -
トリガー > トリガーのタイプ
「Cloud Pub/Sub」 -
トリガー > Cloud Pub/Subトピック
先ほど作成したCloud Pub/Subトピックを指定する -
ランタイム > インスタンスの最大数
デフォルトで「100」となっているので、極端にスケールしすぎないよう「3」に設定
※必須の設定項目ではありませんが、事故を予防するため、最小限にしておくことを推奨します。
4. 「ランタイム」を「Python3.11」に設定し、下記コードをそれぞれ入力する
①main.py
import base64 import functions_framework import requests import json # Triggered from a message on a Cloud Pub/Sub topic. @functions_framework.cloud_event def hello_pubsub(cloud_event): # 取得したWebhook URLを入力する WEB_HOOK_URL = "" # Cloud Pub/Subから取得したデータを変数に入れる question = base64.b64decode(cloud_event.data["message"]["data"]).decode() requests.post(WEB_HOOK_URL, data=json.dumps({ #メッセージ "text" : "元の質問は:" + question, })) return question
②requirements.txt
functions-framework==3.* google-cloud-pubsub requests==2.25.1
検証
今回のアプリは、Slack上でした質問の頭に「元の質問は:」とつける、とてもシンプルなものです。(余分な要素を省くため)
実際に動くかどうか試してみましょう!!
こんな感じでメッセージを送ってみます。
すぐに受付メッセージが来ました。
最終的に、こういった返答が返ってきました。
期待通り、タイムアウトを起こさずにSlackチャットボットの実装ができました。これを応用することで、Cloud Functions側で様々な処理を加え、情報を提供することができそうですね。
まとめ
今回の記事では、Slack APIからCloud Functionsへリクエストを飛ばす際、どのようにして初回レスポンスを返すか、またその後どうやって処理を進めるか、についてご紹介しました。それぞれのリソースの関係性や、実装方法を理解することで、様々な情報を提供するチャットボットとして実装ができそうです。
本記事をもって興味を持った方がいらっしゃいましたら、ぜひぜひ実装してみてください!
テックブログ新着情報のほか、AWSやGoogle Cloudに関するお役立ち情報を配信中!
Follow @twitterインフラエンジニア歴5年のフクナガです。2024 Japan AWS Top Engineers選出されました! 生成 AI 多めで発信していますが、CI/CDやIaCへの関心も高いです。休日はベースを弾いてます。
Recommends
こちらもおすすめ
-
手を動かして GBDT を理解してみる
2019.5.24
-
5分でわかる、1時間でできる、 OSSコントリビューション
2017.10.23
Special Topics
注目記事はこちら
データ分析入門
これから始めるBigQuery基礎知識
2024.02.28
AWSの料金が 10 %割引になる!
『AWSの請求代行リセールサービス』
2024.07.16