Claude API 支持一个名为 tool_choice 的参数,该参数允许您指定 Claude 如何调用工具。在本笔记本中,我们将了解它的工作原理以及何时使用它。
使用 tool_choice 参数时,我们有三个可能的选项:
- 11、auto 允许 Claude 决定是否调用任何提供的工具。
- 2、any 告诉 Claude 它必须使用提供的工具之一,但不强制使用特定工具。
- 3、tool 允许我们强制 Claude 始终使用特定工具。
此图说明了每个选项的工作原理:
让我们详细了解每个选项。我们首先导入 Anthropic SDK:
from anthropic import Anthropic
client = Anthropic()
自动模型 #
将 tool_choice 设置为 auto 可让模型自动决定是否使用工具。如果您根本不使用 tool_choice 参数,这是使用工具时的默认行为。
为了演示这一点,我们将为 Claude 提供一个虚假的网页搜索工具。我们将向 Claude 提问,其中一些问题需要调用网页搜索工具,而另一些问题 Claude 应该能够自行回答。
让我们首先定义一个名为 web_search 的工具。请注意,为了使此演示简单易懂,我们实际上并没有在这里搜索网页。
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:
tool_choice={"type": "auto"}
# Here's the complete function:
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)
#Let's start with a question Claude should be able to answer without using the tool:
chat_with_web_search("What color is the sky?")
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.
When we ask "What color is the sky?", Claude does not use the tool. Let's try asking something that Claude should use the web search tool to answer:
chat_with_web_search("Who won the 2024 Miami Grand Prix?")
Claude wants to use a tool
ToolUseBlock(id='toolu_staging_018nwaaRebX33pHqoZZXDaSw', input={'topic': '2024 Miami Grand Prix winner'}, name='web_search', type='tool_use')
When we ask "Who won the 2024 Miami Grand Prix?", Claude uses the web search tool!
Let's try a few more examples:
# Claude should NOT need to use the tool for this:
chat_with_web_search("Who won the Superbowl in 2022?")
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.
# Claude SHOULD use the tool for this:
chat_with_web_search("Who won the Superbowl in 2024?")
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 确定何时调用工具以及何时不调用。在上面的示例中,我们在系统提示中包含了具体说明:
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.
"""
强制使用模式 #
我们可以使用 tool_choice 强制 Claude 使用特定工具。在下面的示例中,我们定义了两个简单的工具:
- 1.print_sentiment_scores – 一种“欺骗”Claude 生成包含情绪分析数据的结构良好的 JSON 输出的工具。有关此方法的更多信息,请参阅 Anthropic Cookbook 中的使用 Claude 提取结构化 JSON 和工具使用。
- 2.calculator – 一种非常简单的计算器工具,它将两个数字相加。
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:
tool_choice={“type”: “auto”}
请注意,我们故意不向 Claude 提供写得很好的提示,以便更容易看到强制使用特定工具的影响。
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)
让我们看看当我们使用推文调用该函数时会发生什么:天哪,我刚刚做了一顿最不可思议的饭!
analyze_tweet_sentiment("Holy cow, I just made the most incredible meal!")
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 工具,而是直接回复道:
“听到这个消息真是太好了!我实际上没有能力从文本中评估情绪,但听起来你真的为自己做的美味佳肴感到兴奋和自豪
接下来,让我们想象有人发推文说:我爱我的猫!我养了四只,刚刚又领养了两只!猜猜我现在有多少只?
analyze_tweet_sentiment("I love my cats! I had four and just adopted 2 more! Guess how many I have now?")
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 想要调用计算器工具:
ToolUseBlock(id='toolu_staging_01RFker5oMQoY6jErz5prmZg', input={'num1': 4, 'num2': 2}, name='calculator', type='tool_use')
显然,当前的实现并没有达到我们想要的效果(主要是因为我们将其设置为失败)。
因此,让我们通过更新 tool_choice 强制 Claude 始终使用 print_sentiment_scores 工具:
tool_choice={"type": "tool", "name": "print_sentiment_scores"}
除了设置工具类型之外,我们还必须提供特定的工具名称。
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 工具:
analyze_tweet_sentiment("Holy cow, I just made the most incredible meal!")
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 工具:
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 工具:
analyze_tweet_sentiment("I love my cats! I had four and just adopted 2 more! Guess how many I have now?")
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 提供更好的任务背景:
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)
任意模式 #
tool_choice 的最后一个选项是 any,它允许我们告诉 Claude,“您必须调用一个工具,但您可以选择哪一个。”假设我们想使用 Claude 创建一个 SMS 聊天机器人。这个聊天机器人与用户实际“交流”的唯一方式是通过 SMS 文本消息。
在下面的示例中,我们制作了一个非常简单的短信助手,它可以访问两个工具:
1、send_text_to_user – 向用户发送文本消息。
2、get_customer_info – 根据用户名查找客户数据。
我们的想法是创建一个聊天机器人,它总是调用其中一个工具,并且永远不会以非工具响应进行响应。在所有情况下,Claude 都应该通过尝试发送文本消息或调用 get_customer_info 来获取更多客户信息。为了确保这一点,我们将 tool_choice 设置为 any:
tool_choice={"type": "any"}
def send_text_to_user(text):
# Sends a text to the user
# We'll just print out the text to keep things simple:
print(f"TEXT MESSAGE SENT: {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 Wants To Call The {tool_name} Tool=======")
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("Oh dear, that tool doesn't exist!")
else:
print("No tool was called. This shouldn't happen!")
# Let's start simple:
sms_chatbot("Hey there! How are you?")
=======Claude Wants To Call The send_text_to_user Tool=======
TEXT MESSAGE SENT: Hello! I'm doing well, thanks for asking. How can I assist you today?
Claude responds back by calling the send_text_to_user tool.
Next, we'll ask Claude something a bit trickier:
sms_chatbot("I need help looking up an order")
=======Claude Wants To Call The send_text_to_user Tool=======
TEXT MESSAGE SENT: Hi there, to look up your order details I'll need your username first. Can you please provide me with your username?
Claude wants to send a text message, asking a user to provide their username.
Now, let's see what happens when we provide Claude with our username:
sms_chatbot("I need help looking up an order. My username is jenny76")
=======Claude Wants To Call The get_customer_info Tool=======
{'username': 'jenny76', 'email': 'jenny76@email.com', 'purchases': [{'id': 1, 'product': 'computer mouse'}, {'id': 2, 'product': 'screen protector'}, {'id': 3, 'product': 'usb charging cable'}]}
Claude calls the get_customer_info tool, just as we hoped!
Even if we send Claude a gibberish message, it will still call one of our tools:
sms_chatbot("askdj aksjdh asjkdbhas kjdhas 1+1 ajsdh")
=======Claude Wants To Call The send_text_to_user Tool=======
TEXT MESSAGE SENT: I'm afraid I didn't understand your query. Could you please rephrase what you need help with?