Function Calling / Tool Use 报错怎么办?参数格式、超时、不触发全解析
Function Calling 让大模型能调用外部函数------你把函数描述通过 tools 参数传给模型,模型决定何时调用、传什么参数,你的代码负责执行并把结果喂回去。这个闭环里任何一个环节出问题,都会报错。实际开发中踩得最多的坑集中在四类:tools 参数格式不对、模型压根不触发工具、工具执行超时、返回结果解析失败。
一、tools 参数格式错误
这是最高频的报错来源。OpenAI 的 tools 参数有严格的 JSON Schema 规范,写错一个字段或者套错层级,模型要么直接忽略你的工具定义(静默失败),要么返回 400 错误。
常见写法错误
错误示例:用了已废弃的 functions 参数
python
# ❌ 旧写法,openai 1.0+ 已废弃
response = client.chat.completions.create(
model="YOUR_MODEL",
messages=[{"role": "user", "content": "今天天气怎么样?"}],
functions=[{...}], # 废弃参数
function_call="auto" # 废弃参数
)
正确写法:用 tools + tool_choice
python
# ✅ 当前标准写法
from openai import OpenAI
import json
client = OpenAI(api_key="YOUR_API_KEY", base_url="YOUR_BASE_URL")
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名,如:北京、上海"
}
},
"required": ["location"],
"additionalProperties": False
},
"strict": True
}
}
]
response = client.chat.completions.create(
model="YOUR_MODEL",
messages=[{"role": "user", "content": "北京今天天气怎么样?"}],
tools=tools,
tool_choice="auto"
)
关键格式要点
几个容易忽略的细节:
type字段必须是"function",外层包一个function对象,里面才是name、description、parametersadditionalProperties: false在 strict 模式下是必须的,否则 API 会拒绝请求required数组必须列出properties中的所有字段 ,可选字段用["string", "null"]联合类型表示strict: true开启后,模型生成的参数会 100% 符合你定义的 Schema,不会出现缺字段或类型不对的情况
strict 模式的限制
strict 模式不支持所有 JSON Schema 特性。以下关键字不支持:
$ref/$defs(不能用引用)patternProperties/dependenciesformat中的部分值(只支持email、uri、date-time等少量格式)minLength/maxLength/minimum/maximum可以用,但 API 不会强制校验------你的代码需要自己验证
二、模型不触发工具调用
定义了工具,模型却直接用文字回答了,finish_reason 返回的是 "stop" 而不是 "tool_calls"。这种情况排查起来让人头疼,因为没有报错,只是工具没被调用。
原因排查
原因 1:tool_choice 设置问题
tool_choice 默认是 "auto",模型自己决定要不要调用工具。如果模型觉得直接回答就行,它就不会调。这种情况下你不会收到 tool_calls。
python
# 强制模型必须调用至少一个工具
response = client.chat.completions.create(
model="YOUR_MODEL",
messages=[...],
tools=tools,
tool_choice="required" # 强制触发
)
# 或者强制调用某个特定函数
response = client.chat.completions.create(
model="YOUR_MODEL",
messages=[...],
tools=tools,
tool_choice={"type": "function", "function": {"name": "get_weather"}}
)
原因 2:函数描述太模糊
模型完全依赖 description 字段来判断工具该不该用。如果你的描述写的是 "获取数据",模型根本不知道什么场景该调。
python
# ❌ 描述太模糊
"description": "获取数据"
# ✅ 描述具体明确
"description": "根据城市名称查询当前天气,返回温度、湿度、风力等级。用户询问天气相关问题时使用此工具。"
原因 3:参数描述缺失或不清晰
模型需要从用户输入中提取参数值,如果参数描述写得含糊,模型可能不知道该传什么。
python
# ❌ 参数描述缺失
"location": {
"type": "string"
}
# ✅ 带示例的参数描述
"location": {
"type": "string",
"description": "城市名称,例如:北京、上海、深圳"
}
原因 4:工具数量过多
OpenAI 官方建议单次请求不超过 20 个工具定义。工具太多会让模型选择困难,准确率下降。如果你的系统有几十个工具,应该根据上下文做一次预筛选,只传相关的工具子集。
诊断代码
python
def diagnose_response(response):
"""诊断模型为什么没有触发工具调用"""
choice = response.choices[0]
print(f"finish_reason: {choice.finish_reason}")
print(f"message content: {choice.message.content}")
print(f"tool_calls: {choice.message.tool_calls}")
if choice.finish_reason == "tool_calls":
if not choice.message.tool_calls:
print("⚠️ finish_reason 是 tool_calls 但 tool_calls 为空")
else:
for tc in choice.message.tool_calls:
print(f" 工具: {tc.function.name}")
print(f" 参数: {tc.function.arguments}")
elif choice.finish_reason in ["stop", "end_turn"]:
print("⚠️ 模型选择直接回答,未调用工具")
print(" 检查:1) tool_choice 是否为 auto")
print(" 2) 函数描述是否清晰")
print(" 3) 用户输入是否匹配工具用途")
elif choice.finish_reason == "length":
print("⚠️ 响应被 max_tokens 截断")
三、工具执行超时
模型触发了工具调用,但你的函数执行太慢,导致整个请求链路超时。这在调用外部 API、查数据库、做文件处理的场景里很常见。
问题表现
- API 返回 504 Gateway Timeout
- 客户端报
APITimeoutError或APIConnectionError - 多轮对话中,工具结果还没返回,模型已经"忘了"之前的状态
解决方案
方案 1:异步执行 + 超时控制
python
import asyncio
import json
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY", base_url="YOUR_BASE_URL")
async def execute_tool_with_timeout(tool_name, args, timeout_seconds=10):
"""带超时的工具执行"""
try:
result = await asyncio.wait_for(
call_tool(tool_name, args),
timeout=timeout_seconds
)
return json.dumps({"result": result}, ensure_ascii=False)
except asyncio.TimeoutError:
return json.dumps({"error": f"工具 {tool_name} 执行超时"}, ensure_ascii=False)
async def call_tool(name, args):
# 替换为你的实际工具实现
if name == "get_weather":
await asyncio.sleep(2) # 模拟 API 调用
return {"temp": 25, "condition": "晴"}
raise ValueError(f"未知工具: {name}")
方案 2:给工具结果加上错误处理
工具执行失败时,不要直接抛异常中断流程,把错误信息作为工具结果返回给模型,让模型自己决定怎么处理:
python
def safe_execute_tool(tool_name, args_dict):
"""安全执行工具,捕获所有异常"""
try:
result = TOOL_REGISTRY[tool_name](**args_dict)
return json.dumps({"result": result}, ensure_ascii=False)
except KeyError:
return json.dumps({"error": f"工具 {tool_name} 不存在"}, ensure_ascii=False)
except TypeError as e:
return json.dumps({"error": f"参数错误: {e}"}, ensure_ascii=False)
except Exception as e:
return json.dumps({"error": f"执行失败: {e}"}, ensure_ascii=False)
方案 3:Responses API 的 5 分钟窗口
如果你用的是 OpenAI Responses API,requires_action 状态有 5 分钟的有效期。工具执行超过 5 分钟后提交结果会失败。解决方案是轮询检查状态,超时后重新创建请求:
python
import time
MAX_TIMEOUT = 300 # 5 分钟
start = time.time()
while response.status == "requires_action":
if time.time() - start > MAX_TIMEOUT:
# 超时,重新创建请求
response = client.responses.create(...)
start = time.time()
continue
# 执行工具并提交结果
...
四、返回格式解析失败
模型返回了 tool_calls,但 arguments 字段的 JSON 解析不了。这个问题的根因有三个,每个的修法不同。
原因 1:响应被 max_tokens 截断
模型生成参数的 token 数超过了 max_tokens 限制,JSON 被拦腰截断。这种情况下 finish_reason 会是 "length" 而不是 "tool_calls"。
python
choice = response.choices[0]
if choice.finish_reason == "length":
# 参数被截断了
print("⚠️ 工具参数被 max_tokens 截断")
print("解决方案:增大 max_tokens 或精简 Schema")
# 增大 max_tokens 重试
response = client.chat.completions.create(
model="YOUR_MODEL",
messages=[...],
tools=tools,
max_tokens=4096 # 增大
)
原因 2:JSON 解析异常
tool_call.function.arguments 是一个字符串,不是字典,必须用 json.loads() 解析。即使开了 strict 模式,在 token 压力下偶尔还是会出问题(gpt-4o-mini 约 2-5% 的概率)。
python
import json
def parse_tool_arguments(response):
"""安全解析工具参数"""
choice = response.choices[0]
if not choice.message.tool_calls:
return None, "没有工具调用"
results = []
for tc in choice.message.tool_calls:
raw_args = tc.function.arguments
if not raw_args or raw_args.isspace():
results.append({
"tool": tc.function.name,
"error": "参数为空"
})
continue
try:
args = json.loads(raw_args)
results.append({
"tool": tc.function.name,
"args": args
})
except json.JSONDecodeError as e:
results.append({
"tool": tc.function.name,
"error": f"JSON 解析失败: {e}",
"raw": raw_args
})
return results, None
原因 3:Schema 过于宽松
Schema 没有约束好,模型生成的参数虽然能解析,但内容不对------缺少字段、类型不匹配、枚举值越界。
python
def validate_args(args, schema):
"""客户端校验参数,API 不会替你做这件事"""
props = schema["properties"]
for param, value in args.items():
if param not in props:
return False, f"未知参数: {param}"
param_def = props[param]
# 枚举校验
if "enum" in param_def and value not in param_def["enum"]:
return False, f"{param} 不在允许值中: {param_def['enum']}"
# 类型校验
expected_type = param_def["type"]
if isinstance(expected_type, list):
# 联合类型,如 ["string", "null"]
if value is not None and not isinstance(value, str):
return False, f"{param} 类型错误"
elif expected_type == "string" and not isinstance(value, str):
return False, f"{param} 应为 string"
elif expected_type == "number" and not isinstance(value, (int, float)):
return False, f"{param} 应为 number"
# 必填字段校验
for req in schema.get("required", []):
if req not in args:
return False, f"缺少必填字段: {req}"
return True, "校验通过"
完整的多轮工具调用示例
把上面的防御措施整合起来:
python
import json
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY", base_url="YOUR_BASE_URL")
def run_agent(user_query, tools, tool_registry, max_rounds=5):
messages = [{"role": "user", "content": user_query}]
for round_num in range(max_rounds):
response = client.chat.completions.create(
model="YOUR_MODEL",
messages=messages,
tools=tools,
tool_choice="auto"
)
choice = response.choices[0]
# 情况 1:模型直接回答,没有工具调用
if choice.finish_reason in ["stop", "end_turn"]:
return choice.message.content
# 情况 2:响应被截断
if choice.finish_reason == "length":
messages.append({"role": "user", "content": "请继续"})
continue
# 情况 3:工具调用
if choice.finish_reason == "tool_calls" and choice.message.tool_calls:
messages.append(choice.message) # 必须先加 assistant 消息
for tc in choice.message.tool_calls:
try:
args = json.loads(tc.function.arguments)
except json.JSONDecodeError:
result = '{"error": "参数解析失败"}'
else:
func = tool_registry.get(tc.function.name)
if func:
try:
result = json.dumps(func(**args), ensure_ascii=False)
except Exception as e:
result = json.dumps({"error": str(e)}, ensure_ascii=False)
else:
result = '{"error": "工具不存在"}'
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result
})
else:
messages.append({"role": "user", "content": "请使用工具来回答"})
return "达到最大轮次限制"
# 工具注册表
TOOL_REGISTRY = {
"get_weather": lambda location: {"temp": 25, "condition": "晴"}
}
# 运行
answer = run_agent("北京天气怎么样?", tools, TOOL_REGISTRY)
print(answer)
注意上面代码里的一个关键细节:提交工具结果前,必须先把 assistant 消息(包含 tool_calls)追加到 messages 列表里 。漏掉这一步会报 400 错误,提示 messages with role 'tool' must be preceded by a message with a tool_calls field。
快速排错表
| 症状 | 可能原因 | 排查方法 | 修复方案 |
|---|---|---|---|
| API 返回 400 | tools 参数格式错误 |
打印 tools 列表检查 JSON 结构 | 按官方 Schema 格式重写 |
finish_reason 是 stop |
模型未触发工具 | 检查 tool_choice 和函数描述 |
设为 required 或优化描述 |
finish_reason 是 length |
参数被 max_tokens 截断 | 检查 max_tokens 设置 | 增大 max_tokens 或精简 Schema |
tool_calls 为 None |
模型选择直接回答 | 打印完整 response.choices0.message | 优化 prompt 或强制 tool_choice |
json.loads() 报错 |
arguments 字符串不完整 | 打印 raw arguments 字符串 | 检查是否被截断,加 try-except |
| 400: tool result 无效 | 没先追加 assistant 消息 | 检查 messages 顺序 | 先 messages.append(choice.message) |
tool_call_id 不匹配 |
ID 对应错误 | 检查每个 tool_call 的 id | 严格用 tc.id 不要手写 |
| 504 超时 | 工具执行太慢 | 给工具加超时日志 | 异步执行 + 超时控制 |
| 工具被调用但参数错 | Schema 太宽松 | 打印实际参数对比 Schema | 开 strict 模式 + 客户端校验 |
| strict 模式被拒绝 | Schema 不合规 | 查看 API 返回的具体错误信息 | 检查 additionalProperties 和 required |
配置检查清单
开发 Function Calling 功能时,逐项过一遍这个清单:
- tools 参数格式 :外层
type: "function",内层function对象包含name、description、parameters - parameters 结构 :
type: "object",有properties、required、additionalProperties: false - strict 模式 :生产环境开启
strict: true,所有字段在required中,可选字段用联合类型 - 函数描述:描述写清楚什么时候该用这个工具、返回什么数据,不要写一句话的模糊描述
- 参数描述:每个参数有清晰的 description,复杂参数带示例值
- tool_choice :确认是
auto(模型决定)、required(必须调用)还是指定函数 - max_tokens:设置足够大,避免参数生成到一半被截断
- JSON 解析 :
tool_call.function.arguments用json.loads()解析,加 try-except - 消息顺序:工具结果前必须先追加 assistant 消息(含 tool_calls)
- tool_call_id :每个 tool 结果的
tool_call_id必须精确匹配模型返回的 ID - 错误处理:工具执行失败时返回 JSON 错误信息,不要直接抛异常
- 超时控制:工具执行加超时限制,避免整个请求链路卡死
- 客户端校验:API 不强制校验 enum/min/max,你的代码要自己做参数校验
- 工具数量:单次请求不超过 20 个工具,太多会降低准确率
- 日志记录:记录每次工具调用的名称、参数、执行时间、返回结果,方便线上排查