一、背景
1、什么是Tool use
Tool use也叫做Function calling,这是指模型识别访问意图并调用外部工具的能力。例如在一个对话查询中,希望检索互联网上当前最火热的歌曲,或者触发另一个系统的特定的API。这种能力往往和Agent以及知识库搭配使用。需要注意的是,Tool use场景中大语言模型不会直接运行API Call,而是将需要API Call的请求拼接好返回给调用大语言模型的代码。API call的执行过程是完全由程序调用来负责执行的。因此当代码执行API Call获得返回结果之后,还需要将返回结果再次输入到大语言模型中,并且包含上次的聊天记录一起返回。这时即可获得预期的插叙结果。
本文以一个数学计算为例,输入一个计算要求,识别是Tool场景,程序完成Tool use获取结果,再将结果代回到大模型对话,完成整个流程。
二、识别Tool use场景
事先安装Python的最新版的AWS SDK的依赖库文件,如果是旧版本,升级到最新版Boto3。
pip3 install boto3 --upgrade
编写如下Python代码,替换其中的Region和模型名字为实际的环境以及要调用的模型的名字。在以下代码中,通过System Prompt指定了数学计算要通过Tool use来进行。由此识别出来Tool use,然后构建出来JSON文件用于Tool use调用。
import boto3, json, math
region_name = 'us-west-2'
model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
session = boto3.Session(region_name= region_name)
bedrock = session.client(service_name="bedrock-runtime")
tool_list = [
{
"toolSpec": {
"name": "cosine",
"description": "Calculate the cosine of x.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "The number to pass to the function."
}
},
"required": ["x"]
}
}
}
}
]
message_list = []
initial_message = {
"role": "user",
"content": [
{ "text": "What is the cosine of 7?" }
],
}
message_list.append(initial_message)
response = bedrock.converse(
modelId=model_id,
messages=message_list,
inferenceConfig={
"maxTokens": 2000,
"temperature": 0
},
toolConfig={
"tools": tool_list
},
system=[{"text":"You must only do math by using a tool."}]
)
response_message = response['output']['message']
message_list.append(response_message)
# print message for debug
print(json.dumps(response_message, indent=4))
返回信息如下:
{
"role": "assistant",
"content": [
{
"text": "Here is how we can calculate the cosine of 7 using the available tool:"
},
{
"toolUse": {
"toolUseId": "tooluse_iee0b75bTnagdKYmgstAHw",
"name": "cosine",
"input": {
"x": 7
}
}
}
]
}
这表示模型识别出来了这是一个需要Tool use调用的场景,并且将返回结果构建为统一的JSON形式,方便后续代码编写。
三、执行Tool并直接返回结果
在上一步的基础上更进一步,模型判断出来了是调用Tool use,那么就在代码中直接计算,并生成结果。注意,这里的计算结果不返回给模型,而是直接从Python程序一侧调用Tool算好了就print出来。代码如下。
import boto3, json, math
region_name = 'us-west-2'
model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
session = boto3.Session(region_name= region_name)
bedrock = session.client(service_name="bedrock-runtime")
tool_list = [
{
"toolSpec": {
"name": "cosine",
"description": "Calculate the cosine of x.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "The number to pass to the function."
}
},
"required": ["x"]
}
}
}
}
]
message_list = []
initial_message = {
"role": "user",
"content": [
{ "text": "What is the cosine of 7?" }
],
}
message_list.append(initial_message)
response = bedrock.converse(
modelId=model_id,
messages=message_list,
inferenceConfig={
"maxTokens": 2000,
"temperature": 0
},
toolConfig={
"tools": tool_list
},
system=[{"text":"You must only do math by using a tool."}]
)
response_message = response['output']['message']
message_list.append(response_message)
response_content_blocks = response_message['content']
for content_block in response_content_blocks:
if 'toolUse' in content_block:
tool_use_block = content_block['toolUse']
tool_use_name = tool_use_block['name']
print(f"Using tool {tool_use_name}")
if tool_use_name == 'cosine':
tool_result_value = math.cos(math.radians(tool_use_block['input']['x']))
print(tool_result_value)
elif 'text' in content_block:
print(content_block['text'])
返回结果如下:
Here is how we can calculate the cosine of 7 using the available tool:
Using tool cosine
0.992546151641322
可以看到模型返回说要调用Tool之后,就靠Tool自己完成任务了。计算结果不会代入回模型,因此Bedrock服务中模型一侧只接受到了一次调用,模型是不知道计算结果的。
如果希望将计算结果返回给模型进一步处理,那么可以使用如下代码。
四、将Tool运行结果代入模型交互
在上一代断码的最后一个content_block in response_content_blocks
循环中,可将Tool给出的结果,包含确认要使用Tool的历史对话记录,再次代入到模型中,可让模型根据返回结果再做进一步处理。代码如下。
import boto3, json, math
region_name = 'us-west-2'
model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
session = boto3.Session(region_name= region_name)
bedrock = session.client(service_name="bedrock-runtime")
tool_list = [
{
"toolSpec": {
"name": "cosine",
"description": "Calculate the cosine of x.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "The number to pass to the function."
}
},
"required": ["x"]
}
}
}
}
]
message_list = []
initial_message = {
"role": "user",
"content": [
{ "text": "What is the cosine of 7?" }
],
}
message_list.append(initial_message)
response = bedrock.converse(
modelId=model_id,
messages=message_list,
inferenceConfig={
"maxTokens": 2000,
"temperature": 0
},
toolConfig={
"tools": tool_list
},
system=[{"text":"You must only do math by using a tool."}]
)
response_message = response['output']['message']
message_list.append(response_message)
response_content_blocks = response_message['content']
follow_up_content_blocks = []
for content_block in response_content_blocks:
if 'toolUse' in content_block:
tool_use_block = content_block['toolUse']
tool_use_name = tool_use_block['name']
if tool_use_name == 'cosine':
tool_result_value = math.cos(math.radians(tool_use_block['input']['x']))
follow_up_content_blocks.append({
"toolResult": {
"toolUseId": tool_use_block['toolUseId'],
"content": [
{
"json": {
"result": tool_result_value
}
}
]
}
})
if len(follow_up_content_blocks) > 0:
follow_up_message = {
"role": "user",
"content": follow_up_content_blocks,
}
message_list.append(follow_up_message)
response = bedrock.converse(
modelId=model_id,
messages=message_list,
inferenceConfig={
"maxTokens": 2000,
"temperature": 0
},
toolConfig={
"tools": tool_list
},
system=[{"text":"You must only do math by using a tool."}]
)
response_message = response['output']['message']
print(json.dumps(response_message, indent=4))
message_list.append(response_message)
返回结果:
{
"role": "assistant",
"content": [
{
"text": "So the cosine of 7 is 0.7539022543433046."
}
]
}
由此可看到模型处理了Tool use返回的结果。这样加起来两次模型调用,中间一次Tool use,就完成了整个流程。
五、错误处理
在上边的演示中,Tool use的过程没有使用真实的第三方API调用,而是直接用Python的math数学库进行了计算,模拟了一次Tool use。在实际使用环境中,Tool use调用的第三方API可能是存在网络可达、连接失败、超时、没有正常返回预期结果、QoS限流等多种技术问题,此时Tool use调用结果是一堆错误信息,是千奇百怪的。这些错误信息如果返回给大语言模型,刚才的对话和Tool use是无法给出正确答案的,此时需要错误处理。
为了解决Tool use调用第三方API可能的失效问题,可在Tool use返回JSON返回中增加一个标签"status": "error"
表示Tool use的返回结果不可用。这样模型就会理解为此时Tool use调用的第三方API不可用,并且模型最后会给出友好的回答,而不是生硬的抛出错误,或者胡乱回答。这里可通过再次调用大模型时候在Prompt中添加Tool use不可用时候的提示词来解决。
代码样例如下。
import boto3, json, math
region_name = 'us-west-2'
model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
session = boto3.Session(region_name= region_name)
bedrock = session.client(service_name="bedrock-runtime")
tool_list = [
{
"toolSpec": {
"name": "cosine",
"description": "Calculate the cosine of x.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "The number to pass to the function."
}
},
"required": ["x"]
}
}
}
}
]
message_list = []
initial_message = {
"role": "user",
"content": [
{ "text": "What is the cosine of 7?" }
],
}
message_list.append(initial_message)
response = bedrock.converse(
modelId=model_id,
messages=message_list,
inferenceConfig={
"maxTokens": 2000,
"temperature": 0
},
toolConfig={
"tools": tool_list
},
system=[{"text":"You must only do math by using a tool."}]
)
response_message = response['output']['message']
message_list.append(response_message)
response_content_blocks = response_message['content']
follow_up_content_blocks = []
for content_block in response_content_blocks:
if 'toolUse' in content_block:
tool_use_block = content_block['toolUse']
tool_use_name = tool_use_block['name']
if tool_use_name == 'cosine':
tool_result_value = math.cos(tool_use_block['input']['x'])
follow_up_content_blocks.append({
"toolResult": {
"toolUseId": tool_use_block['toolUseId'],
"content": [
{
"text": "invalid function: cosine"
}
],
"status": "error"
}
})
if len(follow_up_content_blocks) > 0:
follow_up_message = {
"role": "user",
"content": follow_up_content_blocks,
}
message_list.append(follow_up_message)
response = bedrock.converse(
modelId=model_id,
messages=message_list,
inferenceConfig={
"maxTokens": 2000,
"temperature": 0
},
toolConfig={
"tools": tool_list
},
system=[{"text":"You must only do math by using a tool. If tool is not available, do not assume, do not provide more info, just say the tool is unavailable."}]
)
response_message = response['output']['message']
print(json.dumps(response_message, indent=4))
message_list.append(response_message)
返回结果如下:
{
"role": "assistant",
"content": [
{
"text": "Unfortunately, the \"cosine\" tool is not available in this environment. Without access to that mathematical function, I cannot provide the cosine of 7. Please let me know if there are any other tools available that I could use to assist with your request."
}
]
}
由此可以看到,Tool use不可用时候,模型也返回了友好信息。
六、多个Tool的识别和匹配场景
如果在一个业务场景中,需要处理多个Tool调用的场景,那么可以定义两个Tool在名称上区分开,然后不同的提问匹配到不通的Tool调用,即可完成业务的选择。代码如下。
import boto3, json, math
region_name = 'us-west-2'
model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
session = boto3.Session(region_name= region_name)
bedrock = session.client(service_name="bedrock-runtime")
tool_list = [
{
"toolSpec": {
"name": "cosine",
"description": "Calculate the cosine of x.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "The number to pass to the function."
}
},
"required": ["x"]
}
}
}
},
{
"toolSpec": {
"name": "sine",
"description": "Calculate the sine of x.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"x": {
"type": "number",
"description": "The number to pass to the function."
}
},
"required": ["x"]
}
}
}
}
]
message_list = []
initial_message = {
"role": "user",
"content": [
{ "text": "What is the sine of 7?" }
],
}
message_list.append(initial_message)
response = bedrock.converse(
modelId=model_id,
messages=message_list,
inferenceConfig={
"maxTokens": 2000,
"temperature": 0
},
toolConfig={
"tools": tool_list
},
system=[{"text":"You must only do math by using a tool."}]
)
response_message = response['output']['message']
message_list.append(response_message)
# print message for debug
print(json.dumps(response_message, indent=4))
response_content_blocks = response_message['content']
follow_up_content_blocks = []
for content_block in response_content_blocks:
if 'toolUse' in content_block:
tool_use_block = content_block['toolUse']
tool_use_name = tool_use_block['name']
if tool_use_name == 'cosine':
tool_result_value = math.cos(math.radians(tool_use_block['input']['x']))
if tool_use_name == 'sine':
tool_result_value = math.sin(math.radians(tool_use_block['input']['x']))
# debug
print(tool_result_value)
follow_up_content_blocks.append({
"toolResult": {
"toolUseId": tool_use_block['toolUseId'],
"content": [
{
"json": {
"result": tool_result_value
}
}
]
}
})
if len(follow_up_content_blocks) > 0:
follow_up_message = {
"role": "user",
"content": follow_up_content_blocks,
}
message_list.append(follow_up_message)
response = bedrock.converse(
modelId=model_id,
messages=message_list,
inferenceConfig={
"maxTokens": 2000,
"temperature": 0
},
toolConfig={
"tools": tool_list
},
system=[{"text":"You must only do math by using a tool."}]
)
response_message = response['output']['message']
print(json.dumps(response_message, indent=4))
message_list.append(response_message)
修改以上代码中的提问,分别询问计算sin和cos函数的要求,即可看到正确使用了tool并返回结果。
七、参考文档
Intro to Tool Use with the Amazon Bedrock Converse API
Call a tool with Amazon Bedrock Tool use (Function calling)
https://docs.aws.amazon.com/bedrock/latest/userguide/tool-use.html