BedrockのTool useをLangChainツールでいい感じにする

例えば、以下のような4つのツールがあるとします。

```python
def add(a: int, b: int) -> int:
    """二つの数値を足し算します。"""
    return a + b


def subtract(a: int, b: int) -> int:
    """一つ目の数値から二つ目の数値を引き算します。"""
    return a - b


def multiply(a: int, b: int) -> int:
    """二つの数値を掛け算します。"""
    return a * b


def divide(a: int, b: int) -> float:
    """一つ目の数値を二つ目の数値で割り算します。結果は小数点数(float)で返します。"""
    if b == 0:
        raise ValueError("0での除算はできません。")
    return a / b
```

これらのツールを使用するには、Converse APIの`toolConfig`パラメーターにツールの定義を指定します。

```python:Converse API呼び出し
response = client.converse(
    modelId=model_id,
    system=[{"text": system_prompt}],
    messages=messages,
    toolConfig={
        "tools": [
            {
                "toolSpec": {
                    "name": "add",
                    "description": "二つの数値を足し算します。",
                    "inputSchema": {
                        "json": {
                            "description": "二つの数値を足し算します。",
                            "properties": {
                                "a": {"title": "A", "type": "integer"},
                                "b": {"title": "B", "type": "integer"},
                            },
                            "required": ["a", "b"],
                            "title": "add",
                            "type": "object",
                        }
                    },
                }
            },
            {
                "toolSpec": {
                    "name": "subtract",
                    "description": "一つ目の数値から二つ目の数値を引き算します。",
                    "inputSchema": {
                        "json": {
                            "description": "一つ目の数値から二つ目の数値を引き算します。",
                            "properties": {
                                "a": {"title": "A", "type": "integer"},
                                "b": {"title": "B", "type": "integer"},
                            },
                            "required": ["a", "b"],
                            "title": "subtract",
                            "type": "object",
                        }
                    },
                }
            },
            {
                "toolSpec": {
                    "name": "multiply",
                    "description": "二つの数値を掛け算します。",
                    "inputSchema": {
                        "json": {
                            "description": "二つの数値を掛け算します。",
                            "properties": {
                                "a": {"title": "A", "type": "integer"},
                                "b": {"title": "B", "type": "integer"},
                            },
                            "required": ["a", "b"],
                            "title": "multiply",
                            "type": "object",
                        }
                    },
                }
            },
            {
                "toolSpec": {
                    "name": "divide",
                    "description": "一つ目の数値を二つ目の数値で割り算します。結果は小数点数(float)で返します。",
                    "inputSchema": {
                        "json": {
                            "description": "一つ目の数値を二つ目の数値で割り算します。結果は小数点数(float)で返します。",
                            "properties": {
                                "a": {"title": "A", "type": "integer"},
                                "b": {"title": "B", "type": "integer"},
                            },
                            "required": ["a", "b"],
                            "title": "divide",
                            "type": "object",
                        }
                    },
                }
            },
        ],
        "toolChoice": {"auto": {}},
    },
)
```

えーっと、、 

**めんどくさい！！！** 

関数のパラメーターが変わったら、JSONも変えないといけないので、面倒です。


ということで、少しでも簡単にツールを指定する方法として、LangChainのツールを使用する方法を紹介します。

:::note warn
無理にConverse APIを使わないで全部LangChainでやったらいいじゃないか　ってのは、今回は禁句です
:::

## ツールをLangChainのツールとして定義する

LangChainのツールは`@tool`デコレーターをつけるだけです。

```python
from langchain_core.tools import tool

@tool
def add(a: int, b: int) -> int:
    """二つの数値を足し算します。"""
    return a + b


@tool
def subtract(a: int, b: int) -> int:
    """一つ目の数値から二つ目の数値を引き算します。"""
    return a - b


@tool
def multiply(a: int, b: int) -> int:
    """二つの数値を掛け算します。"""
    return a * b


@tool
def divide(a: int, b: int) -> float:
    """一つ目の数値を二つ目の数値で割り算します。結果は小数点数(float)で返します。"""
    if b == 0:
        raise ValueError("0での除算はできません。")
    return a / b
```


LangChainツールは`invoke`メソッドで呼び出します。

```python
add.invoke(input={"a": 1, "b": 2})
```

:::note
`@tool`デコレーターを使わずに関数をLangChainツールとする方法もあります。

```python
from langchain_core.tools import StructuredTool

divide_from_func = StructuredTool.from_function(func=divide)
```

将来的にLambda上で動作させたい場合など、できるだけ依存ライブラリーを減らしたい場合があると思います。
こちらの方法だと元の関数をいじらないので、langchain_core（とその依存ライブラリー）を剥がしやすくなると思います。

:::


LangChainツールとして定義したツールは、パラメーターの構造をJSONスキーマとして出力できます。

```python
add.args_schema.model_json_schema()
```
```json
{
    "description": "二つの数値を足し算します。",
    "properties": {
        "a": {
            "title": "A",
            "type": "integer"
        },
        "b": {
            "title": "B",
            "type": "integer"
        }
    },
    "required": [
        "a",
        "b"
    ],
    "title": "add",
    "type": "object"
}
```

これを使って`toolConfig`パラメーターを生成する便利関数を作ります。

```python
def tool_to_spec(tool: StructuredTool):
    name = tool.name
    description = tool.description
    json_schema = tool.args_schema.model_json_schema()

    return {
        "toolSpec": {
            "name": name,
            "description": description,
            "inputSchema": {"json": json_schema},
        }
    }
```

これにより、Converse APIの呼び出しがこうなります

```python
tools = [add, subtract, multiply, divide]

response = client.converse(
    modelId=model_id,
    system=[{"text": system_prompt}],
    messages=messages,
    toolConfig={
        "tools": [tool_to_spec(tool) for tool in tools],
        "toolChoice": {"auto": {}},
    },
)
```

いえーい。シンプル！

:::note

副産物（？）として、様々な既存のLangChainツールをConverse APIで呼び出せます！

```python
from langchain_community.tools import TavilySearchResults

search_tool = TavilySearchResults()

tools = [search_tool]

response = client.converse(
    modelId=model_id,
    system=[{"text": system_prompt}],
    messages=messages,
    toolConfig={
        "tools": [tool_to_spec(tool) for tool in tools],
        "toolChoice": {"auto": {}},
    },
)
```

:::


---

ちなみにConverse APIのresponseから、Toolを呼び出すまでのコードはこんな感じになると思います

```python
for c in response["output"]["message"]["content"]:

    if "toolUse" in c:
        tool_use = c["toolUse"]

        tool_use_id = tool_use["toolUseId"]
        name = tool_use["name"]
        input = tool_use["input"]

        tools_dict = {tool.name: tool for tool in tools}
        tool_result = tools_dict[name].invoke(input=input)
```