Mindket

OpenAI 函数调用:让模型调用你的代码

黑色横幅,标题是“OpenAI API 实战”,下面是“Responses、工具调用、结构化输出与智能体”。

OpenAI 函数调用让模型请求你的应用运行一个你用 JSON Schema 描述过的函数。模型自己从不运行它。模型返回一个带参数的 function_call 项,你的代码运行函数,再把结果作为带相同 call_id 的 function_call_output 发回去。网页搜索和文件搜索是内置工具,由 OpenAI 替你运行。

本节内容

  • 每段描述都要写到新手不靠别的信息也能正确调用这个函数。
  • 一直开着 strict,请求被拒时去修 schema。
  • 把每个回复都当成可能带着好几个调用,并按 id 逐个回应。
  • 每个回合只加载一小组函数,其余的延后加载。
  • 界面展示搜索结果时,把引用链接清楚地显示出来。
全部课程
本页目录
  1. 开始之前
  2. 从头到尾跑通工具调用循环
  3. 定义模型用得好的函数
  4. 处理多个调用,选择 tool_choice,流式读取参数
  5. 加上网页搜索和文件搜索
  6. Ilan Bigio 和 OpenAI Developers 谈怎样写函数、怎样找函数
  7. 常见错误
  8. 自己试一试
  9. 接下来
  10. 自测一下
  11. 常见问题
  12. 资料来源

开始之前

你需要第 1 课的准备:环境变量 OPENAI_API_KEY 里的 API 密钥,装在 Python 3.10 或更高版本上的 openai 包,以及一个支持工具的模型。示例用的是 gpt-6-astra,也就是 2026 年 11 月 5 日文档里用的模型。GPT-6 Astra 和 GPT-6.1 Sol 只能通过 Responses API 调用工具,所以这一课不涉及 Chat Completions。

这一课靠三个词撑起来。函数是你用 JSON Schema 定义的工具,JSON Schema 是一份写明每个参数及其类型的 JSON 文档。函数调用是模型带着自己填好的参数来请求这个函数。函数调用输出是你的代码发回去的结果。模型只负责请求,运不运行由你的应用决定。Responses API 还接受自定义工具和远程 MCP 服务器,这一课只讲函数和两个内置的搜索工具。

如果你学过 Claude API 课程里的工具调用一课,这个循环会很眼熟,只是结构不同。Claude 返回 tool_use 内容块,要求你在一条 user 消息里放 tool_result 块。OpenAI 则在 output 列表里返回 function_call 项,要求你在下一次的 input 里放 function_call_output 项,两者靠 call_id 配对。

从头到尾跑通工具调用循环

带上一个函数,发送“What is my horoscope? I am an Aquarius.”,文档里的流程展示了接下来会发生什么。第一次响应里没有星座运势,只有一个 function_call 项,它写着 get_horoscope 这个名字,参数是一段 JSON 字符串,这里就是星座。你的代码解析这段字符串,运行函数,把结果追加进去。用户读到的文字,要等第二次请求才会生成。整个模式就是这样一分为二。

下面的示例是函数调用指南里的完整示例,原样照搬。JavaScript 版本做的是同一件事,它从 openai/lib/responses/ResponseInputItems 导入 toResponseInputItems,再把结果推进输入数组。Python 版本里有两行做的事很容易被忽略。input_list += response.output 把模型自己的输出项,也就是调用本身和所有推理项,放回历史记录里。指南要求的正是这一点,因为和工具调用一起返回的推理项,必须随着输出一起传回去。

第二次请求加了 instructions,告诉模型只能根据工具的结果来回答。第 1 课讲过,instructions 只对一次请求生效,所以它放在真正需要它的那次调用上。这个循环没有固定的长度。在 Responses 里,这个循环可以按任务需要重复任意多次,每一轮都走同样的五步。

from openai import OpenAI
import json

client = OpenAI()

# 1. Define a list of callable tools for the model
tools = [
    {
        "type": "function",
        "name": "get_horoscope",
        "strict": True,
        "description": "Get today's horoscope for an astrological sign.",
        "parameters": {
            "type": "object",
            "properties": {
                "sign": {
                    "type": "string",
                    "description": "An astrological sign like Taurus or Aquarius",
                },
            },
            "required": ["sign"],
            "additionalProperties": False,
        },
    },
]

def get_horoscope(sign):
    return f"{sign}: Next Tuesday you will befriend a baby otter."

# Create a running input list we will add to over time
input_list = [{"role": "user", "content": "What is my horoscope? I am an Aquarius."}]

