AI个人学习
和实操指南

OpenAI 函数调用(Function calling)

本文于 2025-02-08 21:14 更新,部分内容具有时效性,如有失效,请留言

OpenAI Function calling V2 特点

Function calling V2 的核心目标是赋予 OpenAI 模型与外部世界交互的能力,主要体现在以下两个核心功能:

  1. 数据获取 (Fetching Data) - RAG 的函数调用实现:
    • 本质是 RAG (检索增强生成): Function calling 提供了一种强大的机制来实现检索增强生成 (RAG)。模型可以通过调用预定义的函数,从外部数据源(如知识库、API 等)检索最新的、相关的信息,并将其融入到自身的生成回复中。
    • 解决信息滞后和知识限制: 传统的大语言模型知识是静态的,Function calling 弥补了这一缺陷,使模型能够获取实时信息或特定领域知识,从而生成更准确、更全面的回复。
    • 用例示例:
      • 实时信息查询: 例如,获取当前天气数据、最新的新闻资讯、股票价格等。
      • 知识库搜索: 例如,查询企业内部知识库、产品文档、FAQ 等,为用户提供更专业的解答。
      • API 数据集成: 例如,从外部 API 获取商品信息、航班动态、地理位置信息等,丰富对话内容。
    • 实现方式: 开发者定义用于数据检索的函数 (例如 get_weathersearch_knowledge_base),并在 tools 参数中提供给模型。当模型判断需要外部信息时,会调用这些函数并获取结果,再将结果整合到最终回复中。
  2. 执行动作 (Taking Action) - 模型驱动的自动化:
    • 超越信息提供,驱动实际操作: Function calling 不仅限于信息检索,更进一步,它允许模型驱动外部系统执行实际的操作,实现更深层次的自动化和应用场景。
    • 增强模型的实用性和应用范围: 这使得模型可以不仅仅是对话伙伴,更可以成为智能助手,帮助用户完成各种任务。
    • 用例示例:
      • 自动化工作流: 例如,提交表单、创建日历事件、发送邮件、预订机票酒店等。
      • 应用程序控制: 例如,修改应用程序状态 (UI/前端或后端)、控制智能家居设备等。
      • 代理工作流 (Agentic Workflow): 例如,根据对话内容,将对话移交给更专业的客服系统,或者触发更复杂的自动化流程。
    • 实现方式: 开发者定义用于执行特定操作的函数 (例如 send_emailcreate_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 中可用。

本指南将介绍如何通过 函数调用 让模型访问你自己的函数。 基于系统提示和消息,模型可能会决定调用这些函数——而不是(或除了)生成文本或音频

之后,你需要执行函数代码,将结果发回,模型会将结果整合到其最终回复中。

函数调用(Function calling)-1

示例函数

让我们看看允许模型使用下面定义的真实 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"}}

函数调用(Function calling)-2

你还可以将 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 中设置,例如 idfunction.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\"}"
}
}
未经允许不得转载:首席AI分享圈 » OpenAI 函数调用(Function calling)

首席AI分享圈

首席AI分享圈专注于人工智能学习,提供全面的AI学习内容、AI工具和实操指导。我们的目标是通过高质量的内容和实践经验分享,帮助用户掌握AI技术,一起挖掘AI的无限潜能。无论您是AI初学者还是资深专家,这里都是您获取知识、提升技能、实现创新的理想之地。

联系我们
zh_CN简体中文