メインコンテンツに移動

builders.flash

デベロッパーのためのクラウド活用方法

Amazon Bedrock ガードレールでゲーム内のキャラクターを彩ろう!

2025-10-02 | Author : 川島 拓海

はじめに

ゲームなみなさんこんにちは!Game Solutions Architect の川島です。

昨今は生成 AI が広く使われており、社内だけでなく社外のユーザーから直接見えるところでも生成 AI が使われる事例が出てきています。しかし、実際に社外のユーザーに使ってもらおうとすると、開発者が意図しない入力にも対応する必要があります。なかには悪意のあるプロンプトを入力してくるユーザーがいるかもしれません。そんなときに使えるのが、Amazon Bedrock のガードレール機能です。生成 AI の基盤モデルをホストするサービス Amazon Bedrock で適用できるガードレール機能を使うことで、AI の出力を適切に制御できます。

この記事では、ゲーム内のキャラクターが喋るチャットボットにおいて Amazon Bedrock を利用します。そして、キャラクターが設定を守りつつ回答を返せるよう Amazon Bedrock ガードレールを適用します。ガードレールで制約を設け、キャラクターが不適切な発言をするのを防止することで、生成 AI を利用した幅広い回答を安心してユーザーにお届けできるようになるわけです。

「3. キャラクターを作成する」 からは Unity で Amazon Bedrock 上の生成 AI をチャットに利用したゲームを作ります。そして、「6. Amazon Bedrock ガードレールの設定」からはそのアプリケーションに Amazon Bedrock ガードレールを導入します。それでは内容を見ていきましょう !


X ポスト »
| Facebook シェア » | はてブ »

ご注意

本記事で紹介する AWS サービスを起動する際には、料金がかかります。builders.flash メールメンバー特典の、クラウドレシピ向けクレジットコードプレゼントの入手をお勧めします。

*ハンズオン記事およびソースコードにおける免責事項 »

builders.flash メールメンバー登録

builders.flash メールメンバー登録で、毎月の最新アップデート情報とともに、AWS を無料でお試しいただけるクレジットコードを受け取ることができます。

今すぐ登録 »

1. Amazon Bedrock ガードレールとは

ガードレールの機能
日本語対応
コンテンツフィルター・プロンプト攻撃

Standard Tier でサポート

トピックによる拒否

Standard Tier でサポート

単語フィルター

対応していない

機密情報フィルター

サポートされており最適化されている

コンテキストグラウンディングチェック

対応していない

Amazon Bedrock ガードレールとは、生成 AI の入出力を制御するためのポリシーを設定できる機能です。企業が生成 AI を利用する際、セキュリティやコンプライアンス、ブランドの一貫性などを確保するために、AI の出力を適切に制御する必要があります。ガードレールは、これらの制御を簡単に実装できるようにする仕組みを提供します。

一般的なユースケースとしては、機密情報の漏洩防止、不適切な表現の制限、特定のトピックに関する回答の制御など、様々なポリシーを設定することができます。従来はこれらの制御を個別に実装する必要がありましたが、ガードレールを使用することで、統一された方法で制御ポリシーを管理できるようになりました。

Amazon Bedrock ガードレールは従来、英語・フランス語・スペイン語のサポートのみでした。しかし、2025 年 6 月に 60 の幅広い言語のサポートが追加され、日本語も多くの機能のサポート対象に含まれるようになりました。2025 年 7 月現在、Amazon Bedrock ガードレールが提供する機能と日本語のサポートの状態は下のようになっています。サポートされる言語の詳細はこちらのドキュメントをご覧ください。なお、それぞれの機能の詳細は、実際に設定を行う6. Amazon Bedrock ガードレールの設定 の章で説明しています。

2. 今回利用するサービスの前準備

このブログでは、ゲームエンジンとして Unity を使います。Unity に関する前提知識の説明は省きますので、ご了承ください。ただし、ご自身のよく使っているゲームエンジンがあれば、そのゲームエンジンで利用できるコード定義を介して同様のサービスを作成することは可能です。

Amazon Bedrock の利用するための前準備は、前回の builders.flash の「Amazon Bedrock の利用開始方法」の章をご覧ください。

今回、モデルとして Amazon Bedrock 上の Amazon Nova Premier を利用します。Amazon Nova Premier は、Amazon Nova シリーズのテキスト・画像・動画を理解するマルチモーダルモデルのなかで最も複雑なタスクの実行に適したものです。リージョンは北バージニアリージョン (us-east-1) を利用していますが、Amazon Bedrock ガードレールは東京リージョン (ap-northeast-1) でもご利用いただけます。ご自身で試す際には好きなリージョンをお使いください。

Amazon Bedrock 以外で、本ブログ中で主に使うサービスは下記です。Amazon API Gateway および AWS Lambda は、Amazon Q Developer によって自動で書かれた AWS CDK 内で定義されています。そのため本ブログ中では CDK コードの紹介のみに留めます。

  • Amazon API Gateway
    API の作成・運用ができるサービス

  • AWS Lambda : 
    サーバーを意識せずにコードを実行できるサービス

  • AWS CDK
    - コードでインフラストラクチャを定義できるサービス
    - TypeScript/JavaScript/Python/Java/C#/Go がファーストクラスでサポートされている
    - 今回は Python を利用
また、ブログ中では下記サービスにも触れています。特に Amazon Q Developer は、今回のような内容を簡単に作りたいときに有用な生成 AI のコーディングサービスです。しかし、これらのサービスについては理解していなくても、本ブログを読むのに差し障りはありません。
 
  • Amazon Q Developer
    - ソフトウェア開発の生成 AI アシスタント
    - 今回はブログ中の各種コードの生成に利用

  • Amazon CloudWatch
    - AWS 上のリソースのオブザーバビリティを提供するサービス
    - 今回は AWS Lambda のログを覗く際に少し利用

  • AWS CloudFormation
    - コードでインフラストラクチャを定義できるサービス
    - CDK のリソース作成時には内部的に CloudFormation でリソースが作成される

3. キャラクターを作成する

ここからいよいよ実装です。今回は、ゲームエンジン「Unity」のなかで、架空のゲームの一画面を作成します。ゲームのアプリケーションを開いたホーム画面にキャラクターがいて何か喋っている様子を想像してみてください。生成 AI を用いてキャラクターと対話できるチャットボットを作成するべく、まずはそのキャラクターを作っていきましょう。

今回想定するゲームは理工学系の知識を使って進めるゲームで、そのゲーム内のキャラクターとして、理工学系の知識を幅広く持っているアルパカの研究員を登場させることにします。このゲームやキャラクターの設定は私の思いつきであり、もちろんどんなキャラクターでも大丈夫です。早速、Amazon Nova Canvas に画像を作成してもらいます。アニメ調のイラストで、白衣を着ている二足歩行のアルパカの作成を依頼したら、こちらの画像が出ました。今回はデモ用にこの画像をそのまま使います。

Missing alt text value

アルパカのキャラクター性を決める

続いてこのアルパカのキャラクター性を決めておきましょう。上に述べた通り、理工学系の詳しい知識を持っていることは必須です。また、アルパカなのでやはり語尾は「パカ」であってほしいものです。せっかくですからちょっと変わった口癖もつけたいので、「おもんパカる(慮る)」という動詞をよく使うようにしておきましょう。

最終的にはこのキャラクター性に合った発言を Amazon Bedrock 上の生成 AI に出力させたいので、設定に合うような出力をするようプロンプトを書いてみます。このプロンプトは、後で使うので取っておいてください。
 

あなたは賢明なアルパカのキャラクターです。理工学分野の博士号を持っており、常に冷静に質問に回答します。回答は 2-3 文で、「です」「ます」は使いません。全ての文の語尾は必ず「パカ」とし、質問では語尾が「パカ?」になります。また、口癖として、「思う」「考える」などの動詞の代わりに「おもんパカる」を頻繁に使います。「おもんパカる」を使う場合も語尾は「パカ」になります。\n\n

プロンプトを Amazon Bedrock で設定

これでキャラクターの準備は完了です。このプロンプトを Amazon Bedrock のマネジメントコンソールで入力すると、設定した通りに答えを返してくれることが分かります。必要に応じて、ここでプロンプトを調整してみてください。

Missing alt text value

4. ゲームを動かすコードの作成

まずはゲームのコードを作成するよう、Amazon Q Developer とチャットをしながら対話的にソースコードを生成してもらいます。クライアント側は Unity 上で動く C# のコードで定義します。一方サーバー側は、Amazon API Gateway と AWS Lambda を組み合わせた構成を作り、AWS Lambda 経由で Amazon Bedrock にアクセスさせます。

4-1. サーバーサイドの作成

まず、サーバー側を定義する AWS CDK のコードは下記のようになります。

python
from aws_cdk import (
    Duration,
    Stack,
    aws_lambda as _lambda,
    # api gateway
    aws_apigateway as _apigw,
    aws_iam as iam,
    aws_logs as logs,
    # aws_sqs as sqs,
)
from constructs import Construct

class CdkStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # Lambda 実行ロールを定義
        lambda_role = iam.Role(
            self, "LambdaExecutionRole",
            assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"),
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaBasicExecutionRole")
            ],
            inline_policies={
                "BedrockAccess": iam.PolicyDocument(
                    statements=[
                        iam.PolicyStatement(
                            actions=[
                                "bedrock:InvokeModel"
                            ],
                            resources=["*"]  # You might want to restrict this to specific model ARNs
                        )
                    ]
                )
            }
        )

        # Lambda を定義
        lambda_function = _lambda.Function(
            self, "Lambda",
            runtime=_lambda.Runtime.PYTHON_3_9,
            code=_lambda.Code.from_asset("lambda"),
            handler="index.handler",
            role=lambda_role,
            environment={
                "BEDROCK_MODEL_ID": "us.amazon.nova-premier-v1:0",  # Replace with your custom model ID
            },
            timeout=Duration.seconds(30)  # Increased timeout for model inference
        )

        # API Gateway のログ用 IAM ロールを定義
        api_logging_role = iam.Role(
            self, "ApiGatewayLoggingRole",
            assumed_by=iam.ServicePrincipal("apigateway.amazonaws.com"),
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AmazonAPIGatewayPushToCloudWatchLogs")
            ]
        )

        # API Gateway を定義
        api = _apigw.RestApi(
            self, "Apigw",
            deploy_options=_apigw.StageOptions(
                stage_name="prod",
                logging_level=_apigw.MethodLoggingLevel.INFO,  # ログレベルを INFO に設定
                access_log_destination=_apigw.LogGroupLogDestination(
                    logs.LogGroup(self, "ApiGatewayAccessLogs", retention=logs.RetentionDays.ONE_WEEK)
                ),
                access_log_format=_apigw.AccessLogFormat.clf()  # Common Log Format を使用
            ),
            cloud_watch_role=True  # CloudWatch ロールを有効化
        )
        
        # alpaca リソースを作成
        alpaca = api.root.add_resource("alpaca")
        
        # GET メソッドを追加し、text パラメータを設定
        alpaca.add_method(
            "GET",
            _apigw.LambdaIntegration(lambda_function),
            request_parameters={
                "method.request.querystring.text": True  # Required parameter
            }
        )

API Gateway には alpaca というリソースを作り、GET メソッドでユーザーからの入力を受け付けるようにしています。最終的には Amazon Bedrock ガードレールを利用しますが、この段階ではまだ CDK に定義を含めていません。そのため、この CDK は仮版です。

index.py 関数を作成

続いて AWS Lambda の中身の関数 index.py も同時に作っておきます。前の章で用意したキャラクター設定のプロンプトとユーザーからのプロンプトを組み合わせて、Amazon Bedrock 上の Amazon Nova にリクエストを送っています。こちらも CDK と同じく、Amazon Bedrock ガードレールへのアクセスが含まれない仮版であることにご注意ください。

CDK デプロイ

これが完成したら、上の CDK と合わせて cdk deploy コマンドで自身の CDK をデプロイしましょう。

python
import json
import boto3
import os

# Initialize Bedrock client
bedrock = boto3.client('bedrock-runtime')

# Get the model ID from environment variable or use a default
MODEL_ID = os.environ.get('BEDROCK_MODEL_ID', 'us.amazon.nova-premier-v1:0')  # Cross-region inference profile
GUARDRAIL_ID = os.environ.get('GUARDRAIL_ID', 'GUARDRAIL_ID')  # Guardrail ID
GUARDRAIL_VERSION = os.environ.get('GUARDRAIL_VERSION', '1')  # Guardrail version

def handler(event, context):
    print(f"Lambda function started. Event: {json.dumps(event)}")
    print(f"Context: {context}")
    
    try:
        print("Step 1: Getting query string parameters")
        # Get the query string parameters, defaulting to empty dict if None
        query_params = event.get('queryStringParameters') or {}
        print(f"Query params: {query_params}")
        
        # Get the text parameter
        input_text = query_params.get('text')
        print(f"Text parameter: {input_text}")
        
        # Since text is required in API Gateway config, return 400 if missing
        if input_text is None:
            print("Text parameter is missing, returning 400")
            return {
                'statusCode': 400,
                'headers': {
                    'Content-Type': 'application/json'
                },
                'body': json.dumps({
                    'error': 'Missing required query parameter: input_text'
                })
            }
        
        predefined_instruction = "あなたは賢明なアルパカのキャラクターです。理工学分野の博士号を持っており、常に冷静に質問に回答します。回答は 2-3 文で、「です」「ます」は使いません。全ての文の語尾は必ず「パカ」とし、質問では語尾が「パカ?」になります。また、口癖として、「思う」「考える」などの動詞の代わりに「おもんパカる」を頻繁に使います。「おもんパカる」を使う場合も語尾は「パカ」になります。\n\n"

        text = predefined_instruction + input_text
        
        print("Step 2: Preparing Bedrock request")
        # Prepare the request body for Bedrock (Nova Premier uses Messages API)
        messages = [
            {
                "role": "user",
                "content": [
                    {
                        "text": text
                    }
                ]
            }
        ]
        
        print("Step 3: Calling Bedrock")
        # Call Bedrock with Guardrails
        converse_params = {
            'modelId': MODEL_ID,
            'messages': messages
        }
        
        response = bedrock.converse(**converse_params)
        print(f"Bedrock response received: {type(response)}")
        
        print("Step 4: Processing streaming response")
        # Process the Converse API response
        model_response = ""
        if 'output' in response and 'message' in response['output']:
            content = response['output']['message']['content']
            for item in content:
                if 'text' in item:
                    model_response += item['text']
        
        print(f"Complete model response: {model_response}")
        
        print("Step 5: Returning success response")
        return {
            'statusCode': 200,
            'headers': {
                'Content-Type': 'application/json'
            },
            'body': json.dumps({
                'text': model_response
            })
        }
        
    except Exception as e:
        print(f"ERROR: Exception occurred: {str(e)}")
        print(f"ERROR: Exception type: {type(e)}")
        import traceback
        print(f"ERROR: Traceback: {traceback.format_exc()}")
        return {
            'statusCode': 500,
            'headers': {
                'Content-Type': 'application/json'
            },
            'body': json.dumps({
                'error': str(e)
            })
        }

この方法は本ブログでは割愛しますが、こちらのドキュメント や各種解説ブログを参考にしてください。初期設定さえしてしまえば、 cdk deploy コマンドだけでリソースの作成・変更・削除が簡単にできるようになります。

AWS Lambda のコードの説明

なお、上の AWS Lambda のコード中には print 文が多く含まれています。

Amazon Q Developer に最初コードを出力させた際には、モデル ID の指定や invoke_model リクエストの形式に間違いが含まれていたためエラーが出ました。その際にエラーが出たことを伝えれば Amazon Q Developer がノーヒントで修正してくれることもありますが、今回はログを出力させてそのログを Amazon Q Developer にチャットで共有しながらエラーを修正しました。そのエラーログを出すため、print 文を Amazon Q Developer に挿入してもらった結果がこのコードです。

print 文からの出力は Amazon CloudWatch Logs から確認できます。画像の赤枠で囲っている箇所は特にエラーを示すログです。このようなエラーを取り除いていったのが上の AWS Lambda のファイルです。

Missing alt text value

4-2. クライアントサイドの作成

クライアント側のコードは下記のようになります。alpaca リソースの GET メソッドにアクセスするように、API Gateway へのリクエストも定義しています。

Amazon API Gateway にデプロイされた URL を参照

この C# コードで定義されている SerializeField にはゲーム画面上の UI オブジェクトを入力する必要があるので、この後の「5. Unity 上のゲーム画面を作成する」の章では入力作業を行います。

また、その際には apiUrl 変数の入力も必要です。CDK で作成された API Gateway のリソースを参照して、URL を今のうちにメモしておきましょう。

参照方法はいくつかありますが、今回は CDK 経由から作成された AWS CloudFormation スタックの出力を参照します。マネジメントコンソールの AWS CloudFormation の画面で対象のスタックを開くと、画像の黒塗りの部分に URL が書かれています。もちろん、API Gateway にデプロイされた URL そのものをマネジメントコンソールから確認してもいいです。

Missing alt text value

5. Unity 上のゲーム画面を作成する

それではこの章では、Unity 上でゲームの画面を作成します。「3. キャラクターを作成する」で作成したキャラクターの画像とともに、入力項目、出力項目、送信ボタンを作成します。

今回は、ゲームエンジン「Unity」のなかで、架空のゲームの一画面を作成します。このとき、「4. ゲームを動かすコードの作成」で作成したクライアントサイドのファイルをいずれかのオブジェクトにアタッチして SerializeField を埋めてください。また、送信ボタンをクリックしたら SendRequest() 関数を呼ぶ EventSystem も忘れずに追加してください。

Missing alt text value

アルパカと会話してみる

ここまでできたらゲームの完成です!このアルパカのキャラクターと自由に会話してみましょう!

入力画面にテキストを入力して送信ボタンを押せば、答えが返ってきます。

Missing alt text value

ゲームホーム画面を公開

さて、早速これをゲームのホーム画面としてユーザーに公開しましょう。きっと、ユーザーは喜んでこのアルパカと会話してくれることでしょう。

しかし、ちょっと待ってください。ユーザーによっては、開発者の意図しない不適切な入力を送信することもありえます。一般のユーザーに公開するということは、そのような危険性を孕むものです。

例えば、少し危険な質問をしてみます。この場合は、Amazon Nova 自体が質問を拒否する性質を持っていました。とはいえ、必ずしもどのような質問もこのように拒否してくれるとは限りません。危険な質問を拒否する性能はそれぞれのモデルに依存しており、不十分な点があればプロンプトなどで個別に対処する必要があります。

Missing alt text value

ゲームキャラクターの世界観を保つ必要性

また、キャラクター性を損ないかねない質問も好ましくありません。例えば、文系学問についてこのキャラクターは詳しくない方が世界観を保てるとした場合、文系の学問に関連する質問は拒否してほしいです。また、語尾を変えるようなプロンプトを入力されるのも問題です。しかし現状では、このような質問に対してキャラクターはほとんどそのまま回答してしまいます。ここまで見てきた問題は、プロンプトエンジニアリングで個別に対処できる場合もあります。しかし、個別にプロンプトで拒否していくのは大変です。この問題を解決するために、Amazon Bedrock ガードレール機能を次の章から導入します。

Missing alt text value
Missing alt text value

6. Amazon Bedrock ガードレールの設定

ユーザーの不適切な入力に対して出力を頑強するための一つの方法として、プロンプトで細かく指示する方法があります。しかし、不適切な入力と言ってもその内容は様々で、そのすべてに対応しようとするとモデルごとの特性を見極めた上でプロンプト内で網羅的に対処する必要があります。Amazon Bedrock ガードレールを使えば、不適切な入力をまとめて弾くこともできます。

ガードレールを設定

今回は、AWS マネジメントコンソール上で Amazon Bedrock ガードレールを定義していきましょう。マネジメントコンソールで Amazon Bedrock を開き、左側から「ガードレール」をクリックします。この画面では今あるガードレールの一覧が見えます。右側の「ガードレールを作成」というオレンジのボタンをクリックして、設定画面に遷移しましょう。ここから、下記の項目を順番に設定していきます。

  • ガードレールの詳細
  • コンテンツフィルターの設定
  • 拒否するトピックの追加
  • ワードフィルターの追加
  • 機密情報の追加
  • コンテキストグラウンディングのチェック
