触发条件

在同一个请求的 tools 数组里混用不同格式族的工具声明:

  • OpenAI / function-calling 风格:{"type": "function", "function": {"name": "...", "parameters": {...}}}
  • Gemini 风格:{"googleSearch": {}}、{"codeExecution": {}} 等裸键对象

症状

  • 只发 {"tools": [{"googleSearch": {}}]}:可以通过,接口正常识别。
  • 只发纯 function-calling 数组:可以通过。
  • 把两者放进同一个 tools 数组:被拒,报 schema 校验错误,接口不认。

尝试过的做法

  • 确认单个工具定义本身格式正确({"googleSearch": {}} 单独发没问题)。
  • 尝试直接拼接两种格式的数组 → 失败。

根因(推断)

接口/网关在解析 tools 时按元素形态探测格式族:

  • 数组里出现 {"function": {...}} → 走 OpenAI function-calling 解析器 → 不认 googleSearch;
  • 数组里出现 {"googleSearch": {}} → 走 Gemini 风格解析器 → 不认 function;
  • 混合时探测分支或校验器要求数组内元素同构,直接拒绝。

核心规则:tools 数组格式族必须自洽,不能混用。 单一形态时无歧义,所以能过。

最终解决

  • 全部用 OpenAI 风格:[{"type": "function", "function": {...}}, ...]
  • 或全部用 Gemini 风格:[{"googleSearch": {}}, ...]
  • 需要两类能力时,走代理层做格式转换(把 Gemini 声明映射成 function-calling 或反之),而不是在同一数组里拼接。

变体:格式错层 → 静默丢弃(而非报错)

与"混用被拒"不同的第二种表现:数组内部自洽,但与接收方约定的格式族不一致 → 不报错,整个 tool 被静默丢弃。

触发条件

  • 原请求(如 req.json 抓包)中 tools 是 OpenAI 风格 {"type": "function", "function": {...}},说明这条链路(网关/代理)只认该格式;
  • 用户用 Gemini 原生格式 {"functionDeclarations": [...]} 覆盖 tools(自己做了格式转换),再追加 {"googleSearch": {}}。

症状

  • 请求成功,不报错;
  • 模型回复"只看到 Google search"——25 个函数全部不可见。

根因

  • 网关解析 tools 时按 type + function 形态找函数:functionDeclarations 条目匹配不上任何函数 → 整项丢弃;
  • googleSearch 是 Gemini 原生特殊工具,网关不做转换直接透传 → 存活。
  • 判定信号:请求成功 + 部分工具消失 + 消失的是被改写格式的那部分,说明中间有网关在做格式解析/转换,而非模型或直连 API 的问题。

修复

  • 与产生原请求的格式族保持一致:OpenAI 风格函数数组 + 追加 {"googleSearch": {}} 独立元素(已验证可透传)。
  • 若直连 Gemini API(无网关):functionDeclarations 格式本身正确,但 Claude Code 原生 schema 带的 $schema 和 additionalProperties 字段会被 Gemini 严格校验,需先剥掉;且直连时非法 schema 通常整请求 400,不会出现"只看到部分工具"。

下次如何识别

  • 报错指向 tools / tool / schema 校验,且你的数组里同时存在 function 键条目和 googleSearch/codeExecution 等裸键条目 → 就是这个问题。
  • 单独发某种格式没问题、合在一起被拒,是典型的"格式族探测/同构校验"信号。
  • 请求成功但模型声称只看到部分工具 → 不是模型问题,是覆盖的 tools 格式与链路约定不一致,被网关静默丢弃。用抓包里的原始工具格式对照即可定位。

相关概念


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/AI apps