【第三回】Amazon Bedrockを用いたAIチャットボットを実装

AWS

AWSとDockerを使ったWordPress構築連載、最終回となる第三回目です。 (※これまでの構築手順をまだ読んでいない方は、第1回第2回からご覧ください)

前回までの手順で、独自ドメインとHTTPS通信を備えた本WordPressブログが構築しました。今回はAWSのサービスを活用して、「ユーザーの質問に答えるAIチャットボット」をブログに埋め込みます。

本記事では、生成AIであるAmazon Bedrock(Claudeモデル)を、AWS LambdaAPI Gatewayを経由するサーバーレス・アーキテクチャで構築します。

さらにAIをWeb上に公開する上で、クラウド破産を防ぐためのWAFを用いたセキュリティ対策の設定手順も解説します。

1. Amazon BedrockでAIモデルを有効化する

まずは、チャットボットの頭脳となるAIを準備します。AWSが提供する生成AIサービス「Amazon Bedrock」を使って、AIモデルを利用できる状態にします。

1.1. AIモデルの選択

Bedrockでは、Amazon独自のAIや、Meta社のLlama、Anthropic社のClaudeなど、様々なAIモデルをAPI経由で呼び出すことができます。

今回は、Anthropic社の 「Claude Sonnet」 の最新バージョンを使用します。(※執筆時点での最新は Claude Sonnet 5など)。

【なぜ最高性能のモデル(Fable/Opusなど)を使わないのか?】 

Bedrockには、より複雑な自律タスクをこなす高度なモデル(FableやOpus)も用意されていますが、ブログのチャットボットにこれらを使うのはおすすめしません。理由は2つあります。

  1. API Gatewayのタイムアウト: AIが思考時間が長く、回答に30秒以上かかると、前段のAPI Gatewayがタイムアウトとして通信を強制切断してしまいます。
  2. コストと用途の不一致: ブログの読者との短い会話ラリーにおいて、高額・高性能なモデルはオーバースペックになります。

適度な性能と、瞬時のレスポンスを併せ持つ「Sonnet」モデルが、今回のチャットボットにとって高いコストパフォーマンスを発揮してくれます。

1.2. Playgroundからのモデルアクセス申請

Claudeのようなサードパーティ製のAIを初めて使う場合、セキュリティ上のルールにより「どんな目的でこのAIを使うのか」というユースケースの申請を行う必要があります。これをしないとAPIから呼び出すことができません。

AWSコンソールから、以下の手順で申請を行います。

  1. AWSコンソールの検索バーで 「Bedrock」 と検索し、Amazon Bedrockを開きます。
  2. 左側のメニューを下へスクロールし、「プレイグラウンド (Playgrounds)」 を開き、「モデルを選択」 をクリックします。
  3. カテゴリを「Anthropic」にし、「Claude Sonnet(最新バージョン)」 を選んで「適用」をクリックします。
  4. はじめて使うモデルの場合、「ユースケースの詳細を送信 (Submit use case details)」 といった警告リンクが表示されます。そこをクリックします。

【ユースケース詳細フォームの入力例】 フォームが開いたら、以下の例を参考に英語で入力し、右下の「Submit(送信)」をクリックしてください。(※個人学習用途であれば、正直に書けばすぐに通ります)。

  • Company name (会社名): Personal Project (またはご自身の名前)
  • Company website URL: https://your-domain.com (今回作成したブログのURL)
  • What industry do you operate in?: Education や Technology など
  • Who are the intended users…: External users (外部のユーザー) にチェック
  • Describe your use cases: I am building a simple chatbot on my personal blog to learn AWS Bedrock and generative AI. (AWS Bedrockと生成AIを学ぶために、個人のブログにシンプルなチャットボットを作っています。)

送信後、AWS側で審査が行われます。問題なければ、数分〜最大15分程度で承認されます。 少し待ってから、このPlaygroundの画面で「こんにちは」とチャットを打ってみて、Claudeから返事が返ってくれば、Bedrock側の準備は完了です。

2. AWS Lambdaによるバックエンドプログラムの構築

Bedrockの準備ができたら、次はそのAIを呼び出すためのプログラムを作成します。 今回はサーバーを立てるのではなく、コードを書くだけで実行してくれるサーバーレスサービス「AWS Lambda」を使用し、Pythonで構築します。

