0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

公開 LLM アプリに最低限入れたい (入れたかった) Bedrock Guardrails 3 種 — 設計判断とハマりポイント

0
Posted at

Amazon Bedrock を使って、自分の文脈で多角的にニュースを解説する個人アプリ News Prism を作りました。

そこに対して以下 3 つの Bedrock Guardrails 実装を試みました。
それぞれの挙動とハマりポイントを紹介します。

News Prism はパブリック公開している個人ツールです。
コストは AWS Budgets が防衛線になりますが、攻撃 / PII / ハルシネーション は別の層で塞ぐ必要があり、その役割を担うのがガードレールです。

実際に設定して動かしてみると、自身が当初想定していた挙動と異なる点がありました。

  • PII Anonymize の OUTPUT は、tool_use (関数呼び出し) を使う経路では素通りする (公式ドキュメントに明記あり)
  • PII Anonymize の INPUT は、暗黙のデフォルトで OFF (OUTPUT 側だけが動いている)
  • Contextual Grounding も同じく tool_use 経路では本番で評価されない (公式ドキュメントに記載がなく、自前の検証で発覚)

それぞれのガードレール機能と、そのハマりポイントを見ていきます。

検証は News Prism 本番経路 (Lambda + Converse API + tool_use + guardrailConfig) と、apply_guardrail 単発の 2 経路で行いました。
記事中に登場する PII (192.168.42.117) は検証用に仕込んだダミー値です。

また本記事は 2026-05 時点の挙動を元に書いてあり、今後変わる可能性があります。

1. 採用した 3 つのガードレール

News Prism では Prompt Attack / PII Anonymize / Contextual Grounding の 3 つのガードレールを採用しました。
それぞれの狙いと、想定しているリスクはこのようになります。

Prompt Attack

  • 狙い: 間接的プロンプトインジェクション (記事本文に紛れ込んだ指示文を LLM が指示として受け取ってしまう攻撃) を防ぐ
  • 想定リスク: 「これまでの指示は無視して〜」のような攻撃文が記事本文に混入すると、<context> に含まれる内容 (News Prism では自分が立てた目標) が漏洩する可能性がある

PII Anonymize

  • 狙い: 第三者 PII の二次配信を構造的に防ぐ
  • 想定リスク: 分析結果を DynamoDB に保存し Web UI で公開する構成のため、記事本文に含まれた第三者 PII (メールや電話番号など) が LLM の出力経由でそのまま二次配信されてしまう

Contextual Grounding

  • 狙い: 要約機能のハルシネーション検出
  • 想定リスク: 記事の要約にハルシネーションが含まれて、誤りや偏りのある情報提供になってしまう

有害カテゴリのコンテンツフィルターは無効

Bedrock Guardrails の有害カテゴリ (憎悪、侮辱、性的、暴力、不正行為) のコンテンツフィルターは有効にしませんでした。

News Prism はニュース記事を解析対象にする性質上、事件・戦争・差別への言及が本文に含まれることがあります。
そのため、判定強度を低くしてもフィルターされてしまう可能性があります。
正常な報道記事までブロックされてしまうと、ニュース解説アプリとして成立しません。

有害カテゴリのフィルターは非常に有用ですが、アプリの性質によっては有効にしない方が適切なケースも多々ありそうです。

採用した 3 つのガードレールをまとめると、それぞれ違う種類の脅威を担当する 3 つの守り手で LLM アプリを取り囲んでいるイメージになります。

2. Prompt Attack

プロンプトインジェクション系の入力を弾く、入力側のフィルターです。

コンソール画面

コンソールの「コンテンツフィルター > プロンプト攻撃」設定画面です。

ユーザー入力に対する判定強度を「なし / 低 / 中 / 高」のスライダーで選びます。
News Prism ではインジェクションを強めに弾くため「高」に設定しています。

ガードレールの挙動

News Prism は記事本文を <article> で囲み、guardContentqualifiers=["guard_content"] で wrap して Bedrock に渡しています。

{
  "guardContent": {
    "text": {
      "text": f"<article>\n{article_body}\n</article>",
      "qualifiers": ["guard_content"]
    }
  }
}

<context> (自分が立てた目標) や、ニュース解説の各ペルソナへの指示プロンプトは guardContent に含めずに渡します。
攻撃の入口は記事本文 (= 外部から取り込む入力) なので、評価対象を 記事本文だけ に絞っておくのが安全だ、という設計判断です。

