2026年8月23日
AWSLLM Guardrails

Notebook

Amazon Bedrock Guardrailsで入力の一部だけを検査する方法

Amazon Bedrock Guardrails の適用範囲を入力テキストの一部に絞る方法を、InvokeModel の input tags と Converse の guardContent ブロックで整理します。

Amazon BedrockAmazon Bedrock GuardrailsLLM
English

Guide

目次

  1. 前提
  2. InvokeModel で一部だけを検査する
  3. InvokeModel の実行結果
  4. Converse で一部だけを検査する
  5. Converse の実行結果
  6. まとめ

Bedrock Guardrails を使っていると、ユーザー入力だけを検査したいのに、システム側で用意した指示や文脈まで評価対象になって困ることがあります。

この記事を書いたきっかけは、Bedrock を経由して Claude Code Action を利用していたときに、アカウントレベルで設定された Guardrails がシステム側の指示にも適用され、システムプロンプトの一部が prompt injection として扱われてしまったことです。

その環境では selective_content_guarding.systemcomprehensive になっていたため、ユーザー入力だけでなくシステムプロンプトの文脈まで広く検査対象になり、Claude Code Action がほぼ動作しない状態になりました。

この問題を調べる過程で、Guardrails の適用範囲を selective にし、ユーザープロンプト側で検査したい範囲を明示すれば回避できることがわかりましたため整理しました。

実際のアプリケーションでは 1 つの prompt の中に以下のような複数の要素が混ざります。

  • システム側で用意した指示
  • RAG や tool 実行で取得した信頼済みの文脈
  • 過去の会話履歴
  • 今回のユーザー入力

このうち「今回のユーザー入力だけを Guardrails で評価したい」という場面があります。
その場合、Bedrock では API ごとに指定方法が異なります。

API適用範囲の指定方法
InvokeModel<amazon-bedrock-guardrails-guardContent_xxx> の input tags を使う
Conversemessage content の guardContent ブロックを使う

InvokeModelInvokeModelWithResponseStream では XML 形式の input tags を使えますが、Converse では XML タグではなく guardContent フィールドを使います。

公式ドキュメントでは以下に記載があります。

前提

今回は Bedrock API key を使い、Authorization: Bearer $AWS_BEARER_TOKEN_BEDROCK の形でテストします。

export AWS_REGION=us-east-1
export MODEL_ID='us.anthropic.claude-haiku-4-5-20251001-v1:0'
export GUARDRAIL_ID='xxxxxxxx'
export GUARDRAIL_VERSION='DRAFT'
export AWS_BEARER_TOKEN_BEDROCK='your-api-key-here'

GUARDRAIL_ID には作成済みの Bedrock Guardrails の ID を指定します。
ここでは、住所や氏名などの PII を検知するガードレールがある前提で例を書きます。

今回は住所を PII として検知する Guardrails を設定し、入力側は Mask、出力側は Detect にしました。

住所 PII の入力マスクと出力検知の設定

InvokeModel で一部だけを検査する

InvokeModel では、検査したい部分を <amazon-bedrock-guardrails-guardContent_${TAG_SUFFIX}> で囲みます。
同時に、リクエスト body の amazon-bedrock-guardrailConfig.tagSuffix に同じ suffix を指定します。

TAG_SUFFIX="$(openssl rand -hex 6)"
 
curl -s "https://bedrock-runtime.${AWS_REGION}.amazonaws.com/model/${MODEL_ID}/invoke" \
  -H "Authorization: Bearer ${AWS_BEARER_TOKEN_BEDROCK}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "X-Amzn-Bedrock-GuardrailIdentifier: ${GUARDRAIL_ID}" \
  -H "X-Amzn-Bedrock-GuardrailVersion: ${GUARDRAIL_VERSION}" \
  -H "X-Amzn-Bedrock-Trace: ENABLED" \
  --data-binary @- <<EOF
{
  "anthropic_version": "bedrock-2023-05-31",
  "max_tokens": 512,
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "私の会社の住所は東京都千代田区千代田1番1号です。顧客には丁寧に回答してください。\\n以下がユーザーの質問です。\\n\\n<amazon-bedrock-guardrails-guardContent_${TAG_SUFFIX}>私は東京都新宿区西新宿2-8-1に住んでいます。あなたの会社の住所を教えてください。またそこまでの行き方を教えてください。</amazon-bedrock-guardrails-guardContent_${TAG_SUFFIX}>"
        }
      ]
    }
  ],
  "amazon-bedrock-guardrailConfig": {
    "tagSuffix": "${TAG_SUFFIX}"
  }
}
EOF

