はじめに
こんにちは!トレノケート株式会社で AWS 認定インストラクターをやっております山下光洋 (@yamamanx) です。
2024 年 6 月にスタートしました Developing Generative AI Applications on AWS は、Amazon Bedrock を中心として、AWS で生成 AI アプリケーションを開発する方法やユースケース、AWS 関連サービスも含めた検討事項を学べる 2 日間の AWS 認定クラスルームトレーニングです。
この記事では Developing Generative AI Applications on AWS で受講者の皆さまに体験していただけるデモ環境を、ノーコードで実装していますので、その設計について紹介します。
対象サービス
今回使用するサービスは以下の通りです。リージョンはバージニア北部 (us-east-1) を使用しています。
Amazon API Gateway
Amazon EventBridge
Amazon EventBridge Pipes
Amazon SQS
AWS Step Functions
Amazon Bedrock
Amazon Titan
全体像
受講者の皆さまが直接アクセスできるように、Application Load Balancer、Amazon EC2、AWS Certificate Manager、Amazon Route 53 を使用して、オープンソースのチャットツールの RocketChat を使用しています。ここから呼び出すと応答してくれるチャットボットを、Amazon Titanで用意しています。
RocketChat には Outgoing Webhook、Incoming Webhook があります。Outgoing Webhook により外部の API へチャットへの投稿内容を渡せます。Incoming Webhook により外部からの投稿を POST リクエストで受け付けられます。
Slack や Teams などほかのチャットサービスでも同様の機能がありますので、ここで紹介する設計をほかのチャットツールでもお試しいただけます。適宜設定値などを対象のチャットツールにあわせてください。
前提として、トレーニング中の一時的なチャットボットとして使用し、投稿ユーザーの識別はしません。会話履歴も持たない一問一答式とします。
また、Amazon Bedrock のモデルアクセスで Titan Text G1 - Premier を有効にしているものとします。(2024 年 8 月 11 日現在、Amazon Titan Text G1 - Premier は米国東部 (バージニア北部) リージョンでのみ提供されています。)
アーキテクチャ図
設計
Amazon SQS
Amazon EventBridge のターゲットに設定する SQS キューをあらかじめ作成します。
標準キューでデフォルトの可視性タイムアウトは 5 分で作成します。
本番環境では、デッドレターキューも作成して、最大受信数を設定することを推奨します。そうすれば後続処理でエラーが連続発生しても、余分に繰り返すことなく、後でエラー原因の調査やリトライができます。

Amazon EventBridge
チャットメッセージイベントを受け取るイベントバスを chat-event などの名前で作成します。
イベントバスのルール
作成したイベントバスに以下のようなルールを作成します。channel_name は RocketChat に作成するチャンネル名です。 作成した SQS キューをターゲットに設定します。
{
"source": ["rocketchat.chatbot"],
"detail-type": ["WebhookMessage"],
"detail": {
"channel_name": ["titan"]
}
Amazon API Gateway
EventBridge の対象のイベントバスへ PutEvents できる IAM ポリシーをアタッチした、API Gateway 用の IAM ロールを作成しておきます。
IAM ポリシー
以下のような IAM ポリシーを作成しておきます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "events:PutEvents",
"Resource": "arn:aws:events:us-east-1:*:event-bus/chat-event"
}
]
}
信頼関係ポリシー
IAM ロール作成時に AWS サービスで API Gateway を選択すると次のような信頼関係 (IAM ロールのリソースベースポリシー) が自動作成されます。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
API を構築
RocketChat から POST リクエストを受け取る API を構築します。

統合タイプの設定
統合リクエストの統合タイプで AWS のサービスを選択し、以下の設定にします。
AWS リージョン : EventBridge のリージョン (今回はすべてバージニア北部で作成しています)
AWS のサービス : Amazon CloudWatch Events
HTTP メソッド : POST
アクションタイプ : アクション名を使用
アクション名 : PutEvents
実行ロール : 作成した API Gateway 用の IAM ロール
作成後、マッピングテンプレートに次の設定をします。
コンテンツタイプ : application/json
テンプレート本文
マッピングテンプレートに次の設定をします。
#set($context.requestOverride.header.X-Amz-Target = "AWSEvents.PutEvents")
#set($context.requestOverride.header.Content-Type = "application/x-amz-json-1.1")
{
"Entries": [{
"EventBusName": "chat-event",
"Source": "rocketchat.chatbot",
"DetailType": "WebhookMessage",
"Detail": "$util.escapeJavaScript($input.json('$'))"
}]
}
デプロイ
形式は Amazon EventBridge API Reference の PutEvents にあわせています。Source、DetailType は EventBridge のルールで検知するために固定で設定しています。$input.json('$') が RocketChat から送信された情報です。
API を作成後、新規ステージにデプロイしてステージの URL を控えておきます。
RocketChat の Outgoing Webhook
対象のチャンネルを作成後、管理メニューから「サービス連携」 -「Outgoing WebHook」を選択して新規作成します。
チャンネルはカンマ区切りで設定して、ステージの URLs に API の URL を設定します。

ユーザー名・トークン設定
下にスクロールして、投稿ユーザーは RocketChat に存在するユーザー名を入力します。
RocketChat の Outgoing WebHook ではトークンが設定できるので設定しておきます。

Amazon API Gateway の本文検証
この時点で API Gateway は世界中どこからでも呼び出せる状態です。今回は RocketChat からのみ呼び出せられればいいので限定します。
もしも RocketChat 側で Elastic IP アドレスなどで固定 IP アドレス化しているのであれば、API Gateway のリソースベースのポリシーで送信元 IP アドレスによる制限をしてもいいでしょう。今回は IP アドレスは変わる前提で考えますので、リクエスト本文を検証することで制限します。
API Gateway の「モデル」でコンテンツタイプ application/json のモデルを作成します。
RocketChat のトークンが a1b2c3d4e5f6 の場合の API Gateway モデル
例えば名前は RocketChatToken などにします。
{
"type" : "object",
"properties" : {
"token" : {
"type" : "string",
"enum" : [ "a1b2c3d4e5f6" ]
}
},
"required":["token"]
メソッドリクエストの設定
API Gateway リソースのメソッドリクエストを編集して、リクエストバリデーターを 本文を検証 にします。
「リクエスト本文」に作成したモデルを指定します。これで本文に token キーが含まれていて、値が指定したものであることを検証できます。

より強固な保護
呼び出し元のチャットシステムが、Authorization ヘッダーキーなどに対応している場合は AWS WAF と組み合わせたり、Lambda オーソライザーで検証できるものであればそれらを使用して、より強固に守ることもできます。

途中での確認
ここまでで RocketChat の対象チャンネルで投稿したメッセージが SQS まで届くようになりました。
メッセージを投稿して SQS キューの「メッセージを送受信」- 「メッセージをポーリング」でメッセージが届いているか確認してみましょう。うまくいかない場合は API Gateway で CloudWatch Logs を有効にして、ステージのログ情報を記録して API Gateway から EventBridge への送信が問題なく行われているかを確認したりしてみましょう。
CloudWatch Logs の API Gateway ログの有効化は API Gateway コンソールを使用した CloudWatch による API のログの設定 を参照してください。
またログでメッセージの確認もできますので、EventBridge イベントバスのルールに設定した意図したメッセージとなっているかも確認しましょう。
EventBridge から SQS キューへの送信に必要なキューポリシー (SQS キューのリソースベースのポリシー) は、マネジメントコンソールでターゲットへ設定した際に自動で作成されるので、今回の手順でキューへのメッセージ送信権限の問題は考えにくいです。
RocketChat Incoming WebHook の作成
RocketChat の管理メニュー「サービス連携」-「incoming WebHook」で対象のチャンネルに外部から POST できる URL を作成します。
作成された Webhook URL は後に使います。

Amazon EventBridge 接続の作成
RocketChat の Incoming WebHook URL にトークンが含まれているので、別で認証情報は必要ありませんが、Step Functions の仕様上 EventBridge の接続が必要ですので作成します。
EventBidge の「統合」-「API の送信先」-「接続」タブで「接続を作成」ボタンから API キーを選択してキーと値に適当な値を入力して作成しておきます。
AWS Step Functions ステートマシンの作成
次に Step Functions ステートマシンを作成します。 図が Amazon Titan の場合の完成図です。ステートマシンは Blank で作成します。

ステートマシン名の設定
「設定」で名前を入力して、タイプは Express にします。Express タイプは同期実行が可能となります。これにより、今回の設計ではステートマシン実行時にエラーなどにより失敗した際に、SQS キューにメッセージを残せてリトライができるようになります。
EventBridge Pipes で SQS から呼び出す標準タイプの場合は、ステートマシンにメッセージが送信されたタイミングでキューメッセージが削除されるので、ステートマシン側でリトライなどを実装する必要があります。
IAM ロールは新しいロールを作成として必要な許可が自動で設定されるようにします。
「デザイン」に戻ってワークフローを作成します。

Pass を設定
フローから Map をドラッグ&ドロップします。次に Map の中に Pass をドラッグ&ドロップします。
Map は与えられた配列の数だけ処理をします。SQS キューからメッセージの配列を受け取りますので、最初を Map にしています。
Map の中のフローで 1 つ 1 つのキューメッセージを処理します。

Path の設定
Pass の名前を StringToJson にします。
「入力」タブの「Parameters を使用して入力を変換」に次の設定をします。
{ "body.$": "States.StringToJson($.body)"}
「出力」タブの「OutputPath で出力をフィルタリング」は $.body.detail とします。

出力パス
こうすることで、キューメッセージの body フィールドにある文字列を JSON に変換して、そこから Detail の値だけを抽出しています。

Bedrock InvokeModel を設定
次にフローから Choice をドラッグ&ドロップして名前を IsBot とします。
Default にはアクションから Bedrock を検索して、InvokeModel をドラッグ&ドロップします。

Choise にルールを追加
Choise にルールを追加して、次のルールを設定します。

Bedrock モデルパラメータを設定
追加したルールの先はフローから Success をドラッグ&ドロップして、コメントに bot post と入力しておきます。 Detail の bot が false ではない場合は、Bedrock API を呼び出さずに成功として終了させます。
Bedrock model identifier で Titan Text Premier を選択してモデルパラメータに以下を入力します。
InputText に Detail から text キーの値、RocketChat で投稿されたメッセージを渡しています。ほかにはTitan Text Premierモデルに必要なパラメータを設定しています。

Bedrock モデルパラメータ
以下を入力します。
{
"inputText.$": "$.text",
"textGenerationConfig": {
"temperature": 0,
"topP": 1,
"maxTokenCount": 1024
}
}
出力設定
出力の ResultSelector を使用して結果を変換 には {"results.$": "$.Body.results"} としています。これによって Bedrock InvokeModel 実行後のレスポンスから、results のみを次のステップに渡しています。

Map の設定
results は配列なので次にフローから Map をドラッグ&ドロップします。
項目配列へのパスを指定 で、$.results とします。
Map は results の配列の数だけ内側の処理を実行します。

TranslateText の設定
アクションから Translate を検索して、TranslateText を Map の中にドラッグ&ドロップします。Titan が英語でレスポンスを outputText に返しているので、Amazon Translate で日本語に翻訳します。

TranslateText の API パラメータ
API パラメータには次の設定をしています。
{
"SourceLanguageCode": "auto",
"TargetLanguageCode": "ja",
"Text.$": "$.outputText"
}
出力設定
出力の ResultSelector を使用して結果を変換 では、{"TranslatedText.$": "$.TranslatedText"} としています。こうして次のステートに翻訳後の文章だけを渡しています。

Call third-party API を設定
サードパーティ API の Call third-party API をドラッグ&ドロップし、以下のように入力します。
API エンドポイント : RocketChat Incoming WebHook を入力
メソッド : POST
Authentication : EventBridge で作成した接続の ARN を入力
リクエスト本文 : {"text.$": "$.TranslatedText"}
ここまでできたら Step Functions ステートマシンを作成して保存します。

ポリシーを IAM ロールに追加
Translate 以外の IAM 許可ポリシーが自動で IAM ロールにアタッチされます。Translate はソース言語を auto としたので、Comprehend の権限も必要です。

Translate のポリシー
次のポリシーを IAM ロールに追加します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"translate:TranslateText",
"comprehend:DetectDominantLanguage"
],
"Resource": "*"
}
]
}
Amazon EventBridge Pipes の作成
EventBridge パイプの新規作成をして、ソースに SQS キュー、ターゲットに Step Functions ステートマシンを選択します。
Step Functions ステートマシンの呼び出しタイプは同期(リクエスト-レスポンス)にします。こうすることで、ステートマシン実行時にエラーとなった場合は SQS キューにメッセージが残り、可視性タイムアウトの時間が経過した後にリトライされます。
同期 (リクエスト-レスポンス) は Step Functions ステートマシンのタイプが Express の場合のみ設定可能です。

動作確認
対象のチャンネルでメッセージを投稿すると Amazon Titan で作成されたチャットボットが返信してくれます。

ステートマシンの実行結果
ステートマシンの実行結果も視覚的に表示してくれます。各ステートの入力値、出力値も確認できますので、デバッグもできます。

まとめ
ノーコードで実装できるチャットボットの設計について紹介しました。
Step Functions ステートマシンは Workflow Studio でドラッグ&ドロップなどで作成しますが、実体は Amazon States Language という JSON です。これをコピーすればいくつでも同じステートマシンを作成できますし、編集も可能です。独自のコード開発が必要となるケースもありますが、1つの設計パターンとして検討対象になりましたら幸いです。
Anthropic Claude などほかのモデルを使用する場合は、Bedrock InvokeModel のパラメータとレスポンスが異なりますので、マネジメントコンソールの Bedrock ベースモデルなどで確認しましょう。
クラスルームトレーニングで学ばれる技術を受講者の皆さまが具体的に触れることで、課題解決や価値創造への新たな気付きに繋がっていただければ幸いです。
筆者プロフィール
山下 光洋
トレノケート株式会社
AWS認定インストラクター
AWS 認定インストラクターとして年間 1,000 名以上にトレーニングを提供。Japan AWS Top Engineers などに選出、AWS 認定インストラクターアワード 3 年連続受賞。ソフトウェアハウスでの業務アプリケーション開発、事業会社 IT 部門での内製開発経験を得てインストラクターの傍らプロトタイプビルダーとしても従事。
勉強会、ブログ、Youtube、書籍執筆などの情報発信とコミュニティ活動にも積極的に参加している。

Did you find what you were looking for today?
Let us know so we can improve the quality of the content on our pages