本文结论

  • Function calling 的本质是让模型输出受 schema 约束的工具调用意图,再由程序决定是否执行。
  • 工具描述、参数 schema、tool choice 和工具数量,会直接影响模型选择工具和生成参数的稳定性。
  • 严格模式适合默认开启,但需要遵守 additionalProperties: false、字段 required 和可空类型等约束。
  • Streaming 不只适合展示文本进度,也可以实时观察函数参数是如何逐步生成的。

适合谁读

  • 正在给 LLM 应用接入外部工具、数据库或业务 API 的开发者。
  • 想理解 tool schema、tool choice、并行调用和严格模式如何影响 Agent 行为的人。
  • 准备把 Function calling 放进 Agent Harness 或 MCP 工具体系里的人。

什么是 Function calling

Function calling 是一种强大且灵活的方式,让 LLM 能够与外部系统进行交互,获取模型训练数据之外的大量信息。

如果 AI 应用程序包含复杂的功能或数据库结构,可以将函数调用与工具搜索功能结合,来按需加载工具,不过只有少部分新模型支持 tool_search

Function calling 定义与主要参数

function 的定义就比较简单,也是使用 JSON Schema,例子如下:

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

其中主要的参数和作用如下:

FieldDescription
typefunction
name函数的名称(例如:get_weather
description关于该功能的使用时间与方式的详细说明
parameters用于定义该函数输入参数的 JSON 模式
strict是否对函数调用启用严格模式

然后比较有意思的是 namespaces 的概念,类似于编程语言中的命名空间,有助于帮助 AI 整理和选择各种类似的工具。

{
  "type": "namespace",
  "name": "crm",
  "description": "CRM tools for customer lookup and order management.",
  "tools": [
    {
      "type": "function",
      "name": "get_customer_profile",
      "description": "Fetch a customer profile by customer ID.",
      "parameters": {
        "type": "object",
        "properties": {
          "customer_id": {
            "type": "string"
          }
        },
        "required": ["customer_id"],
        "additionalProperties": false
      }
    },
    {
      "type": "function",
      "name": "list_open_orders",
      "description": "List open orders for a customer ID.",
      "defer_loading": true,
      "parameters": {
        "type": "object",
        "properties": {
          "customer_id": {
            "type": "string"
          }
        },
        "required": ["customer_id"],
        "additionalProperties": false
      }
    }
  ]
}

例如上面这个用于 CRM 系统的搜索工具,还可以定义其他的比如 OA 系统的 xx 工具。一旦你希望你的 AI 应用于庞大的工具生态,可以使用前面提到的 tool_search 来延迟加载部分或全部工具。

最佳实践

  1. 编写详细、清晰的函数名称、参数说明以及使用说明。
    • 明确说明函数用途,参数的用途以及格式,输出的结果代表什么含义。
    • 利用 system prompt 来说明什么时候应该/不该使用某功能。
    • 举例说明常见的情况和边缘情况,尤其是容易反复出现错误的情况。
    • 对于延迟加载的工具,应在函数描述中提供详细的操作指南同时保持命名空间简洁,这分别有助于 LLM 正确使用已加载工具和正确决定应该加载哪个工具。
  2. 遵循软件工程的最佳实践。
    • 让工具直观易用。
    • 用枚举和对象结构来确保无效状态无法被表达出来。
    • 将工具提供给小白,看他们能否正确使用功能,如果不能,将他们提出的问题的答案写在 prompt 中。
  3. 尽量减轻 LLM 负担,使用代码处理相关任务。
    • 例如一个多轮工具调用的 workflow,需要使用同一个 order_id,那就不要在后续的调用中设置这个参数,而是通过代码来传递参数。
    • 将总是按顺序被调用的函数组合在一起,减少 LLM 决策压力。
  4. 为了 LLM 调用准确性,减少初始可用的工具数量。
    • 尝试使用不同数量的工具来评估 AI 应用的表现。
    • 建议每轮开始时,限制可用工具数量在 20 以内。
    • 使用 tool_search 隐藏工具中不常被使用的部分。

处理 Function calling

类似于后端的工作,把收到的请求路由到不同的程序来处理,然后将结果包装成符合格式的形式,然后将分析结果添加到 input 中,反馈给 LLM。

其他配置选项

默认情况下,模型会自行决定何时使用何种工具,以及使用多少个工具。你可以通过 tool_choice 参数来强制指定特定的行为。

  1. auto:自动模式(默认)调用 0 个、1 个或多个函数。
  2. required:要求至少调用一个函数。
  3. forced function:强调必须调用某个特定函数,且只调用一次。
  4. allowed tools:允许使用的工具,限制模型可用的工具范围。

并行函数调用

使用内置工具时无法实现并行函数调用。

模型可能在一轮调用中选择调用多个工具,可以将 parallel_tool_calls 设置为 false 来避免这种情况,这样就能保证恰好调用 0 或 1 个函数。

严格模式

strict 设置为 true 后,可以确保函数调用始终符合函数规范,而不是尽力符合规范,通常建议始终开启严格模式,同时该模式也带来了一些要求:

  • additionalProperties 必须为 parameters 中的每个对象设置为 false
  • 所有的 properties 字段必须标记为 required
  • 在可选字段前加 null 来标记该字段为可选项。

如果在请求中发送了 strict: true 但是架构不符合上述要求,请求会被拒绝。

Streaming 传输

通过流式处理,可以了解 LLM 的处理进度:即能够显示模型处理过程中调用了哪些函数,甚至实时展示函数的参数,只需要将 stream 设置为 true

from openai import OpenAI

client = OpenAI()

tools = [{
    "type": "function",
    "name": "get_weather",
    "description": "Get current temperature for a given location.",
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "City and country e.g. Bogotá, Colombia"
            }
        },
        "required": [
            "location"
        ],
        "additionalProperties": False
    }
}]

stream = client.responses.create(
    model="gpt-5.5",
    input=[{"role": "user", "content": "What's the weather like in Paris today?"}],
    tools=tools,
    stream=True
)

for event in stream:
    print(event)
{"type":"response.output_item.added","response_id":"resp_1234xyz","output_index":0,"item":{"type":"function_call","id":"fc_1234xyz","call_id":"call_1234xyz","name":"get_weather","arguments":""}}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"{\""}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"location"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"\":\""}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"Paris"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":","}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":" France"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"\"}"}
{"type":"response.function_call_arguments.done","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"arguments":"{\"location\":\"Paris, France\"}"}
{"type":"response.output_item.done","response_id":"resp_1234xyz","output_index":0,"item":{"type":"function_call","id":"fc_1234xyz","call_id":"call_1234xyz","name":"get_weather","arguments":"{\"location\":\"Paris, France\"}"}}

以下是代码示例,用来将多个 delta 对象合并为最终的 tool_call 对象:

final_tool_calls = {}

for event in stream:
    if event.type == "response.output_item.added":
        final_tool_calls[event.output_index] = event.item
    elif event.type == "response.function_call_arguments.delta":
        index = event.output_index

        if final_tool_calls[index]:
            final_tool_calls[index].arguments += event.delta

最终的 final_tool_calls[0]

{
  "type": "function_call",
  "id": "fc_1234xyz",
  "call_id": "call_2345abc",
  "name": "get_weather",
  "arguments": "{\"location\":\"Paris, France\"}"
}

常见问题

Function calling 是让模型直接执行函数吗?

不是。模型通常只生成函数名和参数,真正执行函数的是你的程序。程序仍然需要做参数校验、权限判断、错误包装和结果回传。

strict mode 为什么还需要服务端校验?

严格模式能提高模型输出符合 schema 的概率,但它不是业务权限边界。路径、权限、枚举、资源归属、危险动作确认这些检查仍然应该在服务端或 Harness 层完成。

tool choice 应该什么时候使用 required?

当任务流程明确需要至少调用一个工具时可以使用 required。如果问题本身可能直接回答,长期强制调用工具会增加成本,也可能让模型做不必要的动作。

延伸阅读