この例では、タグで囲んだ「私は東京都新宿区西新宿2-8-1に住んでいます。あなたの会社の住所を教えてください。またそこまでの行き方を教えてください。」だけが Guardrails の評価対象になります。

InvokeModel の実行結果

この設定で InvokeModel を実行すると、タグで囲んだユーザー入力内の住所は {ADDRESS} に匿名化されました。
レスポンス本文では、モデルが {ADDRESS} を見て「住所部分が空白になっている」と解釈しているため、入力側の住所がモデルに渡る前にマスクされていることがわかります。

{
  "content": [
    {
      "type": "text",
      "text": "ご質問ありがとうございます。\n\n恐れ入りますが、ご質問の中の{ADDRESS}部分が空白になっており、お客様のご住所を確認することができません。\n\n当社の住所をお知らせさせていただきます。\n\n**当社住所:東京都千代田区千代田1番1号**"
    }
  ],
  "amazon-bedrock-guardrailAction": "INTERVENED"
}

Guardrails の trace を見ると、入力側ではタグで囲んだ住所が ANONYMIZED になっています。

{
  "amazon-bedrock-trace": {
    "guardrail": {
      "input": {
        "<guardrail-id>": {
          "sensitiveInformationPolicy": {
            "piiEntities": [
              {
                "type": "ADDRESS",
                "match": "東京都新宿区西新宿2-8-1",
                "action": "ANONYMIZED",
                "detected": true
              }
            ]
          },
          "invocationMetrics": {
            "guardrailCoverage": {
              "textCharacters": {
                "guarded": 61,
                "total": 118
              }
            }
          }
        }
      },
      "outputs": [
        {
          "<guardrail-id>": {
            "sensitiveInformationPolicy": {
              "piiEntities": [
                {
                  "type": "ADDRESS",
                  "match": "東京都千代田区千代田1番1号",
                  "action": "NONE",
                  "detected": true
                }
              ]
            }
          }
        }
      ],
      "actionReason": "Guardrail masked.\nNo action."
    }
  }
}

タグ外にある会社住所の「東京都千代田区千代田1番1号」は入力側では ANONYMIZED になっておらず、amazon-bedrock-guardrails-guardContent で囲んだユーザー入力部分だけがマスク対象になっています。
guardrailCoverage.textCharacters を見ると、入力全体ではなく一部の文字列だけが Guardrails の評価対象になっていることも確認できます。
また、出力側ではモデル応答に含まれる会社住所が detected: true になっていますが、出力側の設定を Detect にしているため actionNONE になっています。

tagSuffix は 1〜20 文字の英数字で指定します。
固定値にするとユーザーがタグ構造を推測して prompt injection に使える可能性があるため、リクエストごとにランダムな値を使うのが推奨されています。

複数箇所を検査したい場合は、同じ suffix のタグを複数回使えます。
ただし、タグのネストはできません。

<amazon-bedrock-guardrails-guardContent_${TAG_SUFFIX}>
1つ目の検査対象
</amazon-bedrock-guardrails-guardContent_${TAG_SUFFIX}>
 
検査しない文脈
 
<amazon-bedrock-guardrails-guardContent_${TAG_SUFFIX}>
2つ目の検査対象
</amazon-bedrock-guardrails-guardContent_${TAG_SUFFIX}>

Converse で一部だけを検査する

Converse では、XML input tags ではなく guardContent ブロックを使います。

