Gemini 函数参数怎么校验
Gemini 的函数参数要当成不可信输入:官方最佳实践要求在真正执行前校验每一次函数调用,校验至少覆盖类型、范围与调用者权限。(核验 2026-11-05,F87)
一、Gemini 函数参数怎么校验:问题在鉴权与幂等没人替你管
声明里写了 "type": "integer"、写了 required,很多人的校验就到此为止——schema 已经约束了,模型会照着给。
这句话只对了一半。schema 约束的是形状:字段在不在、是不是整数、枚举值合不合法。它约束不了业务值:这笔退款金额合不合理、这张工单是不是这个用户能处理的、这个操作是不是已经执行过。
模型的参数不是用户填的表单,但同样不可信——它是模型基于上下文推断出来的文本,可能对得很准,也可能把 30 写成 90。官方的最佳实践里有一条专门讲这件事:「Validate function calls before executing」(F87)。注意是 before executing,不是 before responding:校验的位置在执行之前,不在事后补救。
二、Gemini 函数参数校验中间件:最小可运行示例
下面这段把四类问题放进同一个校验器:缺字段、范围越界、越权、合法。
## EXPECT: 合法 1 条 | 拒绝 3 条:缺字段:reason、范围越界:amount、越权:ticket_id
SPEC = {
"ticket_id": {"type": str},
"amount": {"type": int, "min": 1, "max": 40},
"reason": {"type": str},
}
OWNER = {"TKT-A": "u-7"}
CALLS = [
{"ticket_id": "TKT-A", "amount": 30, "reason": "重复扣款"},
{"ticket_id": "TKT-A", "amount": 30},
{"ticket_id": "TKT-A", "amount": 90, "reason": "重复扣款"},
{"ticket_id": "TKT-B", "amount": 30, "reason": "重复扣款"},
]
def validate(args, spec, user):
problems = []
for field, rule in spec.items():
if field not in args:
problems.append(f"缺字段:{field}")
continue
# bool 是 int 的子类,不单独挡会被当成合格整数,处理方式见 5.1
if rule["type"] is int and isinstance(args[field], bool):
problems.append(f"类型错:{field}")
continue
if not isinstance(args[field], rule["type"]):
problems.append(f"类型错:{field}")
continue
if "min" in rule and args[field] < rule["min"]:
problems.append(f"范围越界:{field}")
if "max" in rule and args[field] > rule["max"]:
problems.append(f"范围越界:{field}")
if not problems and OWNER.get(args["ticket_id"]) != user:
problems.append("越权:ticket_id")
return problems
results = [(args, validate(args, SPEC, "u-7")) for args in CALLS]
ok = [a for a, p in results if not p]
rejected = [(a, p) for a, p in results if p]
assert len(ok) == 1 and len(rejected) == 3
print(f"合法 {len(ok)} 条 | 拒绝 {len(rejected)} 条:" +
"、".join(p[0] for _, p in rejected))
四条调用里,schema 能挡住的只有第二条(缺 reason)和第三条的一半(amount 是整数没错,只是太大)。第四条 TKT-B 在 schema 眼里完全合法——字段齐、类型对、在范围内,但它属于另一个用户,只有应用侧的权限校验能发现。
三、解释与边界:五道检查,三道必须自己写
类型与必填,schema 能管。 这部分交给 parameters 里的类型与 required 是最省事的,模型侧就会尽量避免给出错误形状。但别指望它百分百——官方的最佳实践仍然要求执行前再校验一次(F87),因为「约束生成」不等于「保证输出」。
范围,schema 只能管一部分。 数字的上下界、字符串长度这些,在结构化输出里有的会被服务端剥离(见 5.3);在函数声明里则会增加状态机的复杂度(F85)。所以金额上限这类规则,写在 schema 里是加分,写在应用侧是保底。
三道必须自己写:鉴权、幂等、审计。先说鉴权。 模型不知道谁在调用,它只看到对话内容。ticket_id 属于谁、这个用户有没有权限操作,只能查你自己的数据。这一道是本节最不能省的一道——漏了它,前面所有校验都是装饰。
幂等,必须自己写。 模型可能在同一轮或跨轮重复提议同一个写操作(见 6.1 的多轮重复)。校验器要能识别「这单已经退过款」,通常靠业务键做唯一约束,而不是靠模型不重复。
审计,必须自己写。 每次调用记下:谁发起、参数是什么、校验结果、执行结果。这条不在官方最佳实践的清单里,但在真实系统里比前四条更常被用到——出事时它是唯一的证据链。
一个容易忽略的限制:参数类型是有限的。 官方明确说 Python 支持的参数类型有限(F86),声明只支持 OpenAPI schema 的一个子集。所以复杂结构(联合类型、嵌套对象、自定义类型)常常要退化成 string 加一段 JSON 字符串,再在应用侧解析——退化之后,校验的责任就整段回到了你的代码里。
校验失败之后,别只抛异常。 校验器最常见的写法是 raise ValueError("参数不对"),然后整轮交互结束。更有效的做法是把「哪个字段、期望什么、实际收到什么」结构化成一条工具结果回传给模型(也就是四步流程里的第四步,见 6.1),让它自己改一次再试。官方在函数调用的四步流程里给了这个回路(F82),用不用在你。实践上的差别很大:回传具体原因,模型第二次多半能给出合法参数;只抛一个笼统的异常,它只会把同样的错误再犯一遍。要注意上限——同一轮里只允许它改一次,改不动就降级,理由与重试上限一致,见 5.4。
校验器本身也会骗人。 只校验「字段在不在、类型对不对」,会让一批业务上不可行的参数通过——金额在范围内但账户余额不足,工单归属正确但状态已经关闭。这类检查写不进通用校验器,得放在具体业务函数的第一行。分界线可以这样划:与业务无关的规则(类型、范围、必填、权限)放通用层,与业务状态相关的规则(余额、状态机、库存)放业务层,两层都过才执行。把这条线划清楚,通用校验器才不会越写越像业务代码。
边界。 本节只管「参数进来时怎么挡住」。schema 怎么写不超复杂度上限,见 5.3、见 6.2;白名单放在哪一层,见 6.3;执行环与幂等的实现,见 6.1。日志脱敏见 12.2。
四、预期输出与异常分支
真跑输出:
合法 1 条 | 拒绝 3 条:缺字段:reason、范围越界:amount、越权:ticket_id
六类分支。模型给了负数金额:范围校验能挡住,但要想清楚业务语义是「退款」还是「扣款」,两者符号规则不同。字段齐、类型对、但对象不存在:ticket_id 格式合法却查无此单,这在鉴权分支里表现为「无权限」,会误导排查——建议把「不存在」与「无权限」分开报。参数全对但重复提交:幂等没做,退款退两次,这是校验器最抓不到的一类。复杂类型退化成字符串:声明里写成 string,实际是序列化后的 JSON,解析失败要在校验器里显式报错,别静默忽略。校验通过但执行超时:校验器不负责超时,执行环要有超时与重试边界,见 11.1。模型给了 schema 之外的字段:多数实现会忽略,但忽略之前先确认它不是你想接收的——多出来的字段常常是模型在表达「我还需要别的参数」。
五、判据卡:校验失败该返回什么
| 该这么做 | 不该这么做 |
|---|---|
| 校验放在执行之前,失败不进业务逻辑 | 先执行,出错再回滚 |
| 鉴权与幂等必须在应用侧做 | 认为 schema 约束了类型就安全 |
| 把「不存在」与「无权限」分开报 | 一律回「参数错误」 |
| 记录每次调用的参数与校验结果 | 只记成功,不记被拒 |
六、来源与核验
https://aistudio.google.com/docs/function-calling.md(核验 2026-11-05,F85、F86、F87)
「执行前校验」与参数类型限制属于「API 结构」档事实,复核周期三个月,见 1.3。若官方放宽支持的参数类型,本节里「退化成字符串处理」的建议要重新评估。
七、动手试
- 把
CALLS第一条的amount改成90,重跑。预期合法 0 条 | 拒绝 4 条,拒绝原因里多出一条范围越界;验收标准是你能说清「上限从哪来」,这个值不该拍脑袋。 - 给
validate加一条幂等检查:维护一个「已处理过的 ticket_id」集合,重复出现时返回「重复提交」。重跑。验收标准:第四条调用同时命中越权与重复时,你能决定先报哪一个、为什么。 - 打开你项目里函数调用的执行入口,找一下鉴权写在哪。验收标准:能指出「这个 ticket 属不属于当前用户」的判断在哪一行;找不到,就说明这一道目前是空的。