Function Calling 让大模型从"只会生成文字"升级为"能够连接真实数据和业务动作"的系统组件。它不是让模型直接运行代码,而是让模型以结构化方式提出调用请求,再由应用程序完成校验、执行和结果回传。
一、什么是 OpenAI Function Calling?
普通大模型只能根据已有上下文生成文本。用户问"今天上海天气如何",模型无法凭空知道实时天气;用户说"帮我查询订单 10086",模型也不能直接访问企业数据库。Function Calling(也称 Tool Calling)解决的正是模型与外部世界之间的连接问题。
开发者先向模型声明一组可用工具,包括工具名称、用途和参数结构。模型分析用户意图后,可以返回一个结构化的函数调用请求,例如调用 get_order_status,并给出参数 {"order_id":"10086"}。应用程序收到请求后,自行检查权限、执行函数,再把结果交回模型,由模型生成适合用户阅读的最终答案。
根据 OpenAI 官方 Function Calling 文档,工具可以连接实时数据、内部系统或具体业务动作,例如天气查询、账户信息读取和退款处理。这里必须强调:模型只提出调用请求,不会替你执行函数。代码执行、身份认证、参数校验、事务控制和审计仍然由你的应用负责。
二、为什么不能只靠提示词?
有人会要求模型"请严格输出 JSON",然后直接解析生成结果。这种方法适合简单演示,但很难支撑生产系统。模型可能添加解释文字、遗漏字段、拼错枚举值,甚至虚构一个根本不存在的操作。Function Calling 则把工具定义和 JSON Schema 一起传入模型,让工具选择与参数生成成为标准化的 API 输出。
它主要带来四项价值:
- **连接实时信息:**访问天气、库存、价格、物流和数据库等训练数据之外的信息。
- **调用业务能力:**创建工单、发送通知、预订会议或触发审批流程。
- **获得结构化参数:**使用 JSON Schema 约束参数名称、类型、必填项和可选值。
- **分离决策与执行:**模型负责理解自然语言,应用负责安全、确定地执行代码。
因此,Function Calling 并不是一个"远程函数执行器",而是一套模型与应用之间的结构化协作协议。
三、一次完整调用经历了什么?
Function Calling 的六步工作流:用户请求、模型判断、生成参数、应用执行、返回结果、生成答案。
- 用户用自然语言提出问题或任务。
- 应用把用户输入和可用工具定义发送给模型。
- 模型判断是否需要工具;需要时返回函数名、调用 ID 和 JSON 参数。
- 应用解析参数,完成权限检查和业务校验,然后执行对应函数。
- 应用使用原始调用 ID,把工具结果作为
function_call_output返回模型。 - 模型结合用户问题与工具结果,生成最终回答;必要时还可能继续调用其他工具。
这是一段循环,而不是固定的"一问一答"。复杂任务可能先查询用户、再查询订单、最后创建售后单。编排层必须持续处理模型输出,直到模型不再请求工具,或者达到预设的最大调用次数。
四、使用 Responses API 实现第一个函数调用
下面用 Python 演示一个订单查询工具。示例以 Responses API 为主线,省略了 API Key 配置、数据库连接和 Web 框架代码。
python
from openai import OpenAI
client = OpenAI()
tools = [
{
"type": "function",
"name": "get_order_status",
"description": "根据订单编号查询当前登录用户有权查看的订单状态。",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单编号,例如 10086"
}
},
"required": ["order_id"],
"additionalProperties": False
},
"strict": True
}
]
response = client.responses.create(
model="gpt-5.4-mini",
input="帮我查询订单 10086 的状态",
tools=tools
)
tool_outputs = []
for item in response.output:
if item.type != "function_call":
continue
arguments = json.loads(item.arguments)
if item.name == "get_order_status":
result = {
"order_id": arguments["order_id"],
"status": "已发货",
"tracking_number": "SF1234567890"
}
else:
result = {"error": "unknown_tool"}
tool_outputs.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": json.dumps(result, ensure_ascii=False)
})
final_response = client.responses.create(
model="gpt-5.4-mini",
previous_response_id=response.id,
input=tool_outputs,
tools=tools
)
print(final_response.output_text)</code></pre>
代码中最重要的不是模拟的订单结果,而是两个关联点:第一次响应返回的 call_id 必须原样带回;第二次请求通过 previous_response_id 延续上下文。工具输出最好使用紧凑、稳定、可序列化的 JSON,而不是把数据库对象或整段日志直接交给模型。
五、工具 Schema 决定调用质量
模型是否能选对工具、填对参数,很大程度上取决于工具定义,而不只是系统提示词。一个名为 process、描述为"处理数据"的工具几乎没有决策价值;get_order_status 配合明确的使用条件,则容易被模型正确理解。
设计工具时可以遵循以下原则:
- 工具名称使用动词加对象,例如
search_products、create_ticket。 - 描述说明"做什么、何时使用、有什么副作用",避免模糊宣传语。
- 参数保持少而清晰,不要要求模型填写可由登录态或服务端推导的数据。
- 固定选项使用
enum,日期、金额和标识符明确格式与单位。 - 开启
strict,并使用严格 JSON Schema 降低参数漂移。
在严格模式下,应把对象允许的字段写进 properties,将必须出现的字段列入 required,并设置 additionalProperties: false。如果某个字段允许为空,可在 Schema 中把它定义为对应类型与 null 的联合类型,而不是放任模型省略任意字段。
六、控制模型何时调用工具
默认情况下,模型会根据上下文自行决定是否调用工具。实际业务还可以通过 tool_choice 调整策略:
- **自动选择:**模型可以直接回答,也可以调用一个或多个工具,适合普通助手。
- **禁止调用:**只允许模型生成文本,适合降级模式或纯内容任务。
- **必须调用:**要求模型至少选择一个工具,适合所有答案都必须来自业务系统的场景。
- **指定函数:**强制调用某个函数,适合已由上游流程确定动作的场景。
模型还可能在一次响应中返回多个调用。例如用户问"比较北京和上海的天气",它可能并行调用两次天气工具。应用必须遍历全部 function_call,不能假设响应里永远只有一个。若工具存在顺序依赖或写入副作用,可关闭并行工具调用,或者由业务编排层明确控制执行顺序。
七、错误处理不能交给模型猜
生产环境中的工具会超时、限流,也会遭遇权限不足、资源不存在和业务规则冲突。工具层应返回结构化错误,而不是把异常堆栈直接暴露给模型:
bash
{
"ok": false,
"error": {
"code": "ORDER_NOT_FOUND",
"message": "未找到订单,或当前用户无权查看",
"retryable": false
}
}
编排层应区分可重试与不可重试错误。网络抖动和部分服务端错误可以指数退避后重试;参数错误和权限错误通常不应原样重试;支付、发券和发送消息等操作只有在提供幂等键后才能安全重试。为了防止模型陷入循环,还应设置单次任务最大工具调用数、整体超时和熔断策略。
八、安全边界:模型输出永远不是授权
Function Calling 最大的误区,是把模型生成的参数视为可信输入。事实上,用户输入、网页内容和工具结果都可能诱导模型执行越权操作。模型说"请退款"并不等于用户具备退款权限,模型生成 tenant_id=1 也不代表它可以访问该租户。
生产系统至少需要以下防线:
- 从服务端会话获取用户和租户身份,不接受模型伪造身份字段。
- 在真正的 Service 或 API 层再次执行权限和数据范围检查。
- 退款、删除、转账等高风险动作要求人工确认,并明确展示对象与影响。
- 对字符串长度、金额范围、日期和枚举执行确定性校验。
- 工具采用最小权限,只返回完成任务所需的字段,并对敏感数据脱敏。
- 记录调用 ID、工具名、脱敏参数、结果、耗时和操作者,形成审计链路。
一个好的工具不是"执行任意 SQL"或"调用任意 URL",而是边界清晰的业务能力,例如"查询我的订单"或"创建待审批退款申请"。能力越具体,测试、安全和治理就越容易。
九、Function Calling 与 Structured Outputs 有什么区别?
两者都能利用 JSON Schema,但目标不同。Function Calling 用于连接外部能力:模型返回函数名和参数,应用执行工具。Structured Outputs 用于约束模型最终生成的内容,例如要求它输出一份包含标题、风险等级和改进建议的 JSON 报告。
| 能力 | 主要用途 | 是否执行外部动作 |
|---|---|---|
| Function Calling | 选择并调用应用提供的工具 | 由应用程序执行 |
| Structured Outputs | 约束模型最终输出的数据结构 | 通常不执行 |
在真实 Agent 中,两者经常组合使用:先通过 Function Calling 查询业务数据或执行动作,再使用 Structured Outputs 生成稳定的结果对象,供前端渲染或下游流程消费。
十、上线前检查清单
- 每个工具是否拥有准确、可区分的名称和描述?
- Schema 是否限制了类型、枚举、长度、必填字段和额外字段?
- 是否处理了多个并行工具调用,而非只读取第一项?
- 登录身份、租户和权限是否全部由服务端可信上下文提供?
- 写操作是否支持幂等、确认、超时和审计?
- 工具错误是否结构化,并区分是否可以重试?
- 是否设置了最大调用次数,防止无限循环和成本失控?
- 是否准备真实测试集,统计工具选择和参数生成的成功率?
结语
OpenAI Function Calling 的核心并不复杂:向模型声明工具,接收结构化调用请求,安全执行函数,再把结果返回模型。但从"能够调用"走到"可靠调用",还需要良好的 Schema、清晰的工具边界、完整的错误模型、严格的权限校验和可追踪的编排循环。
如果把大模型比作负责理解与决策的大脑,那么 Function Calling 就是神经系统,而业务 API 才是真正执行动作的双手。模型可以提出建议和参数,但最终权限必须掌握在确定性的应用代码中。守住这条边界,Function Calling 才能成为构建客服助手、数据分析 Agent、运维助手和企业自动化系统的可靠基础。