2.1. AIを呼び出すためのLambda関数の作成と

  1. AWSコンソールの検索バーで 「Lambda」 と検索し、開きます。
  2. 右上の 「関数の作成」 をクリックし、以下の設定で作成します。
    • オプション: 「一から作成」
    • 関数名: MyAIChatBot (任意の名前)
    • ランタイム: Python 3.12 (または新しいバージョン)
  3. 「関数の作成」 をクリックします。

関数が作成されると、画面の下部に「コードソース」というエディタ画面が現れます。 最初から書かれている lambda_function.py の中身をすべて消し、以下のPythonコードを記述します。

pythonCopyimport json
import boto3

# ★ポイント1: Bedrockを呼び出すクライアントを作成
# リージョンは、最新のモデルが確実に使える 'us-east-1' などを指定します
bedrock = boto3.client(service_name='bedrock-runtime', region_name='us-east-1')

def lambda_handler(event, context):
    try:
        # API Gatewayから送られてきたメッセージ(質問)を受け取る
        body = json.loads(event['body'])
        user_message = body['message']
        
        # Claude向けの指示(プロンプト)フォーマット
        payload = {
            "anthropic_version": "bedrock-2023-05-31",
            "max_tokens": 1000,
            "messages": [
                {
                    "role": "user",
                    "content": [{"type": "text", "text": user_message}]
                }
            ]
        }
        
        # ★ポイント2: Bedrockのモデルを呼び出す(※modelIdは最新のものを指定)
        response = bedrock.invoke_model(
            modelId='anthropic.claude-sonnet-5', 
            contentType='application/json',
            accept='application/json',
            body=json.dumps(payload)
        )
        
        # Bedrockからの返答を読み解く
        response_body = json.loads(response.get('body').read())
        ai_reply = response_body['content'][0]['text']
        
        # API Gateway (ブラウザ) へ返すフォーマット
        return {
            'statusCode': 200,
            'headers': {
                'Access-Control-Allow-Origin': '*', # CORS設定
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Allow-Methods': 'OPTIONS,POST'
            },
            'body': json.dumps({'reply': ai_reply})
        }
        
    except Exception as e:
        print(f"ERROR: {str(e)}")
        return {
            'statusCode': 500,
            'body': json.dumps({'error': str(e)})
        }

コードを貼り付けたら、「Deploy」 ボタンを押して必ず保存してください。

2.2. IAMロールへの権限追加

コードは書けましたが、今のままではLambdaはBedrockを操作する権限を持っていません。そこで、IAMというセキュリティ機能を使って、Lambdaに利用ポリシーを持たせます。

  1. Lambdaの画面で、上部タブの 「設定」 → 「アクセス権限」 を開きます。
  2. 実行ロールの下にある青いリンク(MyAIChatBot-role-xxxx)をクリックし、IAMの画面を開きます。
  3. 「許可を追加」 のプルダウンから 「インラインポリシーを作成」 をクリックします。
  4. 「JSON」 タブをクリックし、最初から入っている文字をすべて消して、以下のJSONを貼り付けます。
jsonCopy{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "bedrock:InvokeModel",
                "aws-marketplace:ViewSubscriptions",
                "aws-marketplace:Subscribe",
                "aws-marketplace:Unsubscribe"
            ],
            "Resource": "*"
        }
    ]
}

【なぜMarketplaceの権限が必要か】 AWSのシステム上、Anthropic社のClaudeモデルは「外部のストアから製品をサブスクリプションして使う」という扱いになっています。そのため、Bedrockを呼び出す権限(InvokeModel)だけでなく、Marketplaceでの利用契約を確認する権限もセットで付与しないと、アクセス拒否エラーになってしまいます。

貼り付けたら「次へ」を押し、ポリシー名に BedrockAndMarketplacePolicy 等と入力して 「ポリシーの作成」 をクリックします。

2.3. Lambdaのタイムアウト上限の延長

Lambdaの実行制限時間を変更します。 AWS Lambdaは、初期設定で「処理が3秒を超えたら、強制終了する」というルールになっています。AIが回答を考えるのには通常5秒〜10秒ほどかかるため、このままでは確実にエラーになります。

  1. Lambdaの 「設定」 タブ → 「一般設定」 を開きます。
  2. 「編集」 ボタンをクリックします。
  3. 「タイムアウト」の項目を、デフォルトの 0 分 3 秒 から 0 分 29 秒 に変更します。
  4. 「保存」 をクリックします。

(※後段に置くAPI Gatewayが「29秒以上返事が来ないとエラーにする」というルールを持っているため、それに合わせる)

これで、バックエンドの構築は完了です!

3. API Gatewayの作成とCORS設定

Lambdaが完成しましたが、このままではインターネット上のブラウザから呼び出すことができません。 そこで、「Amazon API Gateway」 というサービスを使って、ブラウザからの通信を受け付け、Lambdaへ繋げるURLを作成します。

3.1. REST APIとしてのエンドポイントの発行

Lambdaの画面から直接API Gatewayを作成するのが一番簡単です。

  1. Lambdaの画面、「関数の概要」の図にある 「+ トリガーを追加」 をクリックします。
  2. プルダウンから 「API Gateway」 を選択し、以下の設定を行います。
    • Intent (目的): Create a new API
    • API type: REST API を選択します。(※HTTP APIではなく、後で利用制限をかけやすいREST APIを選びます)
    • Security: 今回は後でWAF等の制限をかけるため、一旦 Open にします。
  3. 「追加」 をクリックします。

作成されると、画面に 「API エンドポイント」 というURL(https://xxxx.execute-api...)が表示されます。これがチャットボットの受付となるURLですので、メモ帳などにコピーしておいてください。

3.2. ブラウザからの通信を許可する「CORS」の設定

APIのURLが発行されましたが、このままブログから通信を送っても、ブラウザに「CORS ERROR」文字が出て通信が弾かれてしまいます。

【CORS ERRORとは?】 現代のブラウザは、「あるサイト(あなたのブログ)から、全く違うURL(API Gateway)へ勝手にデータを送ろうとする動き」を、ハッキングの危険があるとしてブロックします。これを防ぐには、API側の設定で「このブログからの通信なら受け入れていいよ」という許可(CORS設定)を設定する必要があります。

API Gatewayの画面に移動し、この許可設定を行います。

  1. AWSコンソールの検索バーで 「API Gateway」 と検索して開きます。
  2. 一覧から、先ほど作成したAPI(MyAIChatBot-API)をクリックします。
  3. 左メニューの 「リソース」 をクリックし、真ん中のペインで /MyAIChatBot の文字をクリックして選択状態にします。
  4. 「CORS の有効化 (Enable CORS)」をクリックします。
  5. 設定画面が出ます。基本的にはデフォルトのままでOKですが、以下が設定されているか確認してください。
    • Access-Control-Allow-Origin: '*' (誰からでも許可する、という意味です)
    • Access-Control-Allow-Methods: POSTOPTIONS などが含まれていること。
  6. 「保存」をクリックします。

3.3. 設定変更後のデプロイ作業

CORSの設定を保存しただけでは、インターネット上のURLにはまだ反映されていません。API Gatewayは、設定を変えるたびにデプロイが必須になります。

  1. 画面右上の 「API をデプロイ」 をクリックします。
  2. デプロイされるステージで default を選択します。
  3. 「デプロイ」 をクリックします。

これで、ブラウザのセキュリティを通過し、ブログからLambdaを呼び出せる玄関口が完成しました。

4. クラウド破産を防ぐセキュリティの構築

APIが完成し、AIを呼び出せるようになりました。 しかし、このままAPIのURLをブログに貼り付けて公開するのはセキュリティ上問題があります

Amazon Bedrockは、使った分だけ料金がかかる「従量課金制」です。 もし悪意のある攻撃者やBotが、プログラムを使って1秒間に何百回もAIに質問を投げ続けてきたら、数時間放置されただけで、莫大な利用料金の請求が発生してしまいます。これをクラウド破産と呼ぶそうです

これを防ぐため、AWSのサービスを用いてゼロトラスト・アーキテクチャを構築します。

4.1. API Gatewayのスロットリング設定

まずは「システム全体で月に何回までしかAIを呼び出さない」という、ストッパーを設定します。

  1. AWSコンソールの「API Gateway」を開き、左メニューから 「使用量プラン (Usage Plans)」 をクリックします。
  2. 「使用量プランの作成」 をクリックし、以下のように設定します。
    • 名前: Bedrock-Limit-Plan 
    • スロットリング : 瞬間的なアクセスの制限です。
      • レート (Rate): 5 
      • バースト (Burst): 10 
    • クォータ : 1ヶ月の利用上限です。
      • 例:1000 リクエスト /  (※これで、月に1,000回=数百円分で自動停止します)
  3. 作成後、そのプランを開き 「関連付けられた API ステージ」 タブを開きます。
  4. 「API ステージの追加」 を押し、作成した MyAIChatBot API と default ステージを選択して保存します。

これで、万が一攻撃を受けても、設定した回数に達した瞬間にAPIがシャットダウンし、高額請求を防ぐことができます。

4.2. AWS WAFを用いた連続アクセスブロックの設定

スロットリングだけでは「1人の悪意ある人が、その月のクォータを全部使い切ってしまう」という嫌がらせを防げません。 そこで、「AWS WAF (Web Application Firewall)」 を導入し、「短時間に異常な回数アクセスしてきたIPアドレスを自動検知してBANする」という設定を行います。

  1. AWSコンソールの検索バーで 「WAF」 と検索し、「WAF & Shield」のダッシュボードを開きます。
  2. 左メニューから 「保護パック (Web ACLs)」 をクリックし、「保護パックを作成」 をクリックします。
  3. ステップ 1 :
    • 名前: AIChatBot-WAF
    • スコープ: リージョンリソース (Regional resources) を選択します。
    • ここで「AWS リソースを追加」を押し、Amazon API Gatewayから MyAIChatBot を選んで紐付けます。
  1. ステップ 2 :
    • AWSが推奨する有料の自動ルールが選択されている場合は、すべてチェックを外すか削除します。
    • 「独自のルールを追加」から レートベースのルール (Rate-based rule) を選択します。
    • 名前: Block-Spam-IP
    • レート制限: 100 
    • アクション: Block
  2. 残りのステップはデフォルトのまま進めます。

これで、短時間のスパム攻撃はWAFで弾き、想定外の大量アクセスもAPI Gatewayのクォータで止まるという、二重のセキュリティ基盤を構築しました。

5. WordPressへのフロントエンド実装

最終工程です。完成したAIチャットボットをWordPressのフロントエンドに埋め込みます。

今回は、画面の右下に常に固定され、クリックすると吹き出しのように開くチャットUIを実装します。

5.1. フッターにチャットUIを配置する

WordPressのウィジット機能を使えば、プラグインなしで全ページにチャットボットを表示させることができます。

  1. WordPressのダッシュボードから 「外観」 → 「カスタマイズ」を開きます。
  1. 利用できるウィジェットの中から 「カスタム HTML」 を探します。
  2. その「カスタム HTML」を、「コンテンツ下部」 のエリアにドラッグ&ドロップで追加します。 (※「インデックスリストボトム」などに入れると、トップページ以外で表示されなくなることがあるため、どのページでも必ず読み込まれるエリアを選んでください)
  1. 開いた入力枠に、以下のHTML/JavaScriptコードをコピーして貼り付けます。

【コード貼り付け時の注意点】const API_URL = "..." の部分を、ご自身がStep3で作成したAPI GatewayのエンドポイントURL に書き換えてから保存してください。

<!-- チャット起動ボタン -->
<button id="ai-chat-btn" onclick="toggleAiChat()" style="position: fixed; bottom: 80px; right: 20px; width: 60px; height: 60px; border-radius: 50%; background-color: #6a5acd; color: white; border: none; font-size: 24px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); cursor: pointer; z-index: 2147483647; display: flex; justify-content: center; align-items: center;">
    💬
</button>

<!-- チャットウィンドウ -->
<div id="ai-chat-window" style="display: none; position: fixed; bottom: 150px; right: 5vw; width: 90vw; max-width: 350px; background: white; border-radius: 12px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); z-index: 2147483647; flex-direction: column; overflow: hidden; border: 1px solid #ccc;">
    
    <!-- ヘッダー部分 -->
    <div style="background-color: #6a5acd; color: white; padding: 15px; font-weight: bold; display: flex; justify-content: space-between; align-items: center;">
        <span>AI サポートボット</span>
        <button onclick="toggleAiChat()" style="background: none; border: none; color: white; font-size: 16px; cursor: pointer;">✖</button>
    </div>

    <!-- チャットの履歴エリア -->
    <div id="ai-chat-box" style="height: 350px; overflow-y: auto; padding: 15px; background: #f9f9f9; font-size: 14px;">
        <div style="background: #e1def5; padding: 10px; border-radius: 8px; margin-bottom: 10px; display: inline-block; max-width: 80%; color: #333;">
            <strong>AI:</strong> こんにちは!AWSやブログについて何でも聞いてください。
        </div>
    </div>

    <!-- 入力エリア -->
    <div style="display: flex; padding: 10px; border-top: 1px solid #eee; background: #fff;">
        <input type="text" id="ai-user-input" placeholder="質問を入力..." onkeypress="handleKeyPress(event)" style="flex-grow: 1; padding: 10px; border: 1px solid #ccc; border-radius: 4px; outline: none; color: #333;">
        <button onclick="sendAiMessage()" style="margin-left: 10px; padding: 10px 15px; background-color: #6a5acd; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">送信</button>
    </div>
</div>

<script>
    // ここにAPI GatewayのURLを貼り付けてください
    const API_URL = "https://xxxx.execute-api.ap-northeast-3.amazonaws.com/default/MyAIChatBot";

    // チャットウィンドウの開閉を行う関数
    function toggleAiChat() {
        const chatWindow = document.getElementById("ai-chat-window");
        if (chatWindow.style.display === "none" || chatWindow.style.display === "") {
            chatWindow.style.display = "flex";
        } else {
            chatWindow.style.display = "none";
        }
    }

    function handleKeyPress(event) {
        if (event.key === 'Enter') sendAiMessage();
    }

    async function sendAiMessage() {
        const inputField = document.getElementById("ai-user-input");
        const chatBox = document.getElementById("ai-chat-box");
        const message = inputField.value.trim();
        if (!message) return;

        // 自分の質問を追加
        chatBox.innerHTML += `
            <div style="text-align: right; margin-bottom: 10px;">
                <div style="background: #6a5acd; color: white; padding: 10px; border-radius: 8px; display: inline-block; max-width: 80%; text-align: left;">
                    <strong>あなた:</strong><br>${message}
                </div>
            </div>`;
        inputField.value = "";
        chatBox.scrollTop = chatBox.scrollHeight;

        // "入力中..." の表示を追加
        const loadingId = "loading-" + Date.now();
        chatBox.innerHTML += `
            <div id="${loadingId}" style="text-align: left; margin-bottom: 10px;">
                <div style="background: #e1def5; padding: 10px; border-radius: 8px; display: inline-block; color: #555;">
                    AIが考え中...
                </div>
            </div>`;
        chatBox.scrollTop = chatBox.scrollHeight;

        try {
            const response = await fetch(API_URL, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ message: message })
            });
            const data = await response.json();
            
            // "入力中..." を消してAIの回答を追加
            document.getElementById(loadingId).remove();
            chatBox.innerHTML += `
                <div style="text-align: left; margin-bottom: 10px;">
                    <div style="background: #e1def5; padding: 10px; border-radius: 8px; display: inline-block; max-width: 80%; color: #333;">
                        <strong>AI:</strong><br>${data.reply.replace(/\n/g, '<br>')}
                    </div>
                </div>`;
            chatBox.scrollTop = chatBox.scrollHeight;
        } catch (error) {
            document.getElementById(loadingId).remove();
            chatBox.innerHTML += `<p style="color:red; text-align:center;">通信エラーが発生しました。</p>`;
        }
    }
</script>

コードを貼り付けて公開すれば完成です。

作成したブログのトップページを開いてみると、 画面の右下に紫色の「💬」ボタンが浮いており、クリックするとチャット画面がスライドして表示されます。

適当に質問を投げかけて、正確な回答が返ってくれば全工程完了です。

全3回にわたる連載にお付き合いいただき、ありがとうございました。 以上が「AWSでのサーバー構築」「Dockerによる環境構築」「ドメインとHTTPSの設定」「サーバーレスでの生成AI組込」「WAFを用いたセキュリティ基盤構築」という、実務で使われる技術の学習記録です。

コメント

タイトルとURLをコピーしました