Amazon BedrockとSlackで生成AIチャットボットアプリを作る (その1:ローカルPC+Socket Modeで動かす)

はじめての「Slack+Bedrockで作るチャットボット」は、意外に簡単でした。
2024.05.06

みなさん、こんにちは!
福岡オフィスの青柳です。

Amazon Bedrockを使った検証を行う際に「自分でチャットアプリを作って試せるといいな」と思ったので、Slackでチャットボットアプリを作ってみることにしました。

初めてなのでステップバイステップで進めることにします。

まずは「Slackを使ったチャットボットアプリ作成の入門」からやってみます。

Slackアプリの開発

今回のコンセプトは以下の通りです。

  • 開発言語は「Python」を使う
  • まずはローカルPCでSlackアプリを動かしてみる

Pythonを使ってSlackアプリを作成するために、Slack公式のフレームワーク「Bolt for Python」を使います。

また、本来であればSlackとのやり取りを行う「HTTP APIサーバー」が必要になるのですが、今回はサーバーを立てずに開発PCなどでもSlackアプリを実行できる「Socket Mode」を利用しました。

「Slack Bolt」フレームワーク

Slackには、Slack APIをプログラムから呼び出すための「Slack SDK」が用意されています。

この基本的なSDKに加えて、Slack公式のフレームワークとして「Slack Bolt」も用意されています。

Slack Boltを使うと、抽象化されたモジュール・クラスによってSlack SDKを使うよりもシンプルな記述でSlackアプリを開発することができます。

Slack BoltはSlack SDKがベースとなっており、Slack Bolt単体で開発を行うことも、Slack BoltとSlack SDKを組み合わせて開発を行うこともできます。

Building an app with Bolt for Python
https://api.slack.com/start/building/bolt-python

Slack Boltを使ったSlackアプリの構成

Slack Boltを使ったSlackアプリはHTTP APIサーバーとして実行され、SlackからのHTTPリクエストを受信して処理を行います。

Slackアプリは、Slack上で発生する「メッセージが書き込まれた」「アプリがメンションされた」など様々な「イベント」のうち、あらかじめ購読 (サブスクリプション) した特定のイベントに対して「メッセージへ返信する」などの処理を行うことができます。

「Socket Mode」を使ってローカルPCでSlackアプリを実行する

Slackアプリを動作させるためにHTTP APIサーバーを立てる方法の他に、「Socket Mode」と呼ばれる機能を使うことができます。

このSocket Modeを使うと、アプリからSlackに対してWebSocketを使った接続が行われ、この接続の中でSlackからアプリに対するリクエストが行われるようになります。

Socket Modeを使う大きな利点は、Slackアプリに対するインバウンドの通信が不要である点です。

そのため、ローカルPC上でSlackアプリを起動して動作を確認することができます。

Slackアプリの設定を行う

それでは、Slackアプリを作成していきます。

まず、Slack側の設定を行います。

「Slack App」の作成

ここで作成する「Slack App」とは、Slackアプリの実体ではなく、「アプリがSlackと連係するためのトークンの払い出し」や「Slackがアプリに対して許可する権限の設定」などを管理するための概念です。

Slackへサインインした状態で https://api.slack.com/apps/ へアクセスします。

「Create New App」をクリックします。

作成方法として、ここでは「From scratch」を選択します。

Slack Appの「名前」の指定と、対象となるSlackの「ワークスペース」の選択を行います。

「Create App」をクリックしてSlack Appを作成します。

「Socket Mode」の有効化

前節で説明した「Socket Mode」を有効化します。 (Socket ModeはSlack App単位の設定となります)

左側のメニューから「Socket Mode」を選択します。

デフォルトでは「Eneble Socket Mode」が「Off」になっているので、「On」にします。

Socket Modeでは「App-Level Token」という種類のトークンが必要になります。

この時にトークンのスコープ (与えられる権限) として「connections:write」が自動的に設定されています。

「Token Name」に任意の文字列を入力して、「Generate」をクリックします。

作成されたトークンが表示されます。 トークンは後ほど必要になりますが、この画面は後からでも確認できますので、ここでは「Done」をクリックして閉じます。

「Bot Token Scopes」の設定

Slackアプリが動作する際に必要な権限を与えます。

今回は、アプリからSlackに対して返信の書き込みを行えるように、「チャットへの書き込み」の権限を与えます。

左側のメニューから「OAuth & Permissions」を選択します。

下の方にスクロールして「Scopes」の設定項目を表示します。

「Bot Token Scopes」が、Slackからアプリに対して許可を与える権限の範囲を示します。

「Add an OAuth Scope」をクリックします。

スコープのリストから「chat:write」を選択します。

「chat:write」のスコープが追加されたことを確認します。

「Event Subscriptions」の設定

特定のイベントが発生した時にSlackアプリが呼び出されるように、イベントの購読 (サブスクリプション) を設定します。

今回は「ユーザーがSlackアプリをメンションした時」のイベントを購読対象にします。

左側のメニューから「Event Subscriptions」を選択します。

「Eneble Events」を「Off」から「On」に変更します。