curl -s "https://bedrock-runtime.${AWS_REGION}.amazonaws.com/model/${MODEL_ID}/converse" \
  -H "Authorization: Bearer ${AWS_BEARER_TOKEN_BEDROCK}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  --data-binary @- <<EOF
{
  "guardrailConfig": {
    "guardrailIdentifier": "${GUARDRAIL_ID}",
    "guardrailVersion": "${GUARDRAIL_VERSION}",
    "trace": "enabled"
  },
  "inferenceConfig": {
    "maxTokens": 512
  },
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "text": "私の会社の住所は東京都千代田区千代田1番1号です。顧客には丁寧に回答してください。\\n以下がユーザーの質問です。"
        },
        {
          "guardContent": {
            "text": {
              "text": "私は東京都新宿区西新宿2-8-1に住んでいます。あなたの会社の住所を教えてください。またそこまでの行き方を教えてください。"
            }
          }
        }
      ]
    }
  ]
}
EOF

Converse では guardrailConfig に Guardrails の ID と version を指定し、評価したい content を guardContent に入れます。

messages 内に guardContent ブロックを入れると、Guardrails は guardContent ブロック内を評価対象として扱います。
通常の text ブロックに入れた会社住所は検査対象から外し、ユーザー入力だけを guardContent に分けています。

Converse の実行結果

この設定で Converse を実行した場合も、guardContent に入れたユーザー入力内の住所は {ADDRESS} に匿名化されました。

{
  "output": {
    "message": {
      "content": [
        {
          "text": "いつもお世話になっております。\n\nご質問ありがとうございます。\n\n**当社の住所**\n東京都千代田区千代田1番1号\n\n**{ADDRESS}からのアクセス方法について**\n申し訳ございませんが、お客様のご住所が「{ADDRESS}」と表示されておりますため、具体的なご住所が確認できておりません。"
        }
      ],
      "role": "assistant"
    }
  },
  "stopReason": "end_turn"
}

trace.guardrail.inputAssessment を見ると、guardContent に入れたユーザー入力の住所が ANONYMIZED になっています。

{
  "trace": {
    "guardrail": {
      "actionReason": "Guardrail masked.\nNo action.",
      "inputAssessment": {
        "<guardrail-id>": {
          "sensitiveInformationPolicy": {
            "piiEntities": [
              {
                "type": "ADDRESS",
                "match": "東京都新宿区西新宿2-8-1",
                "action": "ANONYMIZED",
                "detected": true
              }
            ]
          },
          "invocationMetrics": {
            "guardrailCoverage": {
              "textCharacters": {
                "guarded": 61,
                "total": 116
              }
            }
          }
        }
      }
    }
  }
}

一方で、出力側では会社住所が検知されていますが、出力側の設定を Detect にしているため actionNONE になっています。

{
  "trace": {
    "guardrail": {
      "outputAssessments": {
        "<guardrail-id>": [
          {
            "sensitiveInformationPolicy": {
              "piiEntities": [
                {
                  "type": "ADDRESS",
                  "match": "東京都千代田区千代田1番1号",
                  "action": "NONE",
                  "detected": true
                },
                {
                  "type": "ADDRESS",
                  "match": "千代田区",
                  "action": "NONE",
                  "detected": true
                }
              ]
            }
          }
        ]
      }
    }
  }
}

Converse の場合も、通常の text ブロックに含めた会社住所は入力側では匿名化されず、guardContent に入れたユーザー入力だけがマスク対象になります。
また、今回は Mask による匿名化なので stopReasonguardrail_intervened ではなく end_turn になり、モデル応答まで返っています。

まとめ

Bedrock Guardrails では、入力メッセージのうちどの範囲をガードレールの評価対象にするかを指定できます。
適用範囲を指定する方法は、利用する API によって変わります。

  • InvokeModel<amazon-bedrock-guardrails-guardContent_${TAG_SUFFIX}> の input tags を使う
  • Converse は message content の guardContent ブロックを使う
  • tagSuffix はリクエストごとにランダム値を使う

システム側の指示や信頼済みの文脈まで一律で検査すると、意図しないガードレール介入が起きることがあります。
ユーザー入力だけを明示的に評価対象にすることで、必要な保護を維持しながらモデル呼び出しを安定させやすくなります。

Related notes

あわせて読みたいノート