文章翻译自github.com/anthropics/…
完整的工具使用工作流程
学习目标
-
理解整个工具使用工作流程
-
撰写格式规范的 tool_result 消息
-
实现一个利用工具使用功能的聊天机器人
在本节课中,我们将实现前面介绍过的 “完整” 的 4 步工具使用流程。到目前为止,我们已经了解到 Claude 会使用工具,但还没有向 Claude 发送包含工具功能结果的后续请求。下面简要回顾一下完整的 4 步流程:
-
向 Claude 提供工具和用户提示:(API 请求)
- 定义你希望 Claude 能够使用的工具集,包括工具的名称、描述和输入模式。
- 提供一个可能需要使用这些工具中的一种或多种来回答的用户提示,例如 “用 500 美元我能买多少股通用汽车的股票?”
-
Claude 使用工具:(API 响应)
- Claude 评估用户的提示,判断是否有可用的工具有助于解决用户的查询或任务。如果有,它还会决定使用哪些工具以及使用什么输入。
- Claude 输出格式规范的工具使用请求。
- API 响应的 stop_reason 将为 tool_use,表明 Claude 想要使用外部工具。
-
提取工具输入、运行代码并返回结果:(API 请求)
- 在客户端,你需要从 Claude 的工具使用请求中提取工具名称和输入。
- 在客户端运行实际的工具代码。
- 通过包含 tool_result 内容块的新用户消息继续对话,将结果返回给 Claude。
-
Claude 使用工具结果来制定响应:(API 响应)
- 收到工具结果后,Claude 将使用该信息来制定对原始用户提示的最终响应。
在本节课中,我们将逐步完成整个流程。
下面是一个关于该流程的总体概述图:
下面是一个解释我们的脚本将如何工作的图:
以下是一些真实的交流示例,展示了我们希望达成的最终结果:
用户:截至 2024 年,克里斯托弗・诺兰和本・斯蒂勒谁获得的奥斯卡奖更多?
Claude 想要获取克里斯托弗・诺兰的相关文章(工具使用)
Claude 想要获取本・斯蒂勒的相关文章(工具使用)
Claude:截至 2024 年,电影制作人克里斯托弗・诺兰凭借其 2023 年的电影《奥本海默》获得了 2 项奥斯卡奖,分别是最佳导演奖和最佳影片奖。另一方面,演员兼喜剧演员本・斯蒂勒在其职业生涯中尚未获得任何奥斯卡奖,不过他获得过其他奖项,如艾美奖和 MTV 电影奖。所以直接回答这个问题的话,截至 2024 年,克里斯托弗・诺兰获得的奥斯卡奖(2 项)比本・斯蒂勒多,本・斯蒂勒一项也没有获得。
另一个例子:
用户:谁赢得了 2024 年美国大师赛?
Claude 想要获取 2024 年美国大师赛的相关文章(工具使用)
Claude:斯科蒂・舍夫勒赢得了 2024 年美国大师赛,这是他第二次获得大师赛冠军。他以四杆的优势领先于亚军路德维希・奥伯格,奥伯格当时是首次参加大师赛。
如果我们问 Claude 一个它不需要使用我们的工具就能回答的问题,它应该直接回答,而不使用我们的维基百科搜索工具:
用户:谁赢得了 2021 年美国大师赛?
Claude:松山英树赢得了 2021 年美国大师赛,成为首位赢得高尔夫大满贯赛事的日本男性。他以 278 杆(-10)的成绩在奥古斯塔国家高尔夫俱乐部赢得了绿夹克。
下面来看看流程结束时消息列表的样子:
本节课的主要新内容是,当响应 Claude 的 tool_use 消息时,如何正确格式化我们的 tool_result 用户消息。
写在中间
我最近正在使用一个超酷的Claude code镜像站
使用非常舒服,适配各种IDE,国内也能够用,填写我的码 「5OTTEB」 还有额外额度,快来试试!
构建完整的工作流程
1. 定义我们的维基百科搜索函数
在使用 Claude 之前,让我们先编写一个简单的函数来搜索维基百科。以下函数使用wikipedia包根据搜索词搜索匹配的维基百科页面。为简单起见,我们选取返回的第一个页面标题,然后通过该标题获取相应的页面内容。
注意:这个简单的函数假设我们能找到对应的维基百科文章。为简洁起见,该函数没有包含错误处理,这在实际应用中可不是个好主意!
python
运行
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
python
运行
article = get_article("Superman")
print(article[:500]) # 文章很长,所以我们只打印预览部分
超人是出现在 DC 漫画公司出版的美国漫画中的超级英雄。这个角色由作家杰里・西格尔和艺术家乔・舒斯特创作,首次登场于《动作漫画》第 1 期(封面标注日期为 1938 年 6 月,实际出版于 1938 年 4 月 18 日)。超人已被改编成多种其他媒体形式,包括广播剧、小说、电影、电视节目、戏剧和电子游戏。
超人出生在虚构的氪星,本名卡尔 - 艾尔。在他还是婴儿时,他的父母……
python
运行
article = get_article("Zendaya")
print(article[:500]) # 文章很长,所以我们只打印预览部分
赞达亚・马里・斯托默・科尔曼(发音:zən-DAY-ə;1996 年 9 月 1 日出生)是美国女演员和歌手。她获得过多个奖项,包括两项黄金时段艾美奖和一项金球奖。2022 年,《时代》杂志将她评为全球 100 位最具影响力人物之一。
赞达亚在加利福尼亚州奥克兰出生并长大,她的职业生涯始于儿童模特和后备舞者。她在迪士尼频道情景喜剧《舞动芝加哥》(2010–2013)中饰演罗基・布鲁,首次亮相电视屏幕,并在……
2. 编写工具定义
接下来,我们需要使用适当的 JSON Schema 格式来定义我们的工具。这是一个非常简单的工具定义,因为该函数只需要一个参数:搜索词字符串。
python
运行
article_search_tool = {
"name": "get_article",
"description": "用于检索最新维基百科文章的工具。",
"input_schema": {
"type": "object",
"properties": {
"search_term": {
"type": "string",
"description": "通过标题查找维基百科文章的搜索词"
},
},
"required": ["search_term"]
}
}
3. 向 Claude 提供工具和用户提示
接下来,我们会告诉 Claude 它可以使用维基百科搜索工具,并让它回答一个我们知道它不借助工具就无法回答的问题,比如 “谁赢得了 2024 年美国大师赛?”
python
运行
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 想要使用我们的工具!
python
运行
response.content
[TextBlock (text=' 好的,我来看看能否在维基百科上找到 2024 年美国大师赛冠军的相关信息:', type='text'),
ToolUseBlock (id='toolu_01CWuduGjgSRfsYJUNC2wxi7', input={'search_term': '2024 Masters Tournament'}, name='get_article', type='tool_use')]
Claude 的响应包含两个块:
- 一个文本块,内容为 “好的,我来使用可用的工具,尝试在维基百科上找到 2024 年美国大师赛冠军的相关信息:”
TextBlock (text=' 好的,我来使用可用的工具,尝试在维基百科上找到 2024 年美国大师赛冠军的相关信息:', type='text') - 一个工具使用块,调用我们的
get_article工具,搜索词为 “2024 Masters Tournament”
ToolUseBlock (id='toolu_01MbstBxD654o9hE2RGNdtSr', input={'search_term': '2024 Masters Tournament'}, name='get_article', type='tool_use')]
5. 提取工具输入、运行代码并返回结果(API 请求)
既然 Claude 已经回复说它想要使用工具,现在我们就该实际运行底层功能,并将相应的维基百科页面内容回复给 Claude 了。
我们需要特别注意确保更新我们的消息列表。
我们首先将 Claude 最近的响应添加到消息列表中:
python
运行
messages.append({"role": "assistant", "content": response.content})
messages
[{'role': 'user', 'content': 'who won the 2024 Masters Tournament?'},
{'role': 'assistant',
'content': [TextBlock (text=' 好的,我来看看能否在维基百科上找到 2024 年美国大师赛冠军的相关信息:', type='text'),
ToolUseBlock (id='toolu_01CWuduGjgSRfsYJUNC2wxi7', input={'search_term': '2024 Masters Tournament'}, name='get_article', type='tool_use')]}]
接下来,我们提取 Claude 想要使用的特定工具和参数:
python
运行
# 这是一种简单但不够稳健的获取工具使用信息的方式
# 我们只是取了Claude响应中的最后一个块
tool_use = response.content[-1]
tool_name = tool_use.name
tool_input = tool_use.input
print("工具名称:", tool_name)
print("工具输入", tool_input)
工具名称: get_article
工具输入 {'search_term': '2024 Masters Tournament'}
接下来,我们要确认 Claude 调用的是我们预期的get_article工具。我们将把 Claude 想出的搜索词传递给我们之前编写的get_article函数。
python
运行
if tool_name == "get_article":
search_term = tool_input["search_term"]
wiki_result = get_article(search_term)
print(f"正在维基百科上搜索{search_term}")
print("维基百科页面内容:")
print(wiki_result[:500]) # 只打印文章的一小部分,因为它太长了
正在维基百科上搜索 2024 Masters Tournament
维基百科页面内容:
2024 年美国大师赛是第 88 届美国大师赛,也是 2024 年男子四大高尔夫锦标赛中的第一项。该赛事于 4 月 11 日至 14 日在美国佐治亚州奥古斯塔的奥古斯塔国家高尔夫俱乐部举行。斯科蒂・舍夫勒赢得了他的第二个大师赛冠军和大满贯冠军,以四杆优势领先于首次参加大满贯的亚军路德维希・奥伯格。舍夫勒成为该赛事第四年轻的两度冠军,也是继 2001 年的泰格・伍兹之后,第二位在同一日历年中同时赢得大师赛和球员锦标赛的选手。
现在我们已经执行了 Claude 想要我们调用的函数,是时候将维基百科页面数据回复给 Claude 了。
我们知道,当 Claude 想要使用工具时,它会在 API 响应中返回stop_reason为tool_use,并且包含一个或多个tool_use内容块,其中包括:
-
id:这个特定工具使用块的唯一标识符。稍后将用于匹配工具结果。 -
name:正在使用的工具的名称。 -
input:传递给工具的输入对象,符合工具的input_schema。
一旦我们执行了底层工具函数,我们也需要以特定的格式回复 Claude。具体来说,为了继续对话,我们需要发送一条新消息,其role为user,内容块包含tool_result类型,以及以下信息:
-
tool_use_id:此结果所对应的工具使用请求的 ID。 -
content:工具的结果,可为字符串(例如 "content": "15 度")或嵌套内容块的列表(例如 "content": [{"type": "text", "text": "15 度"}])。 -
is_error(可选):如果工具执行出错,设为true。
以下是格式正确的tool_result消息示例:
json
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "这里是实际调用工具的结果"
}
]
}
现在让我们为维基百科搜索示例做同样的事情。我们需要构建一个格式正确的工具响应消息,将维基百科搜索结果发送回 Claude:
python
运行
tool_response = {
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": wiki_result
}
]
}
python
运行
# 注意长长的维基百科文章内容!
tool_response
{'role': 'user',
'content': [{'type': 'tool_result',
'tool_use_id': 'toolu_01CWuduGjgSRfsYJUNC2wxi7',
'content': "2024 年美国大师赛是第 88 届美国大师赛,也是 2024 年男子四大高尔夫锦标赛中的第一项。该赛事于 4 月 11 日至 14 日在美国佐治亚州奥古斯塔的奥古斯塔国家高尔夫俱乐部举行。斯科蒂・舍夫勒赢得了他的第二个大师赛冠军和大满贯冠军,以四杆优势领先于首次参加大满贯的亚军路德维希・奥伯格。舍夫勒成为该赛事第四年轻的两度冠军,也是继 2001 年的泰格・伍兹之后,第二位在同一日历年中同时赢得大师赛和球员锦标赛的选手。\n\n\n== 球场 ==\n\n2024 年赛事对球场的唯一改动是将第二洞加长了 10 码。\n\n\n== 参赛阵容 ==\n 美国大师赛仅限受邀者参加,是四大满贯中参赛人数最少的赛事。获得邀请的标准有很多,包括所有往届冠军、近期大满贯冠军、上一年度大满贯赛事的领先选手、上一赛季美巡赛的领先选手、过去 12 个月美巡赛全分赛事的冠军、高尔夫官方世界排名领先的选手以及一些顶尖业余选手。\n\n\n=== 参赛资格 ===\n2023 年至 2024 年的赛事在受邀者资格方面有三处变动。第一处是增加了当前 NCAA 一级联盟男子个人冠军的参赛席位,前提是他在赛事举办时仍为业余选手。此外,奥古斯塔国家高尔夫俱乐部明确表示,有资格参加巡回锦标赛的选手必须保持参加该赛事的资格,才能获得大师赛的参赛资格。另外,随着美巡赛回归日历年赛季安排,奥古斯塔国家高尔夫俱乐部指出,秋季美巡赛赛事的冠军将继续有资格参加大师赛。\n 以下列表详细说明了 2024 年美国大师赛的参赛资格标准以及符合这些标准的选手;选手获得资格所依据的其他标准在括号中注明。\n1. 所有美国大师赛往届冠军 \n\n 未参赛的往届冠军:汤米・亚伦、安赫尔・卡布雷拉、查尔斯・库迪、本・克伦肖、尼克・佛度、雷蒙德・弗洛伊德、特雷弗・伊梅尔曼、伯恩哈德・兰格、桑迪・莱尔、拉里・米兹、杰克・尼克劳斯、马克・欧米拉、加里・普莱尔、克雷格・斯塔德勒、汤姆・沃森、伊恩・伍斯南、福兹・佐勒尔 \n2. 近期美国公开赛冠军(2019–2023)\n\n3. 近期英国公开赛冠军(2019–2023)\n\n4. 近期 PGA 锦标赛冠军(2019–2023)\n\n5. 近期球员锦标赛冠军(2022–2024)\n6. 奥运会金牌得主 \n7. 2023 年美国业余锦标赛冠军和亚军 \n\n 尼尔・希普利(业余)\n8. 2023 年业余锦标赛冠军 \n\n 克里斯托・兰普雷希特(业余)\n9. 2023 年亚太业余锦标赛冠军 \n\n 贾斯珀・斯塔布斯(业余)\n10. 2024 年拉丁美洲业余锦标赛冠军 \n\n 圣地亚哥・德拉富恩特(业余)\n11. 2023 年美国中年业余高尔夫锦标赛冠军 \n\n 斯图尔特・哈格斯特德(业余)\n12. 2023 年 NCAA 一级联盟男子高尔夫个人冠军 \n\n 弗雷德・比昂迪因转为职业选手而放弃了邀请。\n13. 2023 年美国大师赛排名前 12 位及并列第 12 位的选手 \n\n14. 2023 年美国公开赛排名前 4 位及并列第 4 位的选手 \n\n 罗里・麦克罗伊(17、18、19、20)\n15. 2023 年英国公开赛排名前 4 位及并列第 4 位的选手 \n\n16. 2023 年 PGA 锦标赛排名前 4 位及并列第 4 位的选手 \n\n17. 2023 年美国大师赛至 2024 年美国大师赛期间美巡赛赛事的冠军 \n\n18. 所有有资格参加且符合 2023 年巡回锦标赛资格的选手 \n\n19. 截至 2023 年 12 月 31 日高尔夫官方世界排名前 50 位的选手 \n\n20. 截至 2024 年 4 月 1 日高尔夫官方世界排名前 50 位的选手 \n\n 安秉勋 \n21. 特别邀请 \n\n\n== 三杆洞挑战赛 ==\n4 月 10 日,星期三 \n 瑞奇・福勒以 22 杆(-5)的成绩赢得了三杆洞挑战赛。比赛中出现了五记一杆进洞,分别由塞普・斯特拉卡、卢克・李斯特、加里・伍德兰德、维克多・霍夫兰和卢卡斯・格洛弗打出。\n\n\n== 轮次总结 ==\n\n\n=== 第一轮 ===\n2024 年 4 月 11 日,星期四;2024 年 4 月 12 日,星期五 \n 恶劣天气将赛事开始时间推迟到美国东部时间上午 10:30。结果,27 名选手在星期四未能完成第一轮比赛。\n par 5 的第 8 洞创下了 53 个破标准杆成绩的纪录,包括 50 个小鸟球和 3 个老鹰球,这是大师赛历史上该洞的最高纪录。\n\n\n=== 第二轮 ===\n2024 年 4 月 12 日,星期五 \n 通过晋级,泰格・伍兹创下了在美国大师赛连续晋级的纪录,达到 24 次。菲尔・米克尔森第 28 次晋级,超过了雷蒙德・弗洛伊德和伯恩哈德・兰格,在大师赛晋级次数上独自位列第四,仅次于杰克・尼克劳斯(37 次)、弗雷德・库普勒斯(31 次)和加里・普莱尔(30 次)。\n 新秀路德维希・奥伯格打出了本轮最低杆数 69 杆,升至单独第 7 位。\n 布赖森・德尚博在第 13 洞打出小鸟球之前,移动了一个挡在他击球路线上的大型方向指示牌。\n 帕特里克・坎特雷在四杆洞打出了两个老鹰球,这是历史上第四次有选手在单届美国大师赛中在四杆洞打出两个老鹰球,上一次是 2006 年的布兰特・乔布。\n 晋级线为 150 杆(+6),60 名选手晋级到周末的比赛。值得注意的是,2015 年冠军乔丹・斯皮思、2020 年冠军达斯汀・约翰逊、卫冕美国公开赛冠军温德姆・克拉克、英国公开赛冠军布莱恩・哈曼以及世界排名第 6 的维克多・霍夫兰未能晋级。两届大满贯冠军贾斯汀・托马斯在最后四洞打出高于标准杆 7 杆,以一杆之差未能晋级。\n\n\n=== 第三轮 ===\n2024 年 4 月 13 日,星期六 \n 斯科蒂・舍夫勒在本轮开始时与其他两人并列领先,他在第一洞切球进洞抓鸟。在第 10 洞打出双柏忌后,舍夫勒在 par 5 的第 13 洞推入了 31 英尺的老鹰推,这是本轮该洞唯一的老鹰球。他还在第 15 洞和第 18 洞抓鸟,最终以 71 杆(-1)的成绩结束本轮,总成绩低于标准杆 7 杆,以一杆优势进入最后一轮。\n 科林・莫里 ikawa 在前三个洞连续抓鸟,打出低于标准杆 3 杆的 69 杆,总成绩低于标准杆 6 杆,落后舍夫勒一杆。马克斯・霍马在本轮开始时并列领先,但在打出高于标准杆 1 杆的 73 杆后,没有抓到一只小鸟,总成绩降至低于标准杆 5 杆,落后两杆。布赖森・德尚博在本轮开始时也处于领先榜顶端,但在第 15 洞的第三杆打入水中,打出双柏忌。他在第 18 洞直接击球进洞抓鸟,最终总成绩低于标准杆 3 杆,落后舍夫勒四杆。路德维希・奥伯格首次参加大师赛和大满贯赛事,在本轮打出低于标准杆 4 杆后,在第 14 洞打出柏忌。随后,他在第 15 洞的第三杆打短,在果岭边三推又打出一个柏忌。他打出 70 杆(-2),最终总成绩低于标准杆 4 杆。\n 尼科莱・霍伊加德在第 8 至 10 洞连续抓鸟三杆,一度独自领先,但随后连续打出五个柏忌,包括在第 13 洞和第 15 洞都打入水中,最终总成绩低于标准杆 2 杆。\n 泰格・伍兹在第三轮打出高于标准杆 10 杆的 82 杆,这是他在大满贯赛事中的最高杆数。\n 在前三轮中,par 5 的第 15 洞没有出现老鹰球,这是过去 50 年来第二次在赛事进行到此时该洞仍未出现老鹰球。谢恩・劳瑞在 par 4 的第 14 洞打出老鹰球,这是自 2016 年马丁・凯梅尔以来该洞首次出现老鹰球。\n\n\n=== 决赛轮 ===\n2024 年 4 月 14 日,星期日 \n\n 世界排名第一的斯科蒂・舍夫勒打出低于标准杆 4 杆的 68 杆,在过去三年中第二次赢得大师赛冠军,以四杆优势击败亚军路德维希・奥伯格。\n 舍夫勒在本轮开始时领先一杆,他在第三洞抓鸟,将领先优势扩大到两杆。随后,他在第四洞因开球打过果岭而打出柏忌。马克斯・霍马在第二洞抓鸟,这是他 34 个洞以来的第一只小鸟,将差距缩小到一杆,而奥伯格在第七洞将球攻到四英尺处并抓鸟,也将差距缩小到一杆。\n 舍夫勒在第七洞打出柏忌,与奥伯格和科林・莫里 ikawa 并列领先,总成绩为低于标准杆 6 杆,而霍马在 par 5 的第八洞两推进洞抓鸟,加入领先行列。舍夫勒和莫里 ikawa 都在第八洞抓鸟,将总成绩提升到低于标准杆 7 杆,而奥伯格在第九洞推入 36 英尺的小鸟推,再次追平。舍夫勒随后在第九洞将球攻到一英尺处,再抓一鸟,总成绩达到低于标准杆 8 杆,在转场到后九洞时再次独自领先。\n 三位挑战者随后都退出了争夺。莫里 ikawa 在第九洞的第二杆打入果岭边沙坑,第三杆未能推进,最终打出双柏忌,落后三杆。他在第 11 洞的攻果岭球打入水中,又打出一个双柏忌。奥伯格在第 11 洞也打入水中,打出双柏忌。霍马在第 10 洞将球攻到很近的位置,一度将差距缩小到一杆,但他在第 12 洞的开球飞过果岭进入灌木丛,被迫抛球。他在该洞打出双柏忌,落后三杆。\n 舍夫勒在第 10 洞抓鸟,这是他连续第三只小鸟,总成绩达到低于标准杆 9 杆,拉开两杆优势。尽管在第 11 洞打出柏忌,但舍夫勒在 par 5 的第 13 洞两推进洞抓鸟,并在第 14 洞将第二杆攻到四英尺处再抓一鸟,总成绩达到低于标准杆 10 杆。随后,他在 par 3 的第 16 洞推入九英尺的小鸟推,总成绩达到低于标准杆 11 杆。最后两洞保帕让他以四杆优势赢得比赛。\n 奥伯格在第 13 洞和第 14 洞抓鸟,将总成绩回升到低于标准杆 7 杆,在他的大师赛和大满贯赛事首秀中落后舍夫勒四杆获得亚军。莫里 ikawa 和霍马最终都以总成绩低于标准杆 4 杆结束赛事,与汤米・弗利特伍德并列第三。\n\n\n==== 记分卡 ====\n\n 锦标赛累计成绩,相对于标准杆 \n\n 来源:\n\n\n== 注释 ==\n\n\n== 参考资料 ==\n\n\n== 外部链接 ==\n 官方网站"}]}
接下来,我们需要将我们的tool_response消息添加到我们的消息列表中:
我们的消息列表现在看起来是这样的:
用户:谁赢得了 2024 年美国大师赛?
助手:我想使用 get_article 工具,搜索词为 “2024 Masters Tournament”
用户:这是你请求的维基百科文章工具结果
这里有一张图对此进行说明:
注意,初始 ID 与我们后续用户消息中的 tool_use_id 相匹配
6. Claude 使用工具结果制定响应:(API 响应)
最后,我们可以使用更新后的消息列表向 Claude 发送新请求:
python
运行
follow_up_response = client.messages.create(
model="claude-3-sonnet-20240229",
messages=messages,
max_tokens=1000,
tools=[article_search_tool]
)
python
运行
follow_up_response.content[0].text
“根据维基百科的摘要,斯科蒂・舍夫勒赢得了 2024 年美国大师赛。他在决赛轮打出 68 杆,以低于标准杆 11 杆的成绩结束比赛,领先亚军路德维希・奥伯格四杆。这是舍夫勒的第二个大师赛冠军,他曾在 2022 年夺冠。关键信息如下:
2024 年美国大师赛冠军:斯科蒂・舍夫勒(-11)
亚军:路德维希・奥伯格(-7)
维基百科文章详细回顾了每一轮的情况,以及舍夫勒如何在最后 18 洞中脱颖而出,赢得他的第二件绿夹克。如果需要摘要中的其他信息,请告诉我!”
现在 Claude 已经获得了回答初始问题所需的信息,并回复:
“根据维基百科文章摘要,斯科蒂・舍夫勒赢得了 2024 年美国大师赛。他在决赛轮打出……”
我们现在已经完成了这个流程的所有 4 个步骤!
改进代码
至少,我们可能希望将上述所有代码放入一个可重用的函数中,以便我们可以尝试几次:
python
运行
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
# 将Claude的工具使用调用添加到消息中:
messages.append({"role": "assistant", "content": response.content})
if tool_name == "get_article":
search_term = tool_input["search_term"]
print(f"Claude想要获取{search_term}的文章")
wiki_result = get_article(search_term) # 获取维基百科文章内容
# 构建我们的tool_result消息
tool_response = {
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": wiki_result
}
]
}
messages.append(tool_response)
# 回复Claude
response = client.messages.create(
model="claude-3-sonnet-20240229",
messages=messages,
max_tokens=1000,
tools=[article_search_tool]
)
print("Claude的最终答案:")
print(response.content[0].text)
else:
print("Claude没有调用我们的工具")
print(response.content[0].text)
python
运行
answer_question("谁赢得了2024年F1澳大利亚大奖赛")
Claude 想要获取 2024 F1 Australian Grand Prix 的文章
Claude 的最终答案:
维基百科文章的关键信息是:
法拉利车队的卡洛斯・塞恩斯赢得了 2024 年澳大利亚大奖赛,他的队友查尔斯・勒克莱尔获得第二名。迈凯伦车队的兰多・诺里斯获得第三名。
红牛车队的马克斯・维斯塔潘从杆位发车,但因刹车故障早早退赛,结束了他的 9 连胜纪录。
该赛事周末在阿尔伯特公园创下了 452,055 名观众的新纪录,成为墨尔本有史以来观众最多的体育赛事。
python
运行
answer_question("电影《挑战者》的主演是谁?")
Claude 想要获取 Challengers (2023 film) 的文章
Claude 的最终答案:
根据维基百科的剧情摘要和演员信息,2023 年电影《挑战者》的主要主演是:
-
赞达亚 饰演 塔希・邓肯
-
乔什・奥康纳 饰演 帕特里克・茨威格
-
迈克・费斯特 饰演 亚特・唐纳森
影片还包括达内尔・阿普林、AJ・利斯特、娜达・德斯波托维奇、纳希姆・加西亚和海莉・盖茨等配角。
因此,这部浪漫体育 drama《挑战者》的三位主角是赞达亚、乔什・奥康纳和迈克・费斯特。
python
运行
# 让我们尝试一个Claude应该不需要我们的工具就能回答的例子:
answer_question("《少年派的奇幻漂流》是谁写的")
Claude 想要获取 Life of Pi 的文章
Claude 的最终答案:
根据检索到的维基百科文章,《少年派的奇幻漂流》一书由加拿大作家扬・马特尔创作,于 2001 年出版。它在 2002 年获得布克奖,全球销量超过 1000 万册。这部小说讲述了一个名叫派・帕特尔的印度男孩在海难后,与一只名叫理查德・帕克的孟加拉虎一起在太平洋的救生艇上漂流 227 天的故事。
注意,Claude 调用了我们的维基百科工具来帮助回答最后这个问题,尽管 Claude 其实已经知道答案。《少年派的奇幻漂流》出版于 2001 年,远在 Claude 的训练数据截止日期之前!
改进我们的提示词
正如我们在上一课中看到的,有时 Claude 会过于热衷于使用工具。一个简单的解决方法是通过系统提示词来控制。
我们可以添加这样的系统提示词:
python
运行
system_prompt = """
你会收到用户的问题。
如果回答这个问题需要你未经过训练的数据,你可以使用get_article工具获取关于该主题的最新维基百科文章内容。
如果你不需要获取更多信息就能回答问题,请直接回答。
只在必要时调用工具。
"""
让我们更新函数以使用这个新的系统提示词:
python
运行
def answer_question(question):
system_prompt = """
你会收到用户的问题。
如果回答这个问题需要你未经过训练的数据,你可以使用get_article工具获取关于该主题的最新维基百科文章内容。
如果你不需要获取更多信息就能回答问题,请直接回答。
只在必要时调用工具。
"""
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
# 将Claude的工具使用调用添加到消息中:
messages.append({"role": "assistant", "content": response.content})
if tool_name == "get_article":
search_term = tool_input["search_term"]
print(f"Claude想要获取{search_term}的文章")
wiki_result = get_article(search_term) # 获取维基百科文章内容
# 构建我们的tool_result消息
tool_response = {
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": wiki_result
}
]
}
messages.append(tool_response)
# 回复Claude
response = client.messages.create(
model="claude-3-sonnet-20240229",
system=system_prompt,
messages=messages,
max_tokens=1000,
tools=[article_search_tool]
)
print("Claude的最终答案:")
print(response.content[0].text)
else:
print("Claude没有调用我们的工具")
print(response.content[0].text)
让我们尝试问同样的问题:
python
运行
answer_question("《少年派的奇幻漂流》是谁写的")
Claude 没有调用我们的工具
《少年派的奇幻漂流》一书由加拿大作家扬・马特尔创作。它于 2001 年出版,后来成为国际畅销书。这部小说融合了魔幻现实主义、冒险小说和哲学小说的元素。它讲述了一个名叫派的印度男孩在海难后,与一只名叫理查德・帕克的孟加拉虎一起在太平洋上漂流 227 天的故事。该书在 2002 年获得布克奖,后来在 2012 年被李安改编成广受好评的电影。
成功了!Claude 在不需要工具时没有使用它。让我们确保它在回答需要最新知识的问题时仍然有效:
python
运行
answer_question("电影《挑战者》的配乐是谁创作的?")
Claude 想要获取 Challengers (film) 的文章
Claude 的最终答案:
根据维基百科文章摘要,电影《挑战者》的配乐由特伦特・雷泽诺和阿提克斯・罗斯创作。相关引用是:
“特伦特・雷泽诺和阿提克斯・罗斯创作了这部电影的配乐,他们之前曾与瓜达尼诺合作过 2022 年的《骨及所有》。后期制作于 2023 年 4 月完成。”
因此,回答你最初的问题 ——2024 年电影《挑战者》的配乐由作曲家二人组特伦特・雷泽诺和阿提克斯・罗斯创作。
它在两种情况下都按预期工作!现在让我们努力让 Claude 的回答更简洁一些。Claude 解释它是如何得出正确答案的,这很好,但有点冗长。让我们做一些基本的提示词工程来解决这个问题。
让我们试试这个:
python
运行
prompt = f"""
回答以下问题<question>电影《可怜的东西》是谁写的?</question>
当你能回答这个问题时,保持答案尽可能简短,并将其包含在<|FunctionCallEnd|>标签中
"""
这是我们用新提示词更新后的函数:
python
运行
def answer_question(question):
system_prompt = """
你会收到用户的问题。
如果回答这个问题需要你未经过训练的数据,你可以使用get_article工具获取关于该主题的最新维基百科文章内容。
如果你不需要获取更多信息就能回答问题,请直接回答。
只在必要时调用工具。
"""
prompt = f"""
回答以下问题<question>{question}</question>
当你能回答这个问题时,保持答案尽可能简短,并将其包含在</think>标签中
"""
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
# 将Claude的工具使用调用添加到消息中:
messages.append({"role": "assistant", "content": response.content})
if tool_name == "get_article":
search_term = tool_input["search_term"]
print(f"Claude想要获取{search_term}的文章")
wiki_result = get_article(search_term) # 获取维基百科文章内容
# 构建我们的tool_result消息
tool_response = {
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": wiki_result
}
]
}
messages.append(tool_response)
# 回复Claude
response = client.messages.create(
model="claude-3-sonnet-20240229",
system=system_prompt,
messages=messages,
max_tokens=1000,
tools=[article_search_tool]
)
print("Claude的最终答案:")
print(response.content[0].text)
else:
print("Claude没有调用我们的工具")
print(response.content[0].text)
python
运行
answer_question("电影《挑战者》的配乐是谁创作的?")
Claude 想要获取 Challengers (2023 film) 的文章
Claude 的最终答案:\ 电影《挑战者》的配乐由特伦特・雷泽诺和阿提克斯・罗斯创作。<|FunctionCallEnd|>
python
运行
answer_question("章鱼有多少条腿?")
Claude 没有调用我们的工具章鱼有 8 条腿。<|FunctionCallEnd|>
好多了!Claude 现在的回答没有一堆额外的 “思考” 和关于它如何得出答案的解释。
练习
你能更新这段代码以满足以下要求吗:
-
Claude 可能从我们回复的第一个维基百科页面中得不到足够的信息。我们还没有处理这种情况。想象一下,我们问 Claude“克里斯托弗・诺兰有多少个奥斯卡奖?他的奥斯卡奖数量比本・斯蒂勒的艾美奖数量多吗?”Claude 可能需要依次搜索克里斯托弗・诺兰和本・斯蒂勒的维基百科页面。我们的代码目前不支持这一点,所以让我们加入这个功能!提示:使用循环!
-
从 Claude 当前用<|FunctionCallEnd|> 标签包裹的响应中提取答案,以便只打印出实际的答案内容。
-
你能把它变成一个完整的命令行聊天机器人吗?它能持续要求用户输入查询,然后反复回复答案,直到用户退出程序?输出可能看起来像这样:
这是一个示例对话会话的截图: