Guardrails for Amazon Bedrock の Contextual Grounding を試す

## 最後の機能である Contextual Grounding

他の機能については以下をご参照ください。

https://qiita.com/kazuneet/items/89aeca6d8884c543daec

https://qiita.com/kazuneet/items/753f63c89739660be500

https://qiita.com/kazuneet/items/b7a26c4be197de23063e

https://qiita.com/kazuneet/items/14968391562ccfe666dd

[Contextual Grounding の詳細はこちら](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-contextual-grounding-check.html)ですが、RAG でのハルシネーションのチェック機能で利用します。

これについては実際に見たほうが速いのでとっととやります。

### Contextual Grounding のガードレールの作成

さて、いきなり問題が。

[2024/8/16 時点で CDK で Contextual Grounding が対応していません](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_bedrock.CfnGuardrail.html)。

[CDK の PR を見てみるとマージ済](https://github.com/aws/aws-cdk/pull/31025)になっているので、近いうちには出てくると思いますが…。

ということで、しかたなく boto3 から実行します。

boto3 で以下 API を叩きます。

```python:test.ipynb
import boto3
from pprint import pprint
br = boto3.client('bedrock')
response = br.create_guardrail(
    name='contextual-grounding',
    contextualGroundingPolicyConfig={
        'filtersConfig': [
            {
                'type': 'GROUNDING',
                'threshold': 0.5
            },
            {
                'type': 'RELEVANCE',
                'threshold': 0.5
            },
        ]
    },
    blockedInputMessaging='そんなの入れちゃダメぇぇぇ！！',
    blockedOutputsMessaging='変なの出ちゃぅぅ！！',
)
guardrail_id = response['guardrailId']
response = br.create_guardrail_version(
    guardrailIdentifier=guardrail_id,
)
version = response['version']
```

これでガードレール及びバージョンの作成が完了です。

### 試してみる

データを４パターン用意してみました。

```python:test.ipynb
contents = [
    # 合っている場合
    [
        {
        'text': {
            'text': 'イギリスの首都はロンドンです。日本の首都は東京です。', 
            'qualifiers': ['grounding_source'] # 検索結果（とする）
        }
        },
        {
            'text': {
                'text': '日本の首都はどこですか？',
                'qualifiers': ['query'] # ユーザーの質問
            }
        },
        {
            'text': {
                'text': '東京です。' # 生成結果
            }
        },
    ],
    # 検索結果がおかしかった場合
    [
        {
        'text': {
            'text': 'イギリスの首都はロンドンです。フランスの首都はパリです。', 
            'qualifiers': ['grounding_source']
        }
        },
        {
            'text': {
                'text': '日本の首都はどこですか？',
                'qualifiers': ['query']
            }
        },
        {
            'text': {
                'text': '東京です。'
            }
        },
    ],
    # 生成結果がおかしかった場合
    [
        {
        'text': {
            'text': 'イギリスの首都はロンドンです。日本の首都は東京です。', 
            'qualifiers': ['grounding_source']
        }
        },
        {
            'text': {
                'text': '日本の首都はどこですか？',
                'qualifiers': ['query']
            }
        },
        {
            'text': {
                'text': '江戸です。'
            }
        },
    ],
    # 何もかもがおかしかった場合
    [
        {
        'text': {
            'text': 'イギリスの首都はロンドンです。フランスの首都はパリです。', 
            'qualifiers': ['grounding_source']
        }
        },
        {
            'text': {
                'text': '日本の首都はどこですか？',
                'qualifiers': ['query']
            }
        },
        {
            'text': {
                'text': '江戸です。'
            }
        },
    ],
]
```

最初の QA 以外はすべて誤りです。
実行してみましょう。

```python:test.ipynb
for content in contents:
    response = brt.apply_guardrail(
        guardrailIdentifier=guardrail_id,
        guardrailVersion=version,
        source='OUTPUT',
        content=content
    )
    pprint(response['assessments'][0])
```
```python:出力
{'contextualGroundingPolicy': {'filters': [{'action': 'NONE',
                                            'score': 0.76,
                                            'threshold': 0.5,
                                            'type': 'GROUNDING'},
                                           {'action': 'NONE',
                                            'score': 0.61,
                                            'threshold': 0.5,
                                            'type': 'RELEVANCE'}]}}
{'contextualGroundingPolicy': {'filters': [{'action': 'NONE',
                                            'score': 0.76,
                                            'threshold': 0.5,
                                            'type': 'GROUNDING'},
                                           {'action': 'NONE',
                                            'score': 0.61,
                                            'threshold': 0.5,
                                            'type': 'RELEVANCE'}]}}
{'contextualGroundingPolicy': {'filters': [{'action': 'NONE',
                                            'score': 0.76,
                                            'threshold': 0.5,
                                            'type': 'GROUNDING'},
                                           {'action': 'NONE',
                                            'score': 0.61,
                                            'threshold': 0.5,
                                            'type': 'RELEVANCE'}]}}
{'contextualGroundingPolicy': {'filters': [{'action': 'NONE',
                                            'score': 0.76,
                                            'threshold': 0.5,
                                            'type': 'GROUNDING'},
                                           {'action': 'NONE',
                                            'score': 0.61,
                                            'threshold': 0.5,
                                            'type': 'RELEVANCE'}]}}
```
うーん、全部同じスコアが出てしまいましたね…。
英語で試してみましょう。

```python:test.ipynb
contents = [
    # 合っている場合
    [
        {
        'text': {
            'text': 'The capital of the United Kingdom is London. The capital of Japan is Tokyo.', 
            'qualifiers': ['grounding_source'] # 検索結果（とする）
        }
        },
        {
            'text': {
                'text': 'What is the capital of Japan?',
                'qualifiers': ['query'] # ユーザーの質問
            }
        },
        {
            'text': {
                'text': 'Tokyo' # 生成結果
            }
        },
    ],
    # 検索結果がおかしかった場合
    [
        {
        'text': {
            'text': 'The capital of the United Kingdom is London. The capital of France is Paris.', 
            'qualifiers': ['grounding_source']
        }
        },
        {
            'text': {
                'text': 'What is the capital of Japan?',
                'qualifiers': ['query']
            }
        },
        {
            'text': {
                'text': 'Tokyo'
            }
        },
    ],
    # 生成結果がおかしかった場合
    [
        {
        'text': {
            'text': 'The capital of the United Kingdom is London. The capital of Japan is Tokyo.', 
            'qualifiers': ['grounding_source']
        }
        },
        {
            'text': {
                'text': 'What is the capital of Japan?',
                'qualifiers': ['query']
            }
        },
        {
            'text': {
                'text': 'Edo'
            }
        },
    ],
    # 何もかもがおかしかった場合
    [
        {
        'text': {
            'text': 'The capital of the United Kingdom is London. The capital of France is Paris', 
            'qualifiers': ['grounding_source']
        }
        },
        {
            'text': {
                'text': 'What is the capital of Japan?',
                'qualifiers': ['query']
            }
        },
        {
            'text': {
                'text': 'Edo'
            }
        },
    ],
]
for content in contents:
    response = brt.apply_guardrail(
        guardrailIdentifier=guardrail_id,
        guardrailVersion=version,
        source='OUTPUT',
        content=content
    )
    pprint(response['assessments'][0])
```
```python:出力
{'contextualGroundingPolicy': {'filters': [{'action': 'NONE',
                                            'score': 1.0,
                                            'threshold': 0.5,
                                            'type': 'GROUNDING'},
                                           {'action': 'NONE',
                                            'score': 1.0,
                                            'threshold': 0.5,
                                            'type': 'RELEVANCE'}]}}
{'contextualGroundingPolicy': {'filters': [{'action': 'BLOCKED',
                                            'score': 0.14,
                                            'threshold': 0.5,
                                            'type': 'GROUNDING'},
                                           {'action': 'NONE',
                                            'score': 1.0,
                                            'threshold': 0.5,
                                            'type': 'RELEVANCE'}]}}
{'contextualGroundingPolicy': {'filters': [{'action': 'BLOCKED',
                                            'score': 0.0,
                                            'threshold': 0.5,
                                            'type': 'GROUNDING'},
                                           {'action': 'NONE',
                                            'score': 0.94,
                                            'threshold': 0.5,
                                            'type': 'RELEVANCE'}]}}
{'contextualGroundingPolicy': {'filters': [{'action': 'BLOCKED',
                                            'score': 0.0,
                                            'threshold': 0.5,
                                            'type': 'GROUNDING'},
                                           {'action': 'NONE',
                                            'score': 0.99,
                                            'threshold': 0.5,
                                            'type': 'RELEVANCE'}]}}
```
さて、最初のパターンから見ていきましょう。

日本の首都はどこですか？に対して、日本の首都は東京であるという検索結果から、東京、という回答を生成したので、GROUNDING, RELEVANCE 共に 1.0 というスコアで通りました。

2 番目のパターンです。
日本の首都はどこですか？に対して、イギリスとフランスの首都しか検索結果に含まれないにもかかわらず、東京、という回答を生成しました。
GROUNDING(検索結果)を使った回答をしていないため、GROUNDING が0.14 という低スコアでブロックされました（しきい値はガードレールを作成した時に設定した0.5にしてあります）。


3 番目のパターンです。
日本の首都はどこですか？に対して、日本の首都は東京であるという検索結果があるにもかかわらず、江戸と回答を生成しました。
やはりこれもGROUNDING(検索結果)を使った回答をしていないため、GROUNDING が0 というスコアでブロックされました。

4 番目も同様です。

しかし、ここまで GROUNDING のチェックしかできなかったので RELEVANCE を試してみましょう。

```python:test.ipynb
contents = [
    # 合っている場合
    [
        {
        'text': {
            'text': 'The capital of the United Kingdom is London. The capital of Japan is Tokyo.', 
            'qualifiers': ['grounding_source'] # 検索結果（とする）
        }
        },
        {
            'text': {
                'text': 'What did you eat last lunch?',
                'qualifiers': ['query'] # ユーザーの質問
            }
        },
        {
            'text': {
                'text': 'Tokyo' # 生成結果
            }
        },
    ],
]
for content in contents:
    response = brt.apply_guardrail(
        guardrailIdentifier=guardrail_id,
        guardrailVersion=version,
        source='OUTPUT',
        content=content
    )
    pprint(response['assessments'][0])
```

```python:text
{'contextualGroundingPolicy': {'filters': [{'action': 'NONE',
                                            'score': 0.53,
                                            'threshold': 0.5,
                                            'type': 'GROUNDING'},
                                           {'action': 'BLOCKED',
                                            'score': 0.25,
                                            'threshold': 0.5,
                                            'type': 'RELEVANCE'}]}}
```
昼何食べた？という質問に対して首都の検索結果という若干頭がバグる検索結果を出し、その検索結果から東京という回答を生成した場合、質問に対して検索結果が関係ないため、RELEVANCE が 0.25 でブロックされました。

しかし、首都は外国の首都でも首都の検索結果を出しているので、スコアが高めに出されていたようですね。

あとはしきい値の調整でがんばる領域ではありますがとりあえず機能することがわかりました。

## まとめ
* Contextual Grounding は2024/8/16 現在 CDK に対応していない
* Contextual Grounding は2024/8/16 現在日本語に対応していない
* Contextual Grounding は英語だとそれなりに機能する

ちゃんちゃん。