Missing alt text value

6-1. ガードレールの詳細

まずはステップ 1 の「ガードレールの詳細を提供」です。まず、名前・説明は分かりやすいものを入力してください。
 
「ブロックされたプロンプトのメッセージ表示」を設定することで、ブロックしたい内容が入出力に含まれた場合は一括で同じメッセージを返せます。今回はこのキャラクターらしい返答内容にしておきます。なお、「応答に同じブロックメッセージを適用します」にチェックを入れると、ユーザーからのプロンプトが不適切な場合にも、モデルの回答が不適切な場合にも同様の回答を返します。今回はチェックを入れましたが、それぞれで別のメッセージを返したい場合はこのチェックを外してそれぞれのメッセージを定義してください。
 
Cross-Region Inference は、日本語にも対応した Standard Tier を利用する場合には必須です。このチェックボックスも埋めておいてください。ここまで設定できたら、右下のオレンジ色の「次へ」をクリックします。
Missing alt text value

6-2. コンテンツフィルターの設定

ここからのステップでは、Amazon Bedrock ガードレールの挙動の詳細を定義していきます。ステップ 2 の「コンテンツフィルターの設定」では、内容に応じた不適切な入出力のフィルタリングを設定します。

まず「有害カテゴリ」のトグルボタンをオンにすると、「憎悪」「侮辱」「性的」「暴力」「不正行為」のそれぞれについての挙動を決められます。右側のバーが「高」に近づくほど、そのトピックに関連する文章がブロックされる可能性が高まります。今回はすべて「高」の設定にしておきます。

また、「Guardrail action」は「Block」と「Detect」があり、Detect にした場合は応答を返さずに検出のみ行います。ユーザー体験は変えずにまずは検出の動作を確認したいという場合は、Detect にしておくのがおすすめです。今回は Block にしておきます。

さらに、モデルの応答に異なるフィルタリングを適用したい場合は「応答に同じ有害カテゴリのフィルターを使用します」のチェックボックスを外すことで個別に設定できます。今回はこのチェックボックスはそのままにしておきます。

Missing alt text value

プロンプト攻撃とコンテンツフィルターの設定

続いてその下の「プロンプト攻撃」の設定です。ここでは、ユーザーがモデルに対して嘘の役割や命令を与えるような入力を制限できます。「プロンプト攻撃フィルターを有効にする」のトグルボタンをオンにすると、上と同様にプロンプト攻撃の Guardrail action や閾値が設定できます。

そのあと、「Content filter tier」を Standard に設定します。ここを設定しないと日本語に対応できないので、ご注意ください。また、前のステップで Cross-Region Inference の設定をしていないとここでエラーが出るので、その場合は前のステップに戻って確認してください。

ここまで設定できたら、右下のオレンジ色の「次へ」のボタンを押します。

Missing alt text value

6-3. 拒否するトピックの追加

次のステップ 3 では、モデルが拒否すべき話題を設定します。今回のゲームは理工学の知識を中心とするものなので、文系学問に関する質問は答えないようにここで設定しておきましょう。まず、「拒否されたトピックを追加」をクリックしてください。

するとポップアップが出てくるので、内容を入力していきます。名前は分かりやすいものにしたうえで、定義に拒否するトピックを追加します。今回は、「歴史学・文学・哲学・語学など、理系・文系で学問を分けたうちの文系の知識に関する質問」としました。入力と出力それぞれの挙動を決められますが、今回はどちらもブロックするようにしておきます。その下ではサンプルフレーズとして拒否すべき例文を追加できます。

デプロイ後にうまくブロックできないと感じた場合は、ここで例文を追加していくといいでしょう。今回は一文だけ「紫式部はどんなことをした人ですか?」と入力しておきます。ここまでできたら、右下のオレンジ色の「Confirm」をクリックして元の画面に戻ります。

Missing alt text value

拒否フィルターの設定

先ほど定義した拒否トピックがあることを確認したうえで、前のステップと同様に「Denied topics tier」を Standard にして日本語に対応できるようにしておきます。そして右下のオレンジ色の「次へ」のボタンをクリックします。

Missing alt text value

6-4. ワードフィルターの追加

このステップでは単語レベルのフィルタリングを行います。「冒涜的な表現フィルター」で一般的に不適切な単語をブロックすると同時に、独自に拒否したい単語やフレーズも追加できます。しかし、1. Amazon Bedrock ガードレールとは で触れた通り、2025 年 7 月現在、この仕様は日本語に対応していません。そのため今回は、念のため「冒涜的な表現フィルター」にチェックだけ入れたうえで、右下のオレンジ色の「次へ」をクリックしましょう。

Missing alt text value

6-5. 機密情報の追加

このステップでは機密情報の検出を設定します。これにより、住所や名前などの機密情報のブロックができるようになります。ブロックできる機密情報の一覧はこちらのドキュメントをご覧ください。

こちらの画像では、右上の「新しい PII を追加」から、2025 年 7 月現在対応している 31 種類の機密情報をすべてブロックするように設定しました。機密情報のみマスクすることもできますし、ユーザー体験は変更せずに検出だけすることもできます。こちらの画像では設定していませんが、正規表現で機密情報を検出・拒否することもできます。設定が完了したら、右下のオレンジ色の「次へ」をクリックしてください。

