文章翻译自https://github.com/anthropics/courses/blob/master/tool_use/05_tool_choice.ipynb
工具选择
Claude API 支持一个名为 tool_choice
的参数,该参数允许你指定希望 Claude 如何调用工具。在本笔记本中,我们将了解它的工作原理以及何时使用它。
使用 tool_choice
参数时,我们有三种可能的选项:
auto
允许 Claude 决定是否调用提供的任何工具。any
告诉 Claude 必须使用提供的工具之一,但不强制使用特定工具。tool
允许我们强制 Claude 始终使用特定工具。
下图展示了每个选项的工作方式:
让我们详细了解每个选项。首先导入 Anthropic SDK:
python
运行
from anthropic import Anthropic
client = Anthropic()
自动(Auto)
将 tool_choice
设置为 auto
允许模型自动决定是否使用工具。如果完全不使用 tool_choice
参数,这是使用工具时的默认行为。
为了演示这一点,我们将为 Claude 提供一个模拟的网络搜索工具。我们会向 Claude 提出一些问题,其中一些需要调用网络搜索工具,而另一些 Claude 应该能够自行回答。
首先定义一个名为 web_search
的工具。请注意,为了简化此演示,我们实际上不会进行网络搜索。
python
运行
def web_search(topic):
print(f"pretending to search the web for {topic}")
web_search_tool = {
"name": "web_search",
"description": "A tool to retrieve up to date information on a given topic by searching the web",
"input_schema": {
"type": "object",
"properties": {
"topic": {
"type": "string",
"description": "The topic to search the web for"
},
},
"required": ["topic"]
}
}
接下来,我们编写一个函数,该函数接受 user_query
并将其与 web_search_tool
一起传递给 Claude。
我们还将 tool_choice
设置为 auto
:
python
运行
tool_choice={"type": "auto"}
完整的函数如下:
python
运行
from datetime import date
def chat_with_web_search(user_query):
messages = [{"role": "user", "content": user_query}]
system_prompt=f"""
Answer as many questions as you can using your existing knowledge.
Only search the web for queries that you can not confidently answer.
Today's date is {date.today().strftime("%B %d %Y")}
If you think a user's question involves something in the future that hasn't happened yet, use the search tool.
"""
response = client.messages.create(
system=system_prompt,
model="claude-3-sonnet-20240229",
messages=messages,
max_tokens=1000,
tool_choice={"type": "auto"},
tools=[web_search_tool]
)
last_content_block = response.content[-1]
if last_content_block.type == "text":
print("Claude did NOT call a tool")
print(f"Assistant: {last_content_block.text}")
elif last_content_block.type == "tool_use":
print("Claude wants to use a tool")
print(last_content_block)
让我们从一个 Claude 应该能够不使用工具就能回答的问题开始:
python
运行
chat_with_web_search("What color is the sky?")
plaintext
Claude did NOT call a tool
Assistant: The sky appears blue during the day. This is because the Earth's atmosphere scatters more blue light from the sun than other colors, making the sky look blue.
当我们问 “天空是什么颜色的?” 时,Claude 不使用工具。让我们尝试问一个 Claude 应该使用网络搜索工具来回答的问题:
python
运行
chat_with_web_search("Who won the 2024 Miami Grand Prix?")
plaintext
Claude wants to use a tool
ToolUseBlock(id='toolu_staging_018nwaaRebX33pHqoZZXDaSw', input={'topic': '2024 Miami Grand Prix winner'}, name='web_search', type='tool_use')
当我们问 “谁赢得了 2024 年迈阿密大奖赛?” 时,Claude 使用了网络搜索工具!
让我们再试几个例子:
python
运行
# Claude 应该不需要使用工具:
chat_with_web_search("Who won the Superbowl in 2022?")
plaintext
Claude did NOT call a tool
Assistant: The Los Angeles Rams won Super Bowl LVI in 2022, defeating the Cincinnati Bengals by a score of 23-20. The game was played on February 13, 2022 at SoFi Stadium in Inglewood, California.
python
运行
# Claude 应该使用工具:
chat_with_web_search("Who won the Superbowl in 2024?")
plaintext
Claude wants to use a tool
ToolUseBlock(id='toolu_staging_016XPwcprHAgYJBtN7A3jLhb', input={'topic': '2024 Super Bowl winner'}, name='web_search', type='tool_use')
你的提示很重要!
当 tool_choice
设置为 auto
时,花时间编写详细的提示非常重要。Claude 通常可能会过于急切地调用工具。编写详细的提示有助于 Claude 确定何时调用工具以及何时不调用。在上面的示例中,我们在系统提示中包含了具体说明:
plaintext
system_prompt=f"""
Answer as many questions as you can using your existing knowledge.
Only search the web for queries that you can not confidently answer.
Today's date is {date.today().strftime("%B %d %Y")}
If you think a user's question involves something in the future that hasn't happened yet, use the search tool.
"""
写在中间
我最近正在使用一个特别cool的Claude code镜像站:
不仅能够in free使用,还特别稳定,没有停过机!填写我的码「5OTTEB」更有额外额度!
强制使用特定工具
我们可以通过 tool_choice
强制 Claude 使用特定工具。在下面的示例中,我们定义了两个简单工具:
print_sentiment_scores
—— 一个 “诱使” Claude 生成包含情感分析数据的结构良好的 JSON 输出的工具。有关此方法的更多信息,请参见 Anthropic 指南中的《使用 Claude 和工具调用提取结构化 JSON》。calculator
—— 一个非常简单的计算器工具,接收两个数字并将它们相加。
python
运行
tools = [
{
"name": "print_sentiment_scores",
"description": "Prints the sentiment scores of a given tweet or piece of text.",
"input_schema": {
"type": "object",
"properties": {
"positive_score": {"type": "number", "description": "The positive sentiment score, ranging from 0.0 to 1.0."},
"negative_score": {"type": "number", "description": "The negative sentiment score, ranging from 0.0 to 1.0."},
"neutral_score": {"type": "number", "description": "The neutral sentiment score, ranging from 0.0 to 1.0."}
},
"required": ["positive_score", "negative_score", "neutral_score"]
}
},
{
"name": "calculator",
"description": "Adds two number",
"input_schema": {
"type": "object",
"properties": {
"num1": {"type": "number", "description": "first number to add"},
"num2": {"type": "number", "description": "second number to add"},
},
"required": ["num1", "num2"]
}
}
]
我们的目标是编写一个名为 analyze_tweet_sentiment
的函数,该函数接收一条推文,并使用 Claude 对该推文进行基本的情感分析。最终,我们将 “强制” Claude 使用 print_sentiment_scores
工具,但首先我们将展示不强制使用工具时的情况。
在这个 “不完善” 的 analyze_tweet_sentiment
函数版本中,我们为 Claude 提供了这两个工具。为了便于比较,我们先将 tool_choice
设置为 auto
:
python
运行
tool_choice={"type": "auto"}
请注意,我们故意没有为 Claude 提供一个精心编写的提示,以便更容易看出强制使用特定工具的影响。
python
运行
def analyze_tweet_sentiment(query):
response = client.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=4096,
tools=tools,
tool_choice={"type": "auto"},
messages=[{"role": "user", "content": query}]
)
print(response)
让我们看看当我们用推文 “天哪,我刚刚做了一顿超赞的饭!” 调用该函数时会发生什么:
python
运行
analyze_tweet_sentiment("Holy cow, I just made the most incredible meal!")
plaintext
ToolsBetaMessage(id='msg_staging_01ApgXx7W7qsDugdaRWh6p21', content=[TextBlock(text="That's great to hear! I don't actually have the capability to assess sentiment from text, but it sounds like you're really excited and proud of the incredible meal you made. Cooking something delicious that you're proud of can definitely give a sense of accomplishment and happiness. Well done on creating such an amazing dish!", type='text')], model='claude-3-sonnet-20240229', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(input_tokens=429, output_tokens=69))
Claude 没有调用我们的 print_sentiment_scores
工具,而是直接回复:
“太好了!实际上我没有评估文本情感的能力,但听起来你对自己做的这顿超赞的饭感到非常兴奋和自豪……”
接下来,假设有人发了这样一条推文:“我爱我的猫!我本来有四只,刚刚又收养了两只!猜猜我现在有多少只?”
python
运行
analyze_tweet_sentiment("I love my cats! I had four and just adopted 2 more! Guess how many I have now?")
plaintext
ToolsBetaMessage(id='msg_staging_018gTrwrx6YwBR2jjhdPooVg', content=[TextBlock(text="That's wonderful that you love your cats and adopted two more! To figure out how many cats you have now, I can use the calculator tool:", type='text'), ToolUseBlock(id='toolu_staging_01RFker5oMQoY6jErz5prmZg', input={'num1': 4, 'num2': 2}, name='calculator', type='tool_use')], model='claude-3-sonnet-20240229', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(input_tokens=442, output_tokens=101))
Claude 想要调用计算器工具:
plaintext
ToolUseBlock(id='toolu_staging_01RFker5oMQoY6jErz5prmZg', input={'num1': 4, 'num2': 2}, name='calculator', type='tool_use')
显然,当前的实现并没有达到我们的预期(主要是因为我们故意设置成这样来展示问题)。
因此,我们通过更新 tool_choice
来强制 Claude 始终使用 print_sentiment_scores
工具:
python
运行
tool_choice={"type": "tool", "name": "print_sentiment_scores"}
除了将类型设置为 tool
之外,我们还必须提供一个特定的工具名称。
python
运行
def analyze_tweet_sentiment(query):
response = client.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=4096,
tools=tools,
tool_choice={"type": "tool", "name": "print_sentiment_scores"},
messages=[{"role": "user", "content": query}]
)
print(response)
现在,如果我们用之前的提示来测试 Claude,它总会调用 print_sentiment_scores
工具:
python
运行
analyze_tweet_sentiment("Holy cow, I just made the most incredible meal!")
plaintext
ToolsBetaMessage(id='msg_staging_018GtYk8Xvee3w8Eeh6pbgoq', content=[ToolUseBlock(id='toolu_staging_01FMRQ9pZniZqFUGQwTcFU4N', input={'positive_score': 0.9, 'negative_score': 0.0, 'neutral_score': 0.1}, name='print_sentiment_scores', type='tool_use')], model='claude-3-sonnet-20240229', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(input_tokens=527, output_tokens=79))
Claude 调用了我们的 print_sentiment_scores
工具:
plaintext
ToolUseBlock(id='toolu_staging_01FMRQ9pZniZqFUGQwTcFU4N', input={'positive_score': 0.9, 'negative_score': 0.0, 'neutral_score': 0.1}, name='print_sentiment_scores', type='tool_use')
即使我们用一条 “数学相关” 的推文来误导 Claude,它仍然会调用 print_sentiment_scores
工具:
python
运行
analyze_tweet_sentiment("I love my cats! I had four and just adopted 2 more! Guess how many I have now?")
plaintext
ToolsBetaMessage(id='msg_staging_01RACamfrHdpvLxWaNwDfZEF', content=[ToolUseBlock(id='toolu_staging_01Wb6ZKSwKvqVSKLDAte9cKU', input={'positive_score': 0.8, 'negative_score': 0.0, 'neutral_score': 0.2}, name='print_sentiment_scores', type='tool_use')], model='claude-3-sonnet-20240229', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(input_tokens=540, output_tokens=79))
尽管我们强制 Claude 调用 print_sentiment_scores
工具,我们仍然应该进行一些基本的提示工程,为 Claude 提供更好的任务背景:
python
运行
def analyze_tweet_sentiment(query):
prompt = f"""
Analyze the sentiment in the following tweet:
<tweet>{query}</tweet>
"""
response = client.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=4096,
tools=tools,
tool_choice={"type": "auto"},
messages=[{"role": "user", "content": prompt}]
)
print(response)
Any(任意工具)
tool_choice
的最后一个选项是 any
,它允许我们告诉 Claude:“你必须调用一个工具,但可以选择任何一个。” 假设我们想使用 Claude 创建一个 SMS 聊天机器人。这个聊天机器人与用户 “交流” 的唯一方式是通过 SMS 短信。
在下面的示例中,我们制作了一个非常简单的短信助手,它可以使用两个工具:
send_text_to_user
—— 向用户发送短信。get_customer_info
—— 根据用户名查找客户数据。
我们的想法是创建一个始终调用这些工具之一、从不以非工具响应进行回复的聊天机器人。在任何情况下,Claude 都应该要么尝试发送短信回复,要么调用 get_customer_info
获取更多客户信息。为了确保这一点,我们将 tool_choice
设置为 any
:
python
运行
tool_choice={"type": "any"}
python
运行
def send_text_to_user(text):
# 向用户发送文本
# 为简单起见,我们只打印文本:
print(f"已发送短信:{text}")
def get_customer_info(username):
return {
"username": username,
"email": f"{username}@email.com",
"purchases": [
{"id": 1, "product": "computer mouse"},
{"id": 2, "product": "screen protector"},
{"id": 3, "product": "usb charging cable"},
]
}
tools = [
{
"name": "send_text_to_user",
"description": "Sends a text message to a user",
"input_schema": {
"type": "object",
"properties": {
"text": {"type": "string", "description": "The piece of text to be sent to the user via text message"},
},
"required": ["text"]
}
},
{
"name": "get_customer_info",
"description": "gets information on a customer based on the customer's username. Response includes email, username, and previous purchases. Only call this tool once a user has provided you with their username",
"input_schema": {
"type": "object",
"properties": {
"username": {"type": "string", "description": "The username of the user in question. "},
},
"required": ["username"]
}
},
]
system_prompt = """
All your communication with a user is done via text message.
Only call tools when you have enough information to accurately call them.
Do not call the get_customer_info tool until a user has provided you with their username. This is important.
If you do not know a user's username, simply ask a user for their username.
"""
def sms_chatbot(user_message):
messages = [{"role": "user", "content":user_message}]
response = client.messages.create(
system=system_prompt,
model="claude-3-sonnet-20240229",
max_tokens=4096,
tools=tools,
tool_choice={"type": "any"},
messages=messages
)
if response.stop_reason == "tool_use":
last_content_block = response.content[-1]
if last_content_block.type == 'tool_use':
tool_name = last_content_block.name
tool_inputs = last_content_block.input
print(f"=======Claude 想要调用 {tool_name} 工具=======")
if tool_name == "send_text_to_user":
send_text_to_user(tool_inputs["text"])
elif tool_name == "get_customer_info":
print(get_customer_info(tool_inputs["username"]))
else:
print("哦,天哪,那个工具不存在!")
else:
print("没有调用任何工具。这不应该发生!")
让我们从简单的开始:
python
运行
sms_chatbot("Hey there! How are you?")
plaintext
=======Claude 想要调用 send_text_to_user 工具=======
已发送短信:Hello! I'm doing well, thanks for asking. How can I assist you today?
Claude 通过调用 send_text_to_user
工具进行回复。
接下来,我们问 Claude 一个稍微棘手的问题:
python
运行
sms_chatbot("I need help looking up an order")
plaintext
=======Claude 想要调用 send_text_to_user 工具=======
已发送短信:Hi there, to look up your order details I'll need your username first. Can you please provide me with your username?
Claude 想发送一条短信,要求用户提供他们的用户名。
现在,让我们看看当我们向 Claude 提供用户名时会发生什么:
python
运行
sms_chatbot("I need help looking up an order. My username is jenny76")
plaintext
=======Claude 想要调用 get_customer_info 工具=======
{'username': 'jenny76', 'email': 'jenny76@email.com', 'purchases': [{'id': 1, 'product': 'computer mouse'}, {'id': 2, 'product': 'screen protector'}, {'id': 3, 'product': 'usb charging cable'}]}
正如我们所希望的那样,Claude 调用了 get_customer_info
工具!
即使我们向 Claude 发送一条杂乱无章的消息,它仍然会调用我们的某个工具:
python
运行
sms_chatbot("askdj aksjdh asjkdbhas kjdhas 1+1 ajsdh")
plaintext
=======Claude 想要调用 send_text_to_user 工具=======
已发送短信:I'm afraid I didn't understand your query. Could you please rephrase what you need help with?