なお guardContent の挙動は all-or-nothing です。
1 つでも置けば、wrap したブロックだけが評価対象になり、wrap していない他のブロックはすべてスキップされます。
逆に 1 つも置かなければ全ブロックが評価対象になります。

本番経路で <article> にインジェクションを仕込んだリクエストを投げると、ニュース解説の各ペルソナ全てが INPUT_BLOCKED になりました。

項目
guardrail_action INPUT_BLOCKED
モデル課金 なし (モデル呼び出しまで進まない)

ブロックされた場合は、そのペルソナだけ「ガードレールがブロックした」と記録し、情報統合や UI 出力など全体の処理は続行します。

ハマりポイント

INPUT_BLOCKED 時は LLM の応答が一切返らない

Prompt Attack で入力がブロックされると、LLM への呼び出し自体が実行されないため、本来返ってくるはずの応答が一切返ってきません。

「LLM の応答は必ず返ってくる」前提で書いたコードは、応答ゼロのケースで例外を投げて落ちます。
INPUT_BLOCKED は応答が無いケースとして明示的に処理する必要があると、最初の実装で踏んでから気が付きました。

3. PII Anonymize

機密情報の匿名化です。
ハマりポイントが 4 つあり、情報量が多いので段落を分けて扱います。

コンソール画面

コンソールから PII type を追加する画面では、INPUT / OUTPUT 両方の Enabled トグルがデフォルトで ON になっています。

そのため、コンソール経由で作成したガードレールは get-guardrail で読み返すと、inputAction / outputAction / inputEnabled / outputEnabled の 4 フィールドが明示で入った状態です。

一方、IaC (Terraform) や API で action だけ書いて作ると、これら 4 フィールドが入らず暗黙のデフォルトに乗っかります (ハマりポイント 2 で扱います)。

ガードレールの挙動

News Prism ではフォーマットが明確な 5 種だけをマスキング対象としました。

  • EMAIL
  • PHONE
  • IP_ADDRESS
  • CREDIT_DEBIT_CARD_NUMBER
  • US_SOCIAL_SECURITY_NUMBER

コンソール上では、登録した PII 5 種が一覧で確認できます (Input / Output Action は全て「マスク」)。

NAME / ADDRESS / USERNAME は OFF にしています。
ニュース記事で著名人名や地名が「[NAME] 氏が [ADDRESS] で発言」のように伏字化されると、記事として読めなくなるためです。

狙いは「PII 保護そのもの」というより「ニュース本文に混入した個人情報の意図しない LLM 再配信を防ぐ」点に絞っています。

ハマりポイント 1: tool_use 経路で OUTPUT が素通り

PII Anonymize の OUTPUT スキャンは、モデルが tool_use (関数呼び出し) で応答する経路では走らない、という公式仕様があります。
News Prism は全ペルソナを tool calling で JSON 出力させる設計なので、OUTPUT 側はそもそも効かない状態でした。

同じ漏洩テキストを 2 経路に投げると挙動が分かれます。

経路 結果
News Prism 本番経路 (Converse + tool_use) guardrail_action: NONE192.168.42.117perspectives.supply_chain_security に生のまま leak
apply_guardrail --source OUTPUT 単発 (同じ guardrail) GUARDRAIL_INTERVENED、5 種 PII すべて ANONYMIZED{IP_ADDRESS} 等のプレースホルダで返却

ガードレールの設定そのものは効いていて、tool_use 経路でだけスキャンが走らない、という構図です。

公式ドキュメントには機密情報フィルターのページにこの 1 行があります。

このフィルターはテキスト出力のみをサポートし、モデルがサポートされている APIs を介して tool_use (関数呼び出し) 出力パラメータで応答する場合、PII 情報を検出しません。

公開 LLM アプリで tool calling を使う構成だと、OUTPUT 側の PII Anonymize は実質効かないことになります。

ハマりポイント 2: 暗黙のデフォルトは OUTPUT のみ

機密情報フィルターを IaC (Terraform) や API 経由で作るとき、action = ANONYMIZE だけ指定すると、暗黙のデフォルトで OUTPUT 側だけが動き、INPUT 側のスキャンは走りません。
INPUT にも効かせたければ inputAction / inputEnabled も明示する必要があります。

コンソールから PII type を追加する画面では INPUT / OUTPUT 両方 Enabled がデフォルトになっているので、UI 経由で作ると踏みません。
IaC ・API ・CLI 経由で作る場合だけ気を付ける必要があります。

同じガードレールで設定を変えて apply_guardrail --source INPUT を叩くと、挙動が分かれます。

