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

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

Mindket 编辑部 · 发布于 2026-11-05 · 更新于 2026-11-05 · https://mindket.com/cn/learn/openai-api/openai-function-calling

## 要点

- 每段描述都要写到新手不靠别的信息也能正确调用这个函数。
- 一直开着 `strict`，请求被拒时去修 schema。
- 把每个回复都当成可能带着好几个调用，并按 id 逐个回应。
- 每个回合只加载一小组函数，其余的延后加载。
- 界面展示搜索结果时，把引用链接清楚地显示出来。

## 开始之前

你需要第 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 课程里的[工具调用一课](https://mindket.com/cn/learn/claude-api/claude-tool-use)，这个循环会很眼熟，只是结构不同。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 里，这个循环可以按任务需要重复任意多次，每一轮都走同样的五步。

````python
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`。指南建议始终打开它。

````json
{
  "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
}
````

| 字段 | 里面放什么 |
| --- | --- |
| `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。

````python
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. **2023年11月6日** `functions` 让位给 `tools`。Chat Completions 弃用 `functions` 参数。
2. **2024年4月29日** `tool_choice` 新增 `required`。模型必须至少调用一个函数。
3. **2024年6月6日** 并行调用可以关闭。在 Chat Completions 和 Assistants API 里。
4. **2025年3月11日** Responses API 发布。一个为智能体和工具打造的新 API。
5. **2025年5月20日** strict 模式支持并行调用。并行调用时也能用严格的工具 schema。
6. **2025年8月7日** 自定义工具。工具调用可以用自由文本输入和输出，而不是 JSON。
7. **2026年3月5日** 工具搜索。模型可以把大量工具推迟到需要时再加载。
8. **2026年9月3日** GPT-6 Astra。它的工具调用必须走 Responses API，Responses 同时加入了异步工具调用。

来源：[OpenAI Docs: Changelog](https://developers.openai.com/api/docs/changelog) (2026-11-05 查阅)

| `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`。

````python
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)
````

| 工具 | 谁来运行 | 你要准备什么 | 返回什么 |
| --- | --- | --- | --- |
| 函数 | 你的应用 | 名称、描述和 JSON Schema | 一个 `function_call` 项，你要回一个输出 |
| `web_search` | OpenAI | `tools` 里的一项，外加可选的过滤条件 | 一个 `web_search_call` 项和一条带 URL 引用的消息 |
| `file_search` | OpenAI | 放进向量存储的已上传文件 | 一个 `file_search_call` 项和一条带文件引用的消息 |

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

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

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

> 在更大的工具生态里，做可扩展的工具搜索。
>
> OpenAI Developers, OpenAI 面向开发者的官方账号，引文为译文, [X](https://x.com/OpenAIDevs/status/2029620984853188738) (2026-11-05 查阅)

[![Ilan Bigio（@ilanbigio）2025 年 1 月 13 日在 X 上发帖：宣布 OpenAI 全新的函数调用指南。我们听取了反馈，做了几处关键改动：篇幅缩短一半、更清楚，新的最佳实践，文档内生成函数，以及一个基于天气 API 的完整示例。](https://mindket.com/blog/shots/x-ilanbigio-function-calling-guide.webp?v=4370c6434138)](https://x.com/ilanbigio/status/1878940258349510764)

Ilan Bigio 在 X 上，2025 年 1 月 13 日

[![OpenAI Developers（@OpenAIDevs）2026 年 3 月 5 日在 X 上发帖：GPT-5.4 发布。原生的电脑操作能力。在 Codex 和 API 里最多 100 万 token 上下文。面向复杂任务的智能体编程。在更大的工具生态里做可扩展的工具搜索。长时间、重工具工作流里更高效的推理。](https://mindket.com/blog/shots/x-openaidevs-gpt-5-4-tool-search.webp?v=e7c533e76632)](https://x.com/OpenAIDevs/status/2029620984853188738)

OpenAI Developers 在 X 上，2026 年 3 月 5 日

[Build Hour: Agentic Tool Calling](https://www.youtube.com/watch?v=7E-qdsVEoB8)

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 课](https://mindket.com/cn/learn/openai-api/openai-structured-outputs)把这一课的严格模式用到模型的回复本身。你把 schema 作为 `text.format` 传入，或者传入一个 Pydantic 或 Zod 类，就能拿到代码可以直接解析、无需再检查的 JSON。

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

## 自测一下

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

   A. OpenAI 的服务器
   B. 模型自己，在推理过程中
   C. 你的应用代码
   D. SDK，不用你开口

   答案：C。 模型只返回一个 function_call 项，由你的代码运行函数并发回输出。

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

   A. 每个属性都有默认值
   B. 属性不超过 20 个
   C. 可选字段不列进 required
   D. additionalProperties 为 false，且每个字段都在 required 里

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

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

   A. 零个或一个
   B. 正好一个
   C. 最多 20 个
   D. 只能调用内置工具

   答案：A。 关闭 parallel_tool_calls 后，模型每个回合只调用零个或一个工具。

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

   A. 函数名
   B. call_id
   C. 响应的 id
   D. 输出项的先后顺序

   答案：B。 每个输出都带着对应 function_call 的 call_id，模型就靠它来配对。

## 常见问题

### 什么是自定义工具，什么时候比函数更合适？

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

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

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

### Chat Completions 还能用函数调用吗？

能，只是请求和响应的结构不同，而且除非你手动打开，否则不是严格模式。[迁移指南](https://developers.openai.com/api/docs/guides/migrate-to-responses)列出了所有改动。GPT-6 Astra 和 GPT-6.1 Sol 调用工具必须用 Responses API，指南自己的 Chat Completions 示例用的是关闭推理的 GPT-5.6 Terra。

### 在微调模型上，严格模式的表现一样吗？

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

## 资料来源

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

[浏览 AI 智能体](https://mindket.com/cn/agents)
