Amazon Bedrock で Tool Use (Function Calling) を利用した JSON Mode をやってみた

# はじめに

Amazon Bedrock の Convert API で Tool Use (Function Calling) が出来るようになりました。具体的な解説や利用方法は、[こちら](https://qiita.com/ren8k/items/3d5f66df251703b8407e) の記事がわかりやすくておすすめです (ありがとうございます！)

Tool Use ではいろいろなことができますが、今回は Bedrock の生成結果を JSON に強制する JSON mode を試してみる備忘録記事です。

Tool Use は、生成 AI 側にツールの情報を与えて、ツールの呼び出しに関するパラメータを JSON で生成することができます。この 「JSON で生成する」特性を利用することで、生成 AI の結果を JSON に強制する手法が JSON mode と言われています。

さっくりやってみましょう。



# Python コード

まず Python コードの全体を記載します。

```python
import json
from pprint import pprint

import boto3

client = boto3.client("bedrock-runtime", region_name="us-east-1")
model_id = "anthropic.claude-3-haiku-20240307-v1:0"
# model_id = "anthropic.claude-3-sonnet-20240229-v1:0"


tool_name = "print_sentiment_scores"
description = "与えられた感情のスコアを print out します。positive_score、negative_score、neutral_score の 3 score の合計は 1.000 となります。"

tool_definition = {
    "toolSpec": {
        "name": tool_name,
        "description": description,
        "inputSchema": {
            "json": {
                "type": "object",
                "properties": {
                    "positive_score": {
                        "type": "number",
                        "description": "ポジティブな感情のスコアで、0.000 から 1.000 の範囲です。",
                    },
                    "negative_score": {
                        "type": "number",
                        "description": "ネガティブな感情のスコアで、0.000 から 1.000 の範囲です。",
                    },
                    "neutral_score": {
                        "type": "number",
                        "description": "中立的な感情のスコアで、0.000 から 1.000 の範囲です。",
                    },
                },
                "required": ["positive_score", "negative_score", "neutral_score"],
            }
        },
    }
}

target_text = "海賊王に、俺はなる！"

prompt = f"""
<text>
{target_text}
</text>

{tool_name} ツールのみを利用すること。
"""

messages = [
    {
        "role": "user",
        "content": [{"text": prompt}],
    }
]

inferenceConfig = ({"maxTokens": 1000, "temperature": 0})

# Send the message to the model
response = client.converse(
    modelId=model_id,
    messages=messages,
    toolConfig={
        "tools": [tool_definition],
        "toolChoice": {
            "tool": {
                "name": tool_name,
            },
        },
    },
    inferenceConfig=inferenceConfig
)


def extract_tool_use_args(content):
    for item in content:
        if "toolUse" in item:
            return item["toolUse"]["input"]
    return None


response_content = response["output"]["message"]["content"]

# pprint(response)

# json部を抽出
tool_use_args = extract_tool_use_args(response_content)
print(json.dumps(tool_use_args, indent=2, ensure_ascii=False))

```



# ポイント解説

以下の tool_definition が、生成 AI 側に教えるツールのインターフェースです。properties に positive_score、negative_score、neutral_score を指定しており、この 3 つの値を JSON で返して欲しい定義を与えています。

```python
tool_definition = {
    "toolSpec": {
        "name": tool_name,
        "description": description,
        "inputSchema": {
            "json": {
                "type": "object",
                "properties": {
                    "positive_score": {
                        "type": "number",
                        "description": "ポジティブな感情のスコアで、0.000 から 1.000 の範囲です。",
                    },
                    "negative_score": {
                        "type": "number",
                        "description": "ネガティブな感情のスコアで、0.000 から 1.000 の範囲です。",
                    },
                    "neutral_score": {
                        "type": "number",
                        "description": "中立的な感情のスコアで、0.000 から 1.000 の範囲です。",
                    },
                },
                "required": ["positive_score", "negative_score", "neutral_score"],
            }
        },
    }
}
```

　

Model は、Claude 3 の Haiku を利用しています。Sonnet と Haiku を試してみましたが、大きな違いはなさそうに見えたので、Haiku を選択しています。

```python
client = boto3.client("bedrock-runtime", region_name="us-east-1")
model_id = "anthropic.claude-3-haiku-20240307-v1:0"
```

　

これが生成をリクエストしている部分です。invoke_model ではなくて、Converse API を呼び出しています。最近追加された API で、こちらで Tool Use (JSON mode) を利用できます。

また、temperature を 0 にしており、生成のゆらぎを小さくなるようにしています。

```python
inferenceConfig = ({"maxTokens": 1000, "temperature": 0})

# Send the message to the model
response = client.converse(
    modelId=model_id,
    messages=messages,
    toolConfig={
        "tools": [tool_definition],
        "toolChoice": {
            "tool": {
                "name": tool_name,
            },
        },
    },
    inferenceConfig=inferenceConfig
)
```

　

これがポジネガ判定を行う文章です。

```python
target_text = "海賊王に、俺はなる！"
```



# 実行例

さっそく実行してみましょう。



「海賊王に、俺はなる！」という文章にしてみたところ、きちんと JSON のみの結果を受け取ることができました。ポジティブとなっていますね。さすが海賊王、前向きです。

```shell
> python3 tool-use-for-jsonmode.py
{
  "positive_score": 0.8,
  "negative_score": 0.1,
  "neutral_score": 0.1
}
```



ではつぎに、「人間は、イヤな時に、ワケなんて言わなくていいんだぜ」と、からくりサーカスの鳴海のセリフです。この一文だけだとネガティブに分類されました。

```shell
> python3 tool-use-for-jsonmode.py
{
  "positive_score": 0.2,
  "negative_score": 0.6,
  "neutral_score": 0.2
}
```



実際は、選択するモデルによって結果はまちまちだったりしていました。おそらくプロンプトの投げ方によってもスコアがかわってくるでしょう。ぜひいろいろ触って検証してみてください。



# 参考 URL

https://github.com/anthropics/courses/blob/master/ToolUse/03_structured_outputs.ipynb

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

https://qiita.com/ren8k/items/3d5f66df251703b8407e