AnthropicとCohereとBedrockのTool Useを比較

先に結論

| 接続先→<br/>↓使用するSDK | Claude 3 (Anthropic) | Claude 3 (Bedrock) | Command R (Cohere) | Command R (Bedrock) |
| --- | :-: | :-: | :-: | :-: |
| [Anthropic Python API library](https://github.com/anthropics/anthropic-sdk-python) | ✅️ | ✅️ | | |
| [Cohere Python SDK](https://github.com/cohere-ai/cohere-python) | | |✅️ | ✅️ |
| Boto3 |  |✅️ | |✅️ |

Boto3を使う場合、呼び出し方は統一されているが、レスポンスが統一されない印象


## Anthropic Python API library

[こちら](https://github.com/anthropics/courses/blob/master/ToolUse/02_your_first_simple_tool.ipynb)を参考に実施します。

```shell
pip install anthropic
```

:::note
Bedrockを使用する場合は`boto3`もインストールします。
:::


クライアントを生成します。

```python
from anthropic import Anthropic

client = Anthropic(api_key="sk-*****")
```

:::note
Bedrockを使用する場合はこうなります。

```python
from anthropic import AnthropicBedrock

client = AnthropicBedrock()
```

:::

ツールの関数を作成します。

```python:calculator関数
def calculator(operation, operand1, operand2):
    if operation == "add":
        return operand1 + operand2
    elif operation == "subtract":
        return operand1 - operand2
    elif operation == "multiply":
        return operand1 * operand2
    elif operation == "divide":
        if operand2 == 0:
            raise ValueError("Cannot divide by zero.")
        return operand1 / operand2
    else:
        raise ValueError(f"Unsupported operation: {operation}")
```

Claudeに教えるツールのスキーマを作成します。

```python:ツールの定義
calculator_tool = {
    "name": "calculator",
    "description": "A simple calculator that performs basic arithmetic operations.",
    "input_schema": {
        "type": "object",
        "properties": {
            "operation": {
                "type": "string",
                "enum": ["add", "subtract", "multiply", "divide"],
                "description": "The arithmetic operation to perform.",
            },
            "operand1": {"type": "number", "description": "The first operand."},
            "operand2": {"type": "number", "description": "The second operand."},
        },
        "required": ["operation", "operand1", "operand2"],
    },
}
```

Claude 3を呼び出します。

```python
response = client.messages.create(
    model="claude-3-haiku-20240307",  # Bedrockの場合は"anthropic.claude-3-haiku-20240307-v1:0"
    system="You have access to tools, but only use them when necessary.  If a tool is not required, respond as normal",
    messages=[
        {
            "role": "user",
            "content": "Multiply 1984135 by 9343116. Only respond with the result",
        }
    ],
    max_tokens=300,
    # Tell Claude about our tool
    tools=[calculator_tool],
)
```

responseを確認します。

```text:response.stop_reason
'tool_use'
```
```text:response.content
[ToolUseBlock(id='toolu_bdrk_01Q1xoTJvFXuPKoLpemL3k6z', input={'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'}, name='calculator', type='tool_use')]
```

`response`からツールの名前とインプットを取得します。

```python
tool_name = response.content[0].name
tool_inputs = response.content[0].input

print("The Tool Name Claude Wants To Call:", tool_name)
print("The Inputs Claude Wants To Call It With:", tool_inputs)
```
```
The Tool Name Claude Wants To Call: calculator
The Inputs Claude Wants To Call It With: {'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'}
```

ツール名「`calculator`」とパラメーター「`{'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'}`」が取得できました。

定義しておいた関数を呼び出します。

```python
operation = tool_inputs["operation"]
operand1 = tool_inputs["operand1"]
operand2 = tool_inputs["operand2"]

result = calculator(operation, operand1, operand2)
print("RESULT IS", result)
```
```
RESULT IS 18538003464660
```

ツール呼び出しができました。


## Cohere Python SDK

同じ内容をCohere Python SDKでやってみましょう。

```shell
pip install cohere
```

:::note
Bedrockを使用する場合は`boto3`もインストールします。
:::


クライアントを生成します。

```python
import cohere

co = cohere.Client(api_key="*****")
```

:::note
Bedrockを使用する場合はこうなります。

```python
co = cohere.BedrockClient(
    aws_region="us-east-1",
)
```
:::

ツールの関数を作成します。

```python:calculator関数
def calculator(operation, operand1, operand2):
    if operation == "add":
        return operand1 + operand2
    elif operation == "subtract":
        return operand1 - operand2
    elif operation == "multiply":
        return operand1 * operand2
    elif operation == "divide":
        if operand2 == 0:
            raise ValueError("Cannot divide by zero.")
        return operand1 / operand2
    else:
        raise ValueError(f"Unsupported operation: {operation}")
```

Command Rに教えるツールのスキーマを作成します。

AnthropicのAPIとは形式が異なります。
例えば、Anthropicでは`enum`として定義していましたが、Cohereは`description`で指定する必要があります。[参考](https://docs.cohere.com/docs/parameter-types-in-tool-use#example--enumerated-values-enums)

```python:ツールの定義
calculator_tool = {
    "name": "calculator_tool",
    "description": "A simple calculator that performs basic arithmetic operations.",
    "parameter_definitions": {
        "operation": {
            "type": "string",
            "description": "The arithmetic operation to perform. Possible enum values: add, subtract, multiply, divide.",
            "required": True,
        },
        "operand1": {
            "type": "number",
            "description": "The first operand.",
            "required": True,
        },
        "operand2": {
            "type": "number",
            "description": "The second operand.",
            "required": True,
        },
    },
}
```

:::note warn
Toolの`name`を「`calculator`」にすると以下のエラーが発生します。

```
status_code: 400, body: {'message': "invalid request: the 'calculator' tool can't be used with a custom definition: choose a different name or remove the provided definition"}
```

`calculator2`や`calculator_tool`ではエラーにならないので、内部的な予約語とバッティングしているのかもしれません。

:::

Command Rを呼び出します。

```python
# user request
message = "Multiply 1984135 by 9343116. Only respond with the result"

response = co.chat(
    message=message,
    tools=[calculator_tool],
    preamble="You have access to tools, but only use them when necessary.  If a tool is not required, respond as normal",
    model="command-r",  # Bedrockの場合はcohere.command-r-v1:0
)
```

responseを確認します。

```text:response.finish_reason
'COMPLETE'
```
```text:response.tool_calls
[ToolCall(name='calculator_tool', parameters={'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'})]
```

`response`からツールの名前とインプットを取得します。

```python
tool_name = response.tool_calls[0].name
tool_inputs = response.tool_calls[0].parameters

print("The Tool Name Claude Wants To Call:", tool_name)
print("The Inputs Claude Wants To Call It With:", tool_inputs)
```
```
The Tool Name Claude Wants To Call: calculator_tool
The Inputs Claude Wants To Call It With: {'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'}
```


定義しておいた関数を呼び出します。

```python
operation = tool_inputs["operation"]
operand1 = tool_inputs["operand1"]
operand2 = tool_inputs["operand2"]

result = calculator(operation, operand1, operand2)
print("RESULT IS", result)
```
```
RESULT IS 18538003464660
```

ツール呼び出しができました。

## Boto3

最後はBoto3です。新しく追加になった[Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html)を使用します。

```shell
pip install boto3
```

クライアントを生成します。

```python
import boto3

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

ツールの関数を作成します。

```python:calculator関数
def calculator(operation, operand1, operand2):
    if operation == "add":
        return operand1 + operand2
    elif operation == "subtract":
        return operand1 - operand2
    elif operation == "multiply":
        return operand1 * operand2
    elif operation == "divide":
        if operand2 == 0:
            raise ValueError("Cannot divide by zero.")
        return operand1 / operand2
    else:
        raise ValueError(f"Unsupported operation: {operation}")
```

使えるツールのスキーマを作成します。
Anthropic Python API libraryやCohere Python SDKと比べ、階層が深い印象です。

enumの記述方法は、Claudeに合わせています。

```python:ツールの定義
calculator_tool = {
    "toolSpec": {
        "name": "calculator_tool",
        "description": "A simple calculator that performs basic arithmetic operations.",
        "inputSchema": {
            "json": {
                "type": "object",
                "properties": {
                    "operation": {
                        "type": "string",
                        "enum": ["add", "subtract", "multiply", "divide"],
                        "description": "The arithmetic operation to perform.",
                    },
                    "operand1": {
                        "type": "number",
                        "description": "The first operand.",
                    },
                    "operand2": {
                        "type": "number",
                        "description": "The second operand.",
                    },
                },
                "required": ["operation", "operand1", "operand2"],
            }
        },
    }
}
```

### Claude 3 (Bedrock)

Claude 3で実行します。

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

response = client.converse(
    modelId=model_id,
    system=[
        {
            "text": "You have access to tools, but only use them when necessary.  If a tool is not required, respond as normal"
        }
    ],
    messages=[
        {
            "role": "user",
            "content": [
                {"text": "Multiply 1984135 by 9343116. Only respond with the result"}
            ],
        }
    ],
    toolConfig={"tools": [calculator_tool]},
    inferenceConfig={"maxTokens": 400},
)

import json
print(json.dumps(response["output"], indent=2, ensure_ascii=False))
```

```json:response["output"]
{
  "message": {
    "role": "assistant",
    "content": [
      {
        "toolUse": {
          "toolUseId": "tooluse_VioZeA2_TSySf54-3AmGQg",
          "name": "calculator_tool",
          "input": {
            "operation": "multiply",
            "operand1": 1984135,
            "operand2": 9343116
          }
        }
      }
    ]
  }
}
```

```text:response["stopReason"]
'tool_use'
```

```text:response["output"]["message"]["content"]
[{'toolUse': {'toolUseId': 'tooluse_bOgY4tZ8QOC9-Fq58nvTHA',
   'name': 'calculator',
   'input': {'operand1': 1984135,
    'operand2': 9343116,
    'operation': 'multiply'}}}]
```

`response`からツールの名前とインプットを取得します。

```python
tool_name = response["output"]["message"]["content"][0]["toolUse"]["name"]
tool_inputs = response["output"]["message"]["content"][0]["toolUse"]["input"]

print("The Tool Name Claude Wants To Call:", tool_name)
print("The Inputs Claude Wants To Call It With:", tool_inputs)
```
```
The Tool Name Claude Wants To Call: calculator_tool
The Inputs Claude Wants To Call It With: {'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'}
```

定義しておいた関数を呼び出します。

```python
operation = tool_inputs["operation"]
operand1 = tool_inputs["operand1"]
operand2 = tool_inputs["operand2"]

result = calculator(operation, operand1, operand2)
print("RESULT IS", result)
```
```
RESULT IS 18538003464660
```

ツール呼び出しができました。

### Command R (Bedrock)

モデルIDを`cohere.command-r-v1:0`に変えて実行してみます。

```python
model_id = "cohere.command-r-v1:0"

response = client.converse(
    modelId=model_id,
    system=[
        {
            "text": "You have access to tools, but only use them when necessary.  If a tool is not required, respond as normal"
        }
    ],
    messages=[
        {
            "role": "user",
            "content": [
                {"text": "Multiply 1984135 by 9343116. Only respond with the result"}
            ],
        }
    ],
    toolConfig={"tools": [calculator_tool]},
    inferenceConfig={"maxTokens": 400},
)

import json
print(json.dumps(response["output"], indent=2, ensure_ascii=False))
```

```json:response["output"]
{
  "message": {
    "role": "assistant",
    "content": [
      {
        "text": "I will use the calculator tool to multiply the two given numbers and respond with the result."
      },
      {
        "toolUse": {
          "toolUseId": "tooluse_3Vx63WQyQmmZ45jFNU8mgA",
          "name": "calculator_tool",
          "input": {
            "operand1": {
              "value": 1984135
            },
            "operand2": {
              "value": 9343116
            },
            "operation": {
              "enum": [
                "multiply"
              ]
            }
          }
        }
      }
    ]
  }
}
```

Claude 3の実行結果と比べると、色々違いがあります。

* Claude 3は`content`内が`toolUse`のみですが、Command Rは`text`と`toolUse`が含まれる（なので、配列の0番目で取得できない）
* `toolUse.input`の内容も微妙に形式が異なるので、受け取った側で頑張る必要がある

```json:Claude 3
"input": {
    "operation": "multiply",
    "operand1": 1984135,
    "operand2": 9343116
}
```
```json:Command R
"input": {
    "operand1": {
        "value": 1984135
    },
    "operand2": {
        "value": 9343116
    },
    "operation": {
        "enum": [
            "multiply"
        ]
    }
}
```

なので、結果を受け取ってパースする処理に変更が必要です。

```python
tool_name = ""
tool_inputs = ""

for content in response["output"]["message"]["content"]:
    if "toolUse" in content:
        tool_name = content["toolUse"]["name"]
        tool_inputs = content["toolUse"]["input"]

print("The Tool Name Claude Wants To Call:", tool_name)
print("The Inputs Claude Wants To Call It With:", tool_inputs)
```

```python
operation = tool_inputs["operation"]["enum"][0]
operand1 = tool_inputs["operand1"]["value"]
operand2 = tool_inputs["operand2"]["value"]

result = calculator(operation, operand1, operand2)
print("RESULT IS", result)
```
```
RESULT IS 18538003464660
```


毎回`enum`や`value`が返ってくるのか、`enum`は必ず配列なのかなど、不確定要素がありそうな印象です。