codecamp

Gemini 工具白名单放在哪一层

Gemini 的工具白名单分三层:不声明、allowed_tools 限定、执行环兜底;tool_choice 有 auto/any/none/validated 四种模式,写在 generation_config 里。(核验 2026-11-05,F84)

一、Gemini 工具白名单放在哪一层:问题不是写不写清单,而是清单管不管得住

「白名单」这个词在提示词工程里被用得很随意——很多人理解的实现,是在系统指令里加一句「你只能使用查询类工具」。这句话有用,但它不是白名单,是请求。模型可以不遵守请求,代码不会因此报错。

真正的白名单要能在模型不配合时也生效。官方在函数调用页给了两个可落地的位置:一是 tool_choice 里的 allowed_tools,用 mode 加 tools 限定这一次请求能用哪些函数(F84);二是工具声明本身——没写进 tools 的函数,模型连知道它的机会都没有。

把这两处再加上执行环的注册表,就形成三层:声明层、协议层、执行层。三层挡的东西不一样,少一层就有绕过路径。

二、Gemini 工具白名单三层校验:最小可运行示例

下面这段把三层依次跑一遍,看每条提议死在哪一层。

## EXPECT: 声明层拦 1 个:delete_all | 协议层拦 1 个:refund | 执行层拦 0 个:无 | 最终可执行 1 个:get_ticket
DECLARED = {"get_ticket", "refund"}          # 声明层:写进 tools 的函数
ALLOWED = {"get_ticket"}                     # 协议层:allowed_tools.tools
REGISTRY = {"get_ticket": lambda **kw: {"ok": True}}   # 执行层:真正实现了的函数
PROPOSALS = ["get_ticket", "refund", "delete_all"]

def review(proposals, declared, allowed, registry):
    passed_decl = [n for n in proposals if n in declared]
    blocked_decl = [n for n in proposals if n not in declared]
    passed_proto = [n for n in passed_decl if n in allowed]
    blocked_proto = [n for n in passed_decl if n not in allowed]
    passed_exec = [n for n in passed_proto if n in registry]
    blocked_exec = [n for n in passed_proto if n not in registry]
    return passed_exec, (blocked_decl, blocked_proto, blocked_exec)

passed, (bd, bp, be) = review(PROPOSALS, DECLARED, ALLOWED, REGISTRY)
assert passed == ["get_ticket"] and bd == ["delete_all"] and bp == ["refund"]
exec_text = "、".join(be) if be else "无"
print(f"声明层拦 {len(bd)} 个:{'、'.join(bd)} | 协议层拦 {len(bp)} 个:{'、'.join(bp)} | "
      f"执行层拦 {len(be)} 个:{exec_text} | 最终可执行 {len(passed)} 个:{'、'.join(passed)}")

三层拦下的不是同一类风险:delete_all 根本没被声明,模型是「凭空发明」了一个名字;refund 声明了、但这次请求不允许;get_ticket 一路通过。真实系统里三层都该有,因为每一层能防的绕过方式不一样。

三、解释与边界:三条机制,加一条数量预算

声明层防「知道」。 没写进 tools 的函数,模型不可能正确调用——它能编出一个名字,但参数格式、字段含义全靠猜,成功率极低。这一层最省事:不想让它做的事,别写进声明。

协议层防「选错」。 tool_choice 写在 generation_config 里,allowed_tools.tools 可以列出这次允许的函数名(F84)。四种模式各管一件事:

模式 行为 适合场景
auto 模型自己决定调不调 默认,绝大多数对话
any 必须调函数 「一定要给出结构化调用」的抽取任务
none 禁止调函数 纯问答,或工具故障时的降级
validated 模型的调用必须符合声明 schema,形状不对由服务端拦住 参数形状必须严格时

模式回答的是「要不要调」,allowed_tools 回答的是「能调哪些」,两个维度不要混。有人在 any 模式下调了半天提示词想让它别调工具——模式已经强制了,改提示词没用。

执行层防「说得到做不到」。 声明了、也允许了,但代码里没实现(或者被 feature flag 关了),这时候注册表返回 None,请求照样能被拦住。这一层是最后一道,也是最容易被忽略的一道——因为它不在 API 文档里,在你自己代码里。

三层之外还有一条数量预算。 官方建议同时激活的工具控制在 10 到 20 个以内(F87)。工具表越长,模型选错的概率越高,而且所有声明会一起参与编译(见 6.4 的复杂度上限)。白名单不只是「允许哪些」,也包括「最多允许几个」。

错误码对应哪一层? unexpected_tool_call 表示模型调用了没声明的工具(F27)——出现它,说明声明层或协议层没有把它收住,先去核对这次请求到底传了哪些 tools,而不是去改提示词。

白名单是长期资产,要按变更管理对待。 三层里最容易腐化的不是代码,是清单:某个功能下线了,声明没删;某个接口改名了,白名单还留着旧名;测试环境放开的工具,上线时忘了收。腐化的直接后果是「模型能调一个已经不存在的东西」,报错信息还指向模型。可操作的做法有三条:清单与代码同仓、改动走评审;按环境区分,测试与生产的 allowed_tools 不共用一份;每次发布前跑一次「声明 — 白名单 — 注册表」三者取交集的脚本,把差集打出来。这个脚本很短,但它能把「以为收紧了、其实没生效」这类问题提前一段暴露。

边界。 本节只管「能不能调」。调了之后参数对不对,见 6.4;多个工具怎么排序,见 6.2;执行环本身怎么写,见 6.1。白名单不是权限系统的全部——真正的权限还在你的鉴权层,别指望工具清单替它干活。

四、预期输出与异常分支

真跑输出:

声明层拦 1 个:delete_all | 协议层拦 1 个:refund | 执行层拦 0 个:无 | 最终可执行 1 个:get_ticket

六类分支。模型编了没声明的工具名:这一层拦得住,但要在日志里记下来,频繁出现说明声明描述漏了能力。声明了但这次不该用:靠 allowed_tools 收窄,别靠删声明——删了会让其他正常场景也用不了。unexpected_tool_call 频发:先核对请求里 tools 的实际内容,再看 tool_choice 的模式,最后才怀疑模型(F27)。any 模式下工具全被白名单排掉了:请求会失败或空转,白名单和模式要一起设,别只改一个。执行层没有对应实现:看起来是「白名单过了」,实际跑起来返回 None,这类最隐蔽,因为它不报错。声明与白名单不一致:某次热更新改了声明、白名单没跟上,表现为「这个功能时好时坏」;发布前跑一次三者交集检查就能提前发现。

五、判据卡:白名单该放在哪一层

该这么做 不该这么做
不想让模型碰的能力,根本不写进声明 写进声明再用提示词禁止
用 allowed_tools 按请求收窄范围 为一次特殊场景删掉整条声明
执行环注册表兜住「声明了但没实现」 假设 API 允许就等于代码能跑
工具总数控制在 10 到 20 个以内 把所有工具挂上去,让模型自己挑

六、来源与核验

  • https://aistudio.google.com/docs/function-calling.md(核验 2026-11-05,F84、F87)
  • https://aistudio.google.com/docs/api-errors.md(核验 2026-11-05,F27)

四种模式与 allowed_tools 的位置属于「API 结构」档事实,复核周期三个月,见 1.3。若官方新增模式或调整字段嵌套,本节的对照表要整表重核,不能只补一行。

七、动手试

  1. 把 ALLOWED 改成 {"get_ticket", "refund"},重跑。预期 协议层拦 0 个、最终可执行 2 个;验收标准是你能说清「这次放宽的是哪一层,以及为什么声明层仍然拦得住 delete_all」。
  2. 把 REGISTRY 里的 get_ticket 删掉,重跑。预期它在执行层被拦;验收标准是给出一个「声明了、允许了、但代码没实现」的真实场景(提示:feature flag 关掉的旧接口)。
  3. 打开你项目的工具清单,数一下这次请求真正传了几个 tools。验收标准:给出数量与各自的用途;超过 20 个,就把不常用的拆到另一条链路去。
Gemini 工具组合怎么排
Gemini 函数参数怎么校验
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }