Function Calling 全解析:大模型如何调用外部工具
大语言模型(LLM)的核心能力是生成文本。无论是 GPT-4、Claude 还是开源的 Llama,它们本质上都是在做同一件事:根据输入预测下一个 token。但现实世界的任务远不止文本生成——查询天气、操作数据库、发送邮件、调用内部 API,这些都需要与外部系统交互。Function Calling(函数调用)正是为解决这一问题而生的机制:它让模型能够以结构化的方式声明"我需要调用某个工具",由应用层执行后将结果返回模型,从而完成闭环。
这不是一个可选的高级特性,而是构建生产级 AI 应用的基础能力。没有 Function Calling,模型只能"说";有了它,模型才能"做"。
核心工作流程
Function Calling 的运行机制可以拆解为四个步骤:定义、决策、执行、响应。理解这四步是掌握整个机制的关键。
图表加载中...
第一步:定义(Define)
开发者通过 JSON Schema 描述可用的函数,包括函数名称、用途说明、参数类型与约束。这些定义随用户消息一起发送给模型。模型不会"看到"函数的实现代码,它只知道函数的接口契约。
第二步:决策(Decide)
模型分析用户输入,判断是否需要调用函数、调用哪个函数、传入什么参数。这一步完全由模型自主完成。如果用户的问题不需要工具(例如"什么是机器学习?"),模型会直接回答而不触发任何函数调用。
第三步:执行(Execute)
模型本身不执行任何函数。 它只输出一段结构化的 JSON,表明意图。实际的函数调用由应用层(你的后端代码)负责执行。这是一个关键的安全设计——模型永远不会直接操作外部系统。
第四步:响应(Respond)
执行结果被送回模型,模型据此生成面向用户的自然语言回复。例如,天气 API 返回 {"temp": 28, "condition": "sunny"},模型会回复"今天北京气温 28 度,晴天,适合户外活动"。
函数定义格式详解
函数定义的质量直接决定模型调用的准确性。以下是两个典型示例:
示例一:天气查询
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气信息,包括温度、天气状况和湿度",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如北京、上海、广州"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,默认摄氏度"
}
},
"required": ["city"]
}
}
}
示例二:数据库查询
{
"type": "function",
"function": {
"name": "query_database",
"description": "根据条件查询用户数据库中的记录。支持按字段筛选和排序。",
"parameters": {
"type": "object",
"properties": {
"table": {
"type": "string",
"enum": ["users", "orders", "products"],
"description": "要查询的数据表名"
},
"filters": {
"type": "object",
"description": "筛选条件,键为字段名,值为匹配值",
"additionalProperties": true
},
"limit": {
"type": "integer",
"description": "返回记录数上限,默认 10",
"default": 10
}
},
"required": ["table"]
}
}
}
函数描述(description)是模型理解工具用途的唯一依据。描述越清晰、越具体,模型的调用决策就越准确。避免写"查询数据"这种模糊描述,应写明查询什么数据、支持哪些条件、返回什么格式。
模型的决策机制
一个常见误解是:模型会"运行"函数。事实上,模型只负责输出结构化的调用意图,本质上就是一段 JSON。
当用户问"北京今天天气怎么样?",模型的输出并不是天气信息,而是:
{
"function_call": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\", \"unit\": \"celsius\"}"
}
}
整个过程可以理解为:
图表加载中...
模型之所以能做出正确的调用决策,是因为它在训练阶段学习了大量的函数调用模式。函数定义中的 description 和 parameters 信息帮助模型建立"用户意图 -> 函数选择 -> 参数填充"的映射关系。
并行函数调用
在复杂场景中,用户的一个请求可能需要同时调用多个函数。例如用户问"北京和上海今天的天气分别怎么样?",模型可以一次性输出两个函数调用:
{
"tool_calls": [
{
"id": "call_1",
"function": { "name": "get_weather", "arguments": "{\"city\": \"北京\"}" }
},
{
"id": "call_2",
"function": { "name": "get_weather", "arguments": "{\"city\": \"上海\"}" }
}
]
}
应用层可以并发执行这两个调用,将所有结果一次性返回给模型。这显著减少了交互轮次,提升了响应速度。并行调用的前提是函数之间没有依赖关系——如果第二个函数的参数依赖第一个函数的返回值,则必须串行执行。
主流平台对比
不同 LLM 服务商在 Function Calling 的 API 设计上存在差异,但核心概念一致:
| 特性 | OpenAI(GPT) | Anthropic(Claude) | 开源模型(如 Qwen) |
|---|---|---|---|
| API 字段名 | tools / tool_choice | tools / tool_use | 因框架而异 |
| 函数定义格式 | JSON Schema | JSON Schema(相同) | JSON Schema(相同) |
| 并行调用 | 支持 | 支持 | 部分支持 |
| 调用结果角色 | role: "tool" | tool_result content block | 因框架而异 |
| 强制调用指定函数 | tool_choice: {"type": "function", "function": {"name": "xxx"}} | tool_choice: {"type": "tool", "name": "xxx"} | 因框架而异 |
| 禁用函数调用 | tool_choice: "none" | tool_choice: {"type": "none"} | 因框架而异 |
尽管 API 字段名不同,JSON Schema 格式的函数定义在各平台间是通用的。如果你的应用需要支持多个模型,建议将函数定义与平台适配层分离。
实战代码示例
以下是一个完整的 Python 示例,展示如何使用 OpenAI SDK 实现 Function Calling:
import json
import openai
# 1. 定义可用工具
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}
]
# 2. 实际的函数实现
def get_weather(city: str, unit: str = "celsius") -> dict:
"""模拟天气 API 调用"""
mock_data = {
"北京": {"temp": 28, "condition": "晴", "humidity": 45},
"上海": {"temp": 31, "condition": "多云", "humidity": 72},
}
return mock_data.get(city, {"temp": 0, "condition": "未知", "humidity": 0})
# 3. 对话主循环
def chat_with_tools(user_message: str):
messages = [{"role": "user", "content": user_message}]
# 第一次调用:让模型决定是否需要工具
response = openai.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
msg = response.choices[0].message
# 如果模型决定调用函数
if msg.tool_calls:
messages.append(msg)
for tool_call in msg.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
# 执行对应的函数
if func_name == "get_weather":
result = get_weather(**func_args)
else:
result = {"error": f"未知函数: {func_name}"}
# 将执行结果添加到消息历史
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
# 第二次调用:模型根据结果生成最终回复
final_response = openai.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
return final_response.choices[0].message.content
# 模型直接回复,无需工具
return msg.content
# 运行
print(chat_with_tools("北京今天天气怎么样?"))
代码中的核心模式是 两次 API 调用:第一次让模型决策,第二次让模型基于工具返回的结果生成最终回复。在生产环境中,你需要加入重试机制、参数校验和超时控制。
最佳实践
编写高质量的函数描述
函数描述是模型理解工具的唯一途径。好的描述应当包含:工具的用途、适用场景、参数含义、返回值说明。
{
"description": "在公司内部知识库中搜索文档。适用于用户询问公司政策、产品文档、技术规范等内部信息时调用。返回最相关的 Top-K 文档片段。"
}
参数约束要严格
利用 JSON Schema 的 enum、minimum、maximum、pattern 等约束条件,减少模型产生无效参数的概率。
做好错误处理
函数执行可能失败。应用层必须捕获异常并返回结构化的错误信息,让模型能据此给出合理的回复,而不是崩溃或返回空值。
try:
result = call_external_api(params)
except TimeoutError:
result = {"error": "服务超时,请稍后重试"}
except Exception as e:
result = {"error": f"调用失败: {str(e)}"}
校验模型输出
模型生成的参数不一定总是合法的。在执行函数前,务必校验参数类型、范围和格式。
永远不要盲目信任模型输出的参数。模型可能产生不存在的枚举值、超出范围的数字、格式错误的日期。在执行前做校验是生产环境的刚性要求。
Function Calling vs 基于 Prompt 的工具调用
在 Function Calling 机制出现之前,开发者通常通过 Prompt 工程让模型输出结构化文本来模拟工具调用。两种方式的对比如下:
| 维度 | Function Calling | Prompt 工程方式 |
|---|---|---|
| 输出格式 | 原生结构化 JSON | 需要从文本中解析 |
| 可靠性 | 高,有 Schema 约束 | 低,格式可能不稳定 |
| 参数校验 | 模型层面自动对齐 | 完全依赖 Prompt 指令 |
| 多函数选择 | 原生支持 | 需要复杂的 Prompt 设计 |
| 并行调用 | 原生支持 | 很难实现 |
| 模型要求 | 需要模型原生支持 | 任何模型均可尝试 |
| 开发成本 | 低,标准化接口 | 高,需要大量调试 |
Function Calling 在可靠性和开发效率上有决定性优势。但对于不支持 Function Calling 的模型(如部分小参数开源模型),Prompt 方式仍然是可行的降级方案。
与 Agent 系统的关系
Function Calling 是构建 AI Agent 的基石。
Agent 的核心能力是"感知 -> 思考 -> 行动"的循环。其中"行动"环节就依赖 Function Calling 来实现。一个典型的 Agent 架构如下:
图表加载中...
在 LangChain、LlamaIndex 等主流 Agent 框架中,工具定义的底层格式就是 Function Calling 的 JSON Schema。Agent 框架在此基础上增加了记忆管理、多步规划、错误重试等高级能力,但工具调用的核心协议始终是 Function Calling。
理解了 Function Calling,你就掌握了 AI Agent 工具调用的底层原理。无论框架如何演进,这一层协议的设计思想都是稳定的:用结构化的契约描述工具能力,让模型在理解用户意图后自主选择和组合工具,由应用层安全执行。