本文结论
- 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
}
其中主要的参数和作用如下:
| Field | Description |
|---|---|
type | function |
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 来延迟加载部分或全部工具。
最佳实践
- 编写详细、清晰的函数名称、参数说明以及使用说明。
- 明确说明函数用途,参数的用途以及格式,输出的结果代表什么含义。
- 利用 system prompt 来说明什么时候应该/不该使用某功能。
- 举例说明常见的情况和边缘情况,尤其是容易反复出现错误的情况。
- 对于延迟加载的工具,应在函数描述中提供详细的操作指南同时保持命名空间简洁,这分别有助于 LLM 正确使用已加载工具和正确决定应该加载哪个工具。
- 遵循软件工程的最佳实践。
- 让工具直观易用。
- 用枚举和对象结构来确保无效状态无法被表达出来。
- 将工具提供给小白,看他们能否正确使用功能,如果不能,将他们提出的问题的答案写在 prompt 中。
- 尽量减轻 LLM 负担,使用代码处理相关任务。
- 例如一个多轮工具调用的 workflow,需要使用同一个
order_id,那就不要在后续的调用中设置这个参数,而是通过代码来传递参数。 - 将总是按顺序被调用的函数组合在一起,减少 LLM 决策压力。
- 例如一个多轮工具调用的 workflow,需要使用同一个
- 为了 LLM 调用准确性,减少初始可用的工具数量。
- 尝试使用不同数量的工具来评估 AI 应用的表现。
- 建议每轮开始时,限制可用工具数量在 20 以内。
- 使用
tool_search隐藏工具中不常被使用的部分。
处理 Function calling
类似于后端的工作,把收到的请求路由到不同的程序来处理,然后将结果包装成符合格式的形式,然后将分析结果添加到 input 中,反馈给 LLM。
其他配置选项
默认情况下,模型会自行决定何时使用何种工具,以及使用多少个工具。你可以通过 tool_choice 参数来强制指定特定的行为。
auto:自动模式(默认)调用 0 个、1 个或多个函数。required:要求至少调用一个函数。forced function:强调必须调用某个特定函数,且只调用一次。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。如果问题本身可能直接回答,长期强制调用工具会增加成本,也可能让模型做不必要的动作。