Function Calling 完整教程:用 GPT 和 Claude API 构建智能工具调用系统(2026)
摘要
- Function Calling(工具调用)让 LLM 在回答时主动调用你定义的函数,实现实时数据查询、外部系统操作等能力
- OpenAI 和 Claude 都支持工具调用,API 格式略有差异,但核心思路一致
- 用一个 OpenAI 兼容的 API 网关(如 Ofox.ai),可以用同一套代码同时驱动 GPT、Claude、DeepSeek 等 50+ 模型
- 生产环境需要重点关注:工具描述质量、错误处理、并发控制、context window 管理
目录
- 什么是 Function Calling?
- OpenAI Function Calling API 格式详解
- Claude Tool Use API 格式详解
- 完整代码实现:天气查询 Agent
- 多工具并行调用
- 生产环境最佳实践
- 案例研究:实测数据
- 常见问题 FAQ
- 总结与行动建议
什么是 Function Calling?
Function Calling(也叫 Tool Use 或工具调用)是一种让 LLM 与外部系统交互的机制。简单来说:
你定义一组函数(工具)的描述,告诉模型”你有这些能力”;当模型判断需要某个工具时,它不直接回答,而是返回一个结构化的”调用请求”,你的代码执行这个请求、拿到结果,再把结果喂给模型,模型据此生成最终回答。
传统 LLM 的局限:只能处理训练截止日期前的静态知识,无法获取实时天气、股票价格、数据库记录。
Function Calling 解决的问题:
- 实时数据获取(天气、股票、汇率)
- 外部系统操作(数据库查询、API 调用、发邮件)
- 结构化数据提取(从非结构化文本中提取字段)
- 多步骤 Agent 工作流(让模型自主规划并执行任务链)
适用场景 vs RAG:RAG 适合查询静态知识库(PDF、文档),Function Calling 适合动态数据和操作型任务。两者可以组合使用。
OpenAI Function Calling API 格式详解
OpenAI 的工具调用通过 tools 参数传入,每个工具是一个 JSON Schema 描述的函数:
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的当前天气。当用户询问天气相关问题时调用此工具。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如:北京、上海、深圳"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,默认 celsius"
}
},
"required": ["city"]
},
"strict": True # 启用 Structured Outputs,强制模型返回合法 JSON
}
}
]
关键字段说明:
| 字段 | 说明 |
|---|---|
type: "function" | 目前只支持 function 类型 |
name | 函数名,仅允许 [a-zA-Z0-9_-],最长 64 字符 |
description | 最重要的字段,模型根据此决定是否调用 |
parameters | 标准 JSON Schema,定义参数结构 |
strict: true | 强制 Structured Outputs,避免参数格式错误 |
模型返回工具调用时的响应格式:
# 当 finish_reason == "tool_calls" 时,模型想要调用工具
response = {
"choices": [{
"finish_reason": "tool_calls",
"message": {
"role": "assistant",
"content": None,
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": '{"city": "北京", "unit": "celsius"}'
# 注意:arguments 是 JSON 字符串,需要 json.loads()
}
}]
}
}]
}
Claude Tool Use API 格式详解
Claude 的工具调用语法与 OpenAI 类似,但字段名有差异:
tools = [
{
"name": "get_weather", # 无需 type 包装
"description": "获取指定城市的当前天气",
"input_schema": { # 注意:是 input_schema,不是 parameters
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
]
OpenAI vs Claude 关键差异对比:
| 方面 | OpenAI | Claude |
|---|---|---|
| 工具结构 | {"type":"function","function":{...}} | 直接 {"name":..., "input_schema":...} |
| Schema 字段名 | parameters | input_schema |
| 停止原因 | finish_reason: "tool_calls" | stop_reason: "tool_use" |
| 工具调用结果字段 | tool_call_id | tool_use_id |
| 内置服务端工具 | 无 | 支持(如 web_search_20250305) |
如果你用的是 OpenAI 兼容接口(比如 Ofox.ai 的 API 网关),即使后端用 Claude,也可以统一用 OpenAI 格式的 tools 参数,网关会自动转换。这样一套代码就能跑所有模型。
完整代码实现:天气查询 Agent
下面是一个完整的、生产可用的 Python 实现,通过 Ofox.ai API 网关调用任意模型:
import json
import os
from openai import OpenAI
# 使用 Ofox.ai 统一网关,一个 Key 调用 GPT/Claude/DeepSeek 等所有模型
client = OpenAI(
api_key=os.environ.get("OFOX_API_KEY"),
base_url="https://api.ofox.ai/v1"
)
# 工具定义
TOOLS = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的实时天气信息。当用户询问天气、温度、降雨概率等问题时使用。不适用于历史天气或长期预报。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,中文或英文均可,例如:北京、Shanghai"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,不指定时默认 celsius"
}
},
"required": ["city"]
},
"strict": True
}
}
]
# 模拟的工具实现(实际项目中替换为真实 API 调用)
def get_weather(city: str, unit: str = "celsius") -> dict:
"""调用天气 API 获取实时数据"""
# 实际实现:requests.get(f"https://api.weather.com/v1/current?city={city}")
return {
"city": city,
"temperature": 22 if unit == "celsius" else 72,
"unit": unit,
"condition": "晴天",
"humidity": "45%",
"wind": "东南风 3级"
}
def run_weather_agent(user_query: str, model: str = "gpt-4o-mini") -> str:
"""
运行天气查询 Agent
Args:
user_query: 用户的自然语言问题
model: 使用的模型,支持任何 Ofox 上架的模型
Returns:
模型的最终回答
"""
messages = [
{"role": "system", "content": "你是一个天气助手,使用工具获取实时天气信息后再回答用户。"},
{"role": "user", "content": user_query}
]
# 第一轮:模型决定是否调用工具
response = client.chat.completions.create(
model=model,
messages=messages,
tools=TOOLS,
tool_choice="auto" # 让模型自行决定
)
message = response.choices[0].message
# 如果模型不需要工具,直接返回
if response.choices[0].finish_reason != "tool_calls":
return message.content
# 处理工具调用
messages.append(message) # 把模型的工具调用意图加入对话历史
for tool_call in message.tool_calls:
# 解析参数(arguments 是 JSON 字符串)
args = json.loads(tool_call.function.arguments)
# 执行对应的工具函数
if tool_call.function.name == "get_weather":
result = get_weather(**args)
else:
result = {"error": f"Unknown tool: {tool_call.function.name}"}
# 把工具执行结果加入对话历史
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
# 第二轮:模型根据工具结果生成最终回答
final_response = client.chat.completions.create(
model=model,
messages=messages,
tools=TOOLS
)
return final_response.choices[0].message.content
# 测试
if __name__ == "__main__":
answer = run_weather_agent("北京今天天气怎么样?需要带伞吗?")
print(answer)
# 输出示例:北京今天晴天,气温 22°C,湿度 45%,东南风 3 级。不需要带伞,是出门的好天气!
几个要点:
- 两轮 API 调用:第一轮模型”思考”要调用哪个工具,第二轮拿到结果后生成回答
tool_call_id必须对应,否则模型会混淆arguments是 JSON 字符串,不是对象,需要json.loads()
多工具并行调用
现代 LLM(GPT-4o、Claude Sonnet)支持在一次响应中返回多个工具调用,大幅降低延迟:
# 一次性注册多个工具
MULTI_TOOLS = [
{"type": "function", "function": {"name": "get_weather", ...}},
{"type": "function", "function": {"name": "search_news", ...}},
{"type": "function", "function": {"name": "get_stock_price", ...}}
]
# 处理并行工具调用(模型可能同时返回多个 tool_calls)
if message.tool_calls:
import asyncio
import concurrent.futures
def execute_tool(tool_call):
args = json.loads(tool_call.function.arguments)
if tool_call.function.name == "get_weather":
return tool_call.id, get_weather(**args)
elif tool_call.function.name == "search_news":
return tool_call.id, search_news(**args)
# ...
# 并行执行所有工具调用,降低总延迟
with concurrent.futures.ThreadPoolExecutor() as executor:
results = list(executor.map(execute_tool, message.tool_calls))
for tool_call_id, result in results:
messages.append({
"role": "tool",
"tool_call_id": tool_call_id,
"content": json.dumps(result, ensure_ascii=False)
})
性能提升:如果三个工具各需 500ms,串行需 1500ms,并行只需 ~500ms。
生产环境最佳实践
1. 工具描述是最关键的工程
工具调用准确率最大的影响因素不是模型,而是工具描述质量:
# ❌ 糟糕的描述
"description": "获取天气"
# ✅ 好的描述:说明适用场景、不适用场景、输出内容
"description": """获取指定城市的实时天气信息(温度、湿度、风速、天气状况)。
适用:用户询问当前天气、今天天气、是否需要带伞等问题。
不适用:历史天气查询、未来 7 天预报(请使用 get_weather_forecast 工具)。
返回:包含温度(celsius)、湿度百分比、风向风速、天气状况描述。"""
2. 设置递归深度上限
避免模型无限循环调用工具:
MAX_TOOL_ROUNDS = 3 # 最多允许 3 轮工具调用
for round_num in range(MAX_TOOL_ROUNDS):
response = client.chat.completions.create(...)
if response.choices[0].finish_reason != "tool_calls":
break # 模型完成了,退出循环
# 处理工具调用...
else:
# 达到最大轮数,强制让模型总结
messages.append({"role": "user", "content": "请根据已有信息给出最终回答。"})
3. 错误处理:让模型从错误中恢复
try:
result = execute_tool(tool_call)
except Exception as e:
# 把错误信息返回给模型,让它尝试恢复或告知用户
result = {
"error": str(e),
"suggestion": "可以尝试使用不同的参数重新调用"
}
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
4. Context Window 管理
工具多了会消耗大量 token。58 个工具大约消耗 55k tokens,几乎撑爆小模型的上下文窗口。
解决方案:
- 动态加载工具:根据用户意图只传入相关工具(3-5 个)
- 工具结果截断:限制工具返回内容的最大长度(如 2000 字符)
- 对话历史压缩:超过 N 轮后压缩早期对话
更多 API 使用技巧,可参考 Ofox.ai 开发文档。
案例研究:实测数据
工具调用准确率对比(Berkeley BFCL V4 基准)
| 模型 | 单工具准确率 | 多工具准确率 | 并行调用 |
|---|---|---|---|
| GPT-4o | 92% | 87% | ✅ 支持 |
| Claude Sonnet 4.5 | 94% | 89% | ✅ 支持 |
| GPT-4o-mini | 85% | 79% | ✅ 支持 |
| DeepSeek-V3 | 83% | 76% | ✅ 支持 |
数据来源:Berkeley Function Calling Leaderboard V4,2025-2026 测试结果
延迟实测(通过 Ofox.ai 调用,北京节点)
| 场景 | 串行工具链 | 并行工具调用 | 提升 |
|---|---|---|---|
| 3 个工具各 300ms | ~1100ms | ~450ms | 59% ↓ |
| 5 个工具各 200ms | ~1300ms | ~380ms | 71% ↓ |
并行调用在多工具场景下延迟优势非常显著。
常见问题 FAQ
Q:Function Calling 和 RAG 应该用哪个?
两者目的不同。RAG(检索增强生成)适合查询静态知识库(如公司文档、产品手册),数据不频繁变化。Function Calling 适合动态数据(实时天气、数据库最新记录)和操作型任务(发邮件、写数据库)。生产系统通常两者组合:RAG 处理知识查询,Function Calling 处理实时数据和操作。
Q:工具调用返回空结果或报错怎么办?
把错误信息以结构化 JSON 形式返回给模型(见上文代码),模型会据此决定是否重试、换参数或告知用户。不要让工具调用静默失败,那会让模型产生幻觉。
Q:strict: true 必须启用吗?
建议在生产环境启用。strict: true 启用 OpenAI 的 Structured Outputs,强制模型返回符合 JSON Schema 的参数,可以消除约 95% 的参数格式错误。代价是首次请求需要额外的 schema 编译时间(约 100ms),之后会缓存。
Q:Claude 的工具调用格式和 OpenAI 不一样,怎么统一?
用 OpenAI 兼容的 API 网关,例如 Ofox.ai。你用 OpenAI SDK 的 tools 格式写代码,网关会自动把请求转为 Claude 的 input_schema 格式。切换模型只需改一行 model 参数,其他代码不用动。
Q:多少个工具会影响性能?
工具数量超过 10 个后,准确率开始下降,context 消耗显著增加。生产环境建议:
- 活跃工具不超过 10 个
- 根据用户意图动态选择相关工具子集(3-5 个)
- 复杂场景可以参考 Anthropic 的 Tool Search 技术,可减少 85% 的工具 token 消耗
Q:Function Calling 调用次数会额外计费吗?
不会。工具定义、调用请求和结果都算在 token 使用量里,没有额外收费。但注意工具定义本身也消耗 token(一般每个工具 100-500 tokens),工具数量多的话要计入成本。
总结与行动建议
Function Calling 是构建真正有用的 AI 应用的核心能力。掌握它,你的 LLM 就不再只是一个”聊天机器人”,而是能操作真实系统、获取实时数据的 Agent。
立即可以做的事:
- 从最简单的场景入手:选一个你现有的 API(天气、CRM、数据库),写一个工具定义,用本文的代码模板测试起来
- 优化工具描述:反复迭代 description,描述适用/不适用场景,是提升准确率投入产出比最高的事
- 统一 API 网关:用 Ofox.ai 的 OpenAI 兼容接口,一套代码跑 GPT、Claude、DeepSeek,方便横向对比和 fallback 切换
- 加上
strict: true:减少参数格式错误,生产环境必备 - 设置最大递归深度:避免工具调用死循环
更多集成示例和 SDK 文档,参考 Ofox.ai 开发者文档。