并行工具调用
让 Claude 同时调用多个工具以提高效率
🌐 查看英文原文 | 源码 Notebook
Claude 3.7 Sonnet 上的并行工具调用
Claude 3.7 Sonnet 可能不太倾向于在单次回复中进行并行工具调用,即使你没有设置 disable_parallel_tool_use。为解决此问题,我们建议引入一个 “批量工具”(batch tool),它可以作为元工具来同时包装对其他工具的调用。我们发现,如果存在此工具,模型将使用它在并行中同时调用多个工具。
让我们来看看这个问题,并详细探讨这个解决方案。
from anthropic import Anthropic
client = Anthropic()
MODEL_NAME = "claude-sonnet-4-6"执行包含多次工具调用的查询
回顾一下,默认行为是允许 Claude 进行并行工具调用。结合默认的 tool_choice 设置为 auto,这意味着 Claude 可以调用任何指定的工具,或者在单个助手回合中调用多个工具。
让我们为 Claude 设置 get_weather 和 get_time 工具。
def get_weather(location):
# Pretend to get the weather, and just return a fixed value.
return f"The weather in {location} is 72 degrees and sunny."
def get_time(location):
# Pretend to get the time, and just return a fixed value.
return f"The time in {location} is 12:32 PM."
weather_tool = {
"name": "get_weather",
"description": "Gets the weather for in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
},
"required": ["location"],
},
}
time_tool = {
"name": "get_time",
"description": "Gets the time in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
},
"required": ["location"],
},
}
def process_tool_call(tool_name, tool_input):
if tool_name == "get_weather":
return get_weather(tool_input["location"])
elif tool_name == "get_time":
return get_time(tool_input["location"])
else:
raise ValueError(f"Unexpected tool name: {tool_name}")接下来,让我们为 Claude 提供这些工具并执行查询。
def make_query_and_print_result(messages, tools=None):
response = client.messages.create(
model=MODEL_NAME,
messages=messages,
max_tokens=1000,
tool_choice={"type": "auto"},
tools=tools or [weather_tool, time_tool],
)
for block in response.content:
match block.type:
case "text":
print(block.text)
case "tool_use":
print(f"Tool: {block.name}({block.input})")
case _:
raise ValueError(f"Unexpected block type: {block.type}")
return response
MESSAGES = [{"role": "user", "content": "What's the weather and time in San Francisco?"}]
response = make_query_and_print_result(MESSAGES)I'll check the current weather and time in San Francisco for you.
Tool: get_weather({'location': 'San Francisco, CA'})
注意,尽管我们同时询问了天气和时间,Claude 却只返回了一个查询天气的工具调用?
让我们看看如果我们调用天气工具并继续会发生什么。
last_tool_call = response.content[1]
MESSAGES.append({"role": "assistant", "content": response.content})
MESSAGES.append(
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": last_tool_call.id,
"content": process_tool_call(response.content[1].name, response.content[1].input),
}
],
}
)
response = make_query_and_print_result(MESSAGES)Tool: get_time({'location': 'San Francisco, CA'})
现在注意 Claude 发起了第二次工具调用来获取时间。虽然这在技术上确实是立即发生的,但可能是浪费的,因为它需要”来回”交互——首先 Claude 请求天气,然后我们需要处理它,然后 Claude 再请求时间,现在我们又要处理_那个_。
Claude 仍然会对结果做正确的事情,但鼓励 Claude 在一次调用中使用两者可能是有益的,这样我们可以同时处理。
引入批量工具
让我们引入一个 batch_tool,让 Claude 有机会使用它将多个工具调用合并为一个。
import json
batch_tool = {
"name": "batch_tool",
"description": "Invoke multiple other tool calls simultaneously",
"input_schema": {
"type": "object",
"properties": {
"invocations": {
"type": "array",
"description": "The tool calls to invoke",
"items": {
"types": "object",
"properties": {
"name": {
"types": "string",
"description": "The name of the tool to invoke",
},
"arguments": {
"types": "string",
"description": "The arguments to the tool",
},
},
"required": ["name", "arguments"],
},
}
},
"required": ["invocations"],
},
}
def process_tool_with_maybe_batch(tool_name, tool_input):
if tool_name == "batch_tool":
results = []
for invocation in tool_input["invocations"]:
results.append(
process_tool_call(invocation["name"], json.loads(invocation["arguments"]))
)
return "\n".join(results)
else:
return process_tool_call(tool_name, tool_input)现在让我们尝试为 Claude 提供现有的天气和时间工具,以及这个新的批量工具,看看当我们执行需要天气和时间的查询时会发生什么。
MESSAGES = [{"role": "user", "content": "What's the weather and time in San Francisco?"}]
response = make_query_and_print_result(MESSAGES, tools=[weather_tool, time_tool, batch_tool])I can help you check both the weather and the time in San Francisco. Let me get that information for you right away.
Tool: batch_tool({'invocations': [{'name': 'get_weather', 'arguments': '{"location": "San Francisco, CA"}'}, {'name': 'get_time', 'arguments': '{"location": "San Francisco, CA"}'}]})
注意这次 Claude 使用了批量工具,一次性查询了时间和天气。这使我们能够同时处理它们,从而可能改善结果的整体延迟。
last_tool_call = response.content[1]
MESSAGES.append({"role": "assistant", "content": response.content})
MESSAGES.append(
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": last_tool_call.id,
"content": process_tool_with_maybe_batch(
response.content[1].name, response.content[1].input
),
}
],
}
)
response = make_query_and_print_result(MESSAGES)Here's the information you requested:
Weather in San Francisco, CA: 72 degrees and sunny
Time in San Francisco, CA: 12:32 PM
Is there anything else you'd like to know about San Francisco?
中文读者提示:本章节的代码和输出为英文原文。如需理解具体实现细节,可参考上方中文导读和代码注释。如有疑问,欢迎在 GitHub Issues 讨论。