Amazon Bedrock Guardrailsをknowledgebaseやtool use、クエリ拡張で試す。

# はじめに
以前の2つの記事 (「[Amazon Bedrock GuardrailsでDenied topicを試す](https://qiita.com/revsystem/items/fb0bdc117c29de3fe42e)」と「[Amazon Bedrok GuradrailsでSensitive information filters(機密情報フィルター)を試す](https://qiita.com/revsystem/items/b12caae7ac162d29a581)」) では、主にコンソールからAmazon Bedrock Guardrailsのガードレールを試しました。今回は、Pythonのコードからガードレールを試してみます。

# ガードレールを作成する
「[Amazon Bedrok GuradrailsでSensitive information filters(機密情報フィルター)を試す](https://qiita.com/revsystem/items/b12caae7ac162d29a581)」 の記事に書いた"[ガードレールを作成する](https://qiita.com/revsystem/items/b12caae7ac162d29a581#%E3%82%AC%E3%83%BC%E3%83%89%E3%83%AC%E3%83%BC%E3%83%AB%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B)"の手順を参考に、`Sensitive information filters`と`contextual grounding check`を有効にします。

Contextual grounding checkを有効にする場合、Grounding score thresholdとRelevance score thresholdの値を0.5程度にしたほうが良さそうです。デフォルトの0.7だとハルシネーションが起きてないにもかかわらず厳しめに判定されブロックされることがあります。

# 参考情報

https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-contextual-grounding-check.html

https://docs.aws.amazon.com/bedrock/latest/userguide/kb-test-config.html

https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-use-converse-api.html

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime/client/converse.html

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agent-runtime/client/retrieve_and_generate.html

https://qiita.com/ren8k/items/64c4a3de56b886942251

https://qiita.com/revsystem/items/303487a39eea5924187c

# ガードレールを試す

## Converse API

Converse APIを使用してガードレールを利用する場合は、APIのパラメーターにguardrailConfigを設定します。ここにはガードレールIDとバージョンを設定します。レスポンスにトレース情報を得る場合は、traceフィールドをenabledに設定します。

<details><summary>invoke_converse_guardrails.py</summary>

```python:invoke_converse_guardrails.py
import json
import logging
import sys

import boto3
from botocore.exceptions import ClientError

logging.basicConfig(format="%(asctime)s [%(levelname)s] %(message)s", level=logging.INFO)
logger = logging.getLogger(__name__)


def get_bedrock_client():
    return boto3.client(
        service_name="bedrock-runtime",
        region_name="us-east-1"
    )


def generate_response(bedrock_client, model_id, message, guardrail_config, inference_config) -> str:
    """メッセージを生成する"""
    try:
        messages = [
            {
                "role": "user",
                "content": [
                    {
                        "guardContent": {
                            "text": {
                                "text": message,
                            }
                        }
                    }
                ]
            }
        ]

        return bedrock_client.converse(
            messages=messages,
            modelId=model_id,
            inferenceConfig=inference_config,
            guardrailConfig=guardrail_config
        )
    except ClientError as err:
        logger.error("A client error occurred: %s", err.response['Error']['Message'])
        raise
    except Exception as e:
        logger.error("An unexpected error occurred: %s", str(e))
        raise


def main():
    # コマンドライン引数を取得する
    if len(sys.argv) < 2:
        print("Usage: python3 ./invoke_converse_guardrails.py <input_text>")
        sys.exit(1)

    model_id = "anthropic.claude-3-5-sonnet-20240620-v1:0"

    # ガードレールID
    guardrail_id = "ガードレールID"
    # ガードレールバージョン
    guardrail_version = "DRAFT"

    guardrail_config = {
        "guardrailIdentifier": guardrail_id,
        "guardrailVersion": guardrail_version,
        "trace": "enabled"
    }

    # 推論設定
    inference_config = {
        "temperature": 0,
        "maxTokens": 500,
        "topP": 0.99,
    }

    # ユーザー入力
    message = sys.argv[1]

    try:
        bedrock_client = get_bedrock_client()
        response = generate_response(
            bedrock_client,
            model_id,
            message,
            guardrail_config,
            inference_config
        )

        print("Response:" + "\n\n" + json.dumps(response, indent=2, ensure_ascii=False))

    except ClientError as err:
        logger.error("A client error occurred: %s", err.response['Error']['Message'])
        raise
    except Exception as e:
        logger.error("An unexpected error occurred: %s", str(e))
        raise


if __name__ == "__main__":
    main()
```
</details>

### ガードレールが発動するコンテンツが含まれない場合

```console
python ./invoke_converse_guardrails.py "身代金要求型の攻撃手法は何というか。"
```

<details><summary>実行結果</summary>

```console
{
  "ResponseMetadata": {
    "RequestId": "cbd129bb-74ee-454d-a8ed-263456cda843",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Tue, 29 Oct 2024 17:46:43 GMT",
      "content-type": "application/json",
      "content-length": "2146",
      "connection": "keep-alive",
      "x-amzn-requestid": "cbd129bb-74ee-454d-a8ed-263456cda843"
    },
    "RetryAttempts": 0
  },
  "output": {
    "message": {
      "role": "assistant",
      "content": [
        {
          "text": "身代金要求型の攻撃手法は「ランサムウェア」(Ransomware) と呼ばれます。これは「身代金」(Ransom) と「ソフトウェア」(Software) を組み合わせた造語です。\n\nランサムウェアの主な特徴と動作は以下の通りです：\n\n1. 感染方法：\n   - フィッシングメール\n   - 悪意のあるウェブサイト\n   - ソフトウェアの脆弱性を利用\n\n2. 攻撃の流れ：\n   - システムに侵入\n   - ファイルの暗号化\n   - 身代金要求メッセージの表示\n\n3. 主な目的：\n   - 金銭の要求（多くの場合、仮想通貨で支払いを要求）\n\n4. 被害：\n   - データへのアクセス不能\n   - システム機能の停止\n   - 情報漏洩の脅威\n\n5. 対策：\n   - 定期的なバックアップ\n   - セキュリティソフトの最新化\n   - OSやソフトウェアの更新\n   - 不審なメールや添付ファイルを開かない\n\nランサムウェアは個人ユーザーから大企業、政府機関まで幅広いターゲットを狙う深刻な脅威となっています。攻撃者は身代金を支払わせるために、被害者のデータを人質に取るという戦略を取ります。"
        }
      ]
    }
  },
  "stopReason": "end_turn",
  "usage": {
    "inputTokens": 26,
    "outputTokens": 445,
    "totalTokens": 471
  },
  "metrics": {
    "latencyMs": 10503
  },
  "trace": {
    "guardrail": {
      "inputAssessment": {
        "qxug23uqmi11": {}
      },
      "outputAssessments": {
        "qxug23uqmi11": [
          {}
        ]
      }
    }
  }
}
```
</details>

### ガードレールが発動するコンテンツが含まれる場合

```console
python ./invoke_converse_guardrails.py "IPA 独立行政法人 情報処理推進機構の連絡先を教えて。"
```

<details><summary>実行結果</summary>

```
{
  "ResponseMetadata": {
    "RequestId": "ec0344cf-34b9-471d-8102-827ef1b73169",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Tue, 29 Oct 2024 17:47:40 GMT",
      "content-type": "application/json",
      "content-length": "3247",
      "connection": "keep-alive",
      "x-amzn-requestid": "ec0344cf-34b9-471d-8102-827ef1b73169"
    },
    "RetryAttempts": 0
  },
  "output": {
    "message": {
      "role": "assistant",
      "content": [
        {
          "text": "IPA（独立行政法人情報処理推進機構）の主な連絡先情報は以下の通りです：\n\n1. 所在地：\n{IP_ADDRESS}\n{ADDRESS}\n{ADDRESS}\n\n2. 電話番号：\n{PHONE}\n\n3. FAX番号：\n{PHONE}\n\n4. Webサイト：\n{URL}\n\n5. お問い合わせフォーム：\nIPAのウェブサイトには、各種お問い合わせに対応するフォームがあります。\n\n6. 営業時間：\n平日 9:00～17:00（土日祝日、年末年始を除く）\n\n具体的な部署や案件によって連絡先が異なる場合がありますので、詳細については公式ウェブサイトで確認するか、代表番号にお問い合わせいただくのが確実です。"
        }
      ]
    }
  },
  "stopReason": "guardrail_intervened",
  "usage": {
    "inputTokens": 35,
    "outputTokens": 289,
    "totalTokens": 324
  },
  "metrics": {
    "latencyMs": 9487
  },
  "trace": {
    "guardrail": {
      "modelOutput": [
        "{\"id\":\"msg_bdrk_016H2cfBxmkNAxtvRGjMwwj9\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-3-5-sonnet-20240620\",\"content\":[{\"type\":\"text\",\"text\":\"IPA（独立行政法人情報処理推進機構）の主な連絡先情報は以下の通りです：\\n\\n1. 所在地：\\n〒113-6591\\n東京都文京区本駒込2-28-8\\n文京グリーンコートセンターオフィス\\n\\n2. 電話番号：\\n03-5978-7501（代表）\\n\\n3. FAX番号：\\n03-5978-7510\\n\\n4. Webサイト：\\nhttps://www.ipa.go.jp/\\n\\n5. お問い合わせフォーム：\\nIPAのウェブサイトには、各種お問い合わせに対応するフォームがあります。\\n\\n6. 営業時間：\\n平日 9:00～17:00（土日祝日、年末年始を除く）\\n\\n具体的な部署や案件によって連絡先が異なる場合がありますので、詳細については公式ウェブサイトで確認するか、代表番号にお問い合わせいただくのが確実です。\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":35,\"output_tokens\":289}}"
      ],
      "inputAssessment": {
        "qxug23uqmi11": {}
      },
      "outputAssessments": {
        "qxug23uqmi11": [
          {
            "sensitiveInformationPolicy": {
              "piiEntities": [
                {
                  "match": "〒113-6591",
                  "type": "IP_ADDRESS",
                  "action": "ANONYMIZED"
                },
                {
                  "match": "東京都文京区本駒込2-28-8",
                  "type": "ADDRESS",
                  "action": "ANONYMIZED"
                },
                {
                  "match": "文京グリーンコートセンターオフィス",
                  "type": "ADDRESS",
                  "action": "ANONYMIZED"
                },
                {
                  "match": "03-5978-7501（代表）",
                  "type": "PHONE",
                  "action": "ANONYMIZED"
                },
                {
                  "match": "03-5978-7510",
                  "type": "PHONE",
                  "action": "ANONYMIZED"
                },
                {
                  "match": "https://www.ipa.go.jp/",
                  "type": "URL",
                  "action": "ANONYMIZED"
                }
              ]
            }
          }
        ]
      }
    }
  }
}
```
</details>

ガードレールがブロックされたコンテンツを検出すると、レスポンスの stopReasonフィールドにguardrail_intervenedが設定されます。コード内のguardrail_configのtraceフィールドをenabledに設定すると、レスポンスのtraceフィールドにguardrailフィールドが追加されます。今回はガードレールにDenied topicsを設定しなかったため、inputAssessmentフィールドは空のままです。そして、Sensitive information filtersにより、マスクされたコンテンツのテキストが{ADDRESS}、{PHONE}、{URL}となっていることがわかります。なぜか、郵便番号はIP_ADDRESSとしてマスクされました。

GROUNDINGやRELEVANCEのスコアも出力されると期待していましたが、実際は出力されませんでした。後述の[つなげてみる](https://qiita.com/revsystem/items/cbfa54cbcdd0f83a0ac2#%E3%81%A4%E3%81%AA%E3%81%92%E3%81%A6%E3%81%BF%E3%82%8B)の実装のようにConverse APIのsystemとmessageのそれぞれにguardContentパラメーターを設定する必要があるようです。


## RetrieveAndGenerate API

事前に、Bedrockにナレッジベースを作成しておく必要があります。ここでは、「独立行政法人情報処理推進機構（IPA）」のホワイトペーパー「[情報セキュリティ白書2024](https://www.ipa.go.jp/publish/wp-security/2024.html)」のPDF版を使用しナレッジベースを作成しました。

RetrieveAndGenerate APIを使用してガードレールを利用する場合は、APIのパラメーターにguardrailConfigurationを設定します。ここにはガードレールIDとバージョンを設定します。traceがサポートされていないため、トレース情報を得るためには後述のModel invocation loggingを使用します。

<details><summary>invoke_retrieve_and_generate.py</summary>

```python:invoke_retrieve_and_generate.py
import json
import logging
import sys

import boto3
from botocore.exceptions import ClientError

logging.basicConfig(format="%(asctime)s [%(levelname)s] %(message)s", level=logging.INFO)
logger = logging.getLogger(__name__)


def get_bedrock_client():
    return boto3.client(
        service_name='bedrock-agent-runtime',
        region_name="us-east-1"
    )


def retrieve_and_generate_response(bedrock_agent_client, model_id, knowledgebase_id, messages, guardrail_config):
    try:
        logger.info("Generating message with model %s", model_id)
        return bedrock_agent_client.retrieve_and_generate(
            input={
                "text": messages
            },
            retrieveAndGenerateConfiguration={
                "type": 'KNOWLEDGE_BASE',
                "knowledgeBaseConfiguration": {
                    "knowledgeBaseId": knowledgebase_id,
                    "modelArn": model_id,
                    'generationConfiguration': {
                        'inferenceConfig': {
                            'textInferenceConfig': {
                                'maxTokens': 2048,
                                'temperature': 0
                            }
                        },
                        # ガードレールを使用する
                        "guardrailConfiguration": guardrail_config,
                    },
                    # セマンティック検索を有効にする
                    'retrievalConfiguration': {
                        'vectorSearchConfiguration': {
                            'numberOfResults': 5,
                            'overrideSearchType': 'SEMANTIC'
                        }
                    },
                    # クエリ分解を有効にする
                    'orchestrationConfiguration': {
                        'queryTransformationConfiguration': {
                            'type': 'QUERY_DECOMPOSITION'
                        }
                    }
                }
            }
        )
    except ClientError as err:
        logger.error("A client error occurred: %s", err.response['Error']['Message'])
        raise
    except Exception as e:
        logger.error("An unexpected error occurred: %s", str(e))
        raise


def main():
    # コマンドライン引数を取得する
    if len(sys.argv) < 2:
        print("Usage: python3 ./invoke_retrieve_and_generate_guardails.py <input_text>")
        sys.exit(1)

    knowledgebase_id = "ナレッジベースID"
    model_id = "anthropic.claude-3-haiku-20240307-v1:0"

    guardrail_id = "ガードレールID"
    guardrail_version = "DRAFT"
    guardrail_config = {
        "guardrailId": guardrail_id,
        "guardrailVersion": guardrail_version,
    }

    # ユーザー入力
    message = sys.argv[1]

    try:
        bedrock_agent_client = get_bedrock_client()
        response = retrieve_and_generate_response(
            bedrock_agent_client,
            model_id,
            knowledgebase_id,
            message,
            guardrail_config
        )
        print("Response:" + "\n\n" + json.dumps(response, indent=2, ensure_ascii=False))
    except ClientError as err:
        logger.error("A client error occurred: %s", err.response['Error']['Message'])
        raise
    except Exception as e:
        logger.error("An unexpected error occurred: %s", str(e))
        raise


if __name__ == "__main__":
    main()

```
</details>

### ガードレールが発動するコンテンツが含まれない場合

```console
python ./invoke_retrieve_and_generate_guardails.py "身代金要求型の攻撃手法は何というか。"
```

<details><summary>実行結果</summary>

```console
{
  "ResponseMetadata": {
    "RequestId": "b9cbbd2a-5bc2-4f78-b0ca-55f75c8d0772",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Tue, 29 Oct 2024 16:57:56 GMT",
      "content-type": "application/json",
      "content-length": "4877",
      "connection": "keep-alive",
      "x-amzn-requestid": "b9cbbd2a-5bc2-4f78-b0ca-55f75c8d0772"
    },
    "RetryAttempts": 0
  },
  "citations": [
    {
      "generatedResponsePart": {
        "textResponsePart": {
          "span": {
            "end": 112,
            "start": 0
          },
          "text": "身代金要求型の攻撃手法は「ランサムウェア攻撃」と呼ばれています。ランサムウェアは、パソコンやサーバーのシステムをロックしたり、ファイルを暗号化することで使用不能にし、その復旧と引き換えに身代金を要求するサイバー攻撃の総称です。"
        }
      },
      "retrievedReferences": [
        {
          "content": {
            "text": "④端末制御：パソコンとC&C（Command and Control）\n\n\nサーバー※ 68 で通信が行われる。\n⑤指令：C&Cサーバーを経由し、遠隔操作が可能になる。\n⑥水平展開・情報探索：侵害範囲拡大や情報探索を\n\n\n行う。\n⑦情報窃取：目的の情報等を窃取する。\n\n\n（b）ネットワーク貫通型攻撃の手口\n\n\n標的組織のネットワークに侵入する手口として、VPN\n製品や Web サーバー等のネットワーク境界に接する機\n器に対し、脆弱性や設定不備を悪用して侵入したり、\n何らかの方法で得た認証情報（IDとパスワード等）を\n使って不正アクセスし組織内のネットワークに侵入する手\n口がある。IPA の J-CRAT（Cyber Rescue and Advice \nTeam against targeted attack of Japan：サイバーレ\nスキュー隊）では、このような手口による攻撃を「ネットワー\nク貫通型攻撃」と呼んでいる※ 69。2023 年には、この手\n口による攻撃の被害が複数確認されたことから、IPA に\nおいても注意喚起を行っている※ 70。なお、標的型攻撃\nメールを用いた攻撃とは侵入の手口が異なるだけで、侵\n入後のウイルス感染や端末制御等、目的達成までの活\n動に違いはない。ネットワーク貫通型攻撃の流れを以下\nに示す（図 1-2-5）。\n①偵察・攻撃準備：標的組織を攻撃するための情報を\n\n\n収集、攻撃手法を選定する。\n②攻撃：VPN 機器等の脆弱性を悪用し不正アクセスを\n\n\n行う。\n③侵入：標的組織のネットワーク内に侵入し内部偵察を\n\n\n行う。\n\n\nたい。\n侵入型ランサムウェア攻撃によるインシデントでは、業\n\n\n務の停止や顧客・取引先の情報漏えい等が発生し、自\n組織内に閉じたインシデントで終わらない傾向がある。そ\nのため、日頃から、経営層を含む顧客や取引先、シス\nテムの運用・保守の委託先等との素早い連絡・調整を\n行うための体制作りが必要である。\n\n\n標的型攻撃とは、ある特定の企業・組織や業界等を\n狙って行われるサイバー攻撃の一種である。フィッシング\nメールやウイルスメールを不特定多数の相手に無差別に\n送り付ける攻撃とは異なり、標的型攻撃は、標的とする\n特定の企業・組織（以下、標的組織）や業界が持つ機\n密情報の窃取等明確な目的をもって行われる。\n\n\n（1）標的型攻撃の手口\n標的型攻撃における侵入の手口として、これまで標\n\n\n的型攻撃メールが用いられていたが、ネットワーク貫通\n型攻撃と呼ばれる手口が確認されるようになってきてい\nる。以下に、それぞれの手口について述べる。\n\n\n（a）標的型攻撃メールを用いた攻撃の手口\n\n\n標的型攻撃メールとは、ウイルスを仕込んだファイルが\n添付されていたり、ウイルスをダウンロードさせるURLリ\nンクが記載されていたりするメールが標的組織の役職員\n宛てに送り付けられてくるものである。以前から用いられ\nている手口であり、継続して観測されている。標的型攻\n撃メールを用いた攻撃の流れを以下に示す（図 1-2-4）。\n①偵察・攻撃準備：標的組織を攻撃するための情報を\n\n\n収集、攻撃手法を選定する。"
          },
          "location": {
            "s3Location": {
              "uri": "s3://xxxx/whitepaper/2024/2024_Chap1.pdf"
            },
            "type": "S3"
          },
          "metadata": {
            "x-amz-bedrock-kb-source-uri": "s3://xxxx/whitepaper/2024/2024_Chap1.pdf",
            "x-amz-bedrock-kb-data-source-id": "0CITRJG2ZA"
          }
        }
      ]
    }
  ],
  "guardrailAction": "NONE",
  "output": {
    "text": "身代金要求型の攻撃手法は「ランサムウェア攻撃」と呼ばれています。ランサムウェアは、パソコンやサーバーのシステムをロックしたり、ファイルを暗号化することで使用不能にし、その復旧と引き換えに身代金を要求するサイバー攻撃の総称です。"
  },
  "sessionId": "edffe6b1-d52a-4689-acc1-fefb33d7d471"
}
```
</details>


### ガードレールが発動するコンテンツが含まれる場合

```console
python ./invoke_retrieve_and_generate_guardails.py "IPA 独立行政法人 情報処理推進機構の連絡先を教えて。"
```

<details><summary>実行結果</summary>

```console
{
  "ResponseMetadata": {
    "RequestId": "535acc68-58e1-4809-87e3-8f88bb6f3f24",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Tue, 29 Oct 2024 17:13:35 GMT",
      "content-type": "application/json",
      "content-length": "4327",
      "connection": "keep-alive",
      "x-amzn-requestid": "535acc68-58e1-4809-87e3-8f88bb6f3f24"
    },
    "RetryAttempts": 0
  },
  "citations": [
    {
      "generatedResponsePart": {
        "textResponsePart": {
          "span": {
            "end": 253,
            "start": 0
          },
          "text": "独立行政法人情報処理推進機構（IPA）の連絡先は以下の通りです：\n\n住所：〒113-6591 {ADDRESS} 文京グリーンコートセンターオフィス16階\n\nセキュリティセンターの連絡先：\n電話番号：03-5978-7527\nFAX番号：03-5978-7518\n\nウェブサイト：{URL}\nセキュリティセンターのウェブサイト：{URL}\n\nまた、IPAはコンピュータウイルス・不正アクセス・脆弱性関連情報に関する発見・被害の届出を受け付けています。これらの届出はウェブフォームやメールで行うことができます。"
        }
      },
      "retrievedReferences": [
        {
          "content": {
            "text": "所属組織名は執\n\n\n筆当時のものです。 \n\n\n \n\n\n安全なウェブサイトの作り方 \n\n\n－ ウェブアプリケーションのセキュリティ実装とウェブサイトの安全性向上のための取り組み－ \n\n\n[発      行] ２００６年 １月３１日 第１版 第１刷 \n\n\n２００６年 ５月１１日 第１版 第２刷 \n\n\n２００６年１１月 １日 改訂第２版 第１刷 \n\n\n２００７年 ３月 １日 改訂第２版 第２刷 \n\n\n２００７年 ９月１０日 改訂第２版 第３刷 \n\n\n２００８年 ３月 ６日 改訂第３版 第１刷 \n\n\n２００８年 ８月 １日 改訂第３版 第２刷 \n\n\n２０１０年 １月２０日 改訂第４版 第１刷 \n\n\n２０１０年 ８月 ５日 改訂第４版 第２刷 \n\n\n２０１１年 ４月 ６日 改訂第５版 第１刷 \n\n\n２０１２年 ３月３０日 改訂第５版 第２刷 \n\n\n２０１２年１２月２６日 改訂第６版 第１刷 \n\n\n２０１５年 ３月１２日 改訂第７版 第１刷 \n\n\n２０１５年 ３月２６日 改訂第７版 第２刷 \n\n\n２０１６年 １月２７日 改訂第７版 第３刷 \n\n\n２０２１年 ３月３１日 改訂第７版 第４刷 \n\n\n[著作・制作] 独立行政法人 情報処理推進機構 技術本部 セキュリティセンター \n\n\n[協      力] 独立行政法人 産業技術総合研究所 情報セキュリティ研究センター  \n\n\n\n\n\n\n\n \n\n\n \n\n\n \n\n\n \n\n\n独立行政法人 情報処理推進機構 \n〒113-6591 \n\n\n東京都文京区本駒込二丁目28番8号 \n\n\n文京グリーンコートセンターオフィス16階 \n\n\nhttp://www.ipa.go.jp \n \n\n\nセキュリティセンター \nTEL: 03-5978-7527  FAX  03-5978-7518 \n\n\nhttp://www.ipa.go.jp/security/ \n\n\nIPA セキュリティセンターでは、経済産業省の告示に基づき、コンピュータウイルス・不正ア\n\n\nクセス・脆弱性関連情報に関する発見・被害の届出を受け付けています。 \n\n\nウェブフォームやメールで届出ができます。"
          },
          "location": {
            "s3Location": {
              "uri": "s3://xxxx/security/vuln/websecurity/000017316.pdf"
            },
            "type": "S3"
          },
          "metadata": {
            "x-amz-bedrock-kb-source-uri": "s3://xxxx/security/vuln/websecurity/000017316.pdf",
            "FileTitle": "安全なウェブサイトの作り方 （全115ページ）(PDF:2.2 MB)",
            "FileURL": "https://www.ipa.go.jp/security/vuln/websecurity/ug65p900000196e2-att/000017316.pdf",
            "Category": "websecurity",
            "PageURL": "https://www.ipa.go.jp/security/vuln/websecurity/about.html",
            "x-amz-bedrock-kb-data-source-id": "0CITRJG2ZA",
            "PageTitle": "安全なウェブサイトの作り方"
          }
        }
      ]
    }
  ],
  "guardrailAction": "INTERVENED",
  "output": {
    "text": "独立行政法人情報処理推進機構（IPA）の連絡先は以下の通りです：\n\n住所：〒113-6591 {ADDRESS} 文京グリーンコートセンターオフィス16階\n\nセキュリティセンターの連絡先：\n電話番号：03-5978-7527\nFAX番号：03-5978-7518\n\nウェブサイト：{URL}\nセキュリティセンターのウェブサイト：{URL}\n\nまた、IPAはコンピュータウイルス・不正アクセス・脆弱性関連情報に関する発見・被害の届出を受け付けています。これらの届出はウェブフォームやメールで行うことができます。"
  },
  "sessionId": "bf050cdb-0252-4783-8f84-52abefa2d505"
}
```
</details>

ガードレールがブロックされたコンテンツを検出すると、レスポンスの guardrailActionフィールドにINTERVENEDが設定されます。Sensitive information filtersにより、マスクされたコンテンツのテキストが{ADDRESS}、{URL}となっていることがわかります。ただし、電話番号やFAX番号はマスクされませんでした。

RetrieveAndGenerate APIはguardrailConfigurationにtraceフィールドがないため出力内容にトレース情報がありません。Bedrock コンソールのBedrock configurationsにあるModel invocation loggingを有効にした際に出力されるログには以下のようにamazon-bedrock-traceがありトレース情報が出力されます。

<details><summary>ログ出力</summary>

```console
"amazon-bedrock-trace": {
"guardrail": {
    "input": {
        "qxug23uqmi11": {
            "invocationMetrics": {
                "guardrailProcessingLatency": 178,
                "usage": {
                    "topicPolicyUnits": 0,
                    "contentPolicyUnits": 0,
                    "wordPolicyUnits": 0,
                    "sensitiveInformationPolicyUnits": 0,
                    "sensitiveInformationPolicyFreeUnits": 0,
                    "contextualGroundingPolicyUnits": 0
                },
                "guardrailCoverage": {
                    "textCharacters": {
                        "guarded": 25,
                        "total": 8713
                    }
                }
            }
        }
    },
    "outputs": [
        {
            "qxug23uqmi11": {
                "contextualGroundingPolicy": {
                    "filters": [
                        {
                            "type": "GROUNDING",
                            "threshold": 0.5,
                            "score": 0.71,
                            "action": "NONE"
                        },
                        {
                            "type": "RELEVANCE",
                            "threshold": 0.5,
                            "score": 0.69,
                            "action": "NONE"
                        }
                    ]
                },
                "invocationMetrics": {
                    "guardrailProcessingLatency": 537,
                    "usage": {
                        "topicPolicyUnits": 0,
                        "contentPolicyUnits": 0,
                        "wordPolicyUnits": 0,
                        "sensitiveInformationPolicyUnits": 1,
                        "sensitiveInformationPolicyFreeUnits": 0,
                        "contextualGroundingPolicyUnits": 8
                    },
                    "guardrailCoverage": {
                        "textCharacters": {
                            "guarded": 216,
                            "total": 216
                        }
                    }
                }
            }
        }
    ]
}
```
</details>

## tool useによるクエリ拡張

Converse APIでtool useを使ったクエリ拡張でガードレールを適用した例です。ここでは、入力されたクエリをもとに複数の検索用キーワード数パターン生成します。Converse APIのパラメーターにtoolConfigを設定して、tool useを有効にします。

<details><summary>invoke_converse_guardrails_tooluse_only.py</summary>

```python:invoke_converse_guardrails_tooluse_only.py
import json
import logging
import sys
from typing import Any, Dict

import boto3
from botocore.exceptions import ClientError

logging.basicConfig(format="%(asctime)s [%(levelname)s] %(message)s", level=logging.INFO)
logger = logging.getLogger(__name__)


def get_bedrock_client():
    return boto3.client(
        service_name="bedrock-runtime",
        region_name="us-east-1"
    )

def load_tool_config() -> Dict[str, Any]:
    return {
        "tools": [
            {
                "toolSpec": {
                    "name": "multi_query_generator",
                    "description": "与えられる質問文に基づいて類義語や日本語と英語の表記揺れを考慮し、多角的な視点からクエリを生成する。",
                    "inputSchema": {
                        "json": {
                            "type": "object",
                            "properties": {
                                "queries": {
                                    "type": "array",
                                    "description": "検索用クエリのリスト。",
                                    "items": {
                                        "type": "object",
                                        "properties": {
                                            "query": {
                                                "type": "string",
                                                "description": "検索用クエリ。日本語と英語を混ぜた多様な単語を空白で区切って記述される。"
                                            }
                                        }
                                    }
                                }
                            },
                            "required": ["queries"]
                        }
                    }
                }
            }
        ],
        "toolChoice": {
            "tool": {
                "name": "multi_query_generator"
            }
        }
    }


def query_generator_system_prompt() -> str:
    return '''与えられる質問文に基づいて、類義語や日本語と英語の表記揺れを考慮し、多角的な視点からクエリを生成します。
    検索エンジンに入力するクエリを最適化し、様々な角度から検索を行うことで、より適切で幅広い検索結果が得られるようにします。
    ツールを使って複数のクエリを作成してください｡形式を<example>に示します｡
    <rule>タグ内のルールに必ず従ってください｡

    <example>
    question: Knowledge Bases for Amazon Bedrock ではどのベクトルデータベースを使えますか？
    query: Knowledge Bases for Amazon Bedrock vector databases engine DB
    query: Amazon Bedrock ナレッジベース ベクトルエンジン vector databases DB
    query: Amazon Bedrock RAG 検索拡張生成 埋め込みベクトル データベース エンジン
    </example>

    <rule>
    - 与えられた質問文に基づいて、検索用クエリを生成してください。
    - 各クエリは30トークン以内とし、日本語と英語を適切に混ぜて使用すること。
    - 広範囲の文書が取得できるよう、多様な単語をクエリに含むこと。
    </rule>'''

def generate_queries(bedrock_client, model_id, message, guardrail_config, inference_config) -> Dict[str, Any]:
    """入力テキストから検索クエリを生成する"""
    try:
        system_prompts = [{"text": query_generator_system_prompt()}]
        messages = [
            {
                "role": "user",
                "content": [
                    {
                            "text":message,
                    }
                ]
            }
        ]

        return bedrock_client.converse(
            system=system_prompts,
            messages=messages,
            modelId=model_id,
            inferenceConfig=inference_config,
            toolConfig=load_tool_config(),
            guardrailConfig=guardrail_config
        )
    except ClientError as err:
        logger.error("A client error occurred: %s", err.response['Error']['Message'])
        raise
    except Exception as e:
        logger.error("An unexpected error occurred: %s", str(e))
        raise

def main():
    # コマンドライン引数を取得する
    if len(sys.argv) < 2:
        print("Usage: python3 ./invoke_converse_guardrails_tooluse_only.py <input_text>")
        sys.exit(1)

    model_id = "anthropic.claude-3-haiku-20240307-v1:0"

    guardrail_id = "qxug23uqmi11"
    guardrail_version = "DRAFT"

    guardrail_config = {
        "guardrailIdentifier": guardrail_id,
        "guardrailVersion": guardrail_version,
        "trace": "enabled"
    }

    inference_config = {
        "temperature": 0,
        "maxTokens": 500,
        "topP": 0.99,
    }

    message = sys.argv[1]

    try:
        bedrock_client = get_bedrock_client()
        response = generate_queries(
            bedrock_client,
            model_id,
            message,
            guardrail_config,
            inference_config
        )

        print("Response:" + "\n\n" + json.dumps(response, indent=2, ensure_ascii=False))

    except ClientError as err:
        logger.error("A client error occurred: %s", err.response['Error']['Message'])
    except Exception as e:
        logger.error("An unexpected error occurred: %s", str(e))

if __name__ == "__main__":
    main()
```
</details>


```console
python ./invoke_converse_guardrails_tooluse_only.py "IPA 独立行政法人 情報処理推進機構の連絡先を教えて。"
```

<details><summary>実行結果</summary>

```
{
  "ResponseMetadata": {
    "RequestId": "3e089db8-992e-4732-840e-46890813b783",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Thu, 31 Oct 2024 06:26:49 GMT",
      "content-type": "application/json",
      "content-length": "827",
      "connection": "keep-alive",
      "x-amzn-requestid": "3e089db8-992e-4732-840e-46890813b783"
    },
    "RetryAttempts": 0
  },
  "output": {
    "message": {
      "role": "assistant",
      "content": [
        {
          "toolUse": {
            "toolUseId": "tooluse_iuXf_P7ERZyoe-N52-duKg",
            "name": "multi_query_generator",
            "input": {
              "queries": [
                {
                  "query": "IPA 独立行政法人 情報処理推進機構 連絡先"
                },
                {
                  "query": "IPA 情報処理推進機構 contact"
                },
                {
                  "query": "IPA 情報処理推進機構 address phone"
                }
              ]
            }
          }
        }
      ]
    }
  },
  "stopReason": "tool_use",
  "usage": {
    "inputTokens": 1288,
    "outputTokens": 126,
    "totalTokens": 1414
  },
  "metrics": {
    "latencyMs": 1564
  },
  "trace": {
    "guardrail": {
      "inputAssessment": {
        "qxug23uqmi11": {}
      }
    }
  }
}
```
</details>

stopReasonフィールドはtool_useになりました。ガードレールにブロック対象となるクエリを生成させることができなかったので、tool useにガードレールを適用できるのか、あるいはブロックされたコンテンツが出力された場合のレスポンスがどのようなものかを検証することができませんでした。
tool useを使用したクエリ拡張生成では、生成するクエリのパターンやルールをシステムプロンプトに記述できます。つまり、Bedrockチャットガードレールのポリシーに該当する内容をシステムプロンプトとして記述できそうです。場合によってはBedrock Guardrailsを使わずに、tool useを使ったクエリ拡張でガードレールを適用できるのかもしれません。

## つなげてみる

tool useを使ったクエリ拡張でガードレールを適用した例と、Retrieverによる検索結果取得、それをもとにした応答生成にガードレールを適用した例をつなげてみます。
拡張クエリによる検索の結果をgrounding_sourceとして、質問をguard_content, queryとしてgenerate_response関数内のguardContentで指定しました。

<details><summary>invoke_converse_guardrails_extend_retreive.py</summary>

```python:invoke_converse_guardrails_extend_retreive.py
import json
import logging
import sys
from typing import Any, Dict, List, Optional

import boto3
from botocore.exceptions import ClientError

logging.basicConfig(format="%(asctime)s [%(levelname)s] %(message)s", level=logging.INFO)
logger = logging.getLogger(__name__)

def get_bedrock_client():
    return boto3.client(
        service_name="bedrock-runtime",
        region_name="us-east-1"
    )

def get_bedrock_agent_client():
    return boto3.client(
        service_name="bedrock-agent-runtime",
        region_name="us-east-1"
    )

def load_tool_config() -> Dict[str, Any]:
    return {
        "tools": [
            {
                "toolSpec": {
                    "name": "multi_query_generator",
                    "description": "与えられる質問文に基づいて類義語や日本語と英語の表記揺れを考慮し、多角的な視点からクエリを生成する。",
                    "inputSchema": {
                        "json": {
                            "type": "object",
                            "properties": {
                                "queries": {
                                    "type": "array",
                                    "description": "検索用クエリのリスト。",
                                    "items": {
                                        "type": "object",
                                        "properties": {
                                            "query": {
                                                "type": "string",
                                                "description": "検索用クエリ。日本語と英語を混ぜた多様な単語を空白で区切って記述される。"
                                            }
                                        }
                                    }
                                }
                            },
                            "required": ["queries"]
                        }
                    }
                }
            }
        ],
        "toolChoice": {
            "tool": {
                "name": "multi_query_generator"
            }
        }
    }


def query_generator_system_prompt() -> str:
    return '''与えられる質問文に基づいて、類義語や日本語と英語の表記揺れを考慮し、多角的な視点からクエリを生成します。
    検索エンジンに入力するクエリを最適化し、様々な角度から検索を行うことで、より適切で幅広い検索結果が得られるようにします。
    ツールを使って複数のクエリを作成してください｡形式を<example>に示します｡
    <rule>タグ内のルールに必ず従ってください｡

    <example>
    question: Knowledge Bases for Amazon Bedrock ではどのベクトルデータベースを使えますか？
    query: Amazon Bedrock ナレッジベース ベクトルエンジン vector databases DB
    </example>

    <rule>
    - 与えられた質問文に基づいて、検索用クエリを生成してください。
    - 各クエリは30トークン以内とし、日本語と英語を適切に混ぜて使用すること。
    - 広範囲の文書が取得できるよう、多様な単語をクエリに含むこと。
    </rule>'''


def generate_queries(bedrock_client, model_id, message, guardrail_config, inference_config) -> Dict[str, Any]:
    """入力テキストから検索クエリを生成する"""
    try:
        system_prompts = [{"text": query_generator_system_prompt()}]

        messages = [
            {
                "role": "user",
                "content": [
                    {
                        "guardContent": {
                            "text": {
                                "text": message,
                            }
                        }
                    }
                ]
            }
        ]

        additional_model_fields = {"top_k": 200}

        return bedrock_client.converse(
            system=system_prompts,
            messages=messages,
            modelId=model_id,
            inferenceConfig=inference_config,
            additionalModelRequestFields=additional_model_fields,
            toolConfig=load_tool_config(),
            guardrailConfig=guardrail_config
        )
    except ClientError as err:
        logger.error("A client error occurred: %s", err.response['Error']['Message'])
        raise
    except Exception as e:
        logger.error("An unexpected error occurred: %s", str(e))
        raise


def extract_tool_use_args(content: List[Dict]) -> Optional[Dict[str, str]]:
    """toolの回答を抽出する"""
    for item in content:
        if "toolUse" in item and "input" in item["toolUse"]:
            return item["toolUse"]["input"]
    return None


def extract_queries(input_data):
    """クエリを抽出する"""
    return {f"query_{key}": value["query"] for key, value in enumerate(input_data["queries"], 1)}


def retrieve_knowledge_base_results(message: str, response_content: List[Dict], knowledgebase_id: str) -> Optional[List[Dict[str, Any]]]:
    """knowledge baseから結果を取得する"""
    retrieval_results = []
    bedrock_agent_client = get_bedrock_agent_client()
    tool_use_args = extract_tool_use_args(response_content)

    if tool_use_args is None:
        logger.warning("No tool use arguments found.")
        return None

    queries = extract_queries(tool_use_args)
    queries['query_0'] = message

    for query_key, query_value in queries.items():
        # logger.info("Query Key: %s, Query Value: %s", query_key, query_value)

        try:
            response = bedrock_agent_client.retrieve(
                knowledgeBaseId=knowledgebase_id,
                retrievalQuery={
                    "text": query_value
                },
                retrievalConfiguration={
                    "vectorSearchConfiguration": {
                        "numberOfResults": 3
                    }
                }
            )

            retrieval_results.extend(response['retrievalResults'])

        except ClientError as err:
            logger.error("A client error occurred: %s", err.response['Error']['Message'])
            raise
        except Exception as e:
            logger.error("An unexpected error occurred: %s", str(e))
            raise

    return retrieval_results


def generate_response(bedrock_client, model_id, message: str, context_str: str, guardrail_config: Dict[str, Any], inference_config) -> str:
    """応答を生成する"""
    try:
        system_prompts = [
            {
                "text": "You are a question answering agent. I will provide you with a set of search results. \
                Here are the search results in:\n<search_results>\n"
            },
            {
                "guardContent": {
                    "text": {
                        "text": context_str,
                        "qualifiers": [
                            "grounding_source"
                        ]
                    }
                }
            },
            {
                "text": "\n</search_results>\n\nYou should provide your answer without any inline citations or \
                references to specific sources within the answer text itself."
            }
        ]
        messages = [
            {
                "role": "user",
                "content": [
                    {
                        "guardContent": {
                            "text": {
                                "text": message,
                                "qualifiers": [
                                    "query",
                                    "guard_content"
                                ]
                            }
                        }
                    }
                ]
            }
        ]

        additional_model_fields = {"top_k": 200}

        response_body = bedrock_client.converse(
            system=system_prompts,
            messages=messages,
            modelId=model_id,
            inferenceConfig=inference_config,
            additionalModelRequestFields=additional_model_fields,
            guardrailConfig=guardrail_config
        )

        return response_body
    except ClientError as err:
        logger.error("A client error occurred: %s", err.response['Error']['Message'])
        raise
    except Exception as e:
        logger.error("An unexpected error occurred: %s", str(e))
        raise


def main():
    # コマンドライン引数を取得する
    if len(sys.argv) < 2:
        print("Usage: python3 ./invoke_converse_guardrails_extend_retreive.py <input_text>")
        sys.exit(1)

    knowledgebase_id = "xxxxxxx"
    model_id="anthropic.claude-3-5-sonnet-20240620-v1:0"

    guardrail_id = "xxxxxxx"
    guardrail_version = "DRAFT"

    guardrail_config = {
        "guardrailIdentifier": guardrail_id,
        "guardrailVersion": guardrail_version,
        "trace": "enabled"
    }

    inference_config = {
        "temperature": 0,
        "maxTokens": 500,
        "topP": 0.99,
    }

    message = sys.argv[1]

    try:
        bedrock_client = get_bedrock_client()

        # Step 1: ユーザー入力テキストを基に検索クエリーを生成する
        response = generate_queries(
            bedrock_client,
            model_id,
            message,
            guardrail_config,
            inference_config
        )

        print("Response:" + "\n\n" + json.dumps(response, indent=2, ensure_ascii=False))

        # Step 2: Retrieverに対してクエリーを実行して検索結果を得る
        retrieval_results = retrieve_knowledge_base_results(
            message,
            response["output"]["message"]["content"],
            knowledgebase_id
        )
        # print(json.dumps(retrieval_results, indent=2, ensure_ascii=False))

        if retrieval_results:
            # Step 3: ユーザー入力テキストと検索結果を基に応答テキストを生成する
            logging.debug("Retrieval results: %s", json.dumps(retrieval_results, indent=2, ensure_ascii=False))
            response_result = generate_response(
                bedrock_client,
                model_id,
                message,
                json.dumps(retrieval_results),
                guardrail_config,
                inference_config
            )
            print("Response:" + "\n\n" + json.dumps(response_result, indent=2, ensure_ascii=False))

        else:
            logger.warning("No retrieval results found.")
    except ClientError  as err:
        logger.error("A client error occurred: %s", err.response['Error']['Message'])
    except Exception as e:
        logger.error("An unexpected error occurred: %s", str(e))
        raise

if __name__ == "__main__":
    main()
```
</details>

### ガードレールが発動するコンテンツが含まれない場合

```console
python ./invoke_converse_guardrails_extend_retreive.py "身代金要求型の攻撃手法は何というか。"
```

<details><summary>実行結果</summary>

```
{
  "ResponseMetadata": {
    "RequestId": "a5ce37ce-7149-486e-a2e5-60c48e4e50b0",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Thu, 31 Oct 2024 09:25:04 GMT",
      "content-type": "application/json",
      "content-length": "965",
      "connection": "keep-alive",
      "x-amzn-requestid": "a5ce37ce-7149-486e-a2e5-60c48e4e50b0"
    },
    "RetryAttempts": 0
  },
  "output": {
    "message": {
      "role": "assistant",
      "content": [
        {
          "toolUse": {
            "toolUseId": "tooluse_wmIhi86jQ2-wRaQ2SjLebQ",
            "name": "multi_query_generator",
            "input": {
              "queries": [
                {
                  "query": "身代金要求型 攻撃手法 サイバー犯罪 名称"
                },
                {
                  "query": "ransomware ランサムウェア 特徴 定義"
                },
                {
                  "query": "malware 種類 身代金 暗号化 脅迫"
                },
                {
                  "query": "サイバーセキュリティ 脅威 extortion 手口"
                },
                {
                  "query": "computer virus 身代金 データ 復号 payment"
                }
              ]
            }
          }
        }
      ]
    }
  },
  "stopReason": "tool_use",
  "usage": {
    "inputTokens": 1150,
    "outputTokens": 167,
    "totalTokens": 1317
  },
  "metrics": {
    "latencyMs": 3603
  },
  "trace": {
    "guardrail": {
      "inputAssessment": {
        "qxug23uqmi11": {}
      }
    }
  }
}

Response:
{
  "ResponseMetadata": {
    "RequestId": "080b383a-51f9-4239-9e25-ce3f8b8ca94a",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Thu, 31 Oct 2024 09:25:24 GMT",
      "content-type": "application/json",
      "content-length": "2382",
      "connection": "keep-alive",
      "x-amzn-requestid": "080b383a-51f9-4239-9e25-ce3f8b8ca94a"
    },
    "RetryAttempts": 0
  },
  "output": {
    "message": {
      "role": "assistant",
      "content": [
        {
          "text": "この種の攻撃手法は「ランサムウェア攻撃」と呼ばれます。\n\nランサムウェア攻撃は、被害組織のデータやシステムを人質に取り、身代金を要求する悪質なサイバー攻撃の一種です。攻撃者は通常、被害者のファイルやシステムを暗号化し、復号のための鍵と引き換えに金銭を要求します。\n\n近年では、単にデータを暗号化するだけでなく、データを盗み出して公開すると脅す「二重の脅迫」を行うケースも増えています。これにより、被害組織はデータ喪失だけでなく、機密情報の流出リスクにも直面することになります。\n\nまた、「ノーウェアランサム攻撃」と呼ばれる新たな手法も出現しています。この手法では、データを暗号化せずに盗み出し、公開すると脅して金銭を要求します。システムの停止などの目に見える被害がないため、発見が遅れる可能性があります。\n\nランサムウェア攻撃は、企業や組織の規模を問わず広く標的とされており、重大な金銭的損失や業務中断、評判の低下などを引き起こす可能性があるため、深刻な脅威となっています。"
        }
      ]
    }
  },
  "stopReason": "end_turn",
  "usage": {
    "inputTokens": 51600,
    "outputTokens": 410,
    "totalTokens": 52010
  },
  "metrics": {
    "latencyMs": 15418
  },
  "trace": {
    "guardrail": {
      "inputAssessment": {
        "qxug23uqmi11": {}
      },
      "outputAssessments": {
        "qxug23uqmi11": [
          {
            "contextualGroundingPolicy": {
              "filters": [
                {
                  "type": "GROUNDING",
                  "threshold": 0.5,
                  "score": 0.81,
                  "action": "NONE"
                },
                {
                  "type": "RELEVANCE",
                  "threshold": 0.5,
                  "score": 0.72,
                  "action": "NONE"
                }
              ]
            }
          }
        ]
      }
    }
  }
}
```
</details>

### ガードレールが発動するコンテンツが含まれる場合

```
python ./invoke_converse_guardrails_extend_retreive.py "IPA 独立行政法人 情報処理推進機構の連絡先を教えて。"
```
<details><summary>実行結果</summary>

```
{
  "ResponseMetadata": {
    "RequestId": "bf3f4327-5980-441c-84be-77085941849e",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Thu, 31 Oct 2024 09:32:03 GMT",
      "content-type": "application/json",
      "content-length": "942",
      "connection": "keep-alive",
      "x-amzn-requestid": "bf3f4327-5980-441c-84be-77085941849e"
    },
    "RetryAttempts": 0
  },
  "output": {
    "message": {
      "role": "assistant",
      "content": [
        {
          "toolUse": {
            "toolUseId": "tooluse_v_bf1B-SSW6qxycW1YTG8A",
            "name": "multi_query_generator",
            "input": {
              "queries": [
                {
                  "query": "IPA 独立行政法人 情報処理推進機構 連絡先"
                },
                {
                  "query": "IPA Information-technology Promotion Agency 住所 電話番号"
                },
                {
                  "query": "情報処理推進機構 コンタクト メールアドレス 問い合わせ"
                },
                {
                  "query": "IPA Japan 所在地 アクセス 地図"
                }
              ]
            }
          }
        }
      ]
    }
  },
  "stopReason": "tool_use",
  "usage": {
    "inputTokens": 1159,
    "outputTokens": 146,
    "totalTokens": 1305
  },
  "metrics": {
    "latencyMs": 3761
  },
  "trace": {
    "guardrail": {
      "inputAssessment": {
        "qxug23uqmi11": {}
      }
    }
  }
}
Response:

{
  "ResponseMetadata": {
    "RequestId": "12ad1692-9ce5-41d5-ae0a-c23d6a8b48a6",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Thu, 31 Oct 2024 09:32:17 GMT",
      "content-type": "application/json",
      "content-length": "3003",
      "connection": "keep-alive",
      "x-amzn-requestid": "12ad1692-9ce5-41d5-ae0a-c23d6a8b48a6"
    },
    "RetryAttempts": 0
  },
  "output": {
    "message": {
      "role": "assistant",
      "content": [
        {
          "text": "IPA（独立行政法人 情報処理推進機構）の連絡先は以下の通りです：\n\n住所：\n{ADDRESS}\n東京都文京区本駒込二丁目28番8号\n{ADDRESS}\n\n電話番号：\n{PHONE}\n\nFAX番号：\n{PHONE}\n\nウェブサイト：\n{URL}\n\nセキュリティセンターのウェブページ：\n{URL}\n\nこれらの連絡先を通じて、IPAに問い合わせや連絡を行うことができます。"
        }
      ]
    }
  },
  "stopReason": "guardrail_intervened",
  "usage": {
    "inputTokens": 54035,
    "outputTokens": 212,
    "totalTokens": 54247
  },
  "metrics": {
    "latencyMs": 9262
  },
  "trace": {
    "guardrail": {
      "modelOutput": [
        "{\"id\":\"msg_bdrk_015U434AZVkLAH21Z4LHQ11v\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-3-5-sonnet-20240620\",\"content\":[{\"type\":\"text\",\"text\":\"IPA（独立行政法人 情報処理推進機構）の連絡先は以下の通りです：\\n\\n住所：\\n〒113-6591\\n東京都文京区本駒込二丁目28番8号\\n文京グリーンコートセンターオフィス16階\\n\\n電話番号：\\n03-5978-7527（セキュリティセンター）\\n\\nFAX番号：\\n03-5978-7518（セキュリティセンター）\\n\\nウェブサイト：\\nhttps://www.ipa.go.jp\\n\\nセキュリティセンターのウェブページ：\\nhttps://www.ipa.go.jp/security/\\n\\nこれらの連絡先を通じて、IPAに問い合わせや連絡を行うことができます。\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":54035,\"output_tokens\":212}}"
      ],
      "inputAssessment": {
        "qxug23uqmi11": {}
      },
      "outputAssessments": {
        "qxug23uqmi11": [
          {
            "sensitiveInformationPolicy": {
              "piiEntities": [
                {
                  "match": "〒113-6591",
                  "type": "ADDRESS",
                  "action": "ANONYMIZED"
                },
                {
                  "match": "文京グリーンコートセンターオフィス16階",
                  "type": "ADDRESS",
                  "action": "ANONYMIZED"
                },
                {
                  "match": "03-5978-7527（セキュリティセンター）",
                  "type": "PHONE",
                  "action": "ANONYMIZED"
                },
                {
                  "match": "03-5978-7518（セキュリティセンター）",
                  "type": "PHONE",
                  "action": "ANONYMIZED"
                },
                {
                  "match": "https://www.ipa.go.jp",
                  "type": "URL",
                  "action": "ANONYMIZED"
                },
                {
                  "match": "https://www.ipa.go.jp/security/",
                  "type": "URL",
                  "action": "ANONYMIZED"
                }
              ]
            },
            "contextualGroundingPolicy": {
              "filters": [
                {
                  "type": "GROUNDING",
                  "threshold": 0.5,
                  "score": 0.74,
                  "action": "NONE"
                },
                {
                  "type": "RELEVANCE",
                  "threshold": 0.5,
                  "score": 0.95,
                  "action": "NONE"
                }
              ]
            }
          }
        ]
      }
    }
  }
}
```
</details>

tool useについては前述のとおり、ガードレールが効いているかは判別できませんでした。最終的なレスポンスでは、Converse APIに対するガードレール機能が働きcontextualGroundingPolicyフィールドにGROUNDINGやRELEVANCEのスコア、sensitiveInformationPolicyフィールドにマスクされたコンテンツが返却されました。ただし、一部のコンテンツはマスクされていないものもありました。

## まとめ
PythonコードからAnthropicのConverse APIやRetrieval and Generate APIを呼び出し、入力と出力でガードレールを試しました。
traceを有効にするとガードレールの振る舞いを確認できました。ただし、Retrieval and Generate APIのguardrailConfigurationはtraceをサポートしていません。traceログを得るにはModel invocation loggingで出力されるログを参照する必要があります。traceの内容をアプリケーションの動作に利用したい場合にこの仕様は不便です。

tool useについては前述のとおり、ガードレールが効いているかは判別できませんでした。使い方が間違っているのかもしれません。しかし、システムプロンプトによる柔軟な挙動の指示ができるので、Bedrock guardrailsを使わず自前でフィルターを定義したほうが制御しやすいかもしれません。

Bedrock Guardrailsが英語以外をサポートしていないため日本語での動作が不安定ですが、「責任あるAI」としてのガードレール機能は期待できます。