Skip to content

Function Calling ​

Function Calling 是 OpenAI API 最强大的能力之一。它允许你向模型描述可用的函数(工具),模型会根据用户的输入自主判断是否需要调用这些函数,并生成符合你定义的参数格式的调用请求。这意味着你可以将模型与外部世界连接起来 —— 查询数据库、调用第三方 API、操作文件系统,几乎可以做任何事情。

在传统的对话模型中,模型只能基于自身训练数据生成文本回复。而 Function Calling 打破了这一限制,让模型成为一个智能的"调度中心":它理解用户意图,决定需要调用哪些函数,并生成正确的参数。你只需要告诉模型"你有哪些工具可用",剩下的判断交给模型来做。

核心概念 ​

ℹ️模型不执行函数

这是理解 Function Calling 最重要的一点:模型本身不会执行任何函数。它只做两件事:

  1. 判断是否需要调用函数来回答用户的问题
  2. 生成结构化的函数调用参数(JSON 格式)

实际的函数执行完全由你的代码负责。模型生成的是"调用意图",而不是"执行结果"。这种设计保证了安全性 —— 你始终掌握着执行的控制权,可以在执行前进行验证、鉴权、限流等任何必要的检查。

完整流程 ​

理解 Function Calling 的最佳方式是掌握它的五步流程。每一次 Function Calling 交互都遵循这个固定的模式,从定义工具开始,到模型生成最终回复结束。

Function Calling 五步流程
1. 定义工具
声明函数名、参数 Schema
2. 发送请求
将工具定义和用户消息一起发送
3. 模型决策
模型判断是否需要调用函数
4. 执行函数
你的代码执行函数并获取结果
5. 返回结果
将函数结果发回模型生成最终回复

第一步:定义工具。 你需要用 JSON Schema 格式描述每个函数的名称、用途和参数结构。描述越清晰,模型判断得越准确。

第二步:发送请求。 将工具定义(tools 参数)和用户消息一起发送给 API。模型会同时看到用户的问题和你提供的工具列表。

第三步:模型决策。 模型分析用户意图后做出判断 —— 如果问题可以直接回答(如"你好"),模型会直接回复文本;如果需要外部数据(如"北京天气怎么样"),模型会生成函数调用。

第四步:执行函数。 你的代码解析模型返回的函数名和参数,调用对应的真实函数(比如天气 API),获取结果。

第五步:返回结果。 将函数执行结果发回给模型,模型结合结果生成自然语言的最终回复。

实战示例:天气查询 ​

下面是一个完整的天气查询示例,涵盖了从工具定义到最终回复的全部流程。我们定义一个 get_weather 函数,让模型在用户询问天气时自动调用它。

python
from openai import OpenAI
import json

client = OpenAI()

# 第一步:定义工具
tools = [{
    "type": "function",
    "name": "get_weather",
    "description": "获取指定城市的当前天气",
    "parameters": {
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "城市名称"},
            "unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"}
        },
        "required": ["city"]
    }
}]

# 第二步:发送请求
response = client.responses.create(
    model="gpt-4.1",
    input="北京今天天气怎么样?",
    tools=tools
)

# 第三步 & 第四步:处理函数调用并执行
for item in response.output:
    if item.type == "function_call":
        args = json.loads(item.arguments)
        # 调用你的天气 API
        weather_data = get_weather(**args)

        # 第五步:将结果返回给模型
        final = client.responses.create(
            model="gpt-4.1",
            input=[
                {"role": "user", "content": "北京今天天气怎么样?"},
                item,  # function_call
                {
                    "type": "function_call_output",
                    "call_id": item.call_id,
                    "output": json.dumps(weather_data)
                }
            ],
            tools=tools
        )
        print(final.output_text)
javascript
import OpenAI from "openai";

const client = new OpenAI();

// 第一步:定义工具
const tools = [{
  type: "function",
  name: "get_weather",
  description: "获取指定城市的当前天气",
  parameters: {
    type: "object",
    properties: {
      city: { type: "string", description: "城市名称" },
      unit: { type: "string", enum: ["celsius", "fahrenheit"], default: "celsius" }
    },
    required: ["city"]
  }
}];

// 第二步:发送请求
const response = await client.responses.create({
  model: "gpt-4.1",
  input: "北京今天天气怎么样?",
  tools
});

// 第三步 & 第四步:处理函数调用并执行
for (const item of response.output) {
  if (item.type === "function_call") {
    const args = JSON.parse(item.arguments);
    // 调用你的天气 API
    const weatherData = await getWeather(args);

    // 第五步:将结果返回给模型
    const final = await client.responses.create({
      model: "gpt-4.1",
      input: [
        { role: "user", content: "北京今天天气怎么样?" },
        item, // function_call
        {
          type: "function_call_output",
          call_id: item.call_id,
          output: JSON.stringify(weatherData)
        }
      ],
      tools
    });
    console.log(final.output_text);
  }
}
bash
# 第二步:发送请求(包含工具定义)
curl https://api.openai.com/v1/responses \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4.1",
    "input": "北京今天天气怎么样?",
    "tools": [{
      "type": "function",
      "name": "get_weather",
      "description": "获取指定城市的当前天气",
      "parameters": {
        "type": "object",
        "properties": {
          "city": {"type": "string", "description": "城市名称"},
          "unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"}
        },
        "required": ["city"]
      }
    }]
  }'

# 模型返回 function_call 后,执行函数获取结果
# 然后发送第五步请求:
curl https://api.openai.com/v1/responses \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4.1",
    "input": [
      {"role": "user", "content": "北京今天天气怎么样?"},
      {"type": "function_call", "name": "get_weather", "arguments": "{\"city\":\"北京\"}", "call_id": "call_xxx"},
      {"type": "function_call_output", "call_id": "call_xxx", "output": "{\"temp\": 22, \"condition\": \"晴\"}"}
    ],
    "tools": [{
      "type": "function",
      "name": "get_weather",
      "description": "获取指定城市的当前天气",
      "parameters": {
        "type": "object",
        "properties": {
          "city": {"type": "string", "description": "城市名称"},
          "unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"}
        },
        "required": ["city"]
      }
    }]
  }'

在上面的示例中,有几个关键细节值得注意:

  • tools 参数中的 description 字段非常重要。模型依赖它来判断何时应该调用这个函数。描述得越精确,模型的判断就越准确。
  • item.call_id 是每次函数调用的唯一标识。在返回结果时必须带上它,这样模型才能将结果与对应的调用匹配起来。
  • 第五步的 input 数组需要包含完整的对话上下文:用户消息、函数调用、函数结果,三者缺一不可。

Strict Mode(严格模式) ​

💡启用 Strict Mode 保证 Schema 合规

在工具定义中设置 "strict": true,可以让模型 100% 遵循你定义的参数 Schema。这意味着模型生成的参数一定符合你指定的类型、枚举值和必填要求,不会出现缺少字段或类型错误的情况。

启用方法很简单,只需在工具定义中加入 strict 字段:

json
{
  "type": "function",
  "name": "get_weather",
  "description": "获取指定城市的当前天气",
  "parameters": { ... },
  "strict": true
}

在生产环境中,强烈建议始终开启 Strict Mode,以避免参数解析错误导致的运行时异常。

Strict Mode 的工作原理是在首次请求时对你的 Schema 进行预编译。OpenAI 会将 Schema 转换为一个约束解码器(constrained decoder),确保模型在 token 生成阶段就只能产生符合 Schema 的输出。这就是为什么它能提供 100% 的 Schema 合规保证,而不仅仅是"尽力而为"。

需要注意的是,Strict Mode 要求你的 Schema 是 JSON Schema 的一个子集。一些高级特性(如 patternProperties、if/then/else)不被支持。但对于绝大多数实际场景来说,支持的子集已经足够使用。