設定 結果
action = ANONYMIZE のみ action: NONE
sensitiveInformationPolicy: null (検知ゼロ)
inputAction = ANONYMIZE / inputEnabled = true を追加 GUARDRAIL_INTERVENED
5 種 PII すべて ANONYMIZED{EMAIL} などのプレースホルダで返却

API は INPUT 側にも対応しているのに、action だけ指定すると OUTPUT のみに乗っかる、という構図です。

公式の GuardrailPiiEntityConfig API ref では action が Required、inputAction / inputEnabled / outputAction / outputEnabled は Optional です。
Optional の 4 フィールドのデフォルト値は記載されていません。

実機で get-guardrail を叩かないと挙動が読めない設計なので、INPUT / OUTPUT 双方を明示する運用にしておくのが安全です。
Terraform AWS provider であれば、v6.8.0 (2025-08-07 リリース) 以降で pii_entities_configinput_action / input_enabled を IaC に書けます。

ハマりポイント 3: マイナンバーは PII Anonymize で守れない

GuardrailPiiEntityConfig API ref の type の有効値一覧 には USA / Canada / UK 別の国別 PII は揃っていますが、マイナンバーのような日本固有の PII は存在しません。
例えば日本のニュースアプリでマイナンバーを扱う場合、PII Anonymize 単独では守れないという点を頭に入れておく必要があります。

ハマりポイント 4: input マスクは下流 LLM の解釈にも影響する

INPUT 側で PII をマスキングして LLM に渡すと、下流の LLM はプレースホルダ ({EMAIL} 等) を「マスクされた値」ではなく「そもそも記載されていなかった情報」として解釈することがあります。
ユーザ向けの要約タスクなら、「具体的な連絡先は記事内で未公開」「該当 IP は記事中で未記載」のような表現で出力される可能性があります。

PII Anonymize を INPUT 側に効かせるときは、下流の LLM 出力がどう変わるかをセットで確認しておくと安心です。

4. Contextual Grounding

ハルシネーションを抑えるための、OUTPUT 側の評価フィルターです。
記事要約ペルソナの中立性を保つために適用を試みました。

コンソール画面

グラウンディング (モデルの応答と参照ソースの整合性) と 関連性 (モデルの応答とユーザークエリの関連性) の 2 つのフィルターを、それぞれ閾値で評価する仕組みです。

ガードレールの挙動

News Prism では記事要約ペルソナでのみ意味があるので、別のガードレールを作成して、要約機能専用に設定することにしました。

  • source = <article> 本文
  • response = 記事要約ペルソナの中立要約
  • 閾値 グラウンディング 0.75 / 関連性 0.75

ハマりポイント 1: ["guard_content"] のみだとグラウンディングは走らない (combined qualifier で解決)

ガードレール profile に対して apply_guardrail --source OUTPUT で矛盾する source/response を投げるとグラウンディングはしっかり動きます。
しかし、当初の News Prism 本番経路の要約ペルソナでは guardrail_action: NONE のまま返っていました。

原因は qualifier 設計の差です。
Contextual Grounding は grounding_sourcequery qualifier が付いたブロックが揃っているメッセージでしか評価しません。

Converse API のグラウンディングソースとクエリにマークを付けるには、各ガードコンテンツブロックの修飾子フィールドを使用します。

News Prism は当初、Prompt Attack のために <article>["guard_content"] のみで wrap していたので、grounding_sourcequery もメッセージ中に存在しませんでした。

qualifiers は List 型なので、1 つのブロックに ["guard_content", "grounding_source"] のように複数の qualifier を指定できます。
実機で検証すると、両 qualifier が付いたブロックは Prompt Attack でも Contextual Grounding でも評価対象になります。

qualifier 構成 Prompt Attack Grounding (apply_guardrail)
<article>["guard_content"] のみ ✅ 反応 ❌ 走らない
<article>["guard_content", "grounding_source"] + 別ブロックに ["query"] ✅ 反応 ✅ BLOCKED

News Prism の要約ペルソナへの呼び出しは、この combined qualifier 構造に切り替えました。

ハマりポイント 2: combined qualifier でも本番経路では走らない (tool_use bypass)

combined qualifier 化後、本番経路で確認するとさらに意外な結果が出ました。
同じガードレール、同じ qualifier 構造、同じ source/response でも、apply_guardrail 単発では BLOCKED、News Prism 本番経路では guardrail_action: NONE が返りました。

