学习目标 #
- 1.了解整个工具使用工作流程
- 2.编写结构正确的 tool_result 消息
- 3.实现利用工具使用的聊天机器人
在本课中,我们将实现我们之前介绍的“完整”4 步工具使用工作流程。到目前为止,我们已经看到 Claude 使用了工具,但我们还没有向 Claude 发送包含我们工具功能结果的后续请求。以下是完整 4 步流程的回顾:
为 Claude 提供工具和用户提示:(API 请求) #
定义您希望 Claude 可以访问的工具集,包括其名称、描述和输入模式。
提供可能需要使用其中一个或多个工具来回答的用户提示,例如“我可以用 500 美元购买多少股通用汽车股票?”
Claude 使用工具:(API 响应) #
Claude 评估用户提示并决定是否有任何可用工具可以帮助用户的查询或任务。如果是,它还会决定使用哪个工具以及使用哪些输入。
Claude 输出格式正确的工具使用请求。
API 响应将具有 tool_use 的 stop_reason,表明 Claude 想要使用外部工具。
提取工具输入、运行代码并返回结果:(API 请求) #
在客户端,您应该从 Claude 的工具使用请求中提取工具名称和输入。
在客户端运行实际的工具代码。
通过使用包含 tool_result 内容块的新用户消息继续对话,将结果返回给 Claude。
Claude 使用工具结果来制定响应:(API 响应) #
收到工具结果后,Claude 将使用该信息来制定对原始用户提示的最终响应。
在本课中,我们将介绍整个过程。下面是该过程的总体概述图:
下面的图表解释了我们的脚本如何工作:
以下是一些真实的交流,说明了我们希望实现的最终结果:
用户:截至 2024 年,谁获得的奥斯卡奖更多,克里斯托弗·诺兰还是本·斯蒂勒?
克劳德想为克里斯托弗·诺兰(工具使用)写一篇文章
克劳德想为本·斯蒂勒(工具使用)写一篇文章
克劳德:截至 2024 年,电影制片人克里斯托弗·诺兰凭借 2023 年的电影《奥本海默》获得了 2 项奥斯卡最佳导演奖和最佳影片奖。另一方面,演员兼喜剧演员本·斯蒂勒迄今为止的职业生涯中还没有获得过任何奥斯卡奖,尽管他还获得了艾美奖和 MTV 电影奖等其他奖项。所以直接回答这个问题,截至 2024 年,克里斯托弗·诺兰获得的奥斯卡奖(2)比本·斯蒂勒多,而本·斯蒂勒没有获得任何奖项。
这是另一个例子:
用户:谁赢得了 2024 年大师赛?
Claude 想要获得一篇关于 2024 年大师赛的文章(工具使用)
Claude:Scottie Scheffler 赢得了 2024 年大师赛,这是他第二次赢得大师赛。他领先亚军 Ludvig Åberg 四杆,后者首次参加大师赛。
如果我们问 Claude 一个它不需要使用我们的工具就能回答的问题,它应该立即回答,而无需使用我们的维基百科搜索工具:
用户:谁赢得了 2021 年大师赛?
Claude:松山英树赢得了 2021 年大师赛,成为首位赢得大满贯高尔夫锦标赛的日本男子。他以 278(-10)的成绩在奥古斯塔国家高尔夫俱乐部夺得绿夹克。
以下是此过程结束时的消息列表:
本课的主要新主题是如何在响应 Claude 的 tool_use 消息时正确格式化我们的 tool_result 用户消息。
构建完整的工作流程 #
定义我们的维基百科搜索功能 #
在使用 Claude 进行任何操作之前,让我们编写一个简单的函数来搜索维基百科。以下函数使用 wikipedia 包根据搜索词搜索匹配的维基百科页面。为简单起见,我们获取第一个返回的页面标题,然后使用它来访问相应的页面内容。
注意:这个简单的函数假设我们找到了一篇维基百科文章。为简洁起见,该函数没有错误处理,这在现实世界中不是一个好主意!
import wikipedia
def get_article(search_term):
results = wikipedia.search(search_term)
first_result = results[0]
page = wikipedia.page(first_result, auto_suggest=False)
return page.content
article = get_article("Superman")
print(article[:500]) # article is very long, so let's just print a preview
Superman is a superhero who appears in American comic books published by DC Comics. The character was created by writer Jerry Siegel and artist Joe Shuster, and debuted in the comic book Action Comics #1 (cover-dated June 1938 and published April 18, 1938). Superman has been adapted to a number of other media, which includes radio serials, novels, films, television shows, theater, and video games.
Superman was born on the fictional planet Krypton with the birth name of Kal-El. As a baby, his par
article = get_article("Zendaya")
print(article[:500]) # article is very long, so let's just print a preview
Zendaya Maree Stoermer Coleman ( zən-DAY-ə; born September 1, 1996) is an American actress and singer. She has received various accolades, including two Primetime Emmy Awards and a Golden Globe Award. Time magazine named her one of the 100 most influential people in the world in 2022.
Born and raised in Oakland, California, Zendaya began her career as a child model and backup dancer. She made her television debut as Rocky Blue in the Disney Channel sitcom Shake It Up (2010–2013) and starred as
2.编写工具定义 #
接下来,我们需要使用正确的 JSON Schema 格式定义我们的工具。这是一个非常简单的工具定义,因为该函数只需要一个参数:搜索词字符串。
article_search_tool = {
"name": "get_article",
"description": "A tool to retrieve an up to date Wikipedia article.",
"input_schema": {
"type": "object",
"properties": {
"search_term": {
"type": "string",
"description": "The search term to find a wikipedia article by title"
},
},
"required": ["search_term"]
}
}
3.为 Claude 提供工具和用户提示 #
接下来,我们将告诉 Claude 它可以访问维基百科搜索工具,并要求它回答一个我们知道没有该工具就无法回答的问题,例如“谁赢得了 2024 年大师赛?”
from anthropic import Anthropic
from dotenv import load_dotenv
load_dotenv()
client = Anthropic()
messages = [{"role": "user", "content": "who won the 2024 Masters Tournament?"}]
response = client.messages.create(
model="claude-3-sonnet-20240229",
messages=messages,
max_tokens=1000,
tools=[article_search_tool]
)
4.Claude 使用该工具(API 响应) #
让我们看看我们收到的响应。Claude 想要使用我们的工具!
response.content
[TextBlock(text='Okay, let me see if I can find information about the winner of the 2024 Masters Tournament on Wikipedia:', type='text'),
ToolUseBlock(id='toolu_01CWuduGjgSRfsYJUNC2wxi7', input={'search_term': '2024 Masters Tournament'}, name='get_article', type='tool_use')]
Claude 的回复包含 2 个块:
一个 TextBlock,文本为“好的,让我使用可用的工具尝试在 Wikipedia 上查找谁赢得了 2024 年大师赛的信息:”
TextBlock(text=’好的,让我使用可用的工具尝试查找谁赢得了 2024 年大师赛的信息:’, type=’text’)
一个 ToolUseBlock,使用 search_term“2024 年大师赛”调用我们的 get_article 工具
ToolUseBlock(id=’toolu_01MbstBxD654o9hE2RGNdtSr’, input={‘search_term’: ‘2024 年大师赛’}, name=’get_article’, type=’tool_use’)]
5.提取工具输入、运行代码并返回结果(API 请求) #
现在 Claude 已经回复告诉我们它想要使用一个工具,现在是时候让我们实际运行底层功能并使用相应的维基百科页面内容回复 Claude 了。
我们需要特别注意确保更新我们的消息列表
我们将首先更新我们的消息列表以包含 Claude 的最新回复:
messages.append({"role": "assistant", "content": response.content})
messages
[{'role': 'user', 'content': 'who won the 2024 Masters Tournament?'},
{'role': 'assistant',
'content': [TextBlock(text='Okay, let me see if I can find information about the winner of the 2024 Masters Tournament on Wikipedia:', type='text'),
ToolUseBlock(id='toolu_01CWuduGjgSRfsYJUNC2wxi7', input={'search_term': '2024 Masters Tournament'}, name='get_article', type='tool_use')]}]
Next, we'll extract the specific tool and arguments that Claude wants to use:
# This is a simple, but brittle way of getting the tool use information
# We're simply taking the last block from Claude's response.
tool_use = response.content[-1]
tool_name = tool_use.name
tool_input = tool_use.input
print("Tool name: ", tool_name)
print("Tool input", tool_input)
Tool name: get_article
Tool input {'search_term': '2024 Masters Tournament'}
接下来,我们将确保 Claude 调用我们期望的 get_article 工具。我们将采用 Claude 提出的 search_term,并将其传递给我们之前编写的 get_article 函数。
if tool_name == "get_article":
search_term = tool_input["search_term"]
wiki_result = get_article(search_term)
print(f"在 Wikipedia 中搜索 {search_term}")
print("WIKIPEDIA 页面内容:")
print(wiki_result[:500]) #只打印一小部分文章,因为文章太长了
Searching Wikipedia for 2024 Masters Tournament
WIKIPEDIA PAGE CONTENT:
The 2024 Masters Tournament was the 88th edition of the Masters Tournament and the first of the men's four major golf championships held in 2024. The tournament was played from April 11–14 at Augusta National Golf Club in Augusta, Georgia, United States. Scottie Scheffler won his second Masters and major, four strokes ahead of runner-up Ludvig Åberg, who was playing in his first major. Scheffler became the fourth-youngest two-time winner of the tournament and the second player, after Tiger Woods
现在我们已经执行了 Claude 希望我们调用的函数,是时候用 Wikipedia 页面数据回复 Claude 了。
我们知道,当 Claude 想要使用某个工具时,它会用 tool_use 的 stop_reason 和 API 响应中的一个或多个 tool_use 内容块来回复我们,其中包括:
id:此特定工具使用块的唯一标识符。这将用于稍后匹配工具结果。
name:正在使用的工具的名称。
input:包含传递给工具的输入的对象,符合工具的 input_schema。
一旦我们执行了底层工具函数,我们还需要用特定的格式回复 Claude。具体来说,要继续对话,我们需要发送一条新消息,其中包含用户角色和包含 tool_result 类型的内容块,以及以下信息:
tool_use_id:这是结果的工具使用请求的 ID。
content:工具的结果,以字符串形式(例如“content”:“15 度”)或嵌套内容块列表形式(例如“content”:[{“type”:“text”,“text”:“15 度”}])。
is_error(可选):如果工具执行导致错误,则设置为 true。
以下是格式正确的 tool_result 消息的示例:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "The result of actually calling the tool goes here"
}
]
}
现在让我们为我们的 Wikipedia 搜索示例执行此操作。我们需要形成一个正确构造的工具响应消息,以将我们的 Wikipedia 搜索结果发送回 Claude:
tool_response = {
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": wiki_result
}
]
}
# 注意维基百科文章内容很长! tool_response
{'role': 'user',
'content': [{'type': 'tool_result',
'tool_use_id': 'toolu_01CWuduGjgSRfsYJUNC2wxi7',
'content': "The 2024 Masters Tournament was the 88th edition of the Masters Tournament and the first of the men's four major golf championships held in 2024. The tournament was played from April 11–14 at Augusta National Golf Club in Augusta, Georgia, United States. Scottie Scheffler won his second Masters and major,"}]}
messages.append(tool_response)
我们的消息列表现在如下所示:
用户:谁赢得了 2024 年大师赛?
助手:我想使用 get_article 工具和 search_term“2024 年大师赛”
用户:这是包含您要求的维基百科文章的工具结果
以下是说明这一点的图表。
请注意,初始 ID 与我们后续用户消息中的 tool_use_id 相匹配
6. Claude 使用工具结果来制定响应:(API 响应) #
最后,我们可以使用更新的消息列表并向 Claude 发送新请求:
follow_up_response = client.messages.create(
model="claude-3-sonnet-20240229",
messages=messages,
max_tokens=1000,
tools=[article_search_tool]
)
follow_up_response.content[0].text
"Based on the summary from Wikipedia, it appears that Scottie Scheffler won the 2024 Masters Tournament. He shot a final round 68 to finish at 11-under par, four shots ahead of runner-up Ludvig Åberg. This was Scheffler's second Masters title, having also won in 2022. So the key details are:\n\n2024 Masters Tournament Winner: Scottie Scheffler (-11)\nRunner-Up: Ludvig Åberg (-7)\n\nThe Wikipedia article provides a detailed recap of each round and how Scheffler was able to separate himself from the rest of the field over the final 18 holes to claim his second green jacket. Let me know if you need any other information from the summary!"
claude现在掌握了回答初始问题所需的信息,并回复道:
“根据维基百科文章摘要,斯科蒂·舍夫勒似乎赢得了 2024 年大师赛。他打进了决赛……’
我们现在已经完成了该过程的所有 4 个步骤!
改进代码 #
至少,我们可能希望将上述所有代码放入可重复使用的函数中,以便我们可以尝试几次:
def answer_question(question):
messages = [{"role": "user", "content": question}]
response = client.messages.create(
model="claude-3-sonnet-20240229",
messages=messages,
max_tokens=1000,
tools=[article_search_tool]
)
if(response.stop_reason == "tool_use"):
tool_use = response.content[-1]
tool_name = tool_use.name
tool_input = tool_use.input
#Add Claude's tool use call to messages:
messages.append({"role": "assistant", "content": response.content})
if tool_name == "get_article":
search_term = tool_input["search_term"]
print(f"Claude wants to get an article for {search_term}")
wiki_result = get_article(search_term) #get wikipedia article content
#construct our tool_result message
tool_response = {
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": wiki_result
}
]
}
messages.append(tool_response)
#respond back to Claude
response = client.messages.create(
model="claude-3-sonnet-20240229",
messages=messages,
max_tokens=1000,
tools=[article_search_tool]
)
print("Claude's final answer:")
print(response.content[0].text)
else:
print("Claude did not call our tool")
print(response.content[0].text)
answer_question("Who won the 2024 F1 Australian Grand Prix")
Claude want to get an article for 2024 F1 Australian Grand Prix
Claude's Final Answer:
The key information from the Wikipedia article is:
Carlos Sainz Jr. of Ferrari won the 2024 Australian Grand Prix, with his teammate Charles Leclerc finishing second. Lando Norris of McLaren finished third.
Max Verstappen of Red Bull Racing started on pole but retired early due to a brake failure, ending his streak of 9 consecutive wins.
The race set a new attendance record at Albert Park of 452,055 spectators over the weekend, making it the most attended sporting event ever in Melbourne.
answer_question("Who stars in the movie Challengers?")
Claude want to get an article for Challengers (2023 film)
Claude's Final Answer:
Based on the plot summary and cast details from Wikipedia, the main stars of the 2023 film Challengers are:
- Zendaya as Tashi Duncan
- Josh O'Connor as Patrick Zweig
- Mike Faist as Art Donaldson
The film also features supporting roles by Darnell Appling, AJ Lister, Nada Despotovich, Naheem Garcia, and Hailey Gates.
So the three leads starring in the romantic sports drama Challengers are Zendaya, Josh O'Connor, and Mike Faist.
#Let's try an example that Claude should NOT need our tool to answer:
answer_question("Who wrote the book 'Life of Pi'")
Claude want to get an article for Life of Pi
Claude's Final Answer:
Based on the Wikipedia article retrieved, the book 'Life of Pi' was written by the Canadian author Yann Martel and published in 2001. It won the Man Booker Prize in 2002 and has sold over 10 million copies worldwide. The novel tells the story of an Indian boy named Pi Patel who survives for 227 days stranded on a lifeboat in the Pacific Ocean after a shipwreck, with a Bengal tiger named Richard Parker as his only companion.
请注意,尽管Claude已经知道答案,但他还是调用了我们的维基百科工具来帮助回答最后一个问题。《少年派的奇幻漂流》出版于 2001 年,远早于Claude的培训结束时间!
改进我们的提示 #
正如我们在上一课中看到的,有时 Claude 过于渴望使用工具。解决这个问题的一个简单方法是通过系统提示。
我们可以添加一个如下所示的系统提示:
system_prompt = """
You will be asked a question by the user.
If answering the question requires data you were not trained on, you can use the get_article tool to get the contents of a recent wikipedia article about the topic.
If you can answer the question without needing to get more information, please do so.
Only call the tool when needed.
"""
让我们更新我们的函数以使用这个新的系统提示:
def answer_question(question):
system_prompt = """
You will be asked a question by the user.
If answering the question requires data you were not trained on, you can use the get_article tool to get the contents of a recent wikipedia article about the topic.
If you can answer the question without needing to get more information, please do so.
Only call the tool when needed.
"""
messages = [{"role": "user", "content": question}]
response = client.messages.create(
model="claude-3-sonnet-20240229",
system=system_prompt,
messages=messages,
max_tokens=1000,
tools=[article_search_tool]
)
if(response.stop_reason == "tool_use"):
tool_use = response.content[-1]
tool_name = tool_use.name
tool_input = tool_use.input
#Add Claude's tool use call to messages:
messages.append({"role": "assistant", "content": response.content})
if tool_name == "get_article":
search_term = tool_input["search_term"]
print(f"Claude wants to get an article for {search_term}")
wiki_result = get_article(search_term) #get wikipedia article content
#construct our tool_result message
tool_response = {
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": wiki_result
}
]
}
messages.append(tool_response)
#respond back to Claude
response = client.messages.create(
model="claude-3-sonnet-20240229",
system=system_prompt,
messages=messages,
max_tokens=1000,
tools=[article_search_tool]
)
print("Claude's final answer:")
print(response.content[0].text)
else:
print("Claude did not call our tool")
print(response.content[0].text)
让我们尝试问同样的问题:
answer_question("Who wrote the book 'Life of Pi'")
Claude did not call our tool
The book 'Life of Pi' was written by the Canadian author Yann Martel. It was published in 2001 and went on to become an international bestseller. The novel blends elements of magical realism, adventure fiction and philosophical fiction. It tells the story of an Indian boy named Pi who survives for 227 days adrift in the Pacific Ocean after a shipwreck, in the company of a Bengal tiger named Richard Parker. The book won the Man Booker Prize in 2002 and was later adapted into an acclaimed film directed by Ang Lee in 2012.
It worked! Claude did not use our tool when it wasn't needed. Let's make sure it still works when answering questions that do require recent knowledge:
answer_question("Who wrote the score for the movie Challengers?")
Claude want to get an article for Challengers (film)
Claude's Final Answer:
Based on the Wikipedia article summary, the score for the film Challengers was composed by Trent Reznor and Atticus Ross. The relevant quote is:
"Trent Reznor and Atticus Ross composed the film's score, having previously worked with Guadagnino on 2022's Bones and All. Post-production was completed by April 2023."
So to answer your original question - the musical score for the 2024 film Challengers was written by the composer duo Trent Reznor and Atticus Ross.
在两种情况下,它都能按预期工作!现在让我们努力让 Claude 的回答更简洁一些。Claude 解释了它是如何得出正确答案的,这很好,但有点冗长。让我们做一些基本的提示工程来解决这个问题。
让我们试试这个:
prompt = f"""
Answer the following question <question>Who wrote the movie Poor Things?</question>
When you can answer the question, keep your answer as short as possible and enclose it in <answer> tags
"""
以下是使用新提示更新后的函数:
def answer_question(question):
system_prompt = """
You will be asked a question by the user.
If answering the question requires data you were not trained on, you can use the get_article tool to get the contents of a recent wikipedia article about the topic.
If you can answer the question without needing to get more information, please do so.
Only call the tool when needed.
"""
prompt = f"""
Answer the following question <question>{question}</question>
When you can answer the question, keep your answer as short as possible and enclose it in <answer> tags
"""
messages = [{"role": "user", "content": prompt}]
response = client.messages.create(
model="claude-3-sonnet-20240229",
system=system_prompt,
messages=messages,
max_tokens=1000,
tools=[article_search_tool]
)
if(response.stop_reason == "tool_use"):
tool_use = response.content[-1]
tool_name = tool_use.name
tool_input = tool_use.input
#Add Claude's tool use call to messages:
messages.append({"role": "assistant", "content": response.content})
if tool_name == "get_article":
search_term = tool_input["search_term"]
print(f"Claude wants to get an article for {search_term}")
wiki_result = get_article(search_term) #get wikipedia article content
#construct our tool_result message
tool_response = {
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": wiki_result
}
]
}
messages.append(tool_response)
#respond back to Claude
response = client.messages.create(
model="claude-3-sonnet-20240229",
system=system_prompt,
messages=messages,
max_tokens=1000,
tools=[article_search_tool]
)
print("Claude's final answer:")
print(response.content[0].text)
else:
print("Claude did not call our tool")
print(response.content[0].text)
answer_question("Who wrote the score for the movie Challengers?")
Claude want to get an article for Challengers (2023 film)
Claude's Final Answer:
<answer>The score for the movie Challengers was composed by Trent Reznor and Atticus Ross.</answer>
answer_question("How many legs does an octopus have?")
Claude did not call our tool
<answer>An octopus has 8 legs.</answer>
好多了!Claude 现在可以直接给出答案,而不需要额外“思考”和解释如何得出答案。
练习 #
您能否更新此代码,使其满足以下要求:
- Claude 可能无法从我们回复的第一个维基百科页面获得足够的信息。我们还没有处理这种情况。想象一下,我们问 Claude“Christopher Nolan 获得了多少个奥斯卡奖?他获得的艾美奖数量是否比 Ben Stiller 多?” Claude 需要搜索 Christopher Nolan 的维基百科页面和 Ben Stiller 的页面,可能一个接一个。我们的代码目前不允许这样做,所以让我们构建该功能!提示:使用循环!
- 从 Claude 当前回复的 标签中提取答案,以便您只打印出实际的答案内容。
- 您能否将其变成一个完整的命令行聊天机器人,不断要求用户输入查询,然后一遍又一遍地回复答案,直到用户退出程序?输出可能看起来像这样:
以下是示例对话会话的屏幕截图: