Amazon Bedrock の ApplyGuardrail API を使用し、モデルの回答に対してハルシネーションを含むか評価する

# はじめに

https://aws.amazon.com/jp/blogs/news/guardrails-for-amazon-bedrock-can-now-detect-hallucinations-and-safeguard-apps-built-using-custom-or-third-party-fms/

先月Amazon Bedrockにおいて、`ApplyGuardrail` API が使用可能になったのでキャッチアップとして試してみます。
本記事では ApplyGuardrailを使用した処理を実装していきます。


# ハルシネーションとは？
生成AIが事実と異なる、あるいは与えられた文脈と矛盾するテキストを生成する現象の事。

参考：https://docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/reduce-hallucinations



# ゴール
`ApplyGuardrail`を使用し、生成AIの回答結果に対してハルシネーションの度合いを数値で評価する


# 環境
- Python：3.12
- Bedrock（claude-v2:1）

# Guardrailの作成

今回はGrounding、Relevanceどちらもスコアはデフォルトの`0.7`で設定しガードレールを作成します。
- Grounding：モデルのレスポンスがソースに基づいているかのスコア
- Relevance：モデルのレスポンスがユーザの入力値とどれくらい関連しているかのスコア


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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/ec56fde6-a3da-5e1c-66b3-4150280f50c7.png)



# Lambdaの作成

今回は下記のようにLambdaのローカル環境に参照ソースファイル（ref.txt）を配置し、
それを読み込ませる形でLambdaを作成していきます。
フォルダ構造は下記の通りです。
```
invoke_bedrock_guardrails
├── lambda_function.py   # メインのLambda関数
├── guardrail_utils.py   # apply_guardrail関数を含むファイル
├── file_utils.py        # 参照ソースを読み込む処理のファイル
└── ref.txt              # 参照ソースファイル
```

## 参照ソース

ref.txt の内容は次の通りです。

```:ref.txt
• 段ボールの中には 15 個のボールがあります。
• 段ボールの中の小さな箱には 30 個のチョコレートが入っています。
• 段ボールの中にあるペンの本数は 25 本です。
• 段ボールに入っているのは 40 枚の紙です。
• 段ボールには 10 個のりんごが詰まっています。
```

## ソースコード

参照ソースの内容とユーザのプロンプトを取得し、モデル（Claude）にリクエストを投げています。
モデルから返ってきたレスポンスを apply_guardrail の引数として渡し、ApplyGuardrail で評価を行います。
詳細はコメントアウトの通りです。

```python:lambda_function.py
import json
import os
import logging
import boto3
from guardrail_utils import apply_guardrail
from file_utils import read_resource_file

logger = logging.getLogger()
logger.setLevel(logging.INFO)
bedrock_runtime = boto3.client('bedrock-runtime', region_name=os.getenv('MODEL_REGION'))
model_id=os.getenv('MODEL_ID')

def lambda_handler(event, context):
    guardrail_id = os.environ.get('GUARDRAIL_ID')
    guardrail_version = os.environ.get('GUARDRAIL_VERSION')
    
    # 参照ソースの読み込み
    knowledge_base = read_resource_file()
    # イベントからユーザーのプロンプトを取得
    user_prompt = event.get('prompt', '')
    
    logger.info(f"User Prompt: {user_prompt}")
    logger.info(f"Knowledge Base: {knowledge_base}")
    
    # プロンプト
    formatted_prompt = f"\n\nHuman: 以下の情報に基づいて回答してください:\n{knowledge_base}\n\n{user_prompt}\n\nAssistant:"
    
    # モデル（Claude）の呼び出し
    model_response = bedrock_runtime.invoke_model(
        modelId=model_id,
        body=json.dumps({
            "prompt": formatted_prompt,
            "max_tokens_to_sample": 500,
            "temperature": 0.5,
        })
    )
    
    # モデルの出力を取得
    model_output = json.loads(model_response['body'].read())['completion']
    logger.info(f"Model Output: {model_output}")
    
    # モデルの出力に対して Guardrail の適用
    guardrail_response = apply_guardrail(bedrock_runtime, guardrail_id, guardrail_version, user_prompt, knowledge_base, model_output)
    
    return {
        'statusCode': 200,
        'body': json.dumps({
            'model_output': model_output,
            'guardrail_response': guardrail_response
        })
    }
```

参照するソースファイル（ref.txt）の読み込みます。

```python:file_utils.py
def read_resource_file():
    with open('ref.txt', 'r') as file:
        return file.read()
```

モデルの回答に対してapply_guardrailで評価を行います。


```python:guardrail_utils.py
import json
import logging
import boto3

logger = logging.getLogger()

def apply_guardrail(bedrock_runtime, guardrail_id, guardrail_version, user_prompt, grounding_source, model_output):
    # ユーザの入力値、参照ソース、モデルの回答を設定
    content = [
        {"text": {"text": user_prompt, "qualifiers": ["query"]}},
        {"text": {"text": grounding_source, "qualifiers": ["grounding_source"]}},
        {"text": {"text": model_output}}
    ]
    
    # モデル出力に対してGuardrailを適用
    response = bedrock_runtime.apply_guardrail(
        guardrailIdentifier=guardrail_id,
        guardrailVersion=guardrail_version,
        source='OUTPUT',
        content=content
    )
    
    logger.info(f"ApplyGuardrail Response: {json.dumps(response, indent=2)}")
    
    return response
```

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

https://aws.amazon.com/jp/blogs/news/guardrails-for-amazon-bedrock-can-now-detect-hallucinations-and-safeguard-apps-built-using-custom-or-third-party-fms/

:::note info

> モデル出力を評価するには、ソースを OUTPUT に設定する必要があります。
[参考リンク](https://aws.amazon.com/jp/blogs/news/guardrails-for-amazon-bedrock-can-now-detect-hallucinations-and-safeguard-apps-built-using-custom-or-third-party-fms/#:~:text=%E3%83%A2%E3%83%87%E3%83%AB%E5%87%BA%E5%8A%9B%E3%82%92,%E3%81%8C%E3%81%82%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82)
```python
source='OUTPUT'
```
:::

# 動作確認

ユーザのプロンプト例として段ボールの中身を聞いてみます。

```json:イベントJSON
{
  "prompt": "段ボールの中身について教えてください。"
}
```

Lambdaをテスト実行してみると下記のようなレスポンスが返ってきました。
- threshold：contextual grounding フィルターの閾値
- score：contextual grounding フィルターでチェックされたスコア
- action：contextual grounding フィルターによって実行されるアクション。
threshold の値を score が上回っていれば `NONE`、下回っていれば `BLOCKED`。

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

```json
～～～
"action": "NONE",
  "outputs": [],
  "assessments": [
    {
      "contextualGroundingPolicy": {
        "filters": [
          {
            "type": "GROUNDING",
            "threshold": 0.7,
            "score": 0.98,
            "action": "NONE"
          },
          {
            "type": "RELEVANCE",
            "threshold": 0.7,
            "score": 1.0,
            "action": "NONE"
          }
        ]
      }
    }
  ]
～～～
```

ハルシネーションを起こしそうな下記の内容でユーザのプロンプトを入力してみます。

```json:イベントJSON
{
  "prompt": "段ボールの中のスマホの数を想像でよいのでざっくり教えて。それ以外の部分は答えないでください。"
}
```
再度Lambdaのテスト実行をしてみると、`score`が0.7を下回り`0.13`と下がりました。
actionも`BLOCKED`になり、`outputs[text]`にガードレールがブロックした際のメッセージが入ってる事が確認できました。


```json
～～～
  "outputs": [
    {
      "text": "Sorry, the model cannot answer this question."
    }
  ],
  "assessments": [
    {
      "contextualGroundingPolicy": {
        "filters": [
          {
            "type": "GROUNDING",
            "threshold": 0.7,
            "score": 0.13,
            "action": "BLOCKED"
          },
          {
            "type": "RELEVANCE",
            "threshold": 0.7,
            "score": 0.93,
            "action": "NONE"
          }
        ]
      }
    }
  ]
  ～～～
  ```

:::note info

ガードレールがブロックした際のメッセージをマネジメントコンソールから確認する際は、
Amazon Bedrock > ガードレール > 作成したガードレール名 > 作業中のドラフト下部のブロックされたメッセージから確認できます。
メッセージの変更は`編集`ボタンから行えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/b7e8ba18-2501-e944-f031-5e3fd7457b68.png)

:::


# 終わりに
本記事ではAmazon Bedrock の ApplyGuardrail API を使用し、
モデルの回答にハルシネーションがあるか評価する処理を実装してみました。
本機能を利用する事で、GROUNDINGフィルターでチェックされたスコアによりハルシネーションを含むか確認できるため、誤った回答をユーザに案内してしまうケースを減らせそうです。


# 参考

https://aws.amazon.com/jp/blogs/news/guardrails-for-amazon-bedrock-can-now-detect-hallucinations-and-safeguard-apps-built-using-custom-or-third-party-fms/

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

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

https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ApplyGuardrail.html#API_runtime_ApplyGuardrail_RequestSyntax

https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_GuardrailTextBlock.html