函数调用
函数调用使开发人员能够将语言模型连接到外部数据和系统。 您可以将一组函数定义为模型有权访问的工具,并且它可以根据对话历史记录在适当的时候使用它们。 然后,您可以在应用程序端执行这些函数,并将结果返回给模型。
在本指南中了解如何通过函数调用扩展 OpenAI 模型的功能。
概述
许多应用程序需要模型调用自定义函数来触发应用程序内的操作或与外部系统交互。
下面介绍如何将函数定义为模型要使用的工具:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from openai import OpenAI
client = OpenAI()
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"},
"unit": {"type": "string", "enum": ["c", "f"]},
},
"required": ["location", "unit"],
"additionalProperties": False,
},
},
}
]
completion = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "What's the weather like in Paris today?"}],
tools=tools,
)
print(completion.choices[0].message.tool_calls)
以下是函数调用可能有用的一些示例:
- 获取数据:使对话助手能够在响应用户之前从内部系统检索数据。
- 采取行动:允许助理根据对话触发操作,例如安排会议或发起订单退货。
- 构建丰富的工作流程:允许助手执行多步骤工作流程,例如数据提取管道或内容个性化。
- 与应用程序 UI 交互:使用函数调用根据用户输入更新用户界面,例如在地图上呈现图钉或导航网站。
您可以在下面的示例部分中找到示例使用案例。
函数调用的生命周期
当您将 OpenAI API 与函数调用一起使用时,模型本身实际上永远不会执行函数 - 相反,它只是生成可用于调用函数的参数。 然后,您负责处理函数在代码中的执行方式。
阅读下面的集成指南,了解有关如何处理函数调用的更多详细信息。
函数调用支持
Chat Completions API、Assistants API、Batch API 和 Realtime API 支持函数调用。
本指南重点介绍如何使用 Chat Completions API 进行函数调用。对于使用 Assistants API 进行函数调用和使用 Realtime API 进行函数调用,我们提供了单独的指南。
支持函数调用的模型
函数调用是在 2023 年 6 月 13 日的版本中引入的。此日期之后发布的所有型号都支持函数调用。gpt-4-turbo
gpt-*
在此日期之前发布的旧模型未经过支持函数调用的训练。
集成指南
在本集成指南中,我们将以订单配送助手为例,介绍如何将函数调用集成到应用程序中。 我们不需要用户与表单交互,而是让他们用自然语言向助手寻求帮助。
我们将介绍如何定义函数和指令,然后如何处理模型响应和函数执行结果。
功能定义
函数调用的起点是在你自己的代码库中选择一个函数,你希望模型能够为其生成参数。
对于此示例,假设您希望允许模型调用代码库中的函数,该函数接受并查询您的数据库以确定给定包裹的交货日期。
您的函数可能如下所示:get_delivery_date
order_id
1
2
3
4
5
6
# This is the function that we want the model to be able to call
def get_delivery_date(order_id: str) -> datetime:
# Connect to the database
conn = sqlite3.connect('ecommerce.db')
cursor = conn.cursor()
# ...
现在我们知道了我们希望允许模型调用哪个函数,我们将创建一个 “函数定义” 来向模型描述该函数。此定义描述了函数的作用(以及可能何时应该调用它)以及调用函数所需的参数。
函数定义的部分应使用 JSON 架构进行描述。如果模型生成函数调用,它将使用此信息根据您提供的架构生成参数。parameters
在此示例中,它可能如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "get_delivery_date",
"description": "Get the delivery date for a customer's order. Call this whenever you need to know the delivery date, for example when a customer asks 'Where is my package'",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The customer's order ID."
}
},
"required": ["order_id"],
"additionalProperties": false
}
}
接下来,我们需要在调用 Chat Completions API 时,在一系列可用的“工具”中提供我们的函数定义。
与往常一样,我们将提供一组 “消息”,例如,其中可能包含您的提示或用户与助手之间的来回对话。
此示例显示了如何调用 Chat Completions API,为处理商店客户查询的助手提供相关工具和消息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
tools = [
{
"type": "function",
"function": {
"name": "get_delivery_date",
"description": "Get the delivery date for a customer's order. Call this whenever you need to know the delivery date, for example when a customer asks 'Where is my package'",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The customer's order ID.",
},
},
"required": ["order_id"],
"additionalProperties": False,
},
}
}
]
messages = [
{
"role": "system",
"content": "You are a helpful customer support assistant. Use the supplied tools to assist the user."
},
{
"role": "user",
"content": "Hi, can you tell me the delivery date for my order?"
}
]
response = openai.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
型号说明
虽然您应该在函数定义中定义如何调用它们,但我们建议在系统提示符中包含有关何时调用函数的说明。
例如,你可以通过这样说来告诉模型何时使用该函数:"Use the 'get_delivery_date' function when the user asks about their delivery date."
处理模型响应
该模型仅在适当时建议函数调用并为定义的函数生成参数。 然后,由您决定应用程序如何根据这些建议处理函数的执行。
如果模型确定应调用某个函数,它将在响应中返回一个字段,您可以使用该字段来确定模型是否生成了函数调用以及参数是什么。tool_calls
除非您自定义工具调用行为,否则模型将根据说明和对话确定何时调用函数。
如果模型决定不调用任何函数
如果模型未生成函数调用,则响应将包含对用户的直接回复,作为常规聊天完成响应。
例如,在这种情况下,可能包含:chat_response.choices[0].message
1
2
3
4
5
6
chat.completionsMessage(
content='Hi there! I can help with that. Can you please provide your order ID?',
role='assistant',
function_call=None,
tool_calls=None
)
在助手用例中,您通常希望向用户显示此响应并让他们响应,在这种情况下,您将再次调用 API(将助手和用户的最新响应附加到 )。messages
假设我们的用户使用他们的订单 ID 进行响应,并且我们向 API 发送了以下请求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
tools = [
{
"type": "function",
"function": {
"name": "get_delivery_date",
"description": "Get the delivery date for a customer's order. Call this whenever you need to know the delivery date, for example when a customer asks 'Where is my package'",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The customer's order ID."
}
},
"required": ["order_id"],
"additionalProperties": False
}
}
}
]
messages = []
messages.append({"role": "system", "content": "You are a helpful customer support assistant. Use the supplied tools to assist the user."})
messages.append({"role": "user", "content": "Hi, can you tell me the delivery date for my order?"})
messages.append({"role": "assistant", "content": "Hi there! I can help with that. Can you please provide your order ID?"})
messages.append({"role": "user", "content": "i think it is order_12345"})
response = client.chat.completions.create(
model='gpt-4o',
messages=messages,
tools=tools
)
如果模型生成了函数调用
如果模型生成了函数调用,它将生成调用的参数(基于您提供的定义)。parameters
下面是一个示例响应,显示了这一点:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Choice(
finish_reason='tool_calls',
index=0,
logprobs=None,
message=chat.completionsMessage(
content=None,
role='assistant',
function_call=None,
tool_calls=[
chat.completionsMessageToolCall(
id='call_62136354',
function=Function(
arguments='{"order_id":"order_12345"}',
name='get_delivery_date'),
type='function')
])
)
处理指示应调用函数的模型响应
假设响应指示应调用函数,您的代码现在将处理此情况:
1
2
3
4
5
6
7
8
9
10
# Extract the arguments for get_delivery_date
# Note this code assumes we have already determined that the model generated a function call. See below for a more production ready example that shows how to check if the model generated a function call
tool_call = response.choices[0].message.tool_calls[0]
arguments = json.loads(tool_call['function']['arguments'])
order_id = arguments.get('order_id')
# Call the get_delivery_date function with the extracted order_id
delivery_date = get_delivery_date(order_id)
提交函数输出
在代码中执行函数后,您需要将函数调用的结果提交回模型。
这将触发另一个模型响应,同时考虑到函数调用结果。
例如,这是将函数调用的结果提交到对话历史记录的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# Simulate the order_id and delivery_date
order_id = "order_12345"
delivery_date = datetime.now()
# Simulate the tool call response
response = {
"choices": [
{
"message": {
"role": "assistant",
"tool_calls": [
{
"id": "call_62136354",
"type": "function",
"function": {
"arguments": "{'order_id': 'order_12345'}",
"name": "get_delivery_date"
}
}
]
}
}
]
}
# Create a message containing the result of the function call
function_call_result_message = {
"role": "tool",
"content": json.dumps({
"order_id": order_id,
"delivery_date": delivery_date.strftime('%Y-%m-%d %H:%M:%S')
}),
"tool_call_id": response['choices'][0]['message']['tool_calls'][0]['id']
}
# Prepare the chat completion call payload
completion_payload = {
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "You are a helpful customer support assistant. Use the supplied tools to assist the user."},
{"role": "user", "content": "Hi, can you tell me the delivery date for my order?"},
{"role": "assistant", "content": "Hi there! I can help with that. Can you please provide your order ID?"},
{"role": "user", "content": "i think it is order_12345"},
response['choices'][0]['message'],
function_call_result_message
]
}
# Call the OpenAI API's chat completions endpoint to send the tool call result back to the model
response = openai.chat.completions.create(
model=completion_payload["model"],
messages=completion_payload["messages"]
)
# Print the response from the API. In this case it will typically contain a message such as "The delivery date for your order #12345 is xyz. Is there anything else I can help you with?"
print(response)
结构化输出
2024 年 8 月,我们推出了结构化输出,可确保模型的输出与指定的 JSON 架构完全匹配。
默认情况下,在使用函数调用时,API 将为您的参数提供最大努力的匹配,这意味着在使用复杂的 schema 时,模型偶尔可能会丢失参数或弄错其类型。
您可以通过在函数定义中设置参数来启用函数调用的结构化输出。
启用此功能后,模型生成的函数参数将被约束为与函数定义中提供的 JSON 架构匹配。strict: true
并行函数调用和结构化输出
当模型通过并行函数调用输出多个函数调用时,模型输出可能与工具中提供的严格架构不匹配。
为了确保严格遵守架构,请通过提供 .使用此设置,模型将一次生成一个函数调用。parallel_tool_calls: false
为什么我不想打开 Structured Outputs(结构化输出)?
不使用结构化输出的主要原因是:
- 如果您需要使用尚不支持的 JSON Schema 的某些功能(了解更多信息),例如递归架构。
- 如果您的每个 API 请求都包含一个新颖的 Schema(即您的 Schema 不是固定的,而是按需生成的,很少重复)。具有新颖 JSON 架构的第一个请求将增加延迟,因为该架构已为未来几代进行预处理和缓存,以限制模型的输出。
结构化输出对零数据保留意味着什么?
当 结构化输出 打开时,提供的架构不符合零数据保留的条件。
支持的架构
使用结构化输出进行函数调用支持 JSON 架构语言的子集。
有关支持的架构的更多信息,请参阅结构化输出指南。
例
使用 SDK 将函数定义传递给模型时,您可以在 nodeJS 中使用 zod,在 python 中使用 Pydantic。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from enum import Enum
from typing import Union
from pydantic import BaseModel
import openai
from openai import OpenAI
client = OpenAI()
class GetDeliveryDate(BaseModel):
order_id: str
tools = [openai.pydantic_function_tool(GetDeliveryDate)]
messages = []
messages.append({"role": "system", "content": "You are a helpful customer support assistant. Use the supplied tools to assist the user."})
messages.append({"role": "user", "content": "Hi, can you tell me the delivery date for my order #12345?"})
response = client.beta.chat.completions.create(
model='gpt-4o-2024-08-06',
messages=messages,
tools=tools
)
print(response.choices[0].message.tool_calls[0].function)
如果您不使用 SDK,只需将参数添加到您的函数定义中即可:strict: true
1
2
3
4
5
6
7
8
9
10
11
{
"name": "get_delivery_date",
"description": "Get the delivery date for a customer's order. Call this whenever you need to know the delivery date, for example when a customer asks \\"Where is my package\\"",
"parameters": {
"type": "object",
"properties": {
"order_id": { "type": "string" }
},
"strict": true
}
}
局限性
当您将结构化输出与函数调用一起使用时,模型将始终遵循您的确切架构,但少数情况除外:
- 当模型的响应被截断时(由于 、 或最大上下文长度)
max_tokens
stop_tokens
- 当模型被拒绝时
- 当有完成原因时
content_filter
高级用法
流式处理工具调用
您可以在生成工具调用和处理函数参数时对其进行流式处理。 如果要在 UI 中显示函数参数,或者不需要等待生成整个函数参数后再执行函数,这将特别有用。
要启用流式处理工具调用,您可以在请求中进行设置。
然后,您可以处理流式处理 delta 并检查是否有任何新工具调用 delta。stream: true
您可以在 API 参考中找到有关流式处理的更多信息。
以下是如何使用节点和 python 开发工具包处理流式处理工具调用的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
from openai import OpenAI
import json
client = OpenAI()
# Define functions
tools = [
{
"type": "function",
"function": {
"name": "generate_recipe",
"description": "Generate a recipe based on the user's input",
"parameters": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Title of the recipe.",
},
"ingredients": {
"type": "array",
"items": {"type": "string"},
"description": "List of ingredients required for the recipe.",
},
"instructions": {
"type": "array",
"items": {"type": "string"},
"description": "Step-by-step instructions for the recipe.",
},
},
"required": ["title", "ingredients", "instructions"],
"additionalProperties": False,
},
},
}
]
response_stream = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": (
"You are an expert cook who can help turn any user input into a delicious recipe."
"As soon as the user tells you what they want, use the generate_recipe tool to create a detailed recipe for them."
),
},
{
"role": "user",
"content": "I want to make pancakes for 4.",
},
],
tools=tools,
stream=True,
)
function_arguments = ""
function_name = ""
is_collecting_function_args = False
for part in response_stream:
delta = part.choices[0].delta
finish_reason = part.choices[0].finish_reason
# Process assistant content
if 'content' in delta:
print("Assistant:", delta.content)
if delta.tool_calls:
is_collecting_function_args = True
tool_call = delta.tool_calls[0]
if tool_call.function.name:
function_name = tool_call.function.name
print(f"Function name: '{function_name}'")
# Process function arguments delta
if tool_call.function.arguments:
function_arguments += tool_call.function.arguments
print(f"Arguments: {function_arguments}")
# Process tool call with complete arguments
if finish_reason == "tool_calls" and is_collecting_function_args:
print(f"Function call '{function_name}' is complete.")
args = json.loads(function_arguments)
print("Complete function arguments:")
print(json.dumps(args, indent=2))
# Reset for the next potential function call
function_arguments = ""
function_name = ""
is_collecting_function_args = False
工具调用行为
API 支持高级功能,例如并行工具调用和强制工具调用功能。
要查看如何强制工具调用的实际示例,请参阅我们的说明书:
了解如何为您的客户服务助理添加确定性元素
边缘情况
当您收到来自 API 的响应时,如果您未使用 SDK,则生产代码应处理许多边缘情况。
通常,API 将返回有效的函数调用,但在某些极端情况下不会发生这种情况:
- 当您指定了并且模型的响应因此被截断时
max_tokens
- 当模型的输出包含受版权保护的材料时
此外,当您强制模型调用函数时,将是 而不是 。finish_reason
"stop"
"tool_calls"
以下是在代码中处理这些不同情况的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Check if the conversation was too long for the context window
if response['choices'][0]['message']['finish_reason'] == "length":
print("Error: The conversation was too long for the context window.")
# Handle the error as needed, e.g., by truncating the conversation or asking for clarification
handle_length_error(response)
# Check if the model's output included copyright material (or similar)
if response['choices'][0]['message']['finish_reason'] == "content_filter":
print("Error: The content was filtered due to policy violations.")
# Handle the error as needed, e.g., by modifying the request or notifying the user
handle_content_filter_error(response)
# Check if the model has made a tool_call. This is the case either if the "finish_reason" is "tool_calls" or if the "finish_reason" is "stop" and our API request had forced a function call
if (response['choices'][0]['message']['finish_reason'] == "tool_calls" or
# This handles the edge case where if we forced the model to call one of our functions, the finish_reason will actually be "stop" instead of "tool_calls"
(our_api_request_forced_a_tool_call and response['choices'][0]['message']['finish_reason'] == "stop")):
# Handle tool call
print("Model made a tool call.")
# Your code to handle tool calls
handle_tool_call(response)
# Else finish_reason is "stop", in which case the model was just responding directly to the user
elif response['choices'][0]['message']['finish_reason'] == "stop":
# Handle the normal stop case
print("Model responded directly to the user.")
# Your code to handle normal responses
handle_normal_response(response)
# Catch any other case, this is unexpected
else:
print("Unexpected finish_reason:", response['choices'][0]['message']['finish_reason'])
# Handle unexpected cases as needed
handle_unexpected_case(response)
Token 使用情况
在后台,函数以模型训练所依据的语法注入系统消息中。这意味着函数计入模型的上下文限制,并作为输入令牌计费。如果您遇到令牌限制,我们建议您限制函数的数量或您为函数参数提供的描述的长度。
如果您在工具规范中定义了许多函数,也可以使用微调来减少使用的令牌数量。
例子
OpenAI 说明书 提供了几个端到端示例,可帮助您实现函数调用。
在我们的介绍性说明书 How to call functions with chat models(如何使用聊天模型调用函数)中,我们概述了模型如何使用函数调用的两个示例。这是您开始时可以遵循的好资源:
从演示函数调用的更多示例中学习
您还可以在下面找到常见使用案例的函数定义示例。
最佳实践
打开 Structured Outputs(结构化输出)方法是将strict: "true"
当 Structured Outputs (结构化输出) 处于打开状态时,模型为函数调用生成的参数将可靠地与您提供的 JSON 架构匹配。
如果您不使用结构化输出,则不能保证参数的结构是正确的,因此我们建议使用像 Pydantic 这样的验证库,以便在使用参数之前先验证参数。
直观命名功能,并附有详细说明
如果您发现模型没有生成对正确函数的调用,则可能需要更新函数名称和描述,以便模型更清楚地了解何时应选择每个函数。避免使用缩写或首字母缩略词来缩短函数和参数名称。
您还可以包含有关何时应调用函数的详细说明。对于复杂函数,您应该包含每个参数的描述,以帮助模型知道它需要什么来要求用户收集该参数。
直观命名函数参数,并附有详细说明
对函数参数使用清晰的描述性名称。如果适用,请在描述中指定参数的预期格式(例如,日期为 YYYY-mm-dd 或 dd/mm/yy)。
考虑在系统消息中提供有关如何以及何时调用函数的其他信息
在系统消息中提供明确的说明可以显著提高模型的函数调用准确性。例如,使用如下说明指导模型:
"Use check_order_status when the user inquires about the status of their order, such as 'Where is my order?' or 'Has my order shipped yet?'".
为复杂场景提供上下文。例如:
"Before scheduling a meeting with schedule_meeting, check the user's calendar for availability using check_availability to avoid conflicts."
尽可能对函数参数使用枚举
如果您的用例允许,您可以使用 enum 来限制参数的可能值。这有助于减少幻觉。
例如,如果您有一个帮助订购 T 恤的 AI 助手,则您可能有一组固定的 T 恤尺寸,您可能希望限制模型从中进行选择。如果您希望模型输出 small、medium 和 large 的 “s”、“m”、“l”,则可以在枚举中提供这些值,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "pick_tshirt_size",
"description": "Call this if the user specifies which size t-shirt they want",
"parameters": {
"type": "object",
"properties": {
"size": {
"type": "string",
"enum": ["s", "m", "l"],
"description": "The size of the t-shirt that the user would like to order"
}
},
"required": ["size"],
"additionalProperties": false
}
}
如果不限制输出,则用户可能会说 “large” 或 “L”,并且模型可能会使用这些值作为参数。 由于您的代码可能需要特定的结构,因此限制模型可以选择的可能值会很有帮助。
保持较少的函数数量以获得更高的准确性
我们建议您在一次 API 调用中使用不超过 20 个工具。
开发人员通常会发现,一旦定义了 10-20 个工具,模型选择正确工具的能力就会降低。
如果您的使用案例要求模型能够在大量自定义函数之间进行选择,您可能需要探索微调(了解更多信息)或分解工具并对其进行逻辑分组以创建多代理系统。
设置 evals 以帮助及时设计您的函数定义和系统消息
对于函数调用的重要用途,我们建议设置一个评估系统,允许您测量调用正确函数的频率或为各种可能的用户消息生成正确参数的频率。了解有关在 OpenAI 说明书上设置评估的更多信息。
然后,您可以使用这些数据来衡量对函数定义和系统消息的调整是会改善还是损害您的集成。
微调可能有助于提高函数调用的准确性
微调模型可以提高您的使用案例的函数调用性能,尤其是在您有大量函数或复杂、细微或类似的函数时。
有关更多信息,请参阅我们的函数调用微调说明书。
了解如何微调函数调用模型