Function Calling(函数调用)是大模型连接真实世界的关键能力:它让 LLM 在对话中自动决定”何时调用哪个外部 API、传什么参数”,把自然语言变成结构化请求。本文用 OpenAI 兼容接口和一段可运行的 Python 代码,带你从零实现 Function Calling,覆盖工具 Schema 定义、调用闭环、并行调用与生产避坑,帮你的 AI 应用真正”动手干活”。
一、Function Calling 解决了什么问题
大模型本身只能”说话”,不能”做事”。要让它查实时天气、查订单、改数据库,必须借助外部工具。传统做法是用人写死的 if-else 去解析用户意图,不仅脆弱,而且每新增一个能力就要改一堆分支,极难维护。Function Calling 把这件事交给模型:你声明一组工具(函数名、描述、参数 JSON Schema),模型根据用户问题自行选择工具并产出合法参数,再由你的代码执行,并把结果回灌给模型,最终生成自然语言回答。这样模型负责”决策”,代码负责”执行”,边界清晰、可扩展,新增工具只需追加一段声明。
更重要的是,它把”理解意图”和”执行动作”解耦。模型不需要知道天气接口怎么鉴权、订单库怎么连,它只产出结构化的 city 或 order_id;真正的权限、凭证、安全边界都握在你手里。这就是为什么 Function Calling 被认为是 Agent(智能体)的能力基石。
二、定义工具:写好 JSON Schema 是第一步
工具的本质是一段 JSON 描述。每个工具包含 name、description 和 parameters(JSON Schema)。description 写得越清楚,模型选得越准;required 字段决定哪些参数不可或缺;枚举用 enum 可以把取值锁死,避免模型自由发挥。
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气,返回温度和天气状况",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称,例如 北京"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"}
},
"required": ["city"]
}
}
}
]
要点:required 字段必须齐全;枚举用 enum 约束取值范围;description 用自然语言说明”什么时候该用这个工具”,能显著降低误调用。经验上,把”不要什么时候用”也写进 description,比只写正向描述更稳。
三、完整调用闭环(Python 实战)
下面用 OpenAI 兼容 SDK 演示一次标准闭环:发请求 → 解析 tool_calls → 本地执行 → 回传 → 拿到最终答案。
from openai import OpenAI
import json
client = OpenAI(base_url="https://your-proxy/v1", api_key="sk-xxx")
messages = [{"role": "user", "content": "北京今天天气怎么样?"}]
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
tool_choice="auto",
)
msg = resp.choices[0].message
if msg.tool_calls:
messages.append(msg) # 先把模型的工具调用存下来
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = get_weather(**args) # 本地执行真实函数
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result, ensure_ascii=False),
})
final = client.chat.completions.create(
model="gpt-4o-mini", messages=messages
)
print(final.choices[0].message.content)
注意:必须把带有 tool_calls 的 assistant 消息原样 append 回 messages,否则模型会因缺少 tool_call_id 而报错;回传消息的 role 用 “tool” 且携带对应的 tool_call_id,这是闭环能否跑通的关键。
1. 让模型”只选工具不啰嗦”
设置 tool_choice=”required” 可强制模型先调用工具;设成具体函数名则锁定唯一工具。在调试阶段非常有用,能快速验证工具链路是否打通,也方便写单测。
2. 并行工具调用
新版模型支持一次返回多个 tool_calls(例如同时查天气和汇率)。代码里用 for 循环逐个执行即可,彼此顺序无关,必要时可并发执行以提升吞吐。注意并行时每个 tool_call 都要有独立的 tool_call_id 回传。
四、一个更真实的例子:订单查询工具
天气只是玩具例子。真实业务里,最常见的工具是”查订单””查库存””发起退款”。下面给一个订单查询工具的完整声明与执行示例,它演示了如何让模型在用户说”帮我看下订单 A2026 到哪了”时,自动调用你的内部接口。
def query_order(order_id: str) -> dict:
# 这里换成你真实的订单服务调用,例如走内网 HTTP 或查数据库
resp = requests.get(f"http://internal.order.svc/orders/{order_id}", timeout=3)
resp.raise_for_status()
data = resp.json()
return {
"order_id": data["id"],
"status": data["status"],
"eta": data["eta"],
"tracking": data["tracking_no"],
}
tools.append({
"type": "function",
"function": {
"name": "query_order",
"description": "根据用户提供的订单号查询物流状态与预计送达时间",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "订单号,例如 A20260901"}
},
"required": ["order_id"]
}
}
})
关键点在于:工具函数内部走的是你完全可控的内部服务,模型拿不到任何凭证,只拿到你决定回传的字段。这就把”模型能做什么”牢牢框在你的业务边界内,安全性和可控性都更好。也正因如此,即便模型”想”做危险动作,也没有通道。
五、进阶:重试、流式与可观测
当工具链路变长,可观测性变得重要。建议在每次工具调用前后打日志:记录模型选了哪个工具、传了什么参数、执行耗时与结果摘要,并用 traceId 串联整轮对话,排障效率会高很多。若工具执行失败,不要直接把原始异常抛给模型,而是返回一段结构化错误(例如 {“error”:”timeout”}),让模型自行决定重试还是改用其它工具。这种”失败可协商”的设计,是 Agent 能稳定跑通多步任务的前提。
另外,工具返回内容要尽量精简。模型上下文有限,把几 KB 的原始 JSON 全塞回去既浪费 token 又容易让模型迷失在细节里。先抽取模型真正需要的字段,再回传,成本和稳定性都会更好。
六、生产环境避坑清单
把 Function Calling 真正上线前,这几条必须过一遍:
| 风险点 | 现象 | 应对 |
| 参数校验 | 模型偶尔漏填 required 或类型错 | 本地用 jsonschema 二次校验,失败返回友好错误而非直接崩溃 |
| 执行超时 | 外部 API 慢导致整体卡死 | 给工具函数加 timeout,配合重试与降级策略 |
| 成本失控 | 模型狂调工具刷 token | 限制单轮最大调用次数,超阈值直接中断 |
| 安全风险 | 工具能删库、能发短信 | 危险操作加白名单与人工确认,绝不把写权限暴露给自动链路 |
七、在框架与私有化环境中落地
如果你用 Java 技术栈,可以直接用 Spring AI 接入大模型 的 ToolCallback 机制声明函数,无需手写 JSON Schema 拼接;想完全私有化、不把数据送出去,可用 Dify + Ollama 私有化部署 在本地跑带工具的大模型。当需要统一对接多家模型、做鉴权与限流时,LiteLLM 统一代理 能把不同厂商的 Function Calling 接口抹平成同一套。若你的工具涉及向量检索,先读 Embedding 模型选型 把召回质量打牢,再接工具链,效果会更稳。
八、小结
Function Calling 把大模型从”聊天机器人”升级成”能办事的 Agent”:你定义工具,模型决定何时调用,代码负责落地。写好 Schema、守住执行边界、做好超时与校验,就能在真实业务里稳定跑起来。下一步,把这些工具串成多步工作流,就是 Agent 的起点。




