BedrockのConverse APIのTool useを使ってRAG評価のテストデータを生成する

前回、以下の投稿でテストデータの生成を行いました。

https://qiita.com/moritalous/items/86aef6d16605333f3d5a

ただ、質問に英語が混じったり、「はい、わかりました。以下の3つの質問を用意しました。」的な不要な出力が含まれたりしました。

代替方法として、BedrockのConverse APIで利用可能なTool useを使ってみました。

## Tool useとは

Tool useは、いわゆるFunction Callingです。ツール（関数）のスキーマ情報をBedrockにわたすことで、どのツールを呼び出すかを生成AIが考え、ツール呼び出しのパラメーターを返却（呼び出し元からすると取得）するものです。

とりあえずやってみた　の投稿はこちらを参照ください。

https://qiita.com/moritalous/items/8b1a15a7dc583fa3a2e1

公式ドキュメントはこちらです。

https://docs.aws.amazon.com/bedrock/latest/userguide/tool-use.html

## Tool useでテストデータを生成する

やりたいことは、

1. まとまった文章を渡す
1. 文章から考えられる質問と回答のペアを取得する

これだけです。

### ポイント１：toolsを定義

Tool useなので、ツールを定義します。といっても、特に難しくもなく、質問と回答を受け取りたいことを伝えた程度です。

```json:toolSpec
 {
    "toolSpec": {
        "name": "create_answer",
        "description": "Create answer",
        "inputSchema": {
            "json": {
                "type": "object",
                "properties": {
                    "question": {
                        "type": "string",
                        "description": "Question",
                    },
                    "answer": {
                        "type": "string",
                        "description": "Answer.",
                    },
                },
                "required": [
                    "question",
                    "answer",
                ],
            }
        },
    }
}
```

### ポイント２：「toolChoice」の使用

Claude 3モデルの場合は、「toolChoice」という項目を指定することで、必ず指定したツールを呼び出してくれます。

```python
toolConfig = {
    "tools": [
        {
            "toolSpec": {
                ...
            }
        }
    ],
    "toolChoice": {"tool": {"name": "create_answer"}},
}
```

## ポイント３：プロンプト

ほとんど凝ってないですが、以下のプロンプトを使用しました。
{context}の部分に、質問と回答を生成したい対象の文章を含めます。

```text:プロンプト
以下のドキュメントをもとに、質問と回答を生成し、ツールを呼び出してください。必ずドキュメントに沿った質問と回答を生成してください。<document>{context}</document>
```


最終的に、コードはこんな感じになりました。

```python
import boto3

bedrock_client = boto3.client("bedrock-runtime")

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

toolConfig = {
    "tools": [
        {
            "toolSpec": {
                "name": "create_answer",
                "description": "Create answer",
                "inputSchema": {
                    "json": {
                        "type": "object",
                        "properties": {
                            "question": {
                                "type": "string",
                                "description": "Question",
                            },
                            "answer": {
                                "type": "string",
                                "description": "Answer.",
                            },
                        },
                        "required": [
                            "question",
                            "answer",
                        ],
                    }
                },
            }
        }
    ],
    "toolChoice": {"tool": {"name": "create_answer"}},
}

context = """
Amazon DataZone できめ細かなアクセス制御が導入
投稿日: 2024年7月3日
本日、Amazon DataZone で、きめ細かなアクセス制御が導入され、データ所有者が行レベルおよび列レベルでデータをきめ細かく制御できるようになりました。お客様は Amazon DataZone を使用して、ガバナンスとアクセス制御により、組織の枠を越えてデータのカタログ化、発見、分析、共有、管理を大規模に行えます。データ所有者は、データセット全体へのアクセスを許可する代わりに、特定のデータレコードへのアクセスを制限できるようになりました。例えば、テーブルに複数の地域のデータが含まれている場合、行フィルターを作成して、別々のプロジェクトに対して別々の地域の行へのアクセスを付与できます。さらに、列フィルターを使用すると、個人を特定できる情報 (PII) を含む列など、特定の列へのアクセスを制限して、必要かつ機密性の低いデータへのアクセスのみをサブスクライバーに許可できます。

使用を開始するために、Amazon DataZone ポータル内で行フィルターと列フィルターを作成できます。ユーザーからデータアセットへのアクセスをリクエストされたら、適切なフィルターを適用してサブスクリプションを承認できます。Amazon DataZone は AWS Lake Formation と Amazon Redshift を使用してこれらのフィルターを適用し、承認されている行と列にのみサブスクライバーがアクセスできるようにします。

Amazon Redshift と AWS Lake Formation の両方でのきめ細かなアクセス制御サポートは、米国東部 (オハイオ)、米国東部 (バージニア北部)、米国西部 (オレゴン)、アジアパシフィック (シンガポール)、アジアパシフィック (シドニー)、アジアパシフィック (東京)、アジアパシフィック (ソウル)、カナダ (中部)、欧州 (フランクフルト)、欧州 (アイルランド)、欧州 (ストックホルム)、欧州 (ロンドン)、南米 (サンパウロ) の AWS リージョンで一般提供されています。

"""

messages = [
    {
        "role": "user",
        "content": [
            {
                "text": f"以下のドキュメントをもとに、質問と回答を生成し、ツールを呼び出してください。必ずドキュメントに沿った質問と回答を生成してください。<document>{context}</document>"
            }
        ],
    }
]

import json

response = bedrock_client.converse(
    modelId=model_id, messages=messages, toolConfig=toolConfig
)

print(json.dumps(response, indent=2, ensure_ascii=False))
```

レスポンスのoutputだけを抜き出しました。

```json:output
 "output": {
    "message": {
      "role": "assistant",
      "content": [
        {
          "toolUse": {
            "toolUseId": "tooluse_lLxlk321Q5uNDfZtFL6BRQ",
            "name": "create_answer",
            "input": {
              "question": "Amazon DataZoneの新機能の概要を説明してください。",
              "answer": "Amazon DataZoneで新たに導入されたきめ細かなアクセス制御の機能は以下のようになります:\n\n- データ所有者がデータセットの行レベルと列レベルでアクセス制御を設定できるようになった\n- 例えば、テーブルに複数の地域のデータが含まれている場合、行フィルターを使って別々のプロジェクトに対して別々の地域のデータにアクセスできるようになった\n- 列フィルターを使って、個人情報を含む列などの特定の列へのアクセスを制限できるようになった\n- これらの機能はAWS Lake Formation、Amazon Redshiftと連携して実現されており、承認された行と列にのみサブスクライバーがアクセスできるようになった"
            }
          }
        }
      ]
    }
  },
```

いい感じですね。

## 複数の質問と回答を生成させる

一つの文章から、複数の質問と回答を生成させたい場合はどうしたらいいのでしょうか？

Toolの定義をArray型にすると、複数生成されました。

```json:Tool
{
    "toolSpec": {
        "name": "create_answer",
        "description": "Create answer",
        "inputSchema": {
            "json": {
                "type": "object",
                "properties": {
                    "questions": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "question": {
                                    "type": "string",
                                    "description": "Question",
                                },
                                "answer": {
                                    "type": "string",
                                    "description": "Answer.",
                                },
                            },
                        },
                    },
                },
                "required": [
                    "questions",
                ],
            }
        },
    }
}
```

```json:生成結果
"toolUse": {
"toolUseId": "tooluse_vxSvWFYIQCesxF327DQwFg",
"name": "create_answer",
"input": {
  "questions": [
    {
      "question": "Amazon DataZoneにはどのような機能が導入されたか？",
      "answer": "Amazon DataZoneにきめ細かなアクセス制御機能が導入されました。データ所有者が行レベルおよび列レベルでデータのアクセス制御を行うことができるようになりました。"
    },
    {
      "question": "データ所有者はどのようなアクセス制御を行えるようになったか？",
      "answer": "データ所有者は、データセット全体へのアクセスを許可する代わりに、特定のデータレコードへのアクセスを制限することができるようになりました。また、個人を特定できる情報 (PII) を含む列など、特定の列へのアクセスを制限することも可能になりました。"
    },
    {
      "question": "行フィルターと列フィルターはどのように使われるか？",
      "answer": "行フィルターを使うことで、同一のテーブルに含まれる別々のプロジェクトに対して、異なる地域のデータへのアクセスを付与することができます。列フィルターを使うことで、必要かつ機密性の低いデータに対してのみサブスクライバーのアクセスを許可することができます。"
    },
    {
      "question": "Amazon DataZoneのきめ細かなアクセス制御機能はどのAWSリージョンで利用可能か？",
      "answer": "Amazon Redshiftと AWS Lake Formationでのきめ細かなアクセス制御機能は、米国東部、米国西部、アジアパシフィック、カナダ、欧州、南米の12のAWSリージョンで一般提供されています。"
    }
  ]
}
}
```

生成される個数を上手に制御できなさそうでしたが、「必ず3個」生成させたい場合は、以下のようにArrayではなく個別の値として定義するのが良さそうです。

```json
{
    "toolSpec": {
        "name": "create_answer",
        "description": "Create answer",
        "inputSchema": {
            "json": {
                "type": "object",
                "properties": {
                    "question": {
                        "type": "string",
                        "description": "Question",
                    },
                    "answer": {
                        "type": "string",
                        "description": "Answer.",
                    },
                    "question2": {
                        "type": "string",
                        "description": "Question",
                    },
                    "answer2": {
                        "type": "string",
                        "description": "Answer to question2.",
                    },
                    "question3": {
                        "type": "string",
                        "description": "Question",
                    },
                    "answer3": {
                        "type": "string",
                        "description": "Answer to question2.",
                    },
                },
                "required": [
                    "question",
                    "answer",
                    "question2",
                    "answer2",
                    "question3",
                    "answer3",
                ],
            }
        },
    }
}
```