例えば、以下のような4つのツールがあるとします。
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
パラメーターにツールの定義を指定します。
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のツールを使用する方法を紹介します。
無理にConverse APIを使わないで全部LangChainでやったらいいじゃないか ってのは、今回は禁句です
ツールをLangChainのツールとして定義する
LangChainのツールは@tool
デコレーターをつけるだけです。
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
メソッドで呼び出します。
add.invoke(input={"a": 1, "b": 2})
@tool
デコレーターを使わずに関数をLangChainツールとする方法もあります。
from langchain_core.tools import StructuredTool
divide_from_func = StructuredTool.from_function(func=divide)
将来的にLambda上で動作させたい場合など、できるだけ依存ライブラリーを減らしたい場合があると思います。
こちらの方法だと元の関数をいじらないので、langchain_core(とその依存ライブラリー)を剥がしやすくなると思います。
LangChainツールとして定義したツールは、パラメーターの構造をJSONスキーマとして出力できます。
add.args_schema.model_json_schema()
{
"description": "二つの数値を足し算します。",
"properties": {
"a": {
"title": "A",
"type": "integer"
},
"b": {
"title": "B",
"type": "integer"
}
},
"required": [
"a",
"b"
],
"title": "add",
"type": "object"
}
これを使ってtoolConfig
パラメーターを生成する便利関数を作ります。
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の呼び出しがこうなります
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": {}},
},
)
いえーい。シンプル!
副産物(?)として、様々な既存のLangChainツールをConverse APIで呼び出せます!
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を呼び出すまでのコードはこんな感じになると思います
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)