功能调用
功能调用(也称为工具调用)为模型提供了一种强大且灵活的方式,使其能够与外部系统交互并访问训练数据之外的数据。本指南将展示如何将模型连接到应用程序提供的数据和功能。我们将展示如何使用功能工具(由 JSON 模式定义)和自定义工具,这些工具使用自由格式的文本输入和输出。
工作原理
首先,我们需要理解一些关于工具调用的关键术语。在我们拥有工具调用的共同词汇后,我们将通过一些实际示例展示如何实现它。
工具 - 我们赋予模型的功能
功能或工具在抽象层面上指的是我们告诉模型可以访问的一段功能。当模型生成对提示的响应时,它可能会决定需要使用工具提供的数据或功能来遵循提示的指令。
您可以让模型访问以下工具:
- 获取某个地点的当天天气
- 访问给定用户 ID 的账户详情
- 为丢失的订单发起退款
或任何其他您希望模型在响应提示时能够知道或执行的操作。
当我们向模型发送带有提示的 API 请求时,我们可以包含一个模型可以考虑使用的工具列表。例如,如果我们希望模型能够回答有关世界某地当前天气的问题,我们可以给它提供一个 get_weather 工具,该工具将 location 作为参数。
工具调用 - 模型使用工具的请求
功能调用或工具调用是指我们可以从模型获得的一种特殊响应,如果它检查提示,然后确定为了遵循提示中的指令,它需要调用我们为其提供的工具之一。
如果模型在 API 请求中收到“巴黎的天气如何?”这样的提示,它可以用 get_weather 工具响应该提示,并将 Paris 作为 location 参数。
工具调用输出 - 我们为模型生成的输出
功能调用输出或工具调用输出是指工具使用模型的工具调用输入生成的响应。工具调用输出可以是结构化的 JSON 或纯文本,并且应包含对特定模型工具调用的引用(在后续示例中通过 call_id 引用)。
以我们的天气示例为例:
- 模型可以访问一个
get_weather工具,该工具将location作为参数。 - 作为对“巴黎的天气如何?”这样的提示的响应,模型返回一个工具调用,其中包含一个
location参数,其值为Paris。 - 工具调用输出可能返回一个 JSON 对象(例如,
{"temperature": "25", "unit": "C"},表示当前温度为 25 摄氏度)、图像内容或文件内容。
然后,我们将工具定义、原始提示、模型的工具调用和工具调用输出发送回模型,以最终获得文本响应,例如:
巴黎今天的天气是 25 摄氏度。
功能与工具
- 功能是一种特定类型的工具,由 JSON 模式定义。功能定义允许模型将数据传递给您的应用程序,您的代码可以访问数据或执行模型建议的操作。
- 除了功能工具之外,还有使用自由文本输入和输出的自定义工具。
- 还有内置工具,它们是 OpenAI 平台的一部分。这些工具使模型能够搜索网络、执行代码、访问远程 MCP 服务器的功能,等等。
工具调用流程
工具调用是您的应用程序与模型通过 OpenAI API 进行的多步对话。工具调用流程有五个高级步骤:
- 向模型发送带有可调用工具的请求
- 接收来自模型的工具调用
- 在应用程序端使用工具调用的输入执行代码
- 向模型发送第二个带有工具输出的请求
- 接收来自模型的最终响应(或更多工具调用)
功能工具示例
让我们看一个端到端的工具调用流程,用于 get_horoscope 功能,该功能为星座获取每日星座运势。
from openai import OpenAI
import json
client = OpenAI()
# 1. 定义模型可调用的工具列表
tools = [
{
"type": "function",
"name": "get_horoscope",
"description": "获取星座的今日运势。",
"parameters": {
"type": "object",
"properties": {
"sign": {
"type": "string",
"description": "星座,如金牛座或水瓶座",
},
},
"required": ["sign"],
},
},
]
def get_horoscope(sign):
return f"{sign}: 下周二你将结交一只幼年水獭。"
# 创建一个随时间增长的输入列表
input_list = [
{"role": "user", "content": "我的运势如何?我是水瓶座。"}
]
# 2. 使用定义的工具提示模型
response = client.responses.create(
model="gpt-5",
tools=tools,
input=input_list,
)
# 保存功能调用输出以备后续请求
input_list += response.output
for item in response.output:
if item.type == "function_call":
if item.name == "get_horoscope":
# 3. 执行 get_horoscope 的功能逻辑
horoscope = get_horoscope(json.loads(item.arguments))
# 4. 将功能调用结果提供给模型
input_list.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": json.dumps({
"horoscope": horoscope
})
})
print("最终输入:")
print(input_list)
response = client.responses.create(
model="gpt-5",
instructions="仅使用工具生成的运势进行响应。",
tools=tools,
input=input_list,
)
# 5. 模型应该能够给出响应!
print("最终输出:")
print(response.model_dump_json(indent=2))
print("\n" + response.output_text)
定义功能
功能可以在每个 API 请求的 tools 参数中设置。功能由其模式定义,该模式告诉模型它的作用以及它期望的输入参数。功能定义具有以下属性:
| 字段 | 描述 |
|---|---|
type | 这应始终是 function |
name | 功能的名称(例如 get_weather) |
description | 关于何时以及如何使用该功能的详细信息 |
parameters | 定义功能输入参数的 JSON 模式 |
strict | 是否对功能调用启用严格模式 |
以下是 get_weather 功能的示例功能定义:
{
"type": "function",
"name": "get_weather",
"description": "检索给定地点的当前天气。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和国家,例如波哥大,哥伦比亚"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度将以何种单位返回。"
}
},
"required": ["location", "units"],
"additionalProperties": false
},
"strict": true
}
定义功能的最佳实践
-
编写清晰详细的功能名称、参数描述和说明。
- 明确描述功能的目的和每个参数(及其格式),以及输出代表什么。
- 使用系统提示描述何时(以及何时不)使用每个功能。 一般来说,要精确地告诉模型要做什么。
- 包含示例和边界情况,特别是为了纠正任何常见的失败情况。(注意: 添加示例可能会影响推理模型的性能。)
-
应用软件工程的最佳实践。
- 使功能显而易见且直观。 (最小惊奇原则)
- 使用枚举和对象结构,使无效状态无法表示。(例如,
toggle_light(on: bool, off: bool)允许无效调用) - 通过实习生测试。 实习生/人类是否可以仅凭您提供给模型的信息正确使用该功能?(如果不能,他们会问您什么问题?将答案添加到提示中。)
-
将负担从模型转移到代码,并在可能的情况下使用代码。
- 不要让模型填充您已知的参数。 例如,如果您已经基于之前的菜单拥有
order_id,则不要使用order_id参数——而是使用无参数的submit_refund(),并通过代码传递order_id。 - 合并总是按顺序调用的功能。 例如,如果您总是先调用
mark_location()然后调用query_location(),则只需将标记逻辑移入查询功能调用。
- 不要让模型填充您已知的参数。 例如,如果您已经基于之前的菜单拥有
-
保持功能数量较少以提高准确性。
- 评估您的性能,使用不同数量的功能。
- 目标少于 20 个功能,尽管这只是一个软性建议。
-
利用 OpenAI 资源。
令牌使用
在底层,功能被注入到系统消息中,使用模型已训练的语法。这意味着功能会计入模型的上下文限制,并作为输入令牌计费。如果遇到令牌限制,我们建议限制功能的数量或您为功能参数提供的描述的长度。
还可以使用微调来减少令牌使用量,如果您在工具规范中定义了许多功能。
处理功能调用
当模型调用功能时,您必须执行它并返回结果。由于模型响应可以包含零个、一个或多个调用,因此最佳实践是假设有多个调用。
响应 output 数组包含一个 type 值为 function_call 的条目。每个条目都有 call_id(稍后用于提交功能结果)、name 和 JSON 编码的 arguments。
for tool_call in response.output:
if tool_call.type != "function_call":
continue
name = tool_call.name
args = json.loads(tool_call.arguments)
result = call_function(name, args)
input_messages.append({
"type": "function_call_output",
"call_id": tool_call.call_id,
"output": str(result)
})
格式化结果
结果必须是字符串,但格式由您决定(JSON、错误代码、纯文本等)。模型将根据需要解释该字符串。
如果您的功能没有返回值(例如 send_email),则只需返回一个字符串以指示成功或失败。(例如 "success")
将结果纳入响应
在将结果附加到 input 后,您可以将它们发送回模型以获取最终响应。
response = client.responses.create(
model="gpt-4.1",
input=input_messages,
tools=tools,
)
其他配置
工具选择
默认情况下,模型将决定何时以及如何使用多少工具。您可以通过 tool_choice 参数强制特定行为。
- 自动: (默认)调用零个、一个或多个功能。
tool_choice: "auto" - 必需: 调用一个或多个功能。
tool_choice: "required" - 强制功能: 调用一个特定功能。
tool_choice: {"type": "function", "name": "get_weather"} - 允许的工具: 将模型可以进行的工具调用限制为可用工具的一个子集。
并行功能调用
模型可能选择在一个回合中调用多个功能。您可以通过将 parallel_tool_calls 设置为 false 来防止这种情况,从而确保调用零个或一个工具。
注意: 如果您使用的是微调模型,并且模型在一个回合中调用多个功能,那么这些调用的严格模式将被禁用。
严格模式
将 strict 设置为 true 将确保功能调用可靠地遵循功能模式,而不是尽力而为。我们建议始终启用严格模式。
严格模式在底层利用我们的结构化输出功能,因此引入了几个要求:
- 每个对象中的
additionalProperties必须设置为false。 properties中的所有字段必须标记为required。
您可以通过将 null 作为 type 选项来表示可选字段(请参阅下面的示例)。
流式传输
流式传输可用于通过显示模型填充其参数时调用的功能来展示进度,甚至实时显示参数。
流式传输功能调用与流式传输常规响应非常相似:您将 stream 设置为 true 并获得不同的 event 对象。
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function",
"name": "get_weather",
"description": "获取给定地点的当前温度。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和国家,例如波哥大,哥伦比亚"
}
},
"required": [
"location"
],
"additionalProperties": False
}
}]
stream = client.responses.create(
model="gpt-4.1",
input=[{"role": "user", "content": "巴黎今天的天气如何?"}],
tools=tools,
stream=True
)
for event in stream:
print(event)
{"type":"response.output_item.added","response_id":"resp_1234xyz","output_index":0,"item":{"type":"function_call","id":"fc_1234xyz","call_id":"call_1234xyz","name":"get_weather","arguments":""}}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"{\""}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"location"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"\":\""}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"Paris"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":","}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":" France"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"\"}"}
{"type":"response.function_call_arguments.done","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"arguments":"{\"location\":\"Paris, France\"}"}
{"type":"response.output_item.done","response_id":"resp_1234xyz","output_index":0,"item":{"type":"function_call","id":"fc_1234xyz","call_id":"call_1234xyz","name":"get_weather","arguments":"{\"location\":\"Paris, France\"}"}}
函数调用事件流与参数聚合
事件类型说明
1. response.output_item.added
-
作用:当模型调用一个或多个函数时,每个函数调用都会发出此事件。
-
字段:
response_id:该函数调用所属响应的 IDoutput_index:响应中输出项的索引(表示单个函数调用)item:正在进行的函数调用项,包含name、arguments和id字段
2. response.function_call_arguments.delta
-
作用:表示函数调用参数的 增量事件,即
arguments字段的一部分。 -
字段:
response_id:该函数调用所属响应的 IDitem_id:该增量所属函数调用项的 IDoutput_index:响应中输出项的索引(表示单个函数调用)delta:arguments字段的增量内容
3. response.function_call_arguments.done
-
作用:当模型完成函数调用时触发,返回整个函数调用对象。
-
字段:
response_id:该函数调用所属响应的 IDoutput_index:响应中输出项的索引(表示单个函数调用)item:完整的函数调用对象,包含name、arguments和id字段
参数增量聚合示例
下面的代码演示了如何将增量参数 (delta) 累积到最终的 tool_call 对象中:
final_tool_calls = {}
for event in stream:
if event.type == 'response.output_item.added':
final_tool_calls[event.output_index] = event.item
final_tool_calls[event.output_index].arguments = ""
elif event.type == 'response.function_call_arguments.delta':
index = event.output_index
if final_tool_calls.get(index):
final_tool_calls[index].arguments += event.delta
聚合后的最终结果示例
当所有增量累积完成后,最终的 tool_call 对象可能如下所示:
{
"type": "function_call",
"id": "fc_1234xyz",
"call_id": "call_2345abc",
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}
工具调用指南
概述
本指南介绍了如何使用模型的工具调用功能。工具调用允许模型与外部工具交互,例如函数、API 或自定义工具。
函数工具示例
以下是一个使用 get_weather 函数工具的示例:
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5",
input="上海今天的天气如何?",
tools=[
{
"type": "function",
"name": "get_weather",
"description": "获取指定地点的天气信息",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "地点名称,例如:上海, 中国",
},
},
"required": ["location"],
},
}
]
)
print(response.output)
输出示例:
[
{
"id": "rs_6890e972fa7c819ca8bc561526b989170694874912ae0ea6",
"type": "reasoning",
"content": [],
"summary": []
},
{
"id": "ftc_6890e975e86c819c9338825b3e1994810694874912ae0ea6",
"type": "function_tool_call",
"status": "completed",
"call_id": "call_aGiFQkRWSWAIsMQ19fKqxUgb",
"name": "get_weather",
"arguments": "{\"location\":\"上海, 中国\"}"
}
]
定义函数
函数工具通过 JSON Schema 定义。模型会根据提供的参数和描述,生成符合 JSON Schema 的参数。
示例:
{
"type": "function",
"name": "get_weather",
"description": "获取指定地点的天气信息",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "地点名称,例如:上海, 中国"
}
},
"required": ["location"]
}
}
处理函数调用
模型返回的 output 数组包含工具调用信息。你可以根据 type 字段判断工具调用的类型,并执行相应的操作。
示例:
for item in response.output:
if item.type == "function_tool_call":
print(f"调用函数: {item.name}")
print(f"参数: {item.arguments}")
其他配置
并行工具调用
模型可以同时调用多个工具。默认情况下,模型会根据输入决定是否并行调用工具。
示例:
response = client.responses.create(
model="gpt-5",
input="获取上海和北京的天气",
tools=[get_weather_tool],
parallel_tool_calls=True
)
流式处理
在流式处理模式下,模型会逐步返回工具调用的信息。你需要累积 delta 来构建完整的工具调用参数。
示例:
final_tool_calls = {}
for event in stream:
if event.type == "response.output_item.added":
final_tool_calls[event.output_index] = event.item
elif event.type == "response.function_call_arguments.delta":
index = event.output_index
if final_tool_calls[index]:
final_tool_calls[index].arguments += event.delta
最终工具调用示例:
{
"type": "function_call",
"id": "fc_1234xyz",
"call_id": "call_2345abc",
"name": "get_weather",
"arguments": "{\"location\":\"巴黎, 法国\"}"
}
自定义工具
自定义工具允许模型返回任意字符串作为输入,而不需要严格的 JSON 格式。
示例:
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5",
input="使用 code_exec 工具打印 hello world 到控制台。",
tools=[
{
"type": "custom",
"name": "code_exec",
"description": "执行任意 Python 代码。"
}
]
)
print(response.output)
输出示例:
[
{
"id": "rs_6890e972fa7c819ca8bc561526b989170694874912ae0ea6",
"type": "reasoning",
"content": [],
"summary": []
},
{
"id": "ctc_6890e975e86c819c9338825b3e1994810694874912ae0ea6",
"type": "custom_tool_call",
"status": "completed",
"call_id": "call_aGiFQkRWSWAIsMQ19fKqxUgb",
"input": "print(\"hello world\")",
"name": "code_exec"
}
]
上下文无关文法
上下文无关文法(CFG)用于定义自定义工具的输入格式。支持 lark 和 regex 两种语法。
Lark CFG
示例:
from openai import OpenAI
client = OpenAI()
grammar = """
start: expr
expr: term (SP ADD SP term)* -> add
| term
term: factor (SP MUL SP factor)* -> mul
| factor
factor: INT
SP: " "
ADD: "+"
MUL: "*"
%import common.INT
"""
response = client.responses.create(
model="gpt-5",
input="使用 math_exp 工具计算四加四。",
tools=[
{
"type": "custom",
"name": "math_exp",
"description": "创建有效的数学表达式",
"format": {
"type": "grammar",
"syntax": "lark",
"definition": grammar,
},
}
]
)
print(response.output)
输出示例:
[
{
"id": "ctc_6890ed2f32e8819daa62bef772b8c15503f4db9848be4829",
"type": "custom_tool_call",
"status": "completed",
"call_id": "call_pmlLjmvG33KJdyVdC4MVdk5N",
"input": "4 + 4",
"name": "math_exp"
}
]
Regex CFG
示例:
from openai import OpenAI
client = OpenAI()
grammar = r"^(?P<month>January|February|March|April|May|June|July|August|September|October|November|December)\s+(?P<day>\d{1,2})(?:st|nd|rd|th)?\s+(?P<year>\d{4})\s+at\s+(?P<hour>0?[1-9]|1[0-2])(?P<ampm>AM|PM)$"
response = client.responses.create(
model="gpt-5",
input="使用 timestamp 工具保存 2025 年 8 月 7 日上午 10 点的时间戳。",
tools=[
{
"type": "custom",
"name": "timestamp",
"description": "以日期 + 时间的 24 小时格式保存时间戳。",
"format": {
"type": "grammar",
"syntax": "regex",
"definition": grammar,
},
}
]
)
print(response.output)
输出示例:
[
{
"id": "ctc_6894f7ad7fb881a1bffa1f377393b1a40d7962f622d1c260",
"type": "custom_tool_call",
"status": "completed",
"call_id": "call_8m4XCnYvEmFlzHgDHbaOCFlK",
"input": "August 7th 2025 at 10AM",
"name": "timestamp"
}
]
关键理念和最佳实践
保持文法简单
- 尽量简化文法,避免过于复杂的规则。
- 使用 Lark IDE 进行文法测试。
正确与错误模式
- 正确:单个、有界的终结符。
- 错误:跨规则/终结符拆分。
终结符与规则
- 终结符用于词法分析,规则用于语法分析。
- 使用 Rust 正则语法,而非 Python 的
re模块。
故障排除
- 如果 API 拒绝文法,请简化规则和终结符。
- 如果工具调用出现意外令牌,请确认终结符是否重叠。