Missing alt text value

6-6. コンテキストグラウンディングのチェック

このステップが最後の設定項目です。ここでは、モデルの応答が正しい論拠に基づいているか、ユーザーの質問の意図に沿っているかを確認できます。ただし、「6-4. ワードフィルターの追加」と同様に、「1. Amazon Bedrock ガードレールとは」で触れた通り、この設定は日本語に対応していません。

こちらの画像ではいずれもトグルボタンをオンにしていますが、特に設定は必要ありません。右下のオレンジ色の「次へ」をクリックしてください。

Missing alt text value

6-7. 確認と作成

最後のステップでは、これまで行った設定を確認できます。一番下までスクロールして、問題なければ右下のオレンジ色の「ガードレールを作成」をクリックしてください。

Missing alt text value

ガードレールの挙動を確認

作成後の画面の右側では、このガードレールの挙動を確認できます。赤枠で囲った、応答に利用するモデルとプロンプトを入力したうえで右下のオレンジ色の「実行」を押すと、ガードレールを適用した後の回答が得られます。

こちらの画像では文系学問に関連する質問をしたので、最終応答がガードレールを適用した結果になっています。「トレースを表示」をクリックすることで、ガードレールが内部的にどのような挙動をしたのかが確認できます。

Missing alt text value

6-8. バージョンの作成

ここまででガードレールの設定ができましたが、これを本番にデプロイするためにはバージョンを作成する必要があります。ガードレール作成完了後に表示されるガードレールの詳細画面から、下部の「バージョンを作成」をクリックします。するとポップアップが表示されるので、説明を入力して「バージョンを作成」をクリックしてください。

Missing alt text value

バージョン表示

すると、下部のバージョン一覧に現在作ったバージョンが表示されます。今回初めて作ったのでバージョンは「1」です。このバージョン名と同時に、上部の「ID」をメモしておいてください。この 2 つはガードレールを参照するためにこの後の章で利用します。

Missing alt text value

7. Amazon Bedrock ガードレールのコードからの利用

この章では、先ほど作ったガードレールを今あるバックエンドに統合します。CDK と Lambda 関数の中身を変更していきます。Amazon Q Developer を利用しつつ、ソースコードを書き換えていきます。

7-1. CDK の変更

まずは CDK の変更です。とはいっても、変更する箇所は大きくは 2 つです。

  • AWS Lambda の実行ロールに bedrock:ApplyGuardrai の権限を渡す
  • AWS Lambda に 2 種類の環境変数を足す

    - GUARDRAIL_ID

    - GUARDRAIL_VERSION        

ソースコードの書き換え

実行ロールの権限は、ガードレールを利用する際に必要なものです。環境変数には、ご自身の作成したガードレールの ID およびバージョンを入力してください。書き換えたあとのソースコードは下記です。

python
from aws_cdk import (
    Duration,
    Stack,
    aws_lambda as _lambda,
    # api gateway
    aws_apigateway as _apigw,
    aws_iam as iam,
    aws_logs as logs,
    # aws_sqs as sqs,
)
from constructs import Construct

class CdkStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # Lambda 実行ロールを定義
        lambda_role = iam.Role(
            self, "LambdaExecutionRole",
            assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"),
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaBasicExecutionRole")
            ],
            inline_policies={
                "BedrockAccess": iam.PolicyDocument(
                    statements=[
                        iam.PolicyStatement(
                            actions=[
                                "bedrock:InvokeModel",
                                "bedrock:ApplyGuardrail"
                            ],
                            resources=["*"]  # You might want to restrict this to specific model ARNs
                        )
                    ]
                )
            }
        )

        # Lambda を定義
        lambda_function = _lambda.Function(
            self, "Lambda",
            runtime=_lambda.Runtime.PYTHON_3_9,
            code=_lambda.Code.from_asset("lambda"),
            handler="index.handler",
            role=lambda_role,
            environment={
                "BEDROCK_MODEL_ID": "us.amazon.nova-premier-v1:0",  # Replace with your custom model ID
                "GUARDRAIL_ID": "GURADRAIL_ID",  # Guardrail ID
                "GUARDRAIL_VERSION": "1"
            },
            timeout=Duration.seconds(30)  # Increased timeout for model inference
        )

        # API Gateway のログ用 IAM ロールを定義
        api_logging_role = iam.Role(
            self, "ApiGatewayLoggingRole",
            assumed_by=iam.ServicePrincipal("apigateway.amazonaws.com"),
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AmazonAPIGatewayPushToCloudWatchLogs")
            ]
        )

        # API Gateway を定義
        api = _apigw.RestApi(
            self, "Apigw",
            deploy_options=_apigw.StageOptions(
                stage_name="prod",
                logging_level=_apigw.MethodLoggingLevel.INFO,  # ログレベルを INFO に設定
                access_log_destination=_apigw.LogGroupLogDestination(
                    logs.LogGroup(self, "ApiGatewayAccessLogs", retention=logs.RetentionDays.ONE_WEEK)
                ),
                access_log_format=_apigw.AccessLogFormat.clf()  # Common Log Format を使用
            ),
            cloud_watch_role=True  # CloudWatch ロールを有効化
        )
        
        # arupaka リソースを作成
        arupaka = api.root.add_resource("arupaka")
        
        # GET メソッドを追加し、text パラメータを設定
        arupaka.add_method(
            "GET",
            _apigw.LambdaIntegration(lambda_function),
            request_parameters={
                "method.request.querystring.text": True  # Required parameter
            }
        )

7-2. Lambda 関数内で入力にガードレールを適用する

続いて Lambda 関数を変更します。

python
import json
import boto3
import os

# Initialize Bedrock client
bedrock = boto3.client('bedrock-runtime')

# Get the model ID from environment variable or use a default
MODEL_ID = os.environ.get('BEDROCK_MODEL_ID', 'us.amazon.nova-premier-v1:0')  # Cross-region inference profile
GUARDRAIL_ID = os.environ.get('GUARDRAIL_ID', 'GUARDRAIL_ID')  # Guardrail ID
GUARDRAIL_VERSION = os.environ.get('GUARDRAIL_VERSION', '1')  # Guardrail version

def handler(event, context):
    print(f"Lambda function started. Event: {json.dumps(event)}")
    print(f"Context: {context}")
    
    try:
        print("Step 1: Getting query string parameters")
        # Get the query string parameters, defaulting to empty dict if None
        query_params = event.get('queryStringParameters') or {}
        print(f"Query params: {query_params}")
        
        # Get the text parameter
        input_text = query_params.get('text')
        print(f"Text parameter: {input_text}")
        
        # Since text is required in API Gateway config, return 400 if missing
        if input_text is None:
            print("Text parameter is missing, returning 400")
            return {
                'statusCode': 400,
                'headers': {
                    'Content-Type': 'application/json'
                },
                'body': json.dumps({
                    'error': 'Missing required query parameter: input_text'
                })
            }
        
        predefined_instruction = "あなたは賢明なアルパカのキャラクターです。理工学分野の博士号を持っており、常に冷静に質問に回答します。回答は 2-3 文で、「です」「ます」は使いません。全ての文の語尾は必ず「パカ」とし、質問では語尾が「パカ?」になります。また、口癖として、「思う」「考える」などの動詞の代わりに「おもんパカる」を頻繁に使います。「おもんパカる」を使う場合も語尾は「パカ」になります。\n\n"

        text = predefined_instruction + input_text
        
        print("Step 2: Preparing Bedrock request")
        # Prepare the request body for Bedrock (Nova Premier uses Messages API)
        messages = [
            {
                "role": "user",
                "content": [
                    {
                        "text": text
                    }
                ]
            }
        ]
        
        print("Step 3: Calling Bedrock")
        # Call Bedrock with Guardrails
        converse_params = {
            'modelId': MODEL_ID,
            'messages': messages
        }
        
        # Add Guardrails if configured
        if GUARDRAIL_ID:
            answer = bedrock.apply_guardrail(
                guardrailIdentifier=GUARDRAIL_ID,
                guardrailVersion=GUARDRAIL_VERSION,
                source='INPUT',
                content=[
                    {
                        'text': {
                            'text': input_text,
                            'qualifiers': [
                                'guard_content'
                            ]
                        }
                    },
                ]
            )

            print(f"Using Guardrail: {GUARDRAIL_ID} version {GUARDRAIL_VERSION}")

            if len(answer['outputs']) > 0:
                return {
                    'statusCode': 200,
                    'headers': {
                        'Content-Type': 'application/json'
                    },
                    'body': json.dumps({
                        'text': answer['outputs'][0]['text']
                    })
                }
        
        response = bedrock.converse(**converse_params)
        print(f"Bedrock response received: {type(response)}")
        
        print("Step 4: Processing streaming response")
        # Process the Converse API response
        model_response = ""
        if 'output' in response and 'message' in response['output']:
            content = response['output']['message']['content']
            for item in content:
                if 'text' in item:
                    model_response += item['text']
        
        print(f"Complete model response: {model_response}")
        
        print("Step 5: Returning success response")
        return {
            'statusCode': 200,
            'headers': {
                'Content-Type': 'application/json'
            },
            'body': json.dumps({
                'text': model_response
            })
        }
        
    except Exception as e:
        print(f"ERROR: Exception occurred: {str(e)}")
        print(f"ERROR: Exception type: {type(e)}")
        import traceback
        print(f"ERROR: Traceback: {traceback.format_exc()}")
        return {
            'statusCode': 500,
            'headers': {
                'Content-Type': 'application/json'
            },
            'body': json.dumps({
                'error': str(e)
            })
        }

まずは、ユーザーの入力が正しいか確認すべく、ガードレールを単体で使いましょう。この際には、apply_guardrail という関数が使えます。Amazon Q Developer と対話しながら作成したコードは下記です。主な変更点は if GUARDRAIL_ID 節の中で、ここでガードレールが不適切な入力を検知した場合に回答を返すようにしています。

CDK デプロイ後、ガードレールの挙動を確認

ここまでで cdk deploy を行うと、ゲーム画面からの不適切な入力に対してキャラクターがガードレールを適用した回答を返してくれるようになります。

Missing alt text value

