Function Calling 完整调用教程:让模型自己调你的函数
Function Calling(函数调用)解决的是同一个核心问题:让模型输出的不再是自由文本,而是一个结构化的调用请求。
传统的 AI 对话,模型给你一段文字,你再自己解析这段文字去执行操作。Function Calling 改变了这个范式------模型直接告诉你「我要调用 get_weather,参数是 location=杭州」,你负责执行函数、把结果传回去,模型再基于真实数据给出最终回答。整个过程中,模型只负责决策,不负责执行。
这套机制是 OpenAI 在 2023 年 6 月引入的(gpt-3.5-turbo-0613 / gpt-4-0613),随后被 Claude、DeepSeek 等各大平台兼容。现在主流的 Function Calling 实现已经相当成熟,支持并行调用(一次回复触发多个函数)、严格模式(Structured Outputs 保证参数格式分毫不差)。
本文聚焦「怎么写对」,完整覆盖:tool 定义 → 调用 → 解析返回 → 执行函数 → 回传结果的闭环。关于报错的具体原因和解决方案,参见另一篇《JSON 模式/结构化输出报错怎么办?》。
1. 完整调用流程:一次对话,两次请求
Function Calling 通常需要两次模型调用才能完成一轮完整交互:
用户输入
↓
第一次请求 → 模型判断要调哪个函数 → 返回 tool_calls
↓
你执行函数,获取结果
↓
第二次请求 → 把函数结果传回去 → 模型生成最终回复
下面用 Python 演示这个闭环。
1.1 定义工具(tools)
python
from openai import OpenAI
import json
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="YOUR_BASE_URL", # OpenAI 用 https://api.openai.com/v1
# DeepSeek 用 https://api.deepseek.com
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称,如杭州、东京"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,默认 celsius"
}
},
"required": ["location"],
"additionalProperties": False # 严格模式建议加,禁止返回未定义字段
}
}
}
]
参数定义要点:
type必须是"object"required列出所有必填字段description字段非常重要,模型靠它理解什么时候该调这个工具additionalProperties: false防止模型返回未定义的额外字段- 工具名称只允许
a-z、A-Z、0-9、_、-,最大 64 字符
1.2 第一次请求:让模型决定调用哪个函数
python
messages = [
{"role": "user", "content": "杭州今天热不热?"}
]
response = client.chat.completions.create(
model="YOUR_MODEL", # 如 gpt-4o、claude-sonnet-4-5、deepseek-v4-flash
messages=messages,
tools=tools,
tool_choice="auto" # auto: 模型自己决定是否调用;required: 强制调用
)
message = response.choices[0].message
print(message.tool_calls)
正常情况下,返回值中 tool_calls 不为空:
python
# message.tool_calls 示例
[
ToolCall(
id="call_abc123",
type="function",
function=Function(
name="get_weather",
arguments='{"location": "杭州", "unit": "celsius"}'
)
)
]
1.3 解析并执行函数
python
def execute_tool(tool_call):
"""根据 tool_call 的 name 和 arguments 执行对应函数"""
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
if function_name == "get_weather":
return get_weather(**arguments)
else:
raise ValueError(f"未知函数: {function_name}")
def get_weather(location: str, unit: str = "celsius") -> dict:
"""这里是你的业务逻辑,比如调用真实天气 API"""
# 模拟返回
return {
"location": location,
"temperature": 28 if unit == "celsius" else 82,
"unit": unit,
"condition": "晴转多云"
}
# 执行所有调用的函数
if message.tool_calls:
for tool_call in message.tool_calls:
result = execute_tool(tool_call)
print(f"函数 {tool_call.function.name} 返回: {result}")
1.4 第二次请求:把结果传回模型
python
# 把模型的 tool_calls 和函数执行结果都加到对话历史里
messages.append(message) # 模型返回的 tool_calls 消息
for tool_call in message.tool_calls:
result = execute_tool(tool_call)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
# 第二次请求
final_response = client.chat.completions.create(
model="YOUR_MODEL",
messages=messages,
tools=tools
)
print(final_response.choices[0].message.content)
# 模型输出:「杭州今天天气晴转多云,气温 28°C。」
这才是用户最终看到的回复。
2. 多工具选择:让模型智能调度
当你有多个工具时,模型会根据用户意图自动选择最合适的一个,或者同时调用多个。
2.1 典型多工具场景
python
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气和空气质量",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "城市名称"}
},
"required": ["location"]
}
}
},
{
"type": "function",
"function": {
"name": "search_flights",
"description": "搜索两个城市间的航班信息",
"parameters": {
"type": "object",
"properties": {
"origin": {"type": "string", "description": "出发城市"},
"destination": {"type": "string", "description": "目的城市"},
"date": {"type": "string", "description": "出发日期,格式 YYYY-MM-DD"}
},
"required": ["origin", "destination", "date"]
}
}
},
{
"type": "function",
"function": {
"name": "send_email",
"description": "发送邮件",
"parameters": {
"type": "object",
"properties": {
"to": {"type": "string", "format": "email", "description": "收件人邮箱"},
"subject": {"type": "string", "description": "邮件主题"},
"body": {"type": "string", "description": "邮件正文"}
},
"required": ["to", "subject", "body"]
}
}
}
]
2.2 并行调用处理
支持并行的模型(GPT-4o、o3、Claude 4.x、DeepSeek-v4 等)可以一次返回多个 tool_calls:
python
# 处理并行调用
tool_results = []
if message.tool_calls:
for tool_call in message.tool_calls:
try:
result = execute_tool(tool_call)
tool_results.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
except Exception as e:
tool_results.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps({"error": str(e)}, ensure_ascii=False)
})
messages.append(message)
messages.extend(tool_results)
并行调用的注意事项:
- 某些严格模式(
strict: true)不支持并行调用,并行时可能不保证参数格式 - 收到多个 tool_calls 时,所有结果必须全部传回模型才能得到最终回复
- 如果某个函数执行失败,建议返回错误 JSON 而不是空内容,这样模型可以决定如何处理
2.3 强制调用特定工具
如果你希望模型必须调用某个工具(而不是自己选择是否调用):
python
# 强制调用单个工具
response = client.chat.completions.create(
model="YOUR_MODEL",
messages=messages,
tools=tools,
tool_choice={
"type": "function",
"function": {"name": "get_weather"}
}
)
3. 严格模式(Structured Outputs)
2024 年 6 月,OpenAI 推出 Structured Outputs,DeepSeek 在 Beta API 中也支持了 strict: true。开启后,模型生成的参数会数学级别保证符合你定义的 JSON Schema,不再依赖模型"努力写对"。
3.1 启用严格模式
python
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"strict": True, # 开启严格模式
"description": "查询天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"],
"additionalProperties": False
}
}
}
]
严格模式对 Schema 的强制要求(OpenAI 规范):
- 所有对象必须设置
additionalProperties: false required数组必须包含对象的所有属性- 不支持
prefixItems(用items代替) - 不支持
minItems/maxItems/minLength/maxLength - 支持
pattern、enum、format(email、hostname、ipv4、ipv6、uuid)
3.2 严格模式 vs 普通模式
| 特性 | 普通模式 | 严格模式(strict: true) |
|---|---|---|
| 参数格式保证 | 模型 best-effort | 100% 格式保证 |
| Schema 约束 | 较宽松 | 严格(必须满足上述要求) |
| 并行调用 | 支持 | 不支持 |
| 适用场景 | 快速迭代 | 生产环境 |
4. 与报错排查文章的区别
本文解决的是「代码怎么写、功能怎么实现」的问题。
那篇《JSON 模式/结构化输出报错怎么办?》解决的是「代码跑起来了但报错了」的问题。两篇文章分工如下:
| 维度 | 本文(调用教程) | 那篇(报错排查) |
|---|---|---|
| 核心问题 | 怎么写对 Function Calling | 写错了怎么排查 |
| 关注点 | 流程、Schema 设计、多工具 | 错误码、模型兼容性、截断 |
| 典型场景 | 第一次接入,不会写 | 接入后遇到具体报错 |
5. 常见坑
坑 1:Schema 漏写 type: "object"
DeepSeek、部分第三方兼容端点会严格校验,缺少 "type": "object" 直接返回 400:
python
# ❌ 错误:漏了 type
"parameters": {
"properties": {"location": {"type": "string"}},
"required": ["location"]
}
# ✅ 正确
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"]
}
坑 2:单属性 Schema 在某些端点报错
OpenAI 社区发现,只有单个属性 的 Schema 在部分模型/端点组合下会报 Invalid schema 错误。解决方法是加一个无意义的辅助字段:
python
# ❌ 可能报错
"parameters": {
"type": "object",
"properties": {"items": {"type": "array", "items": {"type": "string"}}},
"required": ["items"]
}
# ✅ 加一个 count 字段
"parameters": {
"type": "object",
"properties": {
"items": {"type": "array", "items": {"type": "string"}},
"count": {"type": "integer"}
},
"required": ["items", "count"]
}
坑 3:并行调用 + 严格模式混用
严格模式下开启并行调用,参数格式不再保证。生产环境如需两者兼得,需要自己在解析后做 JSON Schema 校验。
坑 4:tool_call_id 不匹配
每次请求中 tool_call.id 都是新生成的,传回时必须使用上一次返回的 id,不能自己造一个。
坑 5:messages 历史里塞了 reasoning_content
DeepSeek-v4-pro 等模型会在响应中包含 reasoning_content(内部推理链),多轮对话时只应把 content 加入历史,reasoning_content 不应加入,否则会干扰后续调用。
快速排错表
| 错误信息 | 原因 | 解决方式 |
|---|---|---|
Invalid schema for function |
Schema 缺少 type: object 或使用了不支持的 JSON Schema 特性 |
检查 Schema,移除 prefixItems、minItems 等不支持的关键字 |
required is required to be an array including every key |
strict 模式下所有属性必须出现在 required 数组中 | 把所有属性加入 required,或把 optional 字段设为 null 联合类型 |
array schema missing items |
数组参数用了 prefixItems 但缺少 items |
改用 items 关键字 |
| 模型不调用任何函数 | system prompt 或 tool description 写得不清楚 | 优化 description,确保 prompt 中明确要求调用工具 |
| 多个 tool_calls 但只有部分执行了 | 并行调用处理逻辑有遗漏 | 遍历所有 tool_calls,全部执行并传回结果 |
| 二次请求后模型回复很奇怪 | messages 历史构建顺序错误 | 按 [user, assistant, tool_result, assistant, tool_result...] 顺序排列 |
finish_reason="length" |
max_tokens 太小导致输出截断 | 增大 max_tokens |
配置检查清单
接入 Function Calling 前,逐项确认以下配置:
工具定义
-
type字段设为"function"(外层)和"object"(parameters) -
name只含a-zA-Z0-9_-,不超过 64 字符 - 每个 property 有
description,模型靠它理解语义 -
required数组列出了所有必填字段 - 严格模式下所有对象加了
additionalProperties: false - 没有使用
prefixItems、minItems、maxItems等不支持的特性
API 调用
- 使用支持 Function Calling 的模型(GPT-4o、Claude 4.x、DeepSeek-v4 等)
-
max_tokens设置足够大,避免截断 - 第一次请求正确传递
tools和tool_choice参数 - 收到
tool_calls后正确解析tool_call.id和function.arguments - 第二次请求把所有
tool消息和原始assistant消息都加入messages -
tool_call_id使用上一次返回的真实 id,不自行生成
结果处理
- 函数执行异常时返回错误 JSON 而非抛异常
- 多 tool_calls 场景下全部执行完毕再发起第二次请求
- 不把
reasoning_content加入多轮对话历史(DeepSeek 等模型) - 解析
arguments用json.loads()而非直接当 dict 用