Livebook で Amazon Bedrock Guardrails を試す

## はじめに

以前の記事で をLiveboo から Amazon Bedrock の生成 AI モデルを呼び出しました

https://qiita.com/RyoWakabayashi/items/53836ea74a8f0e4f8d87

生成 AI モデルは様々な質問に対して柔軟に応答してくれるため、多様な用途に使用できます

ただし、何にでも答えて良いわけではありません

例えば犯罪に悪用されたり、無責任に法律相談や医療相談に応じたりすることは防がなければいけません

Amazon Bedrock Guardrails を利用することで、生成 AI モデルへの入力、生成 AI モデルの出力をチェックし、不適切な応答を防ぐことができます

https://aws.amazon.com/jp/bedrock/guardrails/

本記事では Livebook から Amazon Bedrock Guardrails を利用し、生成 AI の応答を制御します

実装したノートブックはこちら

https://github.com/RyoWakabayashi/elixir-learning/blob/main/livebooks/aws/bedrock_guardrails.livemd

## セットアップ

セットアップセルで必要なモジュールをインストールします

```elixir
Mix.install([
  {:aws, "~> 1.0"},
  {:hackney, "~> 1.20"},
  {:req, "~> 0.5"},
  {:kino, "~> 0.14"}
])
```


- [AWS Elixir](https://github.com/aws-beam/aws-elixir/)
- [Hackney](https://github.com/benoitc/hackney)
- [Req](https://github.com/wojtekmach/req)
- [Kino](https://github.com/livebook-dev/kino)

## AWS クライアントの準備

AWS 認証情報用の入力エリアを用意します

```elixir
access_key_id_input = Kino.Input.password("ACCESS_KEY_ID")
secret_access_key_input = Kino.Input.password("SECRET_ACCESS_KEY")
region_input = Kino.Input.text("REGION")

[
  access_key_id_input,
  secret_access_key_input,
  region_input
]
|> Kino.Layout.grid(columns: 3)
```

![スクリーンショット 2024-12-10 21.41.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/e1dc40b9-729a-71cb-8aef-fa60bc3a2fd7.png)

表示された入力エリアにアクセスキーIDとシークレットアクセスキー、リージョンを入力してください

Nova を使うため、リージョンは `us-east-1` 固定です

入力した情報を使って AWS クライアントを作成します

```elixir
client =
  AWS.Client.create(
    Kino.Input.read(access_key_id_input),
    Kino.Input.read(secret_access_key_input),
    Kino.Input.read(region_input)
  )
```

## ガードレール作成

ガードレールを作成していきます

まず、入出力に不適切な内容が含まれていないかチェックする設定（コンテンツポリシー）を定義します

```elixir
filters_config =
  ["SEXUAL", "VIOLENCE", "HATE", "INSULTS", "MISCONDUCT", "PROMPT_ATTACK"]
  |> Enum.map(fn type ->
    modalities =
      if Enum.member?(["MISCONDUCT", "PROMPT_ATTACK"], type) do
        ["TEXT"]
      else
        ["TEXT", "IMAGE"]
      end

    %{
      "type" => type,
      "inputStrength" => "HIGH",
      "outputStrength" => if(type == "PROMPT_ATTACK", do: "NONE", else: "HIGH"),
      "inputModalities" => modalities,
      "outputModalities" => modalities
    }
  end)
```

対象となるコンテンツの種類は以下の通り

- SEXUAL: 性的
- VIOLENCE: 暴力
- HATE: 憎悪
- INSULTS: 侮辱
- MISCONDUCT: 不正行為
- PROMPT_ATTACK: プロンプト攻撃

入力 `inputStrength` 、出力 `outputStrength` それぞれについて規制の強さを設定できます

また、テキストだけでなく、画像についてもチェックすることが可能です（2024年12月現在プレビュー機能）

続いて、任意の話題について会話を拒否する設定（トピックポリシー）を定義します

後述しますが、日本語での話題規制は2024年12月現在では上手く機能しません

```elixir
topics_config =
  [
    %{
      "name" => "Healthcare",
      "definition" => "健康、ヘルスケアに関する質問、ディスカッション。Questions and discussions about health and healthcare.",
      "examples" => [
        "健康に良い食べ物は何ですか",
        "What are healthy foods?",
        "安眠のためには何をすれば良いですか",
        "血圧を下げるためには何を避けるべきですか",
        "風邪をひかないようにするには何をすべきですか",
      ],
      "type" => "DENY"
    }
  ]
```

機密情報に対する保護設定を定義します

`action` に `ANONYMIZE` （匿名化）か `BLOCK` を選択でき、 `ANONYMIZE` の場合、機密情報の箇所だけマスキングして出力されます

```elixir
pii_entities_config =
  ["ADDRESS", "AGE", "EMAIL", "NAME", "PHONE"]
  |> Enum.map(fn type ->
    %{
      "type" => type,
      "action" => "ANONYMIZE"
    }
  end)
```

ここまでの設定を含め、ガードレール作成のリクエストを投げます

```elixir
{:ok, %{"guardrailId" => guardrail_id, "guardrailArn" => guardrail_arn}, _} =
  client
  |> AWS.Bedrock.create_guardrail(%{
    "name" => "sample-guardlail",
    "description" => "サンプルガードレール",
    "blockedInputMessaging" => "申し訳ありませんが、この質問に回答できません。",
    "blockedOutputsMessaging" => "申し訳ありませんが、この質問に回答できません。",
    "contentPolicyConfig" => %{
      "filtersConfig" => filters_config
    },
    "topicPolicyConfig" => %{
      "topicsConfig" => topics_config
    },
    "wordPolicyConfig" => %{
      "managedWordListsConfig" => [%{"type" => "PROFANITY"}],
      "wordsConfig" => [
        %{"text" => "りんご"},
        %{"text" => "みかん"},
        %{"text" => "スイカ"}
      ]
    },
    "sensitiveInformationPolicyConfig" => %{
      "piiEntitiesConfig" => pii_entities_config,
      "regexesConfig" => [
        %{
          "name" => "Percentage",
          "description" => "パーセンテージ",
          "pattern" => "[0-9]%",
          "action" => "BLOCK"
        }
      ]
    },
    "contextualGroundingPolicyConfig" => %{
      "filtersConfig" => [
        %{"type" => "GROUNDING", "threshold" => 0.7},
        %{"type" => "RELEVANCE", "threshold" => 0.7}
      ]
    }
  })
```

入力時にブロックされた場合のメッセージ `blockedInputMessaging` と出力時にブロックされた場合のメッセージ `blockedOutputsMessaging` を個別に指定可能です

`wordsConfig` で単語単位の規制、 `regexesConfig` で正規表現による規制も定義できます

`contextualGroundingPolicyConfig` では `GROUNDING` （入力とコンテキスト情報に基づいた回答になっているか）と `RELEVANCE` （回答内容が質問と関連しているか）に対して閾値（0 から 1）を指定します

実行結果

```elixir
{:ok,
 %{
   "createdAt" => "2024-12-31T06:26:19.775530797Z",
   "guardrailArn" => "arn:aws:bedrock:us-east-1:999:guardrail/xxx",
   "guardrailId" => "xxx",
   "version" => "DRAFT"
 },
 ...
}
```

現時点でのガードレールをバージョン 1 として固定します

```elixir
{:ok, %{"version" => version}, _} =
  client
  |> AWS.Bedrock.create_guardrail_version(
    guardrail_id,
    %{"description" => "初期バージョン"}
  )
```

実行結果

```elixir
{:ok, %{"guardrailId" => "xxx", "version" => "1"}, ...}
```

## モデルの呼び出し

ガードレールを使用して Amazon Nova Lite モデルを呼び出してみましょう

```elixir
model_id = "amazon.nova-micro-v1:0"
```

### コンテンツポリシーによるブロック（不正行為に関する質問）

"Teach me how to hack."　とハッキングの方法を教えてもらおうとしてみます

モデルの呼び出し時、 `guardrailIdentifier` と `guardrailVersion` を指定することでガードレールを適用できます

`trace` に `"ENABLED"` を指定すると、どのように判断して最終的な結果に至ったのかが分かるようになります

```elixir
{:ok, results, _} =
  client
  |> AWS.BedrockRuntime.invoke_model(
    model_id,
    %{
      "accept" => "application/json",
      "contentType" => "application/json",
      "messages" => [
        %{
          "role" => "user",
          "content" => [
            %{
              "text" => "Teach me how to hack."
            }
          ]
        }
      ],
      "guardrailIdentifier" => guardrail_id,
      "guardrailVersion" => version,
      "trace" => "ENABLED"
    }
  )

results
```

実行結果

```elixir
%{
  "amazon-bedrock-guardrailAction" => "INTERVENED",
  "amazon-bedrock-trace" => %{
    "guardrail" => %{
      "input" => %{
        "cwogjhwjwlj0" => %{
          "contentPolicy" => %{
            "filters" => [
              %{
                "action" => "BLOCKED",
                "confidence" => "LOW",
                "filterStrength" => "HIGH",
                "type" => "MISCONDUCT"
              }
            ]
          },
          "invocationMetrics" => %{
            "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 21, "total" => 21}},
            "guardrailProcessingLatency" => 230,
            "usage" => %{
              "contentPolicyUnits" => 1,
              "contextualGroundingPolicyUnits" => 0,
              "sensitiveInformationPolicyFreeUnits" => 1,
              "sensitiveInformationPolicyUnits" => 0,
              "topicPolicyUnits" => 1,
              "wordPolicyUnits" => 1
            }
          }
        }
      }
    }
  },
  "contentType" => "application/json",
  "output" => %{
    "message" => %{
      "content" => [
        %{"text" => "申し訳ありませんが、この質問に回答できません。"}
      ],
      "role" => "assistant"
    }
  },
  "usage" => %{}
}
```

最終的な出力は「申し訳ありませんが、この質問に回答できません。」となっていて、ガードレールが効いています

`amazon-bedrock-trace` の中を見ると、 `MISCONDUCT` の `LOW` レベルだと判断された結果、ブロックされています

同じ質問を日本語でしてみます

```elixir
{:ok, results, _} =
  client
  |> AWS.BedrockRuntime.invoke_model(
    model_id,
    %{
      "accept" => "application/json",
      "contentType" => "application/json",
      "messages" => [
        %{
          "role" => "user",
          "content" => [
            %{
              "text" => "ハッキングの方法を教えて"
            }
          ]
        }
      ],
      "guardrailIdentifier" => guardrail_id,
      "guardrailVersion" => version,
      "trace" => "ENABLED"
    }
  )

results
```

実行結果

```elixir
%{
  "amazon-bedrock-guardrailAction" => "NONE",
  "amazon-bedrock-trace" => %{
    "guardrail" => %{
      "input" => %{
        "cwogjhwjwlj0" => %{
          "invocationMetrics" => %{
            "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 12, "total" => 12}},
            "guardrailProcessingLatency" => 226,
            "usage" => %{
              "contentPolicyUnits" => 1,
              "contextualGroundingPolicyUnits" => 0,
              "sensitiveInformationPolicyFreeUnits" => 1,
              "sensitiveInformationPolicyUnits" => 0,
              "topicPolicyUnits" => 1,
              "wordPolicyUnits" => 1
            }
          }
        }
      },
      "outputs" => [
        %{
          "cwogjhwjwlj0" => %{
            "invocationMetrics" => %{
              "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 320, "total" => 320}},
              "guardrailProcessingLatency" => 220,
              "usage" => %{
                "contentPolicyUnits" => 1,
                "contextualGroundingPolicyUnits" => 0,
                "sensitiveInformationPolicyFreeUnits" => 1,
                "sensitiveInformationPolicyUnits" => 1,
                "topicPolicyUnits" => 1,
                "wordPolicyUnits" => 1
              }
            }
          }
        }
      ]
    }
  },
  "contentType" => "application/json",
  "output" => %{
    "message" => %{
      "content" => [
        %{
          "text" => "申し訳ありませんが、ハッキングに関する情報を提供することはできません。ハッキングは違法行為であり、個人や組織のプライバシーを侵害し、法律に違反する行為です。そのような行為は倫理的にも許容されません。\n\nセキュリティを強化したい場合、正当な方法で行えば多くのリソースが利用可能です。例えば、セキュリティに関する教育を受ける、信頼できるセキュリティソフトウェアを使用する、定期的なソフトウェアの更新を行う、強力なパスワードを作成するなどの方法があります。\n\n法律を遵守し、他者の個人情報やシステムを侵害しないよう、責任ある行動を心がけることが重要です。もしセキュリティやプライバシーに関する質問がある場合は、専門家に相談することをお勧めします。"
        }
      ],
      "role" => "assistant"
    }
  },
  "stopReason" => "end_turn",
  "usage" => %{"inputTokens" => 9, "outputTokens" => 170, "totalTokens" => 179}
}
```

Amazon Nova Lite 自体が回答を拒んでいますが、ガードレールによるブロックは機能しませんでした

Amazon Bedrock Guardrail はまだ日本語に対応できていないようです

### トピックポリシーによるブロック（ヘルスケアに関する質問）

トピックポリシーで規制したヘルスケアに関する質問をしてみましょう

```elixir
{:ok, results, _} =
  client
  |> AWS.BedrockRuntime.invoke_model(
    model_id,
    %{
      "accept" => "application/json",
      "contentType" => "application/json",
      "messages" => [
        %{
          "role" => "user",
          "content" => [
            %{
              "text" => "What are healthy foods?"
            }
          ]
        }
      ],
      "guardrailIdentifier" => guardrail_id,
      "guardrailVersion" => version,
      "trace" => "ENABLED"
    }
  )

results
```

実行結果

```elixir
%{
  "amazon-bedrock-guardrailAction" => "INTERVENED",
  "amazon-bedrock-trace" => %{
    "guardrail" => %{
      "input" => %{
        "cwogjhwjwlj0" => %{
          "invocationMetrics" => %{
            "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 23, "total" => 23}},
            "guardrailProcessingLatency" => 227,
            "usage" => %{
              "contentPolicyUnits" => 1,
              "contextualGroundingPolicyUnits" => 0,
              "sensitiveInformationPolicyFreeUnits" => 1,
              "sensitiveInformationPolicyUnits" => 0,
              "topicPolicyUnits" => 1,
              "wordPolicyUnits" => 1
            }
          },
          "topicPolicy" => %{
            "topics" => [%{"action" => "BLOCKED", "name" => "Healthcare", "type" => "DENY"}]
          }
        }
      }
    }
  },
  "contentType" => "application/json",
  "output" => %{
    "message" => %{
      "content" => [
        %{"text" => "申し訳ありませんが、この質問に回答できません。"}
      ],
      "role" => "assistant"
    }
  },
  "usage" => %{}
}
```

"Healthcare" に関する話題だと判断され、ブロックされました

こちらも日本語で試してみましょう

```elixir
{:ok, results, _} =
  client
  |> AWS.BedrockRuntime.invoke_model(
    model_id,
    %{
      "accept" => "application/json",
      "contentType" => "application/json",
      "messages" => [
        %{
          "role" => "user",
          "content" => [
            %{
              "text" => "健康に良い食べ物は何ですか"
            }
          ]
        }
      ],
      "guardrailIdentifier" => guardrail_id,
      "guardrailVersion" => version,
      "trace" => "ENABLED"
    }
  )

results
```

実行結果

```elixir
%{
  "amazon-bedrock-guardrailAction" => "INTERVENED",
  "amazon-bedrock-trace" => %{
    "guardrail" => %{
      "input" => %{
        "cwogjhwjwlj0" => %{
          "invocationMetrics" => %{
            "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 13, "total" => 13}},
            "guardrailProcessingLatency" => 250,
            "usage" => %{
              "contentPolicyUnits" => 1,
              "contextualGroundingPolicyUnits" => 0,
              "sensitiveInformationPolicyFreeUnits" => 1,
              "sensitiveInformationPolicyUnits" => 0,
              "topicPolicyUnits" => 1,
              "wordPolicyUnits" => 1
            }
          }
        }
      },
      "modelOutput" => ["{\"output\":{\"message\":{\"content\":[{\"text\":\"健康に良い食べ物はたくさんありますが、以下のようなものは特に栄養価が高いとされています。\\n\\n1. **果物**: 抗酸化物質、ビタミン、ミネラル、食物繊維が豊富です。特にブルーベリー、キウイ、オレンジ、バナナ、リンゴなどが挙げられます。\\n\\n2. **野菜**: 特に葉物野菜（ほうれん草、ケール、spinach、ブロッコリーなど）は鉄分、カルシウム、ビタミンKが豊富です。根菜類（にんじん、かぼちゃなど）もβカロテンが豊富です。\\n\\n3. **全粒穀物**: 白米や白パンよりも、全粒穀物（玄米、オートミール、全粒粉などのパン）の方が食物繊維が多く、糖質の吸収を緩やかにします。\\n\\n4. **魚**: 特に脂肪の多い魚（サーモン、マグロ、サケなど）はオメガ3脂肪酸が豊富で、心臓の健康に良いです。\\n\\n5. **豆類**: インゲン、エンドウ、大豆製品（豆腐、納豆など）はタンパク質、鉄分、食物繊維が豊富です。\\n\\n6. **ナッツと種子**: 特にアーモンド、カシューナッツ、ヘーゼルナッツ、亜麻仁、チアシードなどは健康的な脂肪、タンパク質、食物繊維が豊富です。\\n\\n7. **低脂肪乳製品**: ヨーグルト、低脂肪チーズ、牛乳はカルシウム、ビタミンD、プロバイオティクスが豊富です。\\n\\n8. **健康的な油**: オリーブオイル、ごま油、亜麻仁油は健康的な脂肪酸が豊富です。\\n\\nこれらの食品をバランスよく摂取することで、健康を維持し、病気予防に役立ちます。ただし、食事は個人の体質や健康状態に合わせて調整することが大切です。\"}],\"role\":\"assistant\"}},\"stopReason\":\"end_turn\",\"usage\":{\"inputTokens\":8,\"outputTokens\":488,\"totalTokens\":496}}"],
      "outputs" => [
        %{
          "cwogjhwjwlj0" => %{
            "invocationMetrics" => %{
              "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 660, "total" => 660}},
              "guardrailProcessingLatency" => 225,
              "usage" => %{
                "contentPolicyUnits" => 1,
                "contextualGroundingPolicyUnits" => 0,
                "sensitiveInformationPolicyFreeUnits" => 1,
                "sensitiveInformationPolicyUnits" => 1,
                "topicPolicyUnits" => 1,
                "wordPolicyUnits" => 1
              }
            },
            "topicPolicy" => %{
              "topics" => [%{"action" => "BLOCKED", "name" => "Healthcare", "type" => "DENY"}]
            }
          }
        }
      ]
    }
  },
  "contentType" => "application/json",
  "output" => %{
    "message" => %{
      "content" => [
        %{"text" => "申し訳ありませんが、この質問に回答できません。"}
      ],
      "role" => "assistant"
    }
  },
  "stopReason" => "end_turn",
  "usage" => %{"inputTokens" => 8}
}
```

トピックポリシーの例で入力した質問そのものだったからか、日本語であってもちゃんとブロックしてくれました

少し質問を変えてみます

```elixir
{:ok, results, _} =
  client
  |> AWS.BedrockRuntime.invoke_model(
    model_id,
    %{
      "accept" => "application/json",
      "contentType" => "application/json",
      "messages" => [
        %{
          "role" => "user",
          "content" => [
            %{
              "text" => "朝食を食べることは健康に良いですか"
            }
          ]
        }
      ],
      "guardrailIdentifier" => guardrail_id,
      "guardrailVersion" => version,
      "trace" => "ENABLED"
    }
  )

results
```

実行結果

```elixir
%{
  "amazon-bedrock-guardrailAction" => "NONE",
  "amazon-bedrock-trace" => %{
    "guardrail" => %{
      "input" => %{
        "cwogjhwjwlj0" => %{
          "invocationMetrics" => %{
            "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 17, "total" => 17}},
            "guardrailProcessingLatency" => 270,
            "usage" => %{
              "contentPolicyUnits" => 1,
              "contextualGroundingPolicyUnits" => 0,
              "sensitiveInformationPolicyFreeUnits" => 1,
              "sensitiveInformationPolicyUnits" => 0,
              "topicPolicyUnits" => 1,
              "wordPolicyUnits" => 1
            }
          }
        }
      },
      "outputs" => [
        %{
          "cwogjhwjwlj0" => %{
            "invocationMetrics" => %{
              "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 561, "total" => 561}},
              "guardrailProcessingLatency" => 255,
              "usage" => %{
                "contentPolicyUnits" => 1,
                "contextualGroundingPolicyUnits" => 0,
                "sensitiveInformationPolicyFreeUnits" => 1,
                "sensitiveInformationPolicyUnits" => 1,
                "topicPolicyUnits" => 1,
                "wordPolicyUnits" => 1
              }
            }
          }
        }
      ]
    }
  },
  "contentType" => "application/json",
  "output" => %{
    "message" => %{
      "content" => [
        %{
          "text" => "はい、朝食を食べることは多くの健康上の利点があります。以下にいくつかの主要な利点を挙げます：\n\n1. **エネルギーの供給**：朝食は体にエネルギーを与え、一日中の活動や集中力に必要です。空腹で過ごすことは、集中力の低下や疲労感につながる可能性があります。\n\n2. **代謝の促進**：朝食を食べることで、代謝が活発化し、一日中の新陳代謝をサポートします。\n\n3. **血糖値の安定**：バランスの取れた朝食は血糖値の急激な上昇を防ぎ、エネルギーの安定した供給を保ちます。\n\n4. **体重管理**：朝食を食べることは、一日を通しての食欲をコントロールし、過食を防ぐことができます。研究によると、朝食を食べる人は朝食をスキップする人よりも体重を管理しやすい傾向があります。\n\n5. **栄養摂取の改善**：朝食は栄養素の摂取の良い機会です。果物、野菜、全粒穀物、タンパク質を含むバランスの取れた朝食は、一日の必要なビタミンやミネラルの摂取に役立ちます。\n\n6. **心理的利点**：朝食は、一日を始めるモチベーションを高め、心理的な安定をもたらすことができます。\n\nただし、朝食の質も重要です。過度に高カロリーで、糖分や不飽和脂肪が多い食品は、健康に良くない可能性があります。バランスの取れた、栄養価の高い朝食が最適です。"
        }
      ],
      "role" => "assistant"
    }
  },
  "stopReason" => "end_turn",
  "usage" => %{"inputTokens" => 10, "outputTokens" => 388, "totalTokens" => 398}
}
```

残念ながら、ブロックできませんでした

やはり日本語に対して話題の規制を設けるのは難しいようです

### 単語によるブロック

単語として規制対象に設定した「りんご」を入力に含めます

```elixir
{:ok, results, _} =
  client
  |> AWS.BedrockRuntime.invoke_model(
    model_id,
    %{
      "accept" => "application/json",
      "contentType" => "application/json",
      "messages" => [
        %{
          "role" => "user",
          "content" => [
            %{
              "text" => "りんごの栽培方法を教えて"
            }
          ]
        }
      ],
      "guardrailIdentifier" => guardrail_id,
      "guardrailVersion" => version,
      "trace" => "ENABLED"
    }
  )

results
```

実行結果

```elixir
%{
  "amazon-bedrock-guardrailAction" => "INTERVENED",
  "amazon-bedrock-trace" => %{
    "guardrail" => %{
      "input" => %{
        "cwogjhwjwlj0" => %{
          "invocationMetrics" => %{
            "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 12, "total" => 12}},
            "guardrailProcessingLatency" => 312,
            "usage" => %{
              "contentPolicyUnits" => 1,
              "contextualGroundingPolicyUnits" => 0,
              "sensitiveInformationPolicyFreeUnits" => 1,
              "sensitiveInformationPolicyUnits" => 0,
              "topicPolicyUnits" => 1,
              "wordPolicyUnits" => 1
            }
          },
          "wordPolicy" => %{"customWords" => [%{"action" => "BLOCKED", "match" => "りんご"}]}
        }
      }
    }
  },
  "contentType" => "application/json",
  "output" => %{
    "message" => %{
      "content" => [
        %{"text" => "申し訳ありませんが、この質問に回答できません。"}
      ],
      "role" => "assistant"
    }
  },
  "usage" => %{}
}
```

入力に含まれる「りんご」でブロックされ、生成 AI モデルの呼び出しも行われませんでした

出力に「りんご」が含まれるような質問をしてみます

```elixir
{:ok, results, _} =
  client
  |> AWS.BedrockRuntime.invoke_model(
    model_id,
    %{
      "accept" => "application/json",
      "contentType" => "application/json",
      "messages" => [
        %{
          "role" => "user",
          "content" => [
            %{
              "text" => "バラ科の赤い果物をひらがなで５つ教えて"
            }
          ]
        }
      ],
      "guardrailIdentifier" => guardrail_id,
      "guardrailVersion" => version,
      "trace" => "ENABLED"
    }
  )

results
```

実行結果

```elixir
%{
  "amazon-bedrock-guardrailAction" => "INTERVENED",
  "amazon-bedrock-trace" => %{
    "guardrail" => %{
      "input" => %{
        "cwogjhwjwlj0" => %{
          "invocationMetrics" => %{
            "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 19, "total" => 19}},
            "guardrailProcessingLatency" => 286,
            "usage" => %{
              "contentPolicyUnits" => 1,
              "contextualGroundingPolicyUnits" => 0,
              "sensitiveInformationPolicyFreeUnits" => 1,
              "sensitiveInformationPolicyUnits" => 0,
              "topicPolicyUnits" => 1,
              "wordPolicyUnits" => 1
            }
          }
        }
      },
      "modelOutput" => ["{\"output\":{\"message\":{\"content\":[{\"text\":\"バラ科の赤い果物にはたくさんの種類がありますが、ひらがなで５つ紹介しますね：\\n\\n1. **もも**（桃）\\n2. **いちご**（苺）\\n3. **さくらんぼ**（桜桃）\\n4. **すもも**（杏）\\n5. **りんご**（リンゴ）\\n\\nこれらの果物はバラ科に属し、赤いものが多いです。\"}],\"role\":\"assistant\"}},\"stopReason\":\"end_turn\",\"usage\":{\"inputTokens\":17,\"outputTokens\":96,\"totalTokens\":113}}"],
      "outputs" => [
        %{
          "cwogjhwjwlj0" => %{
            "invocationMetrics" => %{
              "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 139, "total" => 139}},
              "guardrailProcessingLatency" => 226,
              "usage" => %{
                "contentPolicyUnits" => 1,
                "contextualGroundingPolicyUnits" => 0,
                "sensitiveInformationPolicyFreeUnits" => 1,
                "sensitiveInformationPolicyUnits" => 1,
                "topicPolicyUnits" => 1,
                "wordPolicyUnits" => 1
              }
            },
            "wordPolicy" => %{"customWords" => [%{"action" => "BLOCKED", "match" => "りんご"}]}
          }
        }
      ]
    }
  },
  "contentType" => "application/json",
  "output" => %{
    "message" => %{
      "content" => [
        %{"text" => "申し訳ありませんが、この質問に回答できません。"}
      ],
      "role" => "assistant"
    }
  },
  "stopReason" => "end_turn",
  "usage" => %{"inputTokens" => 17}
}
```

生成 AI の出力に「りんご」が含まれるため、ブロックされました

### 機密情報のマスキング

機密情報として「ADDRESS」を指定しているので、住所を出力させてみます

```elixir
{:ok, results, _} =
  client
  |> AWS.BedrockRuntime.invoke_model(
    model_id,
    %{
      "accept" => "application/json",
      "contentType" => "application/json",
      "messages" => [
        %{
          "role" => "user",
          "content" => [
            %{
              "text" => "最高裁判所の住所を教えて"
            }
          ]
        }
      ],
      "guardrailIdentifier" => guardrail_id,
      "guardrailVersion" => version,
      "trace" => "ENABLED"
    }
  )

results
```

実行結果

```elixir
%{
  "amazon-bedrock-guardrailAction" => "INTERVENED",
  "amazon-bedrock-trace" => %{
    "guardrail" => %{
      "input" => %{
        "cwogjhwjwlj0" => %{
          "invocationMetrics" => %{
            "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 12, "total" => 12}},
            "guardrailProcessingLatency" => 249,
            "usage" => %{
              "contentPolicyUnits" => 1,
              "contextualGroundingPolicyUnits" => 0,
              "sensitiveInformationPolicyFreeUnits" => 1,
              "sensitiveInformationPolicyUnits" => 0,
              "topicPolicyUnits" => 1,
              "wordPolicyUnits" => 1
            }
          }
        }
      },
      "modelOutput" => ["{\"output\":{\"message\":{\"content\":[{\"text\":\"日本の最高裁判所は、東京に所在しています。具体的な住所は以下の通りです：\\n\\n最高裁判所\\n東京都千代田区二番町19番地\\n\\nこの住所は、東京の中心部に位置し、法曹界や司法関連の施設に近接しています。また、アクセスについては、最寄りの東京メトロの駅（大手町駅や神田駅など）から徒歩圏内に位置しています。\"}],\"role\":\"assistant\"}},\"stopReason\":\"end_turn\",\"usage\":{\"inputTokens\":10,\"outputTokens\":97,\"totalTokens\":107}}"],
      "outputs" => [
        %{
          "cwogjhwjwlj0" => %{
            "invocationMetrics" => %{
              "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 148, "total" => 148}},
              "guardrailProcessingLatency" => 344,
              "usage" => %{
                "contentPolicyUnits" => 1,
                "contextualGroundingPolicyUnits" => 0,
                "sensitiveInformationPolicyFreeUnits" => 1,
                "sensitiveInformationPolicyUnits" => 1,
                "topicPolicyUnits" => 1,
                "wordPolicyUnits" => 1
              }
            },
            "sensitiveInformationPolicy" => %{
              "piiEntities" => [
                %{
                  "action" => "ANONYMIZED",
                  "match" => "東京都千代田区二番町19番地",
                  "type" => "ADDRESS"
                }
              ]
            }
          }
        }
      ]
    }
  },
  "contentType" => "application/json",
  "output" => %{
    "message" => %{
      "content" => [
        %{
          "text" => "日本の最高裁判所は、東京に所在しています。具体的な住所は以下の通りです：\n\n最高裁判所\n{ADDRESS}\n\nこの住所は、東京の中心部に位置し、法曹界や司法関連の施設に近接しています。また、アクセスについては、最寄りの東京メトロの駅（大手町駅や神田駅など）から徒歩圏内に位置しています。"
        }
      ],
      "role" => "assistant"
    }
  },
  "stopReason" => "end_turn",
  "usage" => %{"inputTokens" => 10}
}
```

住所が匿名化されました

最終的な出力を表示してみます

```elixir
results
|> Map.get("output")
|> Map.get("message")
|> Map.get("content")
|> hd()
|> Map.get("text")
|> Kino.Markdown.new()
```

![スクリーンショット 2025-01-01 11.30.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/917fb551-26f5-b6df-a418-538b93fcf165.png)

住所部分が `{ADDRESS}` になっています

機密情報については日本語にも対応できているようです

### 正規表現によるブロック

正規表現で規制した "[0-9]%" に該当する出力を誘います

```elixir
{:ok, results, _} =
  client
  |> AWS.BedrockRuntime.invoke_model(
    model_id,
    %{
      "accept" => "application/json",
      "contentType" => "application/json",
      "messages" => [
        %{
          "role" => "user",
          "content" => [
            %{
              "text" => "サイコロで1が出る確率は何%ですか"
            }
          ]
        }
      ],
      "guardrailIdentifier" => guardrail_id,
      "guardrailVersion" => version,
      "trace" => "ENABLED"
    }
  )

results
```

実行結果

```elixir
%{
  "amazon-bedrock-guardrailAction" => "INTERVENED",
  "amazon-bedrock-trace" => %{
    "guardrail" => %{
      "input" => %{
        "cwogjhwjwlj0" => %{
          "invocationMetrics" => %{
            "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 17, "total" => 17}},
            "guardrailProcessingLatency" => 254,
            "usage" => %{
              "contentPolicyUnits" => 1,
              "contextualGroundingPolicyUnits" => 0,
              "sensitiveInformationPolicyFreeUnits" => 1,
              "sensitiveInformationPolicyUnits" => 0,
              "topicPolicyUnits" => 1,
              "wordPolicyUnits" => 1
            }
          }
        }
      },
      "modelOutput" => ["{\"output\":{\"message\":{\"content\":[{\"text\":\"サイコロは通常、6面があり、各面には1から6までの数字が書かれています。これらの面はすべて等しい大きさで、均等に振られると仮定します。\\n\\nサイコロの出目は1, 2, 3, 4, 5, 6の6種類があり、それぞれ出現する確率は等しいと仮定されます。したがって、1が出る確率はすべての出目の中で1が出る面の数をその総数の比とします。\\n\\n1が出る確率は \\\\( \\\\frac{1}{6} \\\\) です。\\n\\nこれをパーセントに変換するには、\\\\( \\\\frac{1}{6} \\\\) を100で割ります。\\n\\n\\\\[ \\\\frac{1}{6} \\\\times 100 \\\\approx 16.67\\\\% \\\\]\\n\\nですので、サイコロで1が出る確率は約16.67%です。\"}],\"role\":\"assistant\"}},\"stopReason\":\"end_turn\",\"usage\":{\"inputTokens\":12,\"outputTokens\":204,\"totalTokens\":216}}"],
      "outputs" => [
        %{
          "cwogjhwjwlj0" => %{
            "invocationMetrics" => %{
              "guardrailCoverage" => %{"textCharacters" => %{"guarded" => 316, "total" => 316}},
              "guardrailProcessingLatency" => 220,
              "usage" => %{
                "contentPolicyUnits" => 1,
                "contextualGroundingPolicyUnits" => 0,
                "sensitiveInformationPolicyFreeUnits" => 1,
                "sensitiveInformationPolicyUnits" => 1,
                "topicPolicyUnits" => 1,
                "wordPolicyUnits" => 1
              }
            },
            "sensitiveInformationPolicy" => %{
              "regexes" => [
                %{
                  "action" => "BLOCKED",
                  "match" => "7%",
                  "name" => "Percentage",
                  "regex" => "[0-9]%"
                }
              ]
            }
          }
        }
      ]
    }
  },
  "contentType" => "application/json",
  "output" => %{
    "message" => %{
      "content" => [
        %{"text" => "申し訳ありませんが、この質問に回答できません。"}
      ],
      "role" => "assistant"
    }
  },
  "stopReason" => "end_turn",
  "usage" => %{"inputTokens" => 12}
}
```

出力に "[0-9]%" に該当する箇所があったため、ブロックされました

## まとめ

Amazon Bedrock Guardrails により、生成 AI の回答を制御することができました

ただし、コンテンツやトピックに関するガードレールは日本語に対応していません

単語や正規表現などを使って規制しましょう