OpenAI Function calling V2 特点
Function calling V2 的核心目标是赋予 OpenAI 模型与外部世界交互的能力,主要体现在以下两个核心功能:
- 数据获取 (Fetching Data) - RAG 的函数调用实现:
- 本质是 RAG (检索增强生成): Function calling 提供了一种强大的机制来实现检索增强生成 (RAG)。模型可以通过调用预定义的函数,从外部数据源(如知识库、API 等)检索最新的、相关的信息,并将其融入到自身的生成回复中。
- 解决信息滞后和知识限制: 传统的大语言模型知识是静态的,Function calling 弥补了这一缺陷,使模型能够获取实时信息或特定领域知识,从而生成更准确、更全面的回复。
- 用例示例:
- 实时信息查询: 例如,获取当前天气数据、最新的新闻资讯、股票价格等。
- 知识库搜索: 例如,查询企业内部知识库、产品文档、FAQ 等,为用户提供更专业的解答。
- API 数据集成: 例如,从外部 API 获取商品信息、航班动态、地理位置信息等,丰富对话内容。
- 实现方式: 开发者定义用于数据检索的函数 (例如
get_weather
,search_knowledge_base
),并在tools
参数中提供给模型。当模型判断需要外部信息时,会调用这些函数并获取结果,再将结果整合到最终回复中。
- 执行动作 (Taking Action) - 模型驱动的自动化:
- 超越信息提供,驱动实际操作: Function calling 不仅限于信息检索,更进一步,它允许模型驱动外部系统执行实际的操作,实现更深层次的自动化和应用场景。
- 增强模型的实用性和应用范围: 这使得模型可以不仅仅是对话伙伴,更可以成为智能助手,帮助用户完成各种任务。
- 用例示例:
- 自动化工作流: 例如,提交表单、创建日历事件、发送邮件、预订机票酒店等。
- 应用程序控制: 例如,修改应用程序状态 (UI/前端或后端)、控制智能家居设备等。
- 代理工作流 (Agentic Workflow): 例如,根据对话内容,将对话移交给更专业的客服系统,或者触发更复杂的自动化流程。
- 实现方式: 开发者定义用于执行特定操作的函数 (例如
send_email
,create_calendar_event
),并在tools
参数中提供给模型。模型可以根据用户意图调用这些函数,并传递相应的参数来触发操作。
Function calling V2 的其他关键特性 (支持数据获取和执行动作):
tools
参数和函数模式 (Function Schema): 提供结构化的方式来定义和管理模型可以调用的函数,包括函数名、描述和参数定义,确保模型准确理解和调用函数。- 严格模式 (Strict Mode): 提高函数调用的可靠性和准确性,确保模型输出的函数调用严格符合预定义的模式。
- 工具选择 (Tool Choice) 和并行函数调用控制: 提供更精细的控制,允许开发者根据应用场景调整模型调用函数的行为,例如强制调用特定函数或限制并行调用数量。
- 流式传输 (Streaming): 改善用户体验,可以实时展示函数参数的填充过程,让用户更直观地了解模型的思考过程。
总结:
Function calling V2 的核心价值在于通过 数据获取 (RAG 实现) 和 执行动作 这两大功能,极大地拓展了大语言模型的应用边界。 它不仅使模型能够访问和利用外部信息,生成更智能、更实用的回复,更能驱动外部系统执行操作,实现更高级别的自动化,为构建更强大的 AI 应用奠定了基础。 数据获取作为 RAG 的一种实现方式,是 Function calling V2 在知识密集型应用中的关键能力体现。
以下是 OpenAI 官方 函数调用(Function calling)新版说明,使模型能够获取数据并执行操作。
函数调用 为 OpenAI 模型提供了一种强大而灵活的方式,使其能够与你的代码或外部服务交互,并且主要有两个用例:
获取数据 | 检索最新的信息,并将其整合到模型的回复中 (RAG)。 这对于搜索知识库以及从 API 获取特定数据(例如,当前天气数据)非常有用。 |
执行操作 | 执行诸如提交表单、调用 API、修改应用程序状态(UI/前端或后端)或采取代理工作流操作(如移交对话)等操作。 |
如果你只想让模型生成 JSON,请参阅 OpenAI 关于 structured outputs 的文档,确保模型始终生成符合您提供的 JSON Schema 的响应。
获取天气
使用 get_weather 函数的函数调用示例
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取给定位置的当前温度。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和国家,例如 Bogotá, Colombia"
}
},
"required": [
"location"
],
"additionalProperties": False
},
"strict": True
}
}]
completion = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "今天巴黎的天气怎么样?"}],
tools=tools
)
print(completion.choices[0].message.tool_calls)
输出
[{
"id": "call_12345xyz",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}
}]
发送邮件
使用 send_email 函数的函数调用示例
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "send_email",
"description": "向给定的收件人发送包含主题和消息的电子邮件。",
"parameters": {
"type": "object",
"properties": {
"to": {
"type": "string",
"description": "收件人的电子邮件地址。"
},
"subject": {
"type": "string",
"description": "电子邮件主题行。"
},
"body": {
"type": "string",
"description": "电子邮件消息正文。"
}
},
"required": [
"to",
"subject",
"body"
],
"additionalProperties": False
},
"strict": True
}
}]
completion = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "你能给 ilan@example.com 和 katia@example.com 发送邮件说“hi”吗?"}],
tools=tools
)
print(completion.choices[0].message.tool_calls)
输出
[
{
"id": "call_9876abc",
"type": "function",
"function": {
"name": "send_email",
"arguments": "{\"to\":\"ilan@example.com\",\"subject\":\"Hello!\",\"body\":\"Just wanted to say hi\"}"
}
},
{
"id": "call_9876abc",
"type": "function",
"function": {
"name": "send_email",
"arguments": "{\"to\":\"katia@example.com\",\"subject\":\"Hello!\",\"body\":\"Just wanted to say hi\"}"
}
}
]
搜索知识库
使用 search_knowledge_base 函数的函数调用示例
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "search_knowledge_base",
"description": "查询知识库以检索关于某个主题的相关信息。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "用户问题或搜索查询。"
},
"options": {
"type": "object",
"properties": {
"num_results": {
"type": "number",
"description": "要返回的排名靠前的结果数量。"
},
"domain_filter": {
"type": [
"string",
"null"
],
"description": "可选的域,用于缩小搜索范围(例如,“finance”,“medical”)。 如果不需要,则传递 null。"
},
"sort_by": {
"type": [
"string",
"null"
],
"enum": [
"relevance",
"date",
"popularity",
"alphabetical"
],
"description": "如何对结果进行排序。 如果不需要,则传递 null。"
}
},
"required": [
"num_results",
"domain_filter",
"sort_by"
],
"additionalProperties": False
}
},
"required": [
"query",
"options"
],
"additionalProperties": False
},
"strict": True
}
}]
completion = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "你能在 AI 知识库中找到关于 ChatGPT 的信息吗?"}],
tools=tools
)
print(completion.choices[0].message.tool_calls)
输出
[{
"id": "call_4567xyz",
"type": "function",
"function": {
"name": "search_knowledge_base",
"arguments": "{\"query\":\"What is ChatGPT?\",\"options\":{\"num_results\":3,\"domain_filter\":null,\"sort_by\":\"relevance\"}}"
}
}]
概述
你可以通过让 OpenAI 模型访问 tools 来扩展其功能,tools 可以有两种形式:
函数调用 | 开发者定义的代码。 |
托管工具 | OpenAI 构建的工具。(例如,文件搜索、代码解释器)仅在 Assistants API 中可用。 |
本指南将介绍如何通过 函数调用 让模型访问你自己的函数。 基于系统提示和消息,模型可能会决定调用这些函数——而不是(或除了)生成文本或音频。
之后,你需要执行函数代码,将结果发回,模型会将结果整合到其最终回复中。
示例函数
让我们看看允许模型使用下面定义的真实 get_weather 函数的步骤:
在你的代码库中实现的示例 get_weather 函数
import requests
def get_weather(latitude, longitude):
response = requests.get(f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m,wind_speed_10m&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m")
data = response.json()
return data['current']['temperature_2m']
与之前的流程图不同,此函数需要精确的 latitude 和 longitude,而不是通用的 location 参数。(但是,我们的模型可以自动确定许多位置的坐标!)
函数调用步骤
使用定义的函数调用模型——以及你的系统和用户消息。
步骤 1:使用定义的 get_weather 工具调用模型
from openai import OpenAI
import json
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取给定坐标的当前温度,单位为摄氏度。",
"parameters": {
"type": "object",
"properties": {
"latitude": {"type": "number"},
"longitude": {"type": "number"}
},
"required": ["latitude", "longitude"],
"additionalProperties": False
},
"strict": True
}
}]
messages = [{"role": "user", "content": "今天巴黎的天气怎么样?"}]
completion = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
步骤2:模型决定调用函数——模型返回 名称和输入参数。
completion.choices[0].message.tool_calls
[{
"id": "call_12345xyz",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"latitude\":48.8566,\"longitude\":2.3522}"
}
}]
步骤 3:执行函数代码——解析模型的响应并处理函数调用
执行 get_weather 函数
tool_call = completion.choices[0].message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
result = get_weather(args["latitude"], args["longitude"])
步骤 4:向模型提供结果——以便模型可以将结果整合到其最终回复中。
提供结果并再次调用模型
messages.append(completion.choices[0].message) # append model's function call message
messages.append({ # append result message
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
completion_2 = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
步骤 4:模型响应——将结果整合到其输出中。
completion_2.choices[0].message.content
"巴黎当前温度为 14°C (57.2°F)。"
定义函数
函数可以在每个 API 请求的 tools 参数中,以 function 对象的形式进行设置。
函数由其模式定义,该模式告知模型函数的功能以及期望的输入参数。 它包含以下字段:
字段 | 描述 |
---|---|
name | 函数的名称(例如,get_weather) |
description | 关于何时以及如何使用函数的详细信息 |
parameters | 定义函数输入参数的 JSON schema |
示例函数模式
{
"type": "function",
"function": {
"name": "get_weather",
"description": "检索给定位置的当前天气。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和国家,例如 Bogotá, Colombia"
},
"units": {
"type": "string",
"enum": [
"celsius",
"fahrenheit"
],
"description": "温度将以什么单位返回。"
}
},
"required": [
"location",
"units"
],
"additionalProperties": False
},
"strict": True
}
}
因为 parameters 由 JSON schema 定义,所以你可以利用它的许多丰富特性,如属性类型、枚举、描述、嵌套对象和递归对象。
(可选)使用 pydantic 和 zod 进行函数调用
虽然我们鼓励你直接定义函数模式,但我们的 SDK 提供了帮助程序,可以将 pydantic 和 zod 对象转换为模式。 并非所有 pydantic 和 zod 功能都受支持。
定义对象以表示函数模式
from openai import OpenAI, pydantic_function_tool
from pydantic import BaseModel, Field
client = OpenAI()
class GetWeather(BaseModel):
location: str = Field(
...,
description="城市和国家,例如 Bogotá, Colombia"
)
tools = [pydantic_function_tool(GetWeather)]
completion = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "今天巴黎的天气怎么样?"}],
tools=tools
)
print(completion.choices[0].message.tool_calls)
定义函数的最佳实践
编写清晰详细的函数名称、参数描述和说明。
- 明确描述函数和每个参数的用途(及其格式),以及输出代表什么。
- 使用系统提示来描述何时(以及何时不)使用每个函数。 通常,要 确切地告诉模型该怎么做。
- 包含示例和边缘情况,尤其是在纠正任何重复出现的失败时。(注意: 添加示例可能会降低 推理模型 的性能。)
应用软件工程最佳实践。- 使函数显而易懂且直观。(最小惊喜原则)
- 使用枚举和对象结构,使无效状态无法表示。(例如,toggle_light(on: bool, off: bool) 允许无效调用)
- 通过实习生测试。 实习生/人类是否可以在仅提供你给模型的内容的情况下正确使用该函数?(如果不能,他们会问你什么问题? 将答案添加到提示中。)
尽可能减轻模型的负担并使用代码。
- 不要让模型填充你已经知道的参数。 例如,如果你已经基于之前的菜单获得了 order_id,则不要设置 order_id 参数——而是不设置参数 submit_refund(),并通过代码传递 order_id。
- 合并始终按顺序调用的函数。 例如,如果你总是在 query_location() 之后调用 mark_location(),只需将标记逻辑移至查询函数调用中。为了更高的准确性,请保持函数数量较少。
- 使用不同数量的函数评估你的性能。
- 目标是任何时候函数数量少于 20 个,但这只是一个软性建议。
利用 OpenAI 资源。
- 在 Playground 中生成和迭代函数模式。
- 考虑 fine-tuning 以提高大量函数或困难任务的函数调用准确性。
Token 使用量
在底层,函数以模型经过训练的语法注入到系统消息中。 这意味着函数计入模型的上下文限制,并作为输入 Token 收费。 如果你遇到 Token 限制,我们建议限制函数数量或你为函数参数提供的描述长度。
如果你的工具规范中定义了许多函数,也可以使用 fine-tuning来减少使用的 Token 数量。
处理函数调用
当模型调用函数时,你必须执行它并返回结果。 由于模型响应可能包含零个、一个或多个调用,因此最佳实践是假设有多个调用。
响应包含一个 tool_calls 数组,每个数组都有一个 id(稍后用于提交函数结果)和一个包含 name 和 JSON 编码的 arguments 的 function。
包含多个函数调用的示例响应
[
{
"id": "call_12345xyz",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}
},
{
"id": "call_67890abc",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"Bogotá, Colombia\"}"
}
},
{
"id": "call_99999def",
"type": "function",
"function": {
"name": "send_email",
"arguments": "{\"to\":\"bob@email.com\",\"body\":\"Hi bob\"}"
}
}
]
执行函数调用并附加结果
for tool_call in completion.choices[0].message.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
result = call_function(name, args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
在上面的示例中,我们有一个假设的 call_function 来路由每个调用。 这是一个可能的实现:
函数调用执行和结果追加
def call_function(name, args):
if name == "get_weather":
return get_weather(**args)
if name == "send_email":
return send_email(**args)
格式化结果
结果必须是字符串,但格式由你决定(JSON、错误代码、纯文本等)。 模型将根据需要解释该字符串。
如果你的函数没有返回值(例如 send_email),只需返回一个字符串来指示成功或失败。(例如 "success")
将结果整合到回复中
将结果附加到你的 messages 后,你可以将它们发回模型以获得最终回复。
将结果发回模型
completion = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
最终回复
"巴黎大约 15°C,Bogotá 大约 18°C,并且我已经向 Bob 发送了那封邮件。"
附加配置
工具选择
默认情况下,模型将确定何时以及使用多少工具。 你可以使用 tool_choice 参数强制执行特定行为。
- Auto:(默认)调用零个、一个或多个函数。 tool_choice: "auto"
- Required:调用一个或多个函数。 tool_choice: "required"
- Forced Function:精确调用一个特定函数。 tool_choice: {"type": "function", "function": {"name": "get_weather"}}
你还可以将 tool_choice 设置为 "none",以模拟不传递函数的行为。
并行函数调用
模型可能会选择在一个回合中调用多个函数。 你可以通过将 parallel_tool_calls 设置为 false 来阻止这种情况,这将确保精确地调用零个或一个工具。
注意: 目前,如果模型在一个回合中调用多个函数,则 strict modev将对这些调用禁用。
严格模式
将 strict 设置为 true 将确保函数调用可靠地遵守函数模式,而不是尽力而为。 我们建议始终启用严格模式。
在底层,严格模式通过利用我们的 structured outputs 功能来实现,因此引入了几个要求:
- 对于 parameters 中的每个对象,additionalProperties 必须设置为 false。
- properties 中的所有字段都必须标记为 required。
你可以通过添加 null 作为 type 选项来表示可选字段(请参阅下面的示例)。
启用严格模式
{
"type": "function",
"function": {
"name": "get_weather",
"description": "检索给定位置的当前天气。",
"strict": true,
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和国家,例如 Bogotá, Colombia"
},
"units": {
"type": ["string", "null"],
"enum": ["celsius", "fahrenheit"],
"description": "温度将以什么单位返回。"
}
},
"required": ["location", "units"],
"additionalProperties": False
}
}
}
禁用严格模式
{
"type": "function",
"function": {
"name": "get_weather",
"description": "检索给定位置的当前天气。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和国家,例如 Bogotá, Colombia"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度将以什么单位返回。"
}
},
"required": ["location"],
}
}
}
playground中生成的所有模式都启用了严格模式。
虽然我们建议你启用严格模式,但它有一些限制:
- 不支持 JSON schema 的某些功能。(请参阅 supported schemas)
- 模式在首次请求时会进行额外的处理(然后会被缓存)。 如果你的模式因请求而异,则可能会导致更高的延迟。
- 模式为了性能而被缓存,并且不符合 zero data retention的条件。
流式传输
流式传输可用于显示进度,通过展示在模型填充其参数时调用的函数,甚至实时显示参数。
流式传输函数调用与流式传输常规响应非常相似:你将 stream 设置为 true 并获取带有 delta 对象的块。
流式传输函数调用
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取给定位置的当前温度。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市和国家,例如 Bogotá, Colombia"
}
},
"required": ["location"],
"additionalProperties": False
},
"strict": True
}
}]
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "今天巴黎的天气怎么样?"}],
tools=tools,
stream=True
)
for chunk in stream:
delta = chunk.choices[0].delta
print(delta.tool_calls)
输出 delta.tool_calls
[{"index": 0, "id": "call_DdmO9pD3xa9XTPNJ32zg2hcA", "function": {"arguments": "", "name": "get_weather"}, "type": "function"}]
[{"index": 0, "id": null, "function": {"arguments": "{\"", "name": null}, "type": null}]
[{"index": 0, "id": null, "function": {"arguments": "location", "name": null}, "type": null}]
[{"index": 0, "id": null, "function": {"arguments": "\":\"", "name": null}, "type": null}]
[{"index": 0, "id": null, "function": {"arguments": "Paris", "name": null}, "type": null}]
[{"index": 0, "id": null, "function": {"arguments": ",", "name": null}, "type": null}]
[{"index": 0, "id": null, "function": {"arguments": " France", "name": null}, "type": null}]
[{"index": 0, "id": null, "function": {"arguments": "\"}", "name": null}, "type": null}]
null
然而,你不是将块聚合到单个 content 字符串中,而是将块聚合到编码的 arguments JSON 对象中。
当模型调用一个或多个函数时,每个 delta 的 tool_calls 字段将被填充。 每个 tool_call 包含以下字段:
字段 | 描述 |
---|---|
index | 标识 delta 对应的函数调用 |
id | 工具调用 ID。 |
function | 函数调用 delta(名称和参数) |
type | tool_call 的类型(对于函数调用始终为 function) |
这些字段中的许多字段仅在每个工具调用的第一个 delta 中设置,例如 id、function.name 和 type。
下面是一个代码片段,演示了如何将 delta 聚合到最终的 tool_calls 对象中。
累积 tool_call delta
final_tool_calls = {}
for chunk in stream:
for tool_call in chunk.choices[0].delta.tool_calls or []:
index = tool_call.index
if index not in final_tool_calls:
final_tool_calls[index] = tool_call
final_tool_calls[index].function.arguments += tool_call.function.arguments
累积的 final_tool_calls[0]
{
"index": 0,
"id": "call_RzfkBpJgzeR0S242qfvjadNe",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}
}