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 的一个子集。一些高级特性(如 patternPropertiesif/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_weathersearch_productssend_email 这样的动宾结构命名。避免使用 func1handler 这样没有语义的名称。

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

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

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