「Subscribe to bot events」をクリックして展開します。

「Add Bot User Event」をクリックします。

イベントのリストから「app_mention」を選択します。

「app_mention」が追加されたことを確認して、「Save Changes」で設定を保存します。

(参考) イベント購読によるスコープの追加

イベントの購読を設定した後に、「OAuth & Permissions」の「Scopes」設定画面を確認してみます。

「Bot Token Scopes」にスコープ「app_mentions:read」が追加されている点に注目してください。

これは、さきほど「Event Subscriptions」の設定で「app_mention」のイベントをサブスクライブした時に自動的に設定されたものです。 また、イベントのサブスクリプションが登録されている間は、このスコープは削除できないことにも注意してください。

ワークスペースへのAppのインストール

対象のワークスペースへAppの「インストール」を行い、ワークスペースでSlackアプリが使用できるようにします。 (アプリの実体を作成していないので、まだ動作はしません)

左側のメニューから「Install App」を選択します。

「Install to Workspace」をクリックします。

対象のワークスペース名、与える権限を確認して、「許可する」をクリックします。

Appをインストールしたことによって、Slackアプリに組み込むための「トークン」が発行されました。 このトークンは後ほど必要になりますが、この画面は後からでも確認できますので、ここでは何もしなくて構いません。

これでSlack側の設定は一旦終わりです。

トークンを環境変数へセットする

アプリが動作する際に必要な権限を得るために、発行されたトークンを環境変数へセットします。

App-Level Token

左側のメニューから「Basic Information」を選択します。

下の方にスクロールして「App-Level Tokens」の設定項目を表示します。

トークンの名前の部分をクリックします。

表示されている「App-Level Token」のトークン文字列を、環境変数SLACK_APP_TOKENにセットします。

export SLACK_APP_TOKEN="xapp-********"

Bot User OAuth Token

左側のメニューから「OAuth & Permissions」を選択します。

「OAuth Tokens for Your Workspace」に「Bot User OAuth Token」が表示されていることが確認できます。

表示されている「Bot User OAuth Token」のトークン文字列を、環境変数SLACK_BOT_TOKENにセットします。

export SLACK_BOT_TOKEN="xoxb-********"

Slackアプリのコードを記述する

それでは、Slackアプリの実体となるプログラムコードを記述します。

Amazon Bedrockと連係するアプリを作成する前に、まずはシンプルな動作をするアプリを作成しましょう。

ユーザーがSlackアプリをメンションして何か書き込みを行った際、書き込まれた内容をそのまま返す「オウム返し」アプリです。

まず、必要なPythonモジュールのインストールを行います。

pip install slack-bolt

これで、Slack Bolt for Pythonのモジュール (slack-bolt) がインストールされました。 また、併せてSlack SDK for Pythonのモジュール (slack-sdk) もインストールされます。

続いて、以下のコードを記述します。

app.py

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import os

# アプリの初期化
app = App(token=os.environ.get("SLACK_BOT_TOKEN"))

# Slackイベントハンドラー:Slackアプリがメンションされた時
@app.event("app_mention")
def handle_app_mention_events(event, say):
    say(event["text"])

# アプリを起動
if __name__ == "__main__":
    SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).start()

コード内容を解説していきます。

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

今回、Slack Boltから2つのモジュールをインポートしています。

slack_boltAppモジュールは、Slack Boltの基本となるモジュールです。

slack_bolt.adapter.socket_modeSocketModeHandlerモジュールは、Socket Modeに対応したSlackアプリを作成する際に必要なモジュールです。

# アプリの初期化
app = App(token=os.environ.get("SLACK_BOT_TOKEN"))

アプリの初期化を行います。

この時、アプリに対して権限を与えるために「Bot User OAuth Token」を環境変数から読み込んでセットする必要があります。

# Slackイベントハンドラー:Slackアプリがメンションされた時
@app.event("app_mention")
def handle_app_mention_events(event, say):
    say(event["text"])

Slack Appの「イベントのサブスクリプション」設定で購読したイベントが発生した際に、このイベントハンドラーが実行されます。

@app.event()の引数に対象となるイベントapp_mentionを指定します。

イベントハンドラーの中では、いくつかの種類の引数 (arguments) を使って、いろいろな情報にアクセスしたり、機能を呼び出したりすることができます。

今回は以下の2種類の引数を使用しています。

引数 説明
event Slackから渡される「イベントに関する情報」が格納されている
say Slackに対してメッセージを書き込むことができる

event["text"]にはユーザーが書き込んだテキスト内容が格納されており、say()の引数に与えて呼び出すことで「オウム返し」が行えるという訳です。

# アプリを起動
if __name__ == "__main__":
    SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).start()

最後に、アプリを起動して常駐させます。

Socket Modeでアプリを実行するためにSocketModeHandlerを呼び出しています。

この時、アプリがSlackに対してWebSocketで接続できる権限を与えるために「App-Level Token」を環境変数から読み込んでセットする必要があります。

Slackアプリの実行と動作確認

アプリの実行

全ての準備ができましたので、アプリを実行します。

$ python ./app.py
⚡ Bolt app is running!

上記のように表示されれば、Slackアプリが起動して常駐した状態になっています。

ワークスペースへアプリを追加

動作確認してみましょう。

Slackの画面に移動して、作成したSlackアプリをチャンネルに「追加」します。

アプリをインストールしたいチャンネルで、チャンネルの情報画面を開きます。

「インテグレーション」タブを選択して、「アプリを追加する」をクリックします。

作成したSlackアプリが表示されていると思いますので、「追加」をクリックします。

アプリをメンションしてみる

これで、チャンネル内でアプリを呼び出すことができます。

アプリを「@アプリ名」でメンションして、続けて何か書き込んでみましょう。

やりました! こちらが書き込んだ内容と全く同じ内容を、アプリが返してくれました。

ちょっと改良

アプリが「オウム返し」してくれるようになりましたが、アプリへのメンションまでそっくりそのまま返してもらうのはちょっと不都合です。

メンション部分を除いた書き込み内容のみを「オウム返し」するように改良しましょう。

いったん、Slackアプリを「Ctrl+C」で停止して、プログラムコードを以下のように修正します。

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import os
import re

# アプリの初期化
app = App(token=os.environ.get("SLACK_BOT_TOKEN"))

# Slackイベントハンドラー:Slackアプリがメンションされた時
@app.event("app_mention")
def handle_app_mention_events(event, say):
    input_text = re.sub("<@.+>", "", event["text"]).strip()
    say(input_text)

# アプリを起動
if __name__ == "__main__":
    SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).start()

書き込まれたテキスト内容から、正規表現を使って「メンション部分」を取り除きます。(12行目)

再度Slackアプリを実行して、アプリに対してメンションしてみます。

今度は、メンション部分を含まない「書き込まれた内容」のみを返してくれるようになりました。

Amazon Bedrockとの連係

アプリのシンプルな動作を確認できましたので、いよいよ本命である「Amazon Bedrockとの連係」を組み込んでいきます。

構成は下図のようになります。

IAMポリシー

アプリがBedrockのモデルを呼び出すことができる権限を付与します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "bedrock:InvokeModel",
            "Resource": "*"
        }
    ]
}

boto3モジュールのインストール

Bedrockを呼び出すためにAWS SDK for Python (boto3) を使いますので、Pythonモジュールのインストールを行います。

pip install boto3

コードの修正

まずは、最初の部分を以下のように修正します。

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import boto3
import json
import os
import re

bedrock_runtime = boto3.client("bedrock-runtime", region_name="us-east-1")

モジュールboto3jsonのインポートと、boto3の「bedrock-runtime」クライアントの初期化を追記します。

次に、Amazon Bedrockを呼び出すサブ処理を記述します。 今回は「Claude 3 Haiku」を使用していますが、他のモデルでもOKです。

# Bedrockを使って応答テキストを生成する
def generate_answer(input_text):
    # メッセージをセット
    messages = [
        {
            "role": "user",
            "content": input_text,
        },
    ]

    # リクエストBODYをセット
    request_body = json.dumps({
        "messages": messages,
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 1000,
    })

    # Bedrock APIを呼び出す
    response = bedrock_runtime.invoke_model(
        modelId="anthropic.claude-3-haiku-20240307-v1:0",
        accept="application/json",
        contentType="application/json",
        body=request_body,
    )

    # レスポンスBODYから応答テキストを取り出す
    response_body = json.loads(response.get("body").read())
    output_text = response_body.get("content")[0].get("text")

    # 応答テキストを戻り値として返す
    return output_text

最後に、Slackイベントハンドラーの内容を修正します。

# Slackイベントハンドラー:Slackアプリがメンションされた時
@app.event("app_mention")
def handle_app_mention_events(event, say):
    input_text = re.sub("<@.+>", "", event["text"]).strip()
    output_text = generate_answer(input_text)
    say(output_text)

前の手順で「メンション部分を除いた、書き込まれたテキスト内容」を取得するようにしましたので、このテキスト内容を「Amazon Bedrock呼び出しサブ処理」に与えてコールします。

得られた応答テキストをsay()に与えてSlackへ書き込みます。

試してみる

準備ができましたので、Slackアプリを起動して、何か質問してみましょう。

書き込んだ内容に対して、ちゃんと回答が返ってきました!

「Amazon Bedrockと連係して質問に答えてくれるSlackアプリ」を作ることができました。

おわりに

今回は、「Slackを使ったチャットボットアプリ作成の入門」として「Socket Mode」を使ったSlackアプリの作成を行いました。

Slack Appの設定が何箇所かに分かれているために最初は分かりづらかったですが、ステップバイステップで進めることで何とか理解してアプリを動かすところまでできました。

また、「Slack Bolt」を使ったアプリ開発は、シンプルで分かり易く、コード記述量も少なく済むという印象です。

次回は、より「本番っぽい」アプリにするため、Socket Modeを使わずに「HTTP APIサーバー」を立ててSlackアプリを動かすことに挑戦します。

参考情報

Slack | Bolt for Python - Bolt 入門ガイド
https://slack.dev/bolt-python/ja-jp/tutorial/getting-started