経路 レスポンスの形式 結果
apply_guardrail --source OUTPUT 単発 テキストブロック GUARDRAIL_INTERVENED、GROUNDING score 0.56 で BLOCKED (threshold 0.75 設定)
News Prism 本番経路
(Converse + tool_use、combined qualifier)
tool_use 出力 guardrail_action: NONEmetadata.trace.guardrail キー自体が空

差分は レスポンスの形式 (テキスト vs tool_use) のみ で、qualifier 設計・profile・threshold は同一です。

つまり Contextual Grounding も tool_use 経路では構造的に素通りする ことが、自環境の検証で確かめられました。
これは PII Anonymize OUTPUT 素通り (章 3 ハマりポイント 1) と同型のトラップです。

公式ドキュメントを再確認すると、PII Anonymize ページには tool_use 経路の素通りが 1 行明記されているのに対し、Contextual Grounding ページには tool_use や function calling への言及はありません (英語版・日本語版とも 2026-05 時点で確認)。
PII で起きるのと同じ trap が Grounding でも起きる、というのは公式ドキュメントには書かれていない情報でした。

これを一般化すると、tool_use を採用する LLM アプリでは OUTPUT 側のフィルター全般が構造的に無効化される 可能性が高い、と読み取れます。

News Prism の設計判断: 機能しないが profile は残す

News Prism は個人ツールで、要約のハルシネーションリスクは温度 0.3 + システムプロンプト + ユーザの目視で十分カバーしていると判断しました。
そのためグラウンディングが本番で機能しないことを承知の上で、combined qualifier 実装と news-prism-grounding profile はそのまま残しています。

理由:

  • combined qualifier 自体は Prompt Attack フィルターの挙動に影響を与えず、将来互換も保てる
  • 将来 tool_use を捨ててテキストレスポンスに戻す場合は、自動でグラウンディングが機能する
  • profile + qualifier 設計の痕跡を残すことで「採用したが構造制約で発火しない」設計判断を後から辿れる

商用グレード (複数ユーザ、信頼性要求) のアプリなら、生成後にテキストブロックとして apply_guardrail に投げ直す post-hoc 補完を実装する選択肢もあります。
追加 API call 1 件 + text unit 課金で、tool_use 経路を保ちつつ OUTPUT 側を補完する仕組みとなります。
News Prism では割に合わないと判断しました。

このメタトラップは、テキストレスポンス経路と tool_use レスポンス経路の対比で見るとよく分かります。

同じガードレールでも、レスポンスの形式によって OUTPUT 側のチェックが効く / 素通りするが分かれる、という構図です。

5. ガードレール 3 種の INPUT / OUTPUT マトリクス

ここまでの設計意図と実態を表に並べて確認します。

機能 INPUT 側 OUTPUT 側
Prompt Attack ✅ 効く (本番経路で 4 ペルソナ INPUT_BLOCKED、課金もなし) 存在しない
PII Anonymize inputAction を明示すれば INPUT スキャンが走る (IaC で記述可)
ただし暗黙 default は OFFaction のみ指定だと OUTPUT のみの評価
❌ tool_use 経由で素通り (公式仕様)
マスク対象に指定した PII が LLM 出力に生のまま漏れた
Contextual Grounding 存在しない ❌ tool_use 経由で素通り
(公式ドキュメントに記載なく、自前の検証で確定)

「設定 (resource config)」と「適用 (API 経路 + qualifier)」を分けて眺めると、自分の構成でどの軸が走っているかをチェックしやすくなります。

まとめ

パブリック公開した個人 LLM アプリに 3 種のガードレール (Prompt Attack / PII Anonymize / Contextual Grounding) を入れた (入れたかった) 話でした。
AWS Budgets の設定だけでは効果がない攻撃 / PII / ハルシネーションの各層を構造的に扱う良い機会だったと思います。

実機検証していくと、tool_use (関数呼び出し) を使う経路では OUTPUT 側のフィルターが構造的に素通りする ことが判明しました。
PII Anonymize OUTPUT (公式ドキュメント明記) も Contextual Grounding (公式ドキュメントに記載なく、自前の検証で発覚) も同じハマりポイントで、tool_use する本番では走りません。
News Prism で実効的に動いているのは INPUT 側の 2 種 (Prompt Attack + PII Anonymize INPUT) に着地しました。

この実装を通じて、ガードレールは「保険」として有効化するだけでは不十分なケースがあると学びました。

  • 自分の経路で apply_guardrail を叩いてスキャンが実際に走っているかの確認
  • それぞれの構造制約を理解した上での設計判断

これらを押さえることで、真に効果を発揮するガードレールを備えられると感じました。

今日も小さな学びを。

News Prism 関連記事

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?