LoginSignup
7
5

"Amazon Bedrock Slack Appを作成してみた"の記事を読んでSlack Appを作成してみた

Last updated at Posted at 2023-10-18

はじめに

Amazon Bedrocを試してみようと思い、以下の"作成してみた"をもとにSlack Appを作成してみました。

AWS Chalice を使って、Amazon Bedrock Slack Appを作成してみた

そして、Slack Appの作成やAWS Chaliceの利用が初めてだったこともあり、こちらの記事がとても参考になりました。

AWS Chalice を使って、Slack App (Slack Bolt for Python) を作成してみた

Slack Appが動作するようになるまでにこの2つの記事を行ったり来たりしたので、以下に内容を整理してみました。また、記事で紹介されているコードを実行するだけでは工夫がないので、IAMポリシーの制約や非同期処理を追加しました。

参考情報

前提条件

  • 以下のリソースを作成・削除・変更できる権限をもつAWSユーザー

    • AWS IAM
    • AWS Lambda
    • Amazon API Gateway
    • Amazon S3
    • AWS Chalice
    • Amazon Bedrock
  • 使用するリージョン: us-east-1

環境構築

AWS環境

AWSコンソールからAmazon Bedrockにアクセスします。
左側メニューのModel accessを開きます。いくつかある基盤モデルのなかからAL21 LabsのJurassic-2 Midを選択し、有効にするためのリクエストを送信します。送信後、数分程度でAccess statusがAccess grantedとなります。

参考記事のAWS 環境設定にあるように、aws configureでデフォルトのリージョンやクレデンシャルを設定するか、~/.aws/config~/.aws/credentialsを編集します。

Python環境

$ python3 -m venv .venv
$ . .venv/bin/activate
$ python3 --version
Python 3.11.4
$ pip3 install --upgrade pip
$ pip3 --version
pip 23.0.1 from /home/xxx/.venv/lib/python3.11/site-packages/pip (python 3.11)

Slack Bolt for Pythonのclone

$ git clone https://github.com/slackapi/bolt-python.git

AWS Chaliceのサンプルがあるディレクトリに移動します。

$ cd bolt-python/examples/aws_chalice/

AWS Chaliseの環境設定

Lambda関数に標準で組み込まれているBoto3のバージョンがBedrock対応していないため、Lambda Layerに組み込みむモジュールにboto3 >= 1.28.57を追加します。

requirements.txt
slack_sdk
slack_bolt
boto3 >= 1.28.57

Slack Appの作成

https://api.slack.com/apps?new_app=1 を開き、Slack Appを作成します。
App Nameをbolt-python-chaliceとします。他の名前を設定する場合は、aws_chalice/.chalice/config.jsonとoauth_app.py内のアプリケーション名を変更します。

Basic Information画面のApp Credentialsに表示されているクレデンシャルを後述のconfig.jsonに設定します。

スクリーンショット 2023-10-15 221539.png

OAuth & Permissions画面のScopesにapp_mentions:readchat:writeのOAuthを追加します。

スクリーンショット 2023-10-15 221637.png

AWS Chaliseの設定

config.json

OAuth認証用のサンプルconfigをコピーします。

cp -p .chalice/config.json.oauth .chalice/config.json

.chalice/config.jsonを開き、Slack Appのクレデンシャル情報やスコープを定義します。
Slack のOAuth認証用にS3バケットをあらかじめ作成し、SLACK_INSTALLATION_S3_BUCKET_NAMESLACK_STATE_S3_BUCKET_NAMEにバケット名を定義します。
Lambda関数からBedrockやS3にアクセスするIAMポリシーを後述のpolicy-dev.jsonで追加するため、autogen_policyはfalseとします。

.chalice/config.json
{
  "version": "2.0",
  "app_name": "bolt-python-chalice",
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "environment_variables": {
        "SLACK_SIGNING_SECRET": "App CredentialsのSigning Secretの値",
        "SLACK_CLIENT_ID": "App CredentialsのCLIENT IDの値",
        "SLACK_CLIENT_SECRET": "App CredentialsのClient Secretの値",
        "SLACK_SCOPES": "app_mentions:read,chat:write",
        "SLACK_INSTALLATION_S3_BUCKET_NAME": "S3バケット名",
        "SLACK_STATE_S3_BUCKET_NAME": "S3バケット名"
      },
      "autogen_policy": false,
      "automatic_layer": true
    }
  }
}

ここでは、"dev"というステージを定義しました。"prod"などの名称で別のステージを定義することで環境を別々に管理できるようです。

IAMポリシーの設定

.chalice/policy-dev.jsonを新規作成します。
S3の権限は読み取り、書き込み、削除のみに制限しています。実際の運用では、config.jsonで定義したSLACK_INSTALLATION_S3_BUCKET_NAMEやSLACK_STATE_S3_BUCKET_NAMEのバケットのみ扱えるようResourceでも制限するほうが良いと思います。

