Bedrock Converse API のJSON生成を理解したい

この記事は、AWS Communityの一連の記事の一部を翻訳したものです。
親記事は、[こちら](https://community.aws/content/2hWA16FSt2bIzKs0Z1fgJBwu589/generating-json-with-the-amazon-bedrock-converse-api)からご参照いただけます。

## はじめに

この記事は、Amazon Bedrockのツール利用に関するシリーズの一部です。パート1では[Amazon Bedrock Converse APIのクイックチュートリアル](https://community.aws/content/2hHgVE7Lz6Jj1vFv39zSzzlCilG/getting-started-with-the-amazon-bedrock-converse-api)を紹介し、パート2では[Converse APIでのツール利用方法](https://community.aws/content/2hW5367isgQOkkXLYjp4JB3Pe16/intro-to-tool-use-with-the-amazon-bedrock-converse-api)を解説しました。本記事では、Amazon Bedrockの大規模言語モデルを使って、事前定義したJSON Schemaに準拠したJSONを生成する方法を紹介します。主要なJSON Schema要素の説明と、Pythonコード例もご紹介します。

Amazon Bedrock Converse APIは、Amazon Bedrockを利用した大規模言語モデル（LLM）への一貫したアクセス方法を提供します。ユーザーと生成AIモデル間のターン制メッセージをサポートし、ツール利用（いわゆる「関数呼び出し」）に対応したモデル向けに、ツール定義の一貫したフォーマットも提供します。

**ツール利用**とは、LLMが呼び出し元アプリケーションに対して、モデルが生成したパラメータで関数を呼び出すよう指示できる技術です。利用可能なツールと可能なパラメータをプロンプトと一緒にモデルに渡すと、ツール定義のJSON Schemaに従って、モデルはツールを選択し、パラメータを定義します。その後、アプリケーションが関数を呼び出し、モデルが生成したパラメータを渡します。

また、このツール利用機能を使い、関数呼び出しをスキップして、生成されたJSONを直接利用することもできます。この記事の例では、その方法を実演します。

### なぜJSON生成が重要なのか？

これにより、非構造化コンテンツを構造化データへより確実に変換できます。従来は、膨大な非構造データがドキュメントや顧客フィードバック、メモ欄などに埋もれており、実質的に活用できませんでした。大規模言語モデルによるJSON生成を使えば、その生データを定量的に処理・理解できるデータに変換できます。

Amazon Bedrock Converse APIでJSONを生成する手順は以下の通りです：

1. 呼び出し元アプリケーションが、（A）求められたJSON Schemaを含むツール定義と、（B）呼び出しメッセージをLLMに渡す。
2. モデルが、ツール定義のJSON Schemaに準拠したJSONなどのツール利用リクエストを生成
3. アプリケーションがモデルのツール利用リクエストからJSONを抽出し、処理する。

---

## 開発環境とAWSアカウントのセットアップ

- 最新のAWS SDK（Boto3）とPythonをセットアップ：[Boto3クイックスタート](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html)
- Anthropic Claude 3 SonnetモデルをサポートするAWSリージョンを選択（例：us-west-2）。[モデルのリージョン対応状況](https://docs.aws.amazon.com/bedrock/latest/userguide/models-regions.html)を参照。
- アカウントとリージョンでAmazon Bedrockモデルアクセスを設定。[セットアップ例](https://catalog.workshops.aws/building-with-amazon-bedrock/en-US/prerequisites/bedrock-setup)

:::message
訳者注
- Bedrock利用には、コンソールなどからBedrockでのモデルアクセスのリクエストを行う必要があります。すぐに利用できるモデルもありますが、利用開始までに数時間以上かかるモデルもあります。
- また、モデルが利用可能なリージョンで呼び出す必要があるため、例えばLambda関数などにおいて、Bedrockクライアント作成時に指定が必要なケースもあります。

例：
```py
# Use the Conversation API to send a text message to Anthropic Claude.

import boto3
from botocore.exceptions import ClientError

# Create a Bedrock Runtime client in the AWS Region you want to use.
client = boto3.client("bedrock-runtime", region_name="us-east-1")

# Set the model ID, e.g., Claude 3 Haiku.
model_id = "anthropic.claude-3-haiku-20240307-v1:0"

# Start a conversation with the user message.
user_message = "Describe the purpose of a 'hello world' program in one line."
conversation = [
    {
        "role": "user",
        "content": [{"text": user_message}],
    }
]

try:
    # Send the message to the model, using a basic inference configuration.
    response = client.converse(
        modelId=model_id,
        messages=conversation,
        inferenceConfig={"maxTokens": 512, "temperature": 0.5, "topP": 0.9},
    )

    # Extract and print the response text.
    response_text = response["output"]["message"]["content"][0]["text"]
    print(response_text)

except (ClientError, Exception) as e:
    print(f"ERROR: Can't invoke '{model_id}'. Reason: {e}")
    exit(1)



```
[ソースコード出典](https://docs.aws.amazon.com/ja_jp/code-library/latest/ug/python_3_bedrock-runtime_code_examples.html#anthropic_claude)
:::

---

## 免責事項

- LLMは非決定的です。この記事の結果と異なる場合があります。
- AWSアカウントでコードを実行すると、消費トークン分の料金が発生します。
- 「最小限のプロンプト」を推奨していますが、用途によっては詳細なプロンプトが必要です。
- すべてのモデルがConverse APIの全機能をサポートしているわけではありません。[公式ドキュメント](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html#conversation-inference-supported-models-features)で確認してください。

---

## シナリオ概要

投資運用会社で、毎日何千通もの顧客メールが共通の受信箱に届くとします。通常はカスタマーサービスチームが各メールを確認し、対応の要否や担当部署を判断します。その情報をデータベースに保存したり、他のアプリケーションに渡したりします。しかし、生成AIを使って顧客の要望を解釈し、情報を抽出し、対応部署を推薦できたらどうでしょう？

**例えば、こんなリクエスト：**

> Acme Investments様  
> 貴社のカスタマーサービス担当Shirley Scarryさんに感謝の意を伝えたくご連絡します。先日、口座入金についてShirleyさんとお話しし、とても親切かつ知識豊富で、すべての質問に丁寧に答えてくださいました。Robert Herbfordさんも通話に参加されましたが、正直あまり集中していない様子でした。妻のClaraも追加の質問がありましたが、Shirleyさんが解決してくれました。Shirleyさんのプロ意識と専門性に感謝しています。今後も他の方に貴社を推薦したいと思います。  
> Carson Bradford

**これを、以下のようなJSONデータに抽出したい：**

```json
{
  "summary": "顧客がAcme InvestmentsのShirley Scarry氏の優れたカスタマーサービスを称賛し、Robert Herbford氏は通話中に集中していなかったと指摘。",
  "escalate_complaint": false,
  "overall_sentiment": "Positive",
  "supporting_business_unit": "Customer Service",
  "level_of_concern": 2,
  "customer_names": [
    "Carson Bradford",
    "Clara Bradford"
  ],
  "sentiment_towards_employees": [
    {
      "employee_name": "Shirley Scarry",
      "sentiment": "Positive"
    },
    {
      "employee_name": "Robert Herbford",
      "sentiment": "Negative"
    }
  ]
}
```

これを、以下の方法で行います
- 求める出力に向けて、JSONスキーマを指定したツールを定義する
- Converse API ツール使用リクエストによって生成されたJSONを直接使用する

---

## JSON SchemaによるConverse APIツール定義の解説

まずは、Converse API のツール定義の様々なコンポーネントを一通り確認するところから始めていきましょう。この記事の終わりで、Pythonコードの完全なツール定義を共有しています。

### ツール名と説明

まず、`toolSpec`要素でツール名と説明を定義します。これにより、モデルがツールをどのようなときに呼び出すべきなのか正しく決めることができます。

```json
{
  "toolSpec": {
    "name": "summarize_email",
    "description": "Summarize email content.",
```

参考：
[Anthropicのツール定義ベストプラクティス集](Best practices for tool definitions)
- Bedrockのツール定義は若干の違いもあるものの、ここのベストプラクティスは依然として有効です。

### JSON生成用スキーマ定義

`inputSchema`に、生成したいJSONのスキーマを記述します。

```json
"inputSchema": {
  "json": {
    "type": "object",
    "properties": {
```

### 各プロパティの定義例

- 文字列型プロパティ
- 真偽値型プロパティ
- 数値型プロパティ（範囲指定あり）
- 列挙型プロパティ（enum）
- 文字列配列
- オブジェクト配列
- 必須プロパティ（required）

#### 文字列型プロパティ
```json
"summary": {
    "type": "string",
    "description": "A brief one-line or two-line summary of the email."
},
```

これにより、以下のような生成が行えます。

```json
"summary": "Customer compliments Acme Investments employee Shirley Scarry for her excellent customer service in resolving account deposit questions, but notes Robert Herbford seemed distracted on the call.",
```

文字列型タイプに関して、[こちら](https://json-schema.org/understanding-json-schema/reference/string)で詳細に確認できます。

#### 真偽値型プロパティ
```json
"escalate_complaint": {
    "type": "boolean",
    "description": "Indicates if this email is serious enough to be immediately escalated for further review."
},
```

これにより、以下のような生成が行えます。

```json
"escalate_complaint": false,
```

真偽値型タイプに関して、[こちら](https://json-schema.org/understanding-json-schema/reference/boolean)で詳細に確認できます。

#### 数値型プロパティ（範囲指定あり）
```json
"level_of_concern": {
    "type": "integer",
    "description": "Rate the level of concern for the above content on a scale from 1-10",
    "minimum": 1,
    "maximum": 10
},
```

```json
"level_of_concern": 2,
```


数値型タイプと範囲指定に関して、[こちら](https://json-schema.org/understanding-json-schema/reference/string)で詳細に確認できます。

#### 列挙型プロパティ（enum）
```json
"overall_sentiment": {
    "type": "string",
    "description": "The sender's overall sentiment.",
    "enum": ["Positive", "Neutral", "Negative"]
},
```

```json
"supporting_business_unit": {
    "type": "string",
    "description": "The internal business unit that this email should be routed to.",
    "enum": ["Sales", "Operations", "Customer Service", "Fund Management"]
},
```

これにより、以下のような生成が行えます。

```json
"overall_sentiment": "Positive",
"supporting_business_unit": "Customer Service",
```

列挙型プロパティ（enum）に関して、[こちら](https://json-schema.org/understanding-json-schema/reference/enum)で詳細に確認できます。

#### 文字列配列

```json
"customer_names": {
    "type": "array",
    "description": "An array of customer names mentioned in the email.",
    "items": { "type": "string" }
},
```

これにより、以下のような生成が行えます。

```json
"customer_names": [
    "Carson Bradford",
    "Clara Bradford"
],
```


文字列配列に関して、[こちら](https://json-schema.org/understanding-json-schema/reference/array)で詳細に確認できます。

#### オブジェクト配列
```json
"sentiment_towards_employees": {
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "employee_name": {
                "type": "string",
                "description": "The employee's name."
            },
            "sentiment": {
                "type": "string",
                "description": "The sender's sentiment towards the employee.",
                "enum": ["Positive", "Neutral", "Negative"]
            }
        }
    }
}
```

これにより、以下のような生成が行えます。

```json
"sentiment_towards_employees": [
    {
        "employee_name": "Shirley Scarry",
        "sentiment": "Positive"
    },
    {
        "employee_name": "Robert Herbford",
        "sentiment": "Negative"
    }
]
```


オブジェクト配列に関して、[こちら](https://json-schema.org/understanding-json-schema/reference/object)で詳細に確認できます。

#### 必須プロパティ

```json
"required": [
    "summary",
    "escalate_complaint",
    "overall_sentiment",
    "supporting_business_unit",
    "level_of_concern",
    "customer_names",
    "sentiment_towards_employees"
]
```

必須プロパティに関して、[こちら](https://json-schema.org/understanding-json-schema/reference/object#required)で詳細に確認できます。

---

## Pythonコードによる実装例

### 依存関係の定義とクライアント生成

```python
import boto3, json

session = boto3.Session()
bedrock = session.client(service_name='bedrock-runtime')
```

### summarize_emailツール定義

```py
tool_list = [
    {
        "toolSpec": {
            "name": "summarize_email",
            "description": "Summarize email content.",
            "inputSchema": {
                "json": {
                    "type": "object",
                    "properties": {
                        "summary": {
                            "type": "string",
                            "description": "A brief one-line or two-line summary of the email."
                        },
                        "escalate_complaint": {
                            "type": "boolean",
                            "description": "Indicates if this email is serious enough to be immediately escalated for further review."
                        },
                        "level_of_concern": {
                            "type": "integer",
                            "description": "Rate the level of concern for the above content on a scale from 1-10",
                            "minimum": 1,
                            "maximum": 10
                        },
                        "overall_sentiment": {
                            "type": "string",
                            "description": "The sender's overall sentiment.",
                            "enum": ["Positive", "Neutral", "Negative"]
                        },
                        "supporting_business_unit": {
                            "type": "string",
                            "description": "The internal business unit that this email should be routed to.",
                            "enum": ["Sales", "Operations", "Customer Service", "Fund Management"]
                        },
                        "customer_names": {
                            "type": "array",
                            "description": "An array of customer names mentioned in the email.",
                            "items": { "type": "string" }
                        },
                        "sentiment_towards_employees": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "employee_name": {
                                        "type": "string",
                                        "description": "The employee's name."
                                    },
                                    "sentiment": {
                                        "type": "string",
                                        "description": "The sender's sentiment towards the employee.",
                                        "enum": ["Positive", "Neutral", "Negative"]
                                    }
                                }
                            }
                        }
                    },
                    "required": [
                        "summary",
                        "escalate_complaint",
                        "overall_sentiment",
                        "supporting_business_unit",
                        "level_of_concern",
                        "customer_names",
                        "sentiment_towards_employees"
                    ]
                }
            }
        }
    }
]
```

### Amazon Bedrockに送信するメッセージの作成

```py
content = """Dear Acme Investments,

I am writing to compliment one of your customer service representatives, Shirley Scarry. I recently had the pleasure of speaking with Shirley regarding my account deposit. Shirley was extremely helpful and knowledgeable, and went above and beyond to ensure that all of my questions were answered. Shirley also had Robert Herbford join the call, who wasn't quite as helpful. My wife, Clara Bradford, didn't like him at all.
Shirley's professionalism and expertise were greatly appreciated, and I would be happy to recommend Acme Investments to others based on my experience.
Sincerely,

Carson Bradford
"""

message = {
    "role": "user",
    "content": [
        { "text": f"<content>{content}</content>" },
        { "text": "Please use the summarize_email tool to generate the email summary JSON based on the content within the <content> tags." }
    ],
}
```
あくまで、これはEメールをプロンプトに組み込むための一つの方法に過ぎません。特定の利用例に沿って、XMLタグを使用しない方法や、異なるプロンプトを使用することもできます。

### 必須ツールがあるConverse APIの呼び出し

```python
response = bedrock.converse(
  modelId="anthropic.claude-3-sonnet-20240229-v1:0",
  messages=[message],
  inferenceConfig={
    "maxTokens": 2000,
    "temperature": 0
  },
  toolConfig={
    "tools": tool_list,
    "toolChoice": {
      "tool": {
        "name": "summarize_email"
      }
    }
  }
)
```

### レスポンスからJSONを抽出・表示

```py
response_message = response['output']['message']

response_content_blocks = response_message['content']

content_block = next((block for block in response_content_blocks if 'toolUse' in block), None)

tool_use_block = content_block['toolUse']

tool_result_dict = tool_use_block['input']

print(json.dumps(tool_result_dict, indent=4))

```

---

## 実行結果例

```json
{
  "summary": "顧客がAcme InvestmentsのShirley Scarry氏の優れたカスタマーサービスを称賛し、Robert Herbford氏は通話中に集中していなかったと指摘。",
  "escalate_complaint": false,
  "overall_sentiment": "Positive",
  "supporting_business_unit": "Customer Service",
  "level_of_concern": 2,
  "customer_names": [
    "Carson Bradford",
    "Clara Bradford"
  ],
  "sentiment_towards_employees": [
    {
      "employee_name": "Shirley Scarry",
      "sentiment": "Positive"
    },
    {
      "employee_name": "Robert Herbford",
      "sentiment": "Negative"
    }
  ]
}
```

---

## まとめ

この記事では、JSON Schemaの定義方法と、非構造化コンテンツからJSONを生成する方法を紹介しました。ぜひ、ご自身のユースケースに合わせたスキーマ設計にもチャレンジしてみてください。

---

## 参考リンク

- [Converse API対応モデル機能一覧](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html#conversation-inference-supported-models-features)
- [Amazon Bedrockのツール利用公式ドキュメント](https://docs.aws.amazon.com/bedrock/latest/userguide/tool-use.html)
- [JSON Schemaの概要](https://json-schema.org/understanding-json-schema/about)
- [Building with Amazon Bedrockワークショップガイド](https://catalog.workshops.aws/building-with-amazon-bedrock)

### Continue reading articles in this series about tool use / function calling:  

-    [Getting started with the Amazon Bedrock Converse API](https://community.aws/content/2hHgVE7Lz6Jj1vFv39zSzzlCilG/getting-started-with-the-amazon-bedrock-converse-api)
    
-   [Intro to Tool Use with the Amazon Bedrock Converse API](https://community.aws/content/2hW5367isgQOkkXLYjp4JB3Pe16/intro-to-tool-use-with-the-amazon-bedrock-converse-api)
    
-   [Build a Tool Use-Based Agent Loop with Amazon Bedrock](https://community.aws/content/2hW7srTWRb5idHjY4I8WP5fQFRf/build-a-tool-use-based-agent-loop-with-amazon-bedrock)
    
-   [Generating JSON with the Amazon Bedrock Converse API](https://community.aws/content/2hWA16FSt2bIzKs0Z1fgJBwu589/generating-json-with-the-amazon-bedrock-converse-api)


# 記事一覧

## AWS Bedrock の converse API の使い方
[AWS Bedrock の converse API の使い方](https://zenn.dev/502/articles/d0be104bf362ca)

## Bedrock Converse API の Tool Use とはなんぞや？
[Bedrock Converse API の Tool Use とはなんぞや？](https://zenn.dev/502/articles/a118cd9ff0ce70)

## Bedrock Converse API のJSON生成を理解したい
[Bedrock Converse API のJSON生成を理解したい](https://zenn.dev/502/articles/6650db592b0b0a)

## Bedrock で Tool-use を組み込んだエージェントループを作りたい
[Bedrock で Tool-use を組み込んだエージェントループを作りたい](https://zenn.dev/502/articles/1af0ab10356da7)