目录
Function calling behavior (函数调用行为)
Parallel function calling (并行函数调用)
Example invoking multiple function calls in one response (多函数调用示例)
前言
此篇文章基于OpenAI 官方文档,用中文的方式为大家讲解 Function calling(函数调用),有需要的朋友可以收藏方便后续的学习和使用,此篇文章涵盖基础概念和实际用例,所以我打算分成2-3部分来讲解
Function calling 基本介绍
在一次API调用中,你可以描述函数,并让模型智能地选择输出包含调用一个或多个函数参数的JSON对象。Chat Completions API并不会调用函数,而是由模型生成JSON,你可以在代码中使用这些JSON来调用函数。
最新的模型(gpt-4o、gpt-4-turbo和gpt-3.5-turbo)已经被训练成可以根据输入来检测何时应该调用函数,并且能够生成更加符合函数签名的JSON。具备这一能力的同时也带来了潜在的风险,例如发送电子邮件、在线发布内容、进行购买等动作,所以在执行之前,最好得到用户相应授权。
注:本篇文章主使用Chat Completions API来进行函数调用。关于 Assistants API 中函数调用教程会在以后的播客中为大家更新。
Common use cases (常见用例)
函数调用可以让你更可靠地从模型中获取结构化数据。例如,你可以:
-
创建通过调用外部API回答问题的助手:
- 定义一些函数,例如
send_email(to: string, body: string)
或者get_current_weather(location: string, unit: 'celsius' | 'fahrenheit')
。
- 定义一些函数,例如
-
将自然语言转换为API调用:
- 例如,将“谁是我最重要的客户?”转换为
get_customers(min_revenue: int, created_before: string, limit: int)
,然后调用你内部的API。
- 例如,将“谁是我最重要的客户?”转换为
-
从文本中提取结构化数据:
- 定义一个名为
extract_data(name: string, birthday: string)
的函数,或sql_query(query: string)
。
- 定义一个名为
这些用例还有更多可能性!
函数调用的基本步骤如下:
- 使用用户的查询和在
functions
参数中定义的一组函数调用模型。 - 模型可以选择调用一个或多个函数;如果是这样,内容将是一个符合你自定义模式的字符串化JSON对象(注意:模型可能会生成虚构的参数)。
- 在代码中将字符串解析为JSON,并使用提供的参数(如果存在)调用你的函数。
- 通过将函数响应作为新消息附加,再次调用模型,让模型将结果总结返回给用户。
Supported models (支持的模型)
1. 支持的模型版本:
函数调用功能并不是所有模型版本都支持,只有特定版本的模型经过了函数调用数据的训练。上面列出的模型版本都支持函数调用功能。
- gpt-4o
- gpt-4o-2024-05-13
- gpt-4-turbo
- gpt-4-turbo-2024-04-09
- gpt-4-turbo-preview
- gpt-4-0125-preview
- gpt-4-1106-preview
- gpt-4
- gpt-4-0613
- gpt-3.5-turbo
- gpt-3.5-turbo-0125
- gpt-3.5-turbo-1106
- gpt-3.5-turbo-0613
2. 并行函数调用:
gpt-4o部分模型不仅支持函数调用,还支持并行函数调用。这意味着这些模型可以在一次请求中同时调用多个函数,从而提高效率和响应速度。上面列出的模型版本都支持并行函数调用功能。
- gpt-3.5-turbo-0125
- gpt-4-1106-preview
- gpt-4-0125-preview
- gpt-4-turbo-preview
- gpt-4-turbo-2024-04-09
- gpt-4-turbo
- gpt-4o-2024-05-13
Function calling behavior (函数调用行为)
-
默认行为(auto):当
tool_choice
设置为auto
时,模型会根据输入内容自动决定是否调用函数以及调用哪些函数。这是最灵活的一种方式,适用于大多数场景。 -
强制调用函数(required):如果你希望模型总是调用一个或多个函数,可以将
tool_choice
设置为required
。模型会根据输入内容选择适当的函数来调用。这种方式适用于需要确保函数调用的场景。 -
调用特定函数:如果你有一个特定的函数希望模型总是调用,可以将
tool_choice
设置为包含该函数名称的对象。例如,{"type": "function", "function": {"name": "my_function"}}
。这种方式适用于需要特定函数调用的场景。 -
禁用函数调用(none):如果你不希望模型调用任何函数,只生成面向用户的文本消息,可以将
tool_choice
设置为none
。这种方式适用于只需要模型生成文本回复的场景。
通过这些配置,你可以根据具体需求灵活地控制模型的函数调用行为。
Parallel function calling (并行函数调用)
并行函数调用是模型同时执行多个函数调用的能力,使得这些函数调用的效果和结果可以并行解决。这在函数耗时较长的情况下特别有用,可以减少API的往返次数。例如,模型可能会同时调用获取三个不同位置天气的函数,结果会是一个消息中包含 tool_calls
数组内的三个函数调用,每个都有一个ID。为了响应这些函数调用,需要在对话中添加三个新消息,每个消息包含一个函数调用的结果,并带有一个引用 tool_calls
中ID的 tool_call_id
。
-
并行函数调用的概念:并行函数调用允许模型在一次请求中同时执行多个函数调用。这可以显著减少函数执行时间较长时的等待时间,提高效率。
-
工具调用数组 (
tool_calls
):当模型进行并行函数调用时,结果消息中会包含一个tool_calls
数组,里面有多个函数调用,每个调用都有一个唯一的ID。 -
响应函数调用:为了响应这些并行函数调用,需要在对话中添加相应的消息。每个消息包含一个函数调用的结果,并带有一个引用
tool_calls
中ID的tool_call_id
。 -
示例操作流程:
- 定义一个函数
get_current_weather
。 - 模型根据用户查询同时调用该函数多次,获取不同位置的天气。
- 将函数调用的结果作为新消息发送给模型,让它决定下一步操作。
- 模型生成面向用户的消息,提供多个位置的天气信息。
- 定义一个函数
通过这种方式,可以提高处理复杂查询和长时间函数调用的效率。
Example invoking multiple function calls in one response (多函数调用示例)
以下是一个使用多个函数调用的示例:
from openai import OpenAI
import json
client = OpenAI()
# 示例函数,用于返回相同的天气数据
# 在生产环境中,这可以是你的后端API或外部API
def get_current_weather(location, unit="fahrenheit"):
"""获取指定位置的当前天气"""
if "tokyo" in location.lower():
return json.dumps({"location": "Tokyo", "temperature": "10", "unit": unit})
elif "san francisco" in location.lower():
return json.dumps({"location": "San Francisco", "temperature": "72", "unit": unit})
elif "paris" in location.lower():
return json.dumps({"location": "Paris", "temperature": "22", "unit": unit})
else:
return json.dumps({"location": location, "temperature": "unknown"})
def run_conversation():
# 步骤1:将对话和可用函数发送给模型
messages = [{"role": "user", "content": "What's the weather like in San Francisco, Tokyo, and Paris?"}]
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "获取指定位置的当前天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和州,例如 San Francisco, CA",
},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["location"],
},
},
}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto", # auto 是默认设置,这里明确指定
)
response_message = response.choices[0].message
tool_calls = response_message.tool_calls
# 步骤2:检查模型是否需要调用函数
if tool_calls:
# 步骤3:调用函数
# 注意:JSON响应可能并不总是有效;一定要处理错误
available_functions = {
"get_current_weather": get_current_weather,
} # 这个示例中只有一个函数,但你可以有多个函数
messages.append(response_message) # 用助手的回复扩展对话
# 步骤4:发送每个函数调用的信息和函数响应给模型
for tool_call in tool_calls:
function_name = tool_call.function.name
function_to_call = available_functions[function_name]
function_args = json.loads(tool_call.function.arguments)
function_response = function_to_call(
location=function_args.get("location"),
unit=function_args.get("unit"),
)
messages.append(
{
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response,
}
) # 用函数响应扩展对话
second_response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
) # 获取模型的新响应,其中可以看到函数响应
return second_response
print(run_conversation())
讲解:
-
导入库和初始化客户端:
- 导入
OpenAI
和json
库,初始化OpenAI
客户端。
- 导入
-
定义示例函数
get_current_weather
:- 这个函数根据位置返回当前天气。在生产环境中,这个函数可以调用后端API或外部API获取真实数据。
-
定义对话流程
run_conversation
:- 步骤1:将用户查询和可用函数发送给模型。定义包含用户查询的消息和可用函数的描述。
- 步骤2:检查模型是否想要调用函数。响应消息中会包含
tool_calls
数组,如果模型想要调用函数,这里会有具体的函数调用。 - 步骤3:调用函数。将每个函数调用的参数解析为JSON,并调用相应的函数获取结果。
- 步骤4:将函数调用的信息和响应结果发送给模型。将每个函数调用的结果作为新消息附加到对话中,然后获取模型的新响应。
这个示例展示了如何使用并行函数调用来处理多个函数调用,提高效率和响应速度。
Tokens (令牌)
-
函数注入和令牌计数:
- 函数以特定语法注入到系统消息中,模型能够识别和处理这些函数。
- 这些函数会占用模型的上下文空间,并计算在输入令牌中。这意味着函数描述和参数文档的长度会影响到你可以传递给模型的其他信息量。
-
上下文限制:
- 每个模型都有一个上下文限制,即模型一次可以处理的最大令牌数量。如果你的函数描述过长,可能会导致超过这个限制,从而无法处理完整的输入。
- 为避免这种情况,可以减少定义的函数数量或者简化函数参数的文档描述。
-
微调模型:
- 如果你有大量的函数定义,可以考虑对模型进行微调。通过微调,可以让模型更高效地处理这些函数定义,从而减少令牌的使用。
- 微调可以使模型在理解和调用这些函数时更加高效,从而提高整体性能和响应速度。
通过合理管理函数定义和参数描述,可以优化令牌使用,确保在不超过上下文限制的情况下充分利用模型的能力
总结
这一期我们简单的介绍了 Function calling 的基础概念 关键节点 和使用示例,先帮助大家对Function calling有初步认识,在下一篇文章中,我考虑用一个具体示例,并且做分步讲解,来让我们更好的理解和使用Function calling ,感谢大家,我们下期再见!