AWS トレーニングを活用して、ノーコード実装の生成 AI チャットボットを設計する

2024-09-03
How to be a Developer

Author : 山下 光洋 (トレノケート株式会社)

こんにちは!トレノケート株式会社で 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 などの名前で作成します。作成したイベントバスに以下のようなルールを作成します。

イベントバスのルール

{
  "source": ["rocketchat.chatbot"],
  "detail-type": ["WebhookMessage"],
  "detail": {
    "channel_name": ["titan"]
  }

channel_name は RocketChat に作成するチャンネル名です。  作成した SQS キューをターゲットに設定します。  

Amazon API Gateway

EventBridge の対象のイベントバスへ PutEvents できる IAM ポリシーをアタッチした、API Gateway 用の 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"
        }
    ]
}

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 のモデルを作成します。例えば名前は RocketChatToken などにします。

RocketChat のトークンが a1b2c3d4e5f6 の場合の API Gateway モデル

{
  "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 ロールは新しいロールを作成として必要な許可が自動で設定されるようにします。

デザイン」に戻ってワークフローを作成します。

画像をクリックすると拡大します

フローから Map をドラッグ&ドロップします。次に Map の中に Pass をドラッグ&ドロップします。

Map は与えられた配列の数だけ処理をします。SQS キューからメッセージの配列を受け取りますので、最初を Map にしています。

Map の中のフローで 1 つ 1 つのキューメッセージを処理します。

画像をクリックすると拡大します

Pass の名前を StringToJson にします。

入力」タブの「Parameters を使用して入力を変換」に次の設定をします。

{
  "body.$": "States.StringToJson($.body)"
}

出力」タブの「OutputPath で出力をフィルタリング」は $.body.detail とします。  

画像をクリックすると拡大します

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

画像をクリックすると拡大します

次にフローから Choice をドラッグ&ドロップして名前を IsBot とします。

Default にはアクションから Bedrock を検索して、InvokeModel をドラッグ&ドロップします。

画像をクリックすると拡大します

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

画像をクリックすると拡大します

追加したルールの先はフローから Success をドラッグ&ドロップして、コメントに bot post と入力しておきます。  Detail の bot が false ではない場合は、Bedrock API を呼び出さずに成功として終了させます。  

Bedrock model identifier で Titan Text Premier を選択してモデルパラメータに以下を入力します。

{
  "inputText.$": "$.text",
  "textGenerationConfig": {
    "temperature": 0,
    "topP": 1,
    "maxTokenCount": 1024
  }
}

InputText に Detail から text キーの値、RocketChat で投稿されたメッセージを渡しています。ほかにはTitan Text Premierモデルに必要なパラメータを設定しています。

画像をクリックすると拡大します

出力の ResultSelector を使用して結果を変換 には {"results.$": "$.Body.results"} としています。これによって Bedrock InvokeModel 実行後のレスポンスから、results のみを次のステップに渡しています。

画像をクリックすると拡大します

results は配列なので次にフローから Map をドラッグ&ドロップします。

項目配列へのパスを指定 で、$.results とします。

Map は results の配列の数だけ内側の処理を実行します。

画像をクリックすると拡大します

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

API パラメータには次の設定をしています。

{
  "SourceLanguageCode": "auto",
  "TargetLanguageCode": "ja",
  "Text.$": "$.outputText"
}

画像をクリックすると拡大します

出力の ResultSelector を使用して結果を変換 では、{"TranslatedText.$": "$.TranslatedText"} としています。こうして次のステートに翻訳後の文章だけを渡しています。

画像をクリックすると拡大します

サードパーティ API の Call third-party API をドラッグ&ドロップし、以下のように入力します。

  • API エンドポイント : RocketChat Incoming WebHook を入力
  • メソッド : POST 
  • Authentication : EventBridge で作成した接続の ARN を入力
  • リクエスト本文 :  {"text.$": "$.TranslatedText"} 

ここまでできたら Step Functions ステートマシンを作成して保存します。

画像をクリックすると拡大します

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

次のポリシーを 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 ベースモデルなどで確認しましょう。

クラスルームトレーニングで学ばれる技術を受講者の皆さまが具体的に触れることで、課題解決や価値創造への新たな気付きに繋がっていただければ幸いです。


builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます

筆者プロフィール

山下 光洋
トレノケート株式会社
AWS認定インストラクター

AWS 認定インストラクターとして年間 1,000 名以上にトレーニングを提供。Japan AWS Top Engineers などに選出、AWS 認定インストラクターアワード 3 年連続受賞。ソフトウェアハウスでの業務アプリケーション開発、事業会社 IT 部門での内製開発経験を得てインストラクターの傍らプロトタイプビルダーとしても従事。
勉強会、ブログ、Youtube、書籍執筆などの情報発信とコミュニティ活動にも積極的に参加している。

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する