# 2. Prompt the model with tools defined
response = client.responses.create(
    model="gpt-6-astra",
    tools=tools,
    input=input_list,
)

# Save function call outputs for subsequent requests
input_list += response.output

for item in response.output:
    if item.type == "function_call":
        if item.name == "get_horoscope":
            # 3. Execute the function logic for get_horoscope
            sign = json.loads(item.arguments)["sign"]
            horoscope = get_horoscope(sign)

            # 4. Provide function call results to the model
            input_list.append(
                {
                    "type": "function_call_output",
                    "call_id": item.call_id,
                    "output": horoscope,
                }
            )

response = client.responses.create(
    model="gpt-6-astra",
    instructions="Respond only with a horoscope generated by a tool.",
    tools=tools,
    input=input_list,
)

# 5. The model should be able to give a response!
print(response.output_text)
  1. 带上 tools 列表和用户消息发送请求。
  2. 在 response.output 里找出每个 function_call 项,读取它的 name 和 arguments。
  3. 用 json.loads 解析参数,运行你自己的函数。
  4. 追加一个带相同 call_id 的 function_call_output,结果以字符串形式放进去。
  5. 把变长的输入列表再发一次。回复要么是文字,要么是更多要运行的调用。

定义模型用得好的函数

一个函数定义有五个字段,见下表。parameters 接受标准的 JSON Schema,所以枚举、嵌套对象和每个属性上的描述都能用,递归对象也可以。示例是指南里的 get_weather 定义,它的 units 枚举能防止模型编出一个你的代码读不懂的单位。

指南关于写定义的建议,归结起来是一个它称为“实习生测试”的检验:一个人只拿到你给模型的这些信息,能不能正确使用这个函数?如果不能,他会问的那些问题,就该写进描述里。写明每个参数的含义和格式,再在 instructions 里说清楚什么时候该用、什么时候不该用这个函数。加入示例和边界情况,有助于纠正反复出现的错误,不过指南也提醒,示例可能会让推理模型表现变差。

函数本身也要用心设计。像 toggle_light(on: bool, off: bool) 这样的签名,允许出现说不通的调用,应该改用枚举。别让模型去填你的代码已经知道的值。如果你已经有 order_id,就把 submit_refund() 定义成没有参数,在代码里传入这个 id。总是先后连着运行的两个函数,可以合并成一个。

数量也有讲究。指南建议,每个回合开始时可用的函数少于 20 个,这只是一个宽松的目标。函数定义会被注入系统消息,所以它们占用上下文窗口,并且每次请求都按输入 token 计费。函数多了,就把它们按命名空间分组,比如 crm 或 billing,再给不常用的标上 defer_loading。这样 tool_search 只在模型需要时才加载它们,这要求 gpt-5.4 或更新的模型。

严格模式是最后一个字段,也是最要紧的一个。把 strict 设为 true,模型给出的参数就会完全符合你的 schema,它和结构化输出用的是同一套机制。代价是 schema 要守两条规则:每个对象都把 additionalProperties 设为 false,properties 里的每个字段都要列进 required。可选字段要写成包含 null 的类型列表,比如 ["string", "null"]。

如果你发送 strict: true,而 schema 违反了规则,请求会被拒绝,并列出缺少的约束。如果你不写 strict,Responses 会尝试把 schema 转成严格模式,转不了就退回尽力而为的模式,响应里的工具随后会显示 strict: false。指南建议始终打开它。

{
  "type": "function",
  "name": "get_weather",
  "description": "Retrieves current weather for the given location.",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City and country e.g. Bogotá, Colombia"
      },
      "units": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Units the temperature will be returned in."
      }
    },
    "required": ["location", "units"],
    "additionalProperties": false
  },
  "strict": true
}
出自函数调用指南,2026 年 11 月 5 日阅读。
字段里面放什么
type始终是 function
name模型调用时用的函数名,比如 get_weather
description什么时候、怎样使用这个函数
parameters描述参数的 JSON Schema
strict参数是否必须完全符合 schema

处理多个调用,选择 tool_choice,流式读取参数

一次响应里可能有零个、一个或好几个函数调用,所以指南要求你默认按多个来处理。它的示例响应里有三项:查巴黎天气的 get_weather,查波哥大天气的 get_weather,以及发给 Bob 的 send_email,每一项都有一个以 fc_ 开头的 id、一个以 call_ 开头的 call_id、一个 name,以及 JSON 字符串形式的 arguments。一个小小的路由函数,也就是下面指南里的 call_function,把每个调用分派给对应的代码,遇到不认识的名字就抛出错误。

返回的内容通常是字符串,格式随你定,JSON、错误码或纯文本都行。像 send_email 这样没有返回值的函数,也该返回类似 "success" 的内容,让模型知道它已经运行过。生成图片或文件的函数,可以改为返回一组图片或文件对象。所有输出都追加好之后,下一次请求就把模型需要的东西都给齐了。指南示例里的最终回答是“It's about 15°C in Paris, 18°C in Bogotá, and I've sent that email to Bob.”

默认情况下,模型自己决定什么时候调用工具、调用几个。把 parallel_tool_calls 设为 false,它每个回合就只调用零个或一个工具。从 GPT-5 起的模型,在同时提供内置工具时也能并行调用函数,不过内置工具从不加入并行的函数调用批次。tool_choice 能让你控制得更牢,下表列出了它的各种设置。

allowed_tools 的用处不止于控制。你可以让每次请求的完整 tools 列表保持一模一样,只在每个回合收窄可调用的范围。工具列表不变,提示词缓存就能持续生效,提示词缓存会复用之前请求里已经处理过的提示词前缀。

函数调用也能流式输出。打开 stream 后,response.output_item.added 事件宣布每个 function_call 项的出现,response.function_call_arguments.delta 事件一段一段地送来参数文本,response.function_call_arguments.done 带来完整的调用。界面可以在模型还在写参数时,就显示正在运行哪个函数。这些增量是原始的 JSON 文本,要等 done 事件到了再解析,因为写到一半的参数字符串还不是合法的 JSON。

def call_function(name, args):
    if name == "get_weather":
        return get_weather(**args)
    if name == "send_email":
        return send_email(**args)
    raise ValueError(f"Unknown function: {name}")
OpenAI 更新日志里的函数调用变化
  1. functions 让位给 tools

    Chat Completions 弃用 functions 参数。

  2. tool_choice 新增 required

    模型必须至少调用一个函数。

  3. 并行调用可以关闭

    在 Chat Completions 和 Assistants API 里。

  4. Responses API 发布

    一个为智能体和工具打造的新 API。

  5. strict 模式支持并行调用

    并行调用时也能用严格的工具 schema。

  6. 自定义工具

    工具调用可以用自由文本输入和输出,而不是 JSON。

  7. 工具搜索

    模型可以把大量工具推迟到需要时再加载。

  8. GPT-6 Astra

    它的工具调用必须走 Responses API,Responses 同时加入了异步工具调用。

来源:OpenAI Docs: Changelog (2026年11月5日 查阅)

出自函数调用指南,2026 年 11 月 5 日阅读。
tool_choice模型可以做什么
"auto"(默认)调用零个、一个或多个函数
"required"调用一个或多个函数
{"type": "function", "name": "get_weather"}只调用指定的那一个函数
{"type": "allowed_tools", "mode": "auto", "tools": [...]}只在列出的子集里调用
"none"表现得像没有传入任何函数

加上网页搜索和文件搜索

内置工具在 OpenAI 那边运行。你像添加函数一样把它们加进 tools,用不用由模型决定,但你的代码不用执行任何东西,也不用发回任何输出。下面的示例是网页搜索指南里的第一个示例,只要一项 {"type": "web_search"} 就设置好了。旧的 web_search_preview 工具仍可用于老代码,但它不支持 filters 等较新的控制项。

这时响应里会有一个 web_search_call 项,它的 action 说明模型做了什么:search、open_page 或 find_in_page,后两种只在推理模型上出现。接着是 message,带着 url_citation 注释,给出每个被引用来源的 URL、标题和它在文字里的位置。指南规定,你的界面展示网页结果时,这些引用必须显示成醒目、可点击的链接。

网页搜索有几个设置。filters 可以把结果限定在最多 100 个 allowed_domains 里,或者排除最多 100 个 blocked_domains,域名不带 https 前缀。sources 字段列出模型查阅过的所有 URL,通常比它引用的多。search_context_size 设为 low、medium 或 high,控制模型读取多少搜索结果。external_web_access 设为 false,就只用缓存的结果。不管模型本身的窗口多大,搜索的上下文窗口上限都是 128,000 个 token。需要综合多个来源的问题,指南建议用推理模型,它会边读边调整搜索,耗时几分钟的任务则配合后台模式运行。

文件搜索的答案来自你的文档,而不是网页。你先上传文件,把它们加进一个向量存储,也就是 OpenAI 为了对这些文件做语义和关键词搜索而建立的索引,再等每个文件的状态变成 completed。然后在 tools 里加一个 file_search 项,把这个存储的 id 放进它的 vector_store_ids 列表。响应里会有一个 file_search_call 项,以及一条带 file_citation 注释、写明每个文件名的消息。

文件搜索有两个设置。max_num_results 限制返回多少条结果,能省 token、降延迟,但可能牺牲回答质量。原始搜索结果默认不放进响应,要用 include=["file_search_call.results"] 才能拿到。tool_choice 保持 auto 时,两种搜索都是可选的。搜索必须执行时,就把它设为 required。

from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-6-astra",
    tools=[{"type": "web_search"}],
    input="What was a positive news story from today?",
)

print(response.output_text)
出自网页搜索和文件搜索指南,2026 年 11 月 5 日阅读。
工具谁来运行你要准备什么返回什么
函数你的应用名称、描述和 JSON Schema一个 function_call 项,你要回一个输出
web_searchOpenAItools 里的一项,外加可选的过滤条件一个 web_search_call 项和一条带 URL 引用的消息
file_searchOpenAI放进向量存储的已上传文件一个 file_search_call 项和一条带文件引用的消息

Ilan Bigio 和 OpenAI Developers 谈怎样写函数、怎样找函数

Ilan Bigio 在 OpenAI 做研究和工具,他在 2025-01-13 在 X 上发帖,宣布函数调用指南重写完成。照他的说法,团队听取了反馈:新指南篇幅减半,加了新的最佳实践,还有一个基于天气 API 的完整示例。本课的天气 schema 就出自这份指南的当前版本,所以自己写描述之前,先读读它的最佳实践。

OpenAI Developers 在 2026-03-05 宣布 GPT-5.4,帖子里有一行是:在更大的工具生态里做可扩展的工具搜索。这就是本课定义函数时讲到的 defer_loading 加 tool_search,在 gpt-5.4 及之后的模型上可用。函数多到超过指南的软性目标时,就按命名空间分组,把不常用的推迟加载。

在更大的工具生态里,做可扩展的工具搜索。

OpenAI Developers, OpenAI 面向开发者的官方账号,引文为译文 · X (2026年11月5日 查阅)
Ilan Bigio(@ilanbigio)2025 年 1 月 13 日在 X 上发帖:宣布 OpenAI 全新的函数调用指南。我们听取了反馈,做了几处关键改动:篇幅缩短一半、更清楚,新的最佳实践,文档内生成函数,以及一个基于天气 API 的完整示例。
Ilan Bigio 在 X 上,2025 年 1 月 13 日
OpenAI Developers(@OpenAIDevs)2026 年 3 月 5 日在 X 上发帖:GPT-5.4 发布。原生的电脑操作能力。在 Codex 和 API 里最多 100 万 token 上下文。面向复杂任务的智能体编程。在更大的工具生态里做可扩展的工具搜索。长时间、重工具工作流里更高效的推理。
OpenAI Developers 在 X 上,2026 年 3 月 5 日
OpenAI 自己的 YouTube 频道上,Build Hour 系列讲智能体工具调用的一期。

X 官方嵌入卡片的截图,拍摄于 2026-11-05,时间为 UTC。点击截图可到 X 上打开原帖。X 用 Show more 截断了 Ilan Bigio 的帖子。

常见错误

  • 响应里有三个 function_call,却只读了第一个。
  • 发送 function_call_output 时,没带上它所回应的那个调用的 call_id。
  • 追加了你的输出,却没追加模型自己的输出项,结果推理项一直没传回去。
  • 打开了严格模式,却有字段没列进 required,然后纳闷请求为什么被拒。
  • 让模型去填你的代码早就有的 id。
  • response.function_call_arguments.done 还没到,就开始解析流式传来的参数。
  • 每个回合都加载几十个函数,每次都为它们的定义付费。
  • tool_choice 还是 auto,却指望每个问题都触发网页搜索。
  • 展示搜索得来的回答,却不附引用链接。
  • 新代码还用 web_search_preview,而它会忽略域名过滤。

自己试一试

  • 运行星座运势示例,在第一次调用后打印 response.output。里面有哪些类型的输出项?
  • 把天气 schema 里的 "units" 从 required 中去掉,保持 strict 开启,读一读报错。
  • 同时问两个城市的天气,数一数有几个 function_call 项。
  • 把 parallel_tool_calls 设为 false,再问一次同样的问题。
  • 加上 web_search,用 filters 只允许一个你信任的域名,比较引用有什么不同。
  • 把一个 PDF 上传到向量存储,问一个只有这份文件能回答的问题。

接下来

第 3 课把这一课的严格模式用到模型的回复本身。你把 schema 作为 text.format 传入,或者传入一个 Pydantic 或 Zod 类,就能拿到代码可以直接解析、无需再检查的 JSON。

远程 MCP 服务器是 Responses API 接受的另一种工具。等你自己的函数多到一个文件装不下时,可以去了解这个协议,以及服务器怎样连接到 AI 应用。

自测一下

  1. 模型决定调用 get_horoscope 时,由谁来运行它?

    查看答案

    C. 你的应用代码 模型只返回一个 function_call 项,由你的代码运行函数并发回输出。

  2. 打开严格模式后,parameters 里的每个对象必须遵守哪条规则?

    查看答案

    D. additionalProperties 为 false,且每个字段都在 required 里 严格模式要求 additionalProperties 设为 false,每个属性都列进 required,可选字段在类型里加上 null。

  3. 你把 parallel_tool_calls 设为 false,模型在这个回合能调用几个工具?

    查看答案

    A. 零个或一个 关闭 parallel_tool_calls 后,模型每个回合只调用零个或一个工具。

  4. 什么把 function_call_output 和它回应的那个调用联系起来?

    查看答案

    B. call_id 每个输出都带着对应 function_call 的 call_id,模型就靠它来配对。

常见问题

什么是自定义工具,什么时候比函数更合适?

自定义工具接收自由格式的文本,而不是 JSON 参数。模型会把一段字符串直接交给你的代码,比如交给 code_exec 工具的一段 Python。指南建议在两种情况下用它:把输入包成 JSON 毫无必要的时候,或者你想用语法来约束输入的时候,语法可以用 Lark 写,也可以写成正则表达式。

网页搜索除了 token 还要另外收费吗?

要。每一次 search 动作都按一次工具调用计费,另算在模型读写的 token 之外,当前的费率见价格页面。Responses 里的网页搜索,也和你调用它的那个模型共用速率限制。

Chat Completions 还能用函数调用吗?

能,只是请求和响应的结构不同,而且除非你手动打开,否则不是严格模式。迁移指南列出了所有改动。GPT-6 Astra 和 GPT-6.1 Sol 调用工具必须用 Responses API,指南自己的 Chat Completions 示例用的是关闭推理的 GPT-5.6 Terra。

在微调模型上,严格模式的表现一样吗?

不完全一样。微调模型会在第一次请求时处理每个新的 schema 并缓存下来,所以每次请求的 schema 都不同,就会增加延迟。缓存的 schema 不适用零数据保留。如果微调模型在一个回合里调用了好几个函数,这些调用的严格模式会被关闭。

资料来源

  1. OpenAI Docs: Function calling (2026年11月5日 查阅)https://developers.openai.com/api/docs/guides/function-calling
  2. OpenAI Docs: Using tools (2026年11月5日 查阅)https://developers.openai.com/api/docs/guides/tools
  3. OpenAI Docs: Web search (2026年11月5日 查阅)https://developers.openai.com/api/docs/guides/tools-web-search
  4. OpenAI Docs: File search (2026年11月5日 查阅)https://developers.openai.com/api/docs/guides/tools-file-search
  5. OpenAI Docs: Migrate to the Responses API (2026年11月5日 查阅)https://developers.openai.com/api/docs/guides/migrate-to-responses
  6. OpenAI Docs: Changelog (2026年11月5日 查阅)https://developers.openai.com/api/docs/changelog
  7. X: Ilan Bigio announces the rewritten function calling guide (2026年11月5日 查阅)https://x.com/ilanbigio/status/1878940258349510764
  8. X: OpenAI Developers announce GPT-5.4 with tool search (2026年11月5日 查阅)https://x.com/OpenAIDevs/status/2029620984853188738
  9. YouTube (OpenAI): Build Hour: Agentic Tool Calling (2026年11月5日 查阅)https://www.youtube.com/watch?v=7E-qdsVEoB8

分享本节课

看看会调用真实工具的智能体

在 Mindket 浏览其他开发者上架的 AI 智能体,看看每个用了哪些工具,再把你自己的也上架。

浏览 AI 智能体

本节课的 Markdown 版本 · OpenAI API 实战课:Responses、工具调用、结构化输出与智能体 · 全部课程