特性 ⇅Strict Mode ⇅非 Strict Mode ⇅
Schema 遵循100% 保证尽力而为
支持的 SchemaJSON Schema 子集完整 JSON Schema
首次延迟略高(需编译 Schema)无额外延迟
推荐场景生产环境快速原型开发

从表格可以看出,两种模式各有适用场景。在开发阶段快速迭代时,非 Strict Mode 更灵活;当应用上线后,切换到 Strict Mode 可以消除参数格式问题带来的潜在风险。

多工具调用 ​

模型支持在一次响应中调用多个函数。例如,用户问"北京和上海的天气分别怎么样?",模型可能同时生成两个 get_weather 调用,分别传入不同的城市参数。你的代码需要遍历 response.output 中的所有 function_call 项,逐一执行并收集结果。

在处理多工具调用时,你可以选择并行执行所有函数以提高效率。每个函数调用都有独立的 call_id,所以结果不会混淆。将所有结果一次性返回给模型,它会综合所有信息生成最终回复。

python
# 并行处理多个函数调用
import asyncio

async def handle_tool_calls(response, tools):
    tool_calls = [item for item in response.output if item.type == "function_call"]

    # 并行执行所有函数
    results = await asyncio.gather(*[
        execute_function(call.name, json.loads(call.arguments))
        for call in tool_calls
    ])

    # 构建返回消息
    messages = [{"role": "user", "content": "北京和上海的天气分别怎么样?"}]
    for call, result in zip(tool_calls, results):
        messages.append(call)
        messages.append({
            "type": "function_call_output",
            "call_id": call.call_id,
            "output": json.dumps(result)
        })

    return await client.responses.create(
        model="gpt-4.1",
        input=messages,
        tools=tools
    )

常见陷阱 ​

⚠️Function Calling 常见问题

在使用 Function Calling 时,以下几个常见问题需要特别注意:

  1. 始终验证函数参数。 即使开启了 Strict Mode,你仍然应该在执行函数前验证参数的业务合法性。例如,模型可能生成一个格式正确但值不合理的参数(如查询一个不存在的城市)。

  2. 处理模型不调用函数的情况。 模型可能判断不需要调用任何函数就能回答问题。你的代码必须同时处理"有函数调用"和"无函数调用"两种分支,不要假设模型一定会调用函数。

  3. 注意函数描述的质量。 模糊或错误的函数描述会导致模型在不该调用时调用,或在该调用时不调用。花时间写好 description 和参数的 description 字段,这是调优 Function Calling 效果的关键。

  4. 控制工具数量。 虽然 API 支持定义大量工具,但工具过多会增加模型的决策难度和 Token 消耗。建议将相关工具控制在 20 个以内,并按功能分组组织。

  5. 设置超时和重试。 函数执行可能失败或超时(尤其是调用外部 API 时)。确保你的代码有适当的错误处理、超时机制和重试逻辑,并在函数执行失败时向模型返回有意义的错误信息。

最佳实践 ​

编写高质量的工具定义是 Function Calling 成功的关键。以下几条实践建议可以帮助你获得更好的效果:

函数命名要清晰直观。 使用 get_weather、search_products、send_email 这样的动宾结构命名。避免使用 func1、handler 这样没有语义的名称。

参数描述要具体。 不要只写 "city": {"type": "string"},而应该加上 "description": "城市名称,如:北京、上海、广州"。具体的描述和示例能显著提高模型生成正确参数的概率。

善用 enum 约束。 当参数只有有限的合法值时,使用 enum 列出所有选项。这比在 description 中文字描述更可靠,模型会严格从枚举值中选择。

返回精简的结果。 函数执行结果不需要包含所有字段,只返回模型需要的信息即可。过多的无关信息会浪费 Token,也可能干扰模型的最终回复质量。