7-3. Lambda 関数内で入出力全体にガードレールを適用する

python
import json
import boto3
import os

# Initialize Bedrock client
bedrock = boto3.client('bedrock-runtime')

# Get the model ID from environment variable or use a default
MODEL_ID = os.environ.get('BEDROCK_MODEL_ID', 'us.amazon.nova-premier-v1:0')  # Cross-region inference profile
GUARDRAIL_ID = os.environ.get('GUARDRAIL_ID', 'GUARDRAIL_ID')  # Guardrail ID
GUARDRAIL_VERSION = os.environ.get('GUARDRAIL_VERSION', '1')  # Guardrail version

def handler(event, context):
    print(f"Lambda function started. Event: {json.dumps(event)}")
    print(f"Context: {context}")
    
    try:
        print("Step 1: Getting query string parameters")
        # Get the query string parameters, defaulting to empty dict if None
        query_params = event.get('queryStringParameters') or {}
        print(f"Query params: {query_params}")
        
        # Get the text parameter
        input_text = query_params.get('text')
        print(f"Text parameter: {input_text}")
        
        # Since text is required in API Gateway config, return 400 if missing
        if input_text is None:
            print("Text parameter is missing, returning 400")
            return {
                'statusCode': 400,
                'headers': {
                    'Content-Type': 'application/json'
                },
                'body': json.dumps({
                    'error': 'Missing required query parameter: input_text'
                })
            }
        
        predefined_instruction = "あなたは賢明なアルパカのキャラクターです。理工学分野の博士号を持っており、常に冷静に質問に回答します。回答は 2-3 文で、「です」「ます」は使いません。全ての文の語尾は必ず「パカ」とし、質問では語尾が「パカ?」になります。また、口癖として、「思う」「考える」などの動詞の代わりに「おもんパカる」を頻繁に使います。「おもんパカる」を使う場合も語尾は「パカ」になります。\n\n"

        text = predefined_instruction + input_text
        
        print("Step 2: Preparing Bedrock request")
        # Prepare the request body for Bedrock (Nova Premier uses Messages API)
        messages = [
            {
                "role": "user",
                "content": [
                    {
                        "text": text
                    }
                ]
            }
        ]
        
        print("Step 3: Calling Bedrock")
        # Call Bedrock with Guardrails
        converse_params = {
            'modelId': MODEL_ID,
            'messages': messages
        }
        
        # Add Guardrails if configured
        if GUARDRAIL_ID:
            converse_params['guardrailConfig'] = {
                'guardrailIdentifier': GUARDRAIL_ID,
                'guardrailVersion': GUARDRAIL_VERSION
            }
            print(f"Using Guardrail: {GUARDRAIL_ID} version {GUARDRAIL_VERSION}")
        
        response = bedrock.converse(**converse_params)
        print(f"Bedrock response received: {type(response)}")
        
        print("Step 4: Processing streaming response")
        # Process the Converse API response
        model_response = ""
        if 'output' in response and 'message' in response['output']:
            content = response['output']['message']['content']
            for item in content:
                if 'text' in item:
                    model_response += item['text']
        
        print(f"Complete model response: {model_response}")
        
        print("Step 5: Returning success response")
        return {
            'statusCode': 200,
            'headers': {
                'Content-Type': 'application/json'
            },
            'body': json.dumps({
                'text': model_response
            })
        }
        
    except Exception as e:
        print(f"ERROR: Exception occurred: {str(e)}")
        print(f"ERROR: Exception type: {type(e)}")
        import traceback
        print(f"ERROR: Traceback: {traceback.format_exc()}")
        return {
            'statusCode': 500,
            'headers': {
                'Content-Type': 'application/json'
            },
            'body': json.dumps({
                'error': str(e)
            })
        }

「7-2. Lambda 関数内で入力にガードレールを適用する」では、apply_guardrail 関数を使ってガードレールの機能を単体で利用しました。ここでは、入出力全体をにガードレールを適用すべく、Amazon Bedrock 上のモデルを呼びだす converse 関数の中でガードレールを呼び出します。この場合、書き換えた後の関数は下記のようになります。

入出力全体のガードレールの挙動確認

これで入出力全体にガードレールが適用できました。適切な質問には回答を返し、不適切な質問は拒否しています。

Missing alt text value
Missing alt text value

まとめ

Amazon Bedrock ガードレールを用いたゲーム内のチャットボットを作ることができました。

一般に、生成 AI のアプリケーションを外部に公開することは、柔軟に回答を返せるというメリットがある一方で、その柔軟性の裏返しとして不適切な回答を返す可能性が否めないという問題もあります。しかし、ガードレールを利用することで、自由に回答できる範囲を制限することができ、その制限内で自由にキャラクターを彩ることができるようになります。

実際に外部に公開する際には綿密なアプリケーションのテストと改善が必要ですが、そのサイクルの手助けとして、本ブログで紹介した Amazon Bedrock ガードレールが役に立ちましたら幸いです。

筆者プロフィール

川島 拓海

アマゾン ウェブ サービス ジャパン合同会社
ソリューションアーキテクト

ゲーム業界に特化したソリューションアーキテクトとしてお客様を支援しております。
食べることとお笑いがちょっとだけ好きです。

Missing alt text value