Function Calling 原理与工程落地
1. Function Calling 到底是什么 Function Calling 不是让大模型自己去联网、查库、发请求。大模型本身并不会直接执行代码,它做的是“决策”:判断当前问题是否需要调用工具,如果需要,就按照开发者提前定义好的 J
1. Function Calling 到底是什么
Function Calling 不是让大模型自己去联网、查库、发请求。大模型本身并不会直接执行代码,它做的是“决策”:判断当前问题是否需要调用工具,如果需要,就按照开发者提前定义好的 JSON Schema,输出一段结构化的调用请求。
真正执行动作的是你的后端代码。它拿到模型输出的函数名和参数后,再去查数据库、请求 API、调用内部服务,最后把工具返回结果塞回对话,让模型基于真实结果生成最终回答。
用一句话概括:Function Calling 是把用户的自然语言意图,翻译成后端系统可以安全执行的结构化函数调用。
2. 它解决的不是“聊天”,而是“行动”
普通大模型回答问题,本质是在生成文本。可是业务系统里很多需求不是“回答一句话”就够了,而是要完成一个动作:查订单、订会议室、改地址、拉报表、发短信、创建工单。
如果没有 Function Calling,开发者只能让模型输出“我需要查询订单”,再靠关键词、正则或 if/else 去猜模型想干什么。这种方式最大的问题是不可控:模型说法稍微换一下,后端解析就可能失败;工具一多,判断逻辑会越来越乱。
Function Calling 的核心变化是:模型不再用自然语言“描述动作”,而是输出机器可解析的 tool_calls。它把“我要查北京天气”变成 get_weather({city: 北京})。这一步看似简单,却把大模型从聊天窗口拉进了真实业务系统。
3. 三个角色:开发者、模型、宿主代码
理解 Function Calling,先把三个角色分清楚。
开发者负责写工具说明书,也就是工具 Schema。它告诉模型:有哪些工具,每个工具能做什么,什么时候该用,需要哪些参数,参数有什么限制。
模型负责读说明书,然后判断当前用户问题是否需要工具。如果需要,它输出结构化调用请求,包含函数名和参数。
宿主代码负责真正执行。它要校验参数、鉴权、路由函数、调用外部 API、处理超时和异常,再把结果返回给模型。
开发者:定义工具边界,控制哪些能力能被模型看到。
模型:选择工具并生成参数,但不直接执行任何动作。
宿主代码:执行真实函数,并承担安全、权限、稳定性责任。
4. 工具 Schema:description 不是注释,是决策依据
工具 Schema 可以理解成给模型看的接口文档。它不只是为了让后端解析,更重要的是帮助模型判断“该不该调用这个工具”和“参数应该怎么填”。
其中最容易被低估的是 description。很多人只写“查询天气”“查询订单”,这会导致模型误调。更好的写法是把工具边界、适用场景、不适用场景、参数格式、限制条件都写清楚。
如果工具用于生产系统,建议尽量启用严格模式,并把参数约束写进 Schema,而不是只靠 prompt 口头提醒。OpenAI 官方文档也建议使用 strict mode,让函数调用更可靠地遵循 Schema;严格模式下还要求对象设置 additionalProperties: false,并把 properties 中字段列入 required。
5. 一个完整的工具定义示例
下面这个例子用的是 Chat Completions 风格。重点不在模型名称,而在工具定义结构:name 要稳定,description 要讲清边界,parameters 要明确类型、枚举、必填字段和 additionalProperties。
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": (
"查询中国大陆城市的实时天气。"
"仅用于用户明确询问当前天气、温度、风向、湿度。"
"不支持未来多日预报。city 必须是中文城市名。"
),
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如 北京、上海,不要带省份"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,默认使用 celsius"
}
},
"required": ["city", "unit"],
"additionalProperties": False
},
"strict": True
}
}
]这个 Schema 的价值在于,它把“模型可能会猜”的地方尽量变成了“系统明确规定”。模型如果要调用 get_weather,就必须给出 city 和 unit,不能随便生成未知字段。
6. 运行时流程:两轮对话加一次中间执行
Function Calling 的运行时不是一次请求结束,而是一个闭环。
第一轮,你把用户问题和工具列表发给模型。模型判断需要工具时,不直接输出最终答案,而是返回 tool_calls。这个返回值的意思是:我还不能回答,我需要你先帮我执行这个函数。
中间执行阶段,你的代码读取 tool_calls,解析函数名和参数,做参数校验、鉴权、路由,然后调用真实业务函数。
第二轮,你把工具执行结果放回对话历史,再请求模型。模型拿到真实结果后,才生成最终自然语言答案。
from openai import OpenAI
import json
client = OpenAI()
messages = [{"role": "user", "content": "北京今天天气怎么样?"}]
# 第一轮:模型判断是否需要调用工具
completion = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
tools=tools,
)
assistant_msg = completion.choices[0].message
messages.append(assistant_msg)
# 中间执行:你的代码执行真正的函数
for tool_call in assistant_msg.tool_calls or []:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
if name == "get_weather":
result = get_weather(city=args["city"], unit=args["unit"])
else:
result = {"error": "unknown tool"}
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
# 第二轮:模型基于工具结果生成最终答案
final = client.chat.completions.create(
model="gpt-5.5",
messages=messages,
tools=tools,
)
print(final.choices[0].message.content)7. 并行调用:能并行,但不能乱并行
当用户的问题包含多个互不依赖的子任务时,模型可以在一次响应里返回多个 tool_calls。例如“查北京、上海、广州三个城市天气”,每个城市之间没有依赖关系,代码可以并发执行。
但如果任务存在依赖,就不能并行。比如“先查我的订单,再根据订单号查物流”,第二步依赖第一步结果,必须串行执行。这个判断在 Agent 编排里非常重要,因为错误并行会造成参数缺失、重复请求甚至业务状态错乱。
import asyncio
async def run_tool_call(tool_call):
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
if name == "get_weather":
return await async_get_weather(**args)
raise ValueError(f"unsupported tool: {name}")
# 只有工具之间互不依赖时,才适合并行执行
results = await asyncio.gather(*[
run_tool_call(call)
for call in assistant_msg.tool_calls
])8. 工程落地:必须加防线
Function Calling 让模型可以连接真实业务系统,也意味着风险会被放大。一个工具如果能查数据、发消息、改订单、删文件,就不能直接让模型“想调就调”。
生产环境里,工具调用必须像普通后端接口一样治理:工具白名单、参数校验、用户鉴权、租户隔离、限流、超时、重试、熔断、幂等、审计日志,一个都不能少。
尤其是高风险动作,例如付款、删除、发短信、发邮件、修改用户资料,建议加入二次确认或审批流。模型可以提出动作建议,但最终执行权要掌握在业务系统手里。
ALLOWED_TOOLS = {"get_weather", "query_order"}
def call_function(name: str, args: dict, user_ctx: dict):
if name not in ALLOWED_TOOLS:
return {"error": "tool_not_allowed"}
# 业务参数二次校验,不只依赖模型输出
validate_args_by_schema(name, args)
# 权限校验:用户只能查自己的订单
if name == "query_order":
assert args["user_id"] == user_ctx["user_id"]
return query_order(args["order_id"], user_ctx["user_id"])
if name == "get_weather":
return get_weather(args["city"], args.get("unit", "celsius"))9. Function Calling、RAG、MCP、Agent 的关系
很多人会把 Function Calling、RAG、MCP、Agent 混在一起。其实它们处在不同层次。
Function Calling 解决的是“怎么把自然语言变成结构化函数调用”。RAG 解决的是“怎么从知识库找证据”。MCP 更像工具和资源连接协议,解决“工具怎么被统一暴露、发现和调用”。Agent 则是在目标驱动下做多步规划、调用工具、观察结果、继续决策。
在一个真实系统里,这几者经常组合使用:Agent 负责规划,RAG 负责查知识,Function Calling 负责调用业务 API,MCP 负责统一连接外部工具服务。
10. 如何评估 Function Calling 做得好不好
工具调用不能只看最终回答像不像人话,更要看链路质量。一个系统可能最终话术很好听,但工具选错了、参数填错了、权限绕过了,这都属于严重问题。
评估时至少要拆成四层:工具选择是否准确,参数是否正确,执行是否稳定,最终回答是否忠实使用了工具结果。Berkeley Function Calling Leaderboard 这类评测也把函数调用准确性作为独立能力来衡量,说明工具调用本身已经是大模型工程里的关键能力。
工具选择准确率:该调用时是否调用,不该调用时是否克制。
参数正确率:字段、类型、枚举、格式、业务约束是否正确。
执行成功率:API 成功率、超时率、重试次数、错误恢复能力。
最终答案忠实度:回答是否基于工具结果,而不是继续编造。
安全指标:越权调用、危险动作、敏感信息泄露、提示注入是否被拦截。
11. 上线前排雷:Demo 能跑不代表生产可用
Function Calling 的 demo 很容易做出来,难的是把它放进真实业务。真实用户会省略参数、表达模糊、连续追问、夹杂无关信息,工具也会超时、报错、返回空数据。系统如果没有完整防线,就会从“智能助手”变成“不稳定的自动化脚本”。
上线前建议准备一批对抗样例:闲聊场景、模糊参数、错误城市、越权订单、注入文本、工具超时、重复提交、并行依赖、超长工具结果。每一类都要有明确的预期行为。
12. 面试时可以这样总结
Function Calling 的核心不是“让模型调用 API”,而是建立一套清晰的分工机制:开发者用 JSON Schema 描述工具,模型根据用户问题决定是否调用并生成结构化参数,宿主代码执行真实函数,再把结果交回模型生成最终答案。
这套机制解决了过去靠自然语言解析工具意图的不稳定问题,也让工具调用可以被审计、限权、重试和评估。真正落地时,Schema 设计、description 质量、参数校验、权限控制、异常处理、并行/串行编排、链路观测,都会直接决定系统是否可靠。
记住最关键的一句话:模型负责决策,代码负责执行,业务系统负责安全边界。
要点速读
1. Function Calling 到底是什么 Function Calling 不是让大模型自己去联网、查库、发请
- 1. Function Calling 到底是什么 Function Calling 不是让大模型自己去联网、查库、发请
- 更多细节仍在持续更新中
- 更多细节仍在持续更新中