函数调用(Function Calling)是AI智能体连接外部世界的核心能力,但实践中它也是最容易出现故障的环节。不少开发者遇到过类似的情况:智能体明明正确选择了要调用的工具,服务端却抛出参数类型不匹配的异常,比如期望integer却收到了string、数组字段被传成了单个对象、枚举值不合法等。这类问题单靠重试往往无法解决,需要从工具定义、模型输出和服务端校验三个层面系统性地排查和修复。本文将详细分析参数类型不匹配的常见表现和根本原因,并给出可直接落地的修复方案。

一、参数类型不匹配的常见表现与根本原因
要有效修复问题,首先要能准确识别故障类型。参数类型不匹配在日志中通常表现为以下几种形式:一是JSON解析直接失败,模型返回的arguments字段不是合法的JSON字符串;二是类型校验不通过,比如工具定义中声明参数为number,实际收到的是"25"这样的字符串;三是结构层级错误,期望数组却收到对象,或者缺少required声明的必需字段。
造成这些问题的根本原因主要有三个。第一,工具的JSON Schema定义不规范。很多开发者在写工具描述时偷懒,把所有参数都定义成string类型,或者遗漏了required字段,模型自然无法生成符合预期的参数。第二,模型输出本身具有不确定性。大模型本质上是概率生成器,即使Schema写得很清晰,在复杂场景下仍可能生成不合规的输出。第三,服务端缺乏防御性校验。如果后端接口直接信任模型传来的参数,不做二次校验和类型转换,任何一次模型输出异常都会导致整个调用链路崩溃。
理解了这三层原因,就能明白修复方案必须是立体的:规范工具定义是源头治理,强制结构化输出是过程控制,服务端校验是最后防线。下面分别展开讲解。
二、规范工具定义:写好JSON Schema是第一步
工具定义的质量直接决定模型生成参数的质量。一个规范的工具定义应该包含清晰的name、description,以及严格遵循JSON Schema标准的parameters结构。下面是一个常见的问题写法与正确写法的对比。
先看一个典型的问题定义:
# 有问题的工具定义
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"days": {"type": "string"} # 类型错误,天数应该是整数
}
}
# 缺少 required 字段
}
}
]
这个定义存在多处隐患:description过于简短,模型无法理解使用场景;days被定义成string,导致模型可能传"3"这样的字符串;没有required声明,模型可能省略关键参数。正确的写法应该是:
# 规范的工具定义
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市未来若干天的天气预报,days取值范围1到7",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如:北京、上海"
},
"days": {
"type": "integer",
"enum": [1, 2, 3, 4, 5, 6, 7],
"description": "查询的天数,必须是1到7之间的整数"
}
},
"required": ["city", "days"]
}
}
}
]
这里有几个关键技巧值得注意。使用enum约束枚举值,可以把模型输出限制在合法范围内;在description中明确写出取值范围和格式要求,相当于给了模型额外的提示信息;required字段必须完整声明所有必需参数。此外,如果某个参数实际是嵌套结构,务必在properties中逐层定义子对象的类型,不要笼统地写成object就完事,因为模型在没有子字段定义的情况下,几乎必然生成不符合预期的嵌套结构。
三、服务端防御性校验:最后一道防线不能省
即使工具定义再规范,也不能完全信任模型的输出。服务端必须有独立的参数校验层,对模型传来的参数做类型检查、默认值填充和自动转换。推荐使用Pydantic这样的校验库来实现,它可以在参数不合法时给出明确的错误信息,甚至将错误信息回传给模型让它自我修正。
from pydantic import BaseModel, Field, field_validator
from typing import List
class WeatherArgs(BaseModel):
city: str = Field(..., min_length=1, description="城市名称")
days: int = Field(..., ge=1, le=7, description="查询天数")
@field_validator("days", mode="before")
@classmethod
def coerce_days(cls, v):
# 模型传字符串数字时自动转换,例如 "3" -> 3
if isinstance(v, str) and v.strip().isdigit():
return int(v)
return v
def safe_tool_call(raw_args: str):
import json
try:
args = json.loads(raw_args)
except json.JSONDecodeError:
return {"error": "参数不是合法的JSON,请重新生成"}
try:
validated = WeatherArgs(**args)
except Exception as e:
# 把校验错误信息回传给模型,触发自我修正
return {"error": f"参数校验失败: {e}"}
return get_weather_impl(validated.city, validated.days)
这段代码体现了三个重要的防御策略。第一是宽容转换,通过field_validator的mode="before"钩子,把"3"这类字符串数字自动转成整数,避免因格式小差异导致调用失败。第二是错误回传机制,当校验失败时,不要直接抛异常终止流程,而是把错误信息以工具结果的形式返回给模型,模型看到具体错误描述后通常能在下一轮自动修正参数。第三是边界约束,使用ge和le限定数值范围,配合min_length等约束字符串,把不合法输入挡在业务逻辑之外。
这种错误回传自我修正的模式在实践中非常有效,一般一到两轮循环就能让模型修正绝大多数参数错误,比简单的整体重试成功率高得多。
四、提示词优化与结构化输出:从源头减少类型错误
除了Schema层面的约束,系统提示词的引导作用同样不可忽视。建议在系统提示中明确告知模型调用规范,例如:调用工具时必须严格按照参数定义的类型生成值,数值型参数不要加引号,数组参数必须使用JSON数组格式等。这些看似简单的说明,能显著降低类型错误的发生率。
系统提示词示例: 1. 调用工具时严格遵循参数类型定义。 2. integer和number类型的参数直接写数字,禁止加引号。 3. array类型的参数必须用中括号包裹,即使只有一个元素。 4. 不确定的参数宁可留空让工具报错,也不要编造值。
另一个强力手段是使用模型提供的结构化输出能力。部分大模型API支持response_format或strict模式的函数调用,开启后模型会被强制生成完全符合Schema的参数,类型不匹配问题可以从根本上消除。如果你的模型支持这类特性,强烈建议开启。例如某些API允许在函数定义中设置strict为true,并要求所有字段都列入required,模型输出将严格匹配Schema定义。
最后,还要考虑降级容错。对于可转换的类型错误(字符串转数字、单对象转单元素数组),服务端自动转换即可;对于无法自动修复的错误(缺少必需参数、枚举值非法),采用错误回传加有限次重试的策略,设置最多两到三轮循环,超过次数则记录日志并通知用户,避免陷入无限重试消耗token的死循环。
总结来看,Agent函数调用参数类型不匹配是一个多环节叠加的问题,规范的JSON Schema定义、严格的Pydantic服务端校验、清晰的提示词引导加上结构化输出能力,四层措施组合使用,可以让智能体的函数调用稳定性提升到一个新水平。建议开发者从工具定义规范入手排查,再逐步补齐服务端校验和错误回传机制,这套方案在各类智能体框架中都是通用的。