大模型Function Calling是什么?模型究竟是怎么“调用工具”的?
时间:2026-10-23 来源:华清远见
学习大模型的 Function Calling(函数调用)既简单又有趣。通过 Function Calling,可以让一个”只会输出文字”的模型去查天气、搜网页、查数据库、发邮件——完成那些靠语言模型本身根本做不到的事情。所以,想要构建真正能用的 AI 应用,学习 Function Calling 是很有必要的。
正如您所知道的,大模型本质上是一个文本生成器:它只能读取文本、输出文本。它不知道今天是几号,不能联网,也不会执行任何代码。请看下面的对比实例,同样是问天气,有没有 Function Calling 的区别:
实例:没有提供工具时
用户提问:
杭州今天多少度?要带伞吗?
模型的输出:
抱歉,我无法获取实时天气数据。建议您通过天气类应用或网站查询最新信息。
实例:提供了工具时
模型的输出(不再说”抱歉”,而是一段 JSON):
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_current_weather",
"arguments": "{\"location\": \"杭州\", \"unit\": \"celsius\"}"
}
}
]
}
通过上面的对比,我们了解了 Function Calling 能带来什么。接下来让我们看看它到底是什么。
什么是 Function Calling?
Function Calling(函数调用),也叫 Tool Use(工具调用),是一种让大模型在对话过程中“请求执行外部函数”的机制。请注意”请求”两个字:模型只负责提出请求,真正执行函数的,永远是您编写的程序。
就像其他 API 参数一样,您必须在使用工具之前,先把工具声明随请求发送给模型。工具声明的一般形式为:
{
"type": "function",
"function": {
"name": "函数名",
"description": "这个工具是干什么的、什么时候该用",
"parameters": { "": "JSON Schema 参数定义" }
}
}
在这里,type 是工具类型,目前主流 API 中固定为 "function";name 是函数名,必须是一个合法的标识符;description 是写给模型看的”使用说明书”,模型完全靠它来决定要不要用这个工具;parameters 是一个 JSON Schema,用来约束参数的名称、类型与必填项。以下是有效的工具声明:
get_current_weather /* 查询某个城市的实时天气 */
web_search /* 联网搜索最新资料 */
run_sql_query /* 在数据库上执行只读查询 */
send_email /* 给指定收件人发送邮件 */
所有工具对模型来说都是一样的,都只是一段 JSON 文本。不同工具之间唯一的不同,是 description 与参数 Schema 不同——模型从不关心、也永远看不到函数内部是怎么实现的。
模型究竟是怎么”调用工具”的?
这是最容易被误解的地方,先把结论说清楚:
模型从不执行任何代码。 所谓”调用工具”,是模型输出了一段结构化的 JSON 文本,表示”我想调用某个函数、参数是什么”;随后由您的程序解析并执行,再把结果交还给模型。
整个协作过程如下:

分步来看:
第 1 步:登记工具。 您在请求中附带 tools 数组。注意:发出去的只有”说明书”(名称、描述、参数结构),不包含任何实现代码。
第 2 步:模型决策。 模型读用户的问题,逐个对照工具的 description,判断”这个问题我自己能答,还是需要某个工具”。这本质上是它的老本行——根据上下文预测下一段文本,只不过您用 tools 参数告诉了它:“现在允许你输出一种特殊格式”。
第 3 步:模型输出”调用请求”。 它不直接回答用户,而是返回一条 finish_reason 为 "tool_calls" 的消息,内容是函数名 + JSON 参数。
第 4 步:您的程序执行。 解析 arguments、做参数校验、调用真正的函数——天气 API、数据库、搜索引擎,全部运行在您自己的环境中。
第 5 步:结果回填。 把函数返回值包装成 role 为 "tool" 的消息,带上 tool_call_id,追加到对话历史,再次请求模型。
第 6 步:模型生成最终回答。 模型看到工具结果之后,才用自然语言回答用户。
请看下面的实例,这是第一次请求返回的原始内容:
{
"id": "chatcmpl-xxx",
"choices": [
{
"finish_reason": "tool_calls",
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_current_weather",
"arguments": "{\"location\": \"杭州\", \"unit\": \"celsius\"}"
}
}
]
}
}
]
}
请注意两个细节:
finish_reason 是 "tool_calls" 而不是 "stop"。API 客户端靠它判断:“模型想调工具了,轮到您执行。”
arguments 是一个字符串,不是 JSON 对象。它是被 JSON 编码过的参数,需要您自己反序列化。
完整实例
下面用 Python(OpenAI SDK 风格)完整演示一次 Function Calling:
from openai import OpenAI
import json
client = OpenAI() # 需要事先配置 API Key
# 第 1 步:登记工具(只有说明书,没有代码)
tools = [{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "查询指定城市的当前天气,当用户询问天气时使用",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名,如:杭州"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,默认 celsius"
}
},
"required": ["location"]
}
}
}]
# 真正干活的函数:在您自己的环境里执行
def get_current_weather(location: str, unit: str = "celsius") -> str:
# 演示用假数据;实际项目中这里调用真实天气 API
data = {"location": location, "temp": 31, "condition": "晴", "unit": unit}
return json.dumps(data, ensure_ascii=False)
messages = [
{"role": "user", "content": "杭州今天多少度?要带伞吗?"}
]
# 第 2 步:第一次请求 —— 模型只返回"想调用什么"
resp = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
assistant_msg = resp.choices[0].message
messages.append(assistant_msg) # 别忘了把这条也记入历史
# 第 3 步:模型提出调用请求?那就由您来执行
if assistant_msg.tool_calls:
for tc in assistant_msg.tool_calls:
args = json.loads(tc.function.arguments) # 注意:参数是字符串
result = get_current_weather(**args) # 真正执行!
messages.append({ # 第 4 步:结果回填
"role": "tool",
"tool_call_id": tc.id, # 与请求一一对应
"content": result,
})
# 第 5 步:带着结果再次请求
final = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
print(final.choices[0].message.content)
当上面的代码被执行时,它会产生下列结果:
杭州现在 31°C,天气晴,不需要带伞。
模型没有调用工具,怎么办?
在变量声明的时候,如果没有确切的地址可以赋值,为指针赋一个 NULL 值是一个良好的编程习惯——类似地,您必须同时处理”模型不想调工具”的情况。当模型认为仅凭自身知识就能回答时(比如用户问”1+1 等于几”),它会直接返回普通文本,此时 finish_reason 为 "stop",tool_calls 为空。
如需检查模型是否想调用工具,您可以使用 if 语句,如下所示:
if msg.tool_calls: # 模型想调用工具 → 解析、执行、回填
if not msg.tool_calls: # 模型直接回答 → msg.content 就是最终答案
您还可以用 tool_choice 参数主动控制模型的”动手欲望”:

并行调用与工具循环
并行调用:如果一个问题需要多个互不依赖的工具,模型会在一轮里同时返回多个 tool_calls。例如用户问”北京和上海今天哪个更热?“:
"tool_calls": [
{
"id": "call_001",
"function": {
"name": "get_current_weather",
"arguments": "{\"location\": \"北京\"}"
}
},
{
"id": "call_002",
"function": {
"name": "get_current_weather",
"arguments": "{\"location\": \"上海\"}"
}
}
]
您的程序应依次(或并发)执行这两个函数,回填两条 role 为 "tool" 的消息,tool_call_id 分别对应 call_001、call_002。
工具循环(Agent Loop):复杂任务往往需要连续调用多次工具,标准写法是一个循环:
while True:
resp = client.chat.completions.create(
model="gpt-4o", messages=messages, tools=tools)
msg = resp.choices[0].message
messages.append(msg)
if not msg.tool_calls: # 模型不再要求调用工具
print(msg.content) # 这就是最终回答
break
for tc in msg.tool_calls: # 执行每一个调用并回填结果
result = execute(tc)
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result,
})
模型可以在这个循环里”调用 → 看结果 → 再调用 → 再看结果”,直到它认为信息足够为止——这正是所谓 AI Agent(智能体)的核心骨架。
Function Calling 详解
在使用 Function Calling 时,有很多相关的概念,这些概念都很简单,但都很重要。下面列出了必须清楚的一些重要概念:

各平台的术语对照(机制完全相同,只是字段名不同):

无论哪一家,本质都是同一个三段式:模型输出结构化请求 → 程序执行 → 结果回填。
注意事项与常见误区
1.模型永远不执行代码。 Function Calling 输出的只是 JSON 文本,执行权 100% 在您的程序手里。任何”模型自己调了 API”的说法,省略的都是中间那段您写的代码。
2.arguments 是字符串,不是对象。 需要自行 json.loads;解析失败或参数不合法时应当报错处理,而不是硬着头皮执行。
3.参数可能有”幻觉”。 模型可能编造不存在的城市、漏掉必填项、写错枚举值。执行前务必校验参数;必要时把错误信息回填给模型,让它重试。
4.description 决定调用准确率。 模型是靠读说明书选工具的。“查询指定城市当前天气,当用户询问天气时使用”远好于”调用这个函数”。工具越多,各工具的描述越要写得互不重叠。
5.注意安全边界。 工具在您的环境中执行,等于给了模型一只”手”。数据库、Shell、支付、删除类工具必须做权限最小化、参数白名单,敏感操作加人工确认。
6.注意 token 成本。 tools 清单会随每一次请求一起发送并计入上下文,工具定义越多、越长,费用越高。
7.结果要对号入座。 回填结果时 tool_call_id 必须与调用一一对应,并行调用时尤其容易出错。