Bedrockの権限は、指定されたBedrockモデルを呼び出しメンションで入力した内容を使用して推論を実行する権限のみに絞っています。

.chalice/policy-dev.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "bedrock:InvokeModel"
            ],
            "Resource": "arn:aws:bedrock:us-east-1::foundation-model/ai21.j2-mid-v1"
        }
    ]
}

サンプルアプリの修正

OAuth認証用のapp.pyをコピーします。

cp -p oauth_app.py app.py

App()の初期化部分を以下のように修正します。

app.py(抜粋)
bolt_app = App(
    process_before_response=True,
    oauth_flow=LambdaS3OAuthFlow(),
)

handle_app_mentions()をBedrockにクエリを渡し、スレッドに返信するように書き換えます。

app.py(抜粋)
import json
import re
import boto3
def handle_app_mentions(body, say, logger):
    logger.info(body)

    event = body["event"]
    channel = event["channel"]
    ts = event["ts"]

    # Slackでメンションを受け取ったらメッセージからメンションを取り除く
    mention_text = re.sub(r'<@.*?> ', "", event["text"])

    # 応答を生成
    # Bedrock Clientの作成
    bedrock_client = boto3.client('bedrock-runtime')

    titanInputText = f"{mention_text}\n"

    bedrock_body = {
        "prompt": titanInputText,
        "maxTokens": 1024,
        "temperature": 0.7,
        "topP": 1,
    }

    body_bytes = json.dumps(bedrock_body).encode('utf-8')

    response = bedrock_client.invoke_model(
                    accept="*/*",
                    body=body_bytes,
                    contentType="application/json",
                    modelId="ai21.j2-mid-v1",
                )

    resp_body = response["body"].read()
    resp_body_json = json.loads(resp_body.decode('utf-8'))
    logging.info(resp_body_json["completions"][0]["data"]["text"])

    text_prompts = resp_body_json["completions"][0]["data"]["text"]

    # スレッドに返信
    say(f"{text_prompts}", thread_ts = ts)

クエリの内容によってはBedrockからの応答に時間がかるため、Slack APIがタイムアウトエラーを起こすこす場合があります。

Slack API サーバーから HTTP リクエストを受けるアプリケーションは、3 秒以内に HTTP レスポンスを返す必要があります。

3 秒以内に応答を返さなかった場合は「タイムアウトエラー」という扱いになります。Events API の場合は 3 回まで同じイベントの再送が発生します。

これにより、最初のリクエストを含めると合計で4回のイベントが発生します。つまり、1回のクエリに対してBedrockが4回も回答を送信します。これを避けるため、Lazyリスナーを使用すると良いようです。

app.py(抜粋)
def respond_to_slack_within_3_seconds(ack):
    ack()

bolt_app.event("app_mention")(
    ack=respond_to_slack_within_3_seconds,
    lazy=[handle_app_mentions]
)

app.py全体

app.pyの全体はこのようになります。

app.py
import logging
import json
import re
import boto3
from chalice.app import Chalice, Response

from slack_bolt import App
from slack_bolt.adapter.aws_lambda.chalice_handler import ChaliceSlackRequestHandler
from slack_bolt.adapter.aws_lambda.lambda_s3_oauth_flow import LambdaS3OAuthFlow

# process_before_response must be True when running on FaaS
bolt_app = App(
    process_before_response=True,
    oauth_flow=LambdaS3OAuthFlow(),
)

#@bolt_app.event("app_mention")
def handle_app_mentions(body, say, logger):
    logger.info(body)

    event = body["event"]
    channel = event["channel"]
    ts = event["ts"]

    # Slackでメンションを受け取ったらメッセージからメンションを取り除く
    mention_text = re.sub(r'<@.*?> ', "", event["text"])

    # 応答を生成
    # Bedrock Clientの作成
    bedrock_client = boto3.client('bedrock-runtime')

    titanInputText = f"{mention_text}\n"

    bedrock_body = {
        "prompt": titanInputText,
        "maxTokens": 1024,
        "temperature": 0.7,
        "topP": 1,
    }

    body_bytes = json.dumps(bedrock_body).encode('utf-8')

    response = bedrock_client.invoke_model(
                    accept="*/*",
                    body=body_bytes,
                    contentType="application/json",
                    modelId="ai21.j2-mid-v1",
                )

    resp_body = response["body"].read()
    resp_body_json = json.loads(resp_body.decode('utf-8'))
    logging.info(resp_body_json["completions"][0]["data"]["text"])

    text_prompts = resp_body_json["completions"][0]["data"]["text"]

    # スレッドに返信
    say(f"{text_prompts}", thread_ts = ts)

def respond_to_slack_within_3_seconds(ack):
    ack()

bolt_app.event("app_mention")(
    ack=respond_to_slack_within_3_seconds,
    lazy=[handle_app_mentions]
)

ChaliceSlackRequestHandler.clear_all_log_handlers()
logging.basicConfig(format="%(asctime)s %(message)s", level=logging.DEBUG)

# Don't change this variable name "app"
app = Chalice(app_name="bolt-python-chalice")
slack_handler = ChaliceSlackRequestHandler(app=bolt_app, chalice=app)


@app.route(
    "/slack/events",
    methods=["POST"],
    content_types=["application/x-www-form-urlencoded", "application/json"],
)
def events() -> Response:
    return slack_handler.handle(app.current_request)


@app.route("/slack/install", methods=["GET"])
def install() -> Response:
    return slack_handler.handle(app.current_request)


@app.route("/slack/oauth_redirect", methods=["GET"])
def oauth_redirect() -> Response:
    return slack_handler.handle(app.current_request)

デプロイ

deploy.shを実行しデプロイします。Lambda関数に加え、Lambda LayerやAPI Gateway、IAMロール、IAMポリシーがまとめてデプロイされます。

$ ./deploy.sh

2回目以降は以下のコマンドでデプロイできます。

$ chalice deploy

デプロイが成功すると、以下のような情報がコンソールに出力されます。

Resources deployed:
  - Lambda Layer ARN: arn:aws:lambda:us-east-1:123456789012:layer:bolt-python-chalice-dev-managed-layer:1
  - Lambda ARN: arn:aws:lambda:us-east-1:123456789012:function:bolt-python-chalice-dev
  - Rest API URL: https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/api/

このように、Lambda関数やLayer、API Gatewayが作成されました。
スクリーンショット 2023-10-18 234521.png

Slack Appの設定

Request URL,Redirect URLを設定

Event Subscriptions画面のEnable EventsをOnにし、Request URLにさきほど出力されたRest API URLの末尾にslack/eventsを追加したURL
https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/api/slack/eventsを入力します。
Verified✓ と表示されれば、正しいURLが入力されたことになります。

スクリーンショット 2023-10-15 221804.png

メンションイベントに応答するために、Subscribe to bot eventsにapp_mentionを追加します。

スクリーンショット 2023-10-15 221837.png

OAuth & Permissions画面のRedirect URLsにRest API URLの末尾にslack/oauth_redirectを追加したURL https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/api/slack/oauth_redirectを入力します。

スクリーンショット 2023-10-15 221900.png

Slack Workspaseにアプリをインストール

Rest API URLの末尾にslack/installを追加したURL https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/api/slack/installにアクセスします。インストールボタンが表示されるのでクリックしSlack Workspaseにアプリをインストールします。

スクリーンショット 2023-10-15 213015.png

スクリーンショット 2023-10-15 213411.png

もしくは、Install App画面のInstall to Workspaceをクリックしても同様の画面が表示されます。

スクリーンショット 2023-10-15 214046.png

インストールに成功すると、Thank you!画面が表示されます。Slackアプリをインストールしている場合はclick hereのリンク、Webブラウザを使用している場合は、this linkをクリックしてSlackを開きます。

スクリーンショット 2023-10-15 214923.png

動作確認

任意のチャンネルに@bolt-python-chaliceを招待し、メンションを送信します。 "こんにちは!"に対する返信が不自然ですが、生成AIについては自然な日本語でした。
今回はモデルにAL21 LabsのJurassic-2 Midを使用しました。日本語に対応しているAnthropicのClaudeだと違った応答になるかもしれません。

スクリーンショット 2023-10-16 003916.png

コードの作成を依頼してみました。これもきちんと答えてくれました。

スクリーンショット 2023-10-18 231744.png

ログは、CloudWatchロググループbolt-python-chalice-devで確認できます。

Chaliceが作成したリソース

作成したリソース

.chalis/deployed/dev.json にChaliceが作成したリソースの情報が記録されています。

.chalis/deployed/dev.json
{
  "resources": [
    {
      "name": "managed-layer",
      "resource_type": "lambda_layer",
      "layer_version_arn": "arn:aws:lambda:us-east-1:123456789012:layer:bolt-python-chalice-dev-managed-layer:1"
    },
    {
      "name": "api_handler_role",
      "resource_type": "iam_role",
      "role_arn": "arn:aws:iam::123456789012:role/bolt-python-chalice-dev-api_handler",
      "role_name": "bolt-python-chalice-dev-api_handler"
    },
    {
      "name": "api_handler",
      "resource_type": "lambda_function",
      "lambda_arn": "arn:aws:lambda:us-east-1:123456789012:function:bolt-python-chalice-dev"
    },
    {
      "name": "rest_api",
      "resource_type": "rest_api",
      "rest_api_id": "xxxxxxxxxx",
      "rest_api_url": "https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/api/"
    }
  ],
  "schema_version": "2.0",
  "backend": "api"
}

作成したリソースの削除

デプロイコマンドでデプロイされたリソースは以下のコマンドで削除できます。

chalice delete

削除後に再度デプロイするとRest API URLが以前とは別のもになります。その場合、Slack Appの再設定が必要です。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5