Gemini 3.7 Flash 模型调用失败排查记录

2026年09月04日1 次阅读0 人喜欢
Geminicli-proxy-apiNew-API踩坑记录API网关
所属合集

更新了一个代理工具后,我的 Gemini 模型突然不能用了——五层代理链路排查实录?

昨天下午,我像往常一样让 Hermes Agent 跑一个任务,用的是 Gemini 3.7 Flash。结果直接炸了:

text 复制代码
HTTP 400: unknown provider for model gemini-3.7-flash

第一反应:这不对啊,昨天还能用的。

先说一下我的架构,不然后面看不懂。我搭了一套自建 AI 代理链路:

Hermes Agent → New-API 网关 → cli-proxy-api (CPA) → Google Antigravity/Codex 订阅

简单说就是把订阅制的 AI 服务转成 API 端点。CPA 是中间层,负责把请求路由到正确的上游 provider。New-API 在上面做模型管理和分发。Hermes 是最上层的 Agent 框架。

这套链路跑了几个月,一直很稳。直到今天。

/v1/models 在骗你

出错之后,我第一件事是去查 /v1/models。打开一看,gemini-3.7-flash 好端端地躺在列表里。

那没事了?不对,400 报错明摆着说这个模型找不到 provider。

这就是第一个坑:/v1/models 是一个目录,不是一个健康检查。 它告诉你"我知道这个模型存在",但不保证"我能正确路由这个模型"。看到模型在列表里,你会产生虚假的安全感。我盯着那个列表看了两分钟,心想这不是好好的吗?

后来我才意识到,这个 API 的语义是有问题的。一个模型出现在 catalog 里,不代表它能工作。这跟去餐厅看菜单上写着"龙虾",点的时候告诉你"今天没有"是一个道理。

第一刀砍向 Hermes 配置——砍错了?

我用的是 Hermes Agent,它有一个 reasoning_effort 参数,可以控制模型的推理强度。之前我配置过 gemini-3.7-flash-high,也就是高推理模式。

我的第一个猜测:是不是 reasoning_effort 的值和模型名冲突了?Hermes 在拼接模型名的时候会不会搞出一个不存在的变体?

我翻了一遍配置,把 reasoning_effort 相关的设置检查了个遍。没发现问题。Hermes 这边的配置是干净的。

第一刀砍空了。

第二刀砍向 New-API 渠道——看起来没问题

New-API 是我的模型网关,管理着多个上游渠道。我登进去看 channel 配置。

gemini-3.7-flash 在模型列表里,渠道状态正常,token 也没过期。一切看起来都对。

但问题是,New-API 本身不做模型路由决策。它只是个转发层,把请求甩给下游的 CPA。所以 New-API 这边看到的"正常",其实只是"我知道这个模型名字",跟 /v1/models 一样,是目录层面的信息,不是路由层面的。

第二刀也砍空了。

第三刀砍向 Google 上游——但错误根本没到那一层

这时候我开始怀疑是不是 Google 那边改了什么。Gemini 3.7 Flash 是比较新的模型,会不会 Google 调整了 API 接口?

但我回头看错误信息:unknown provider for model gemini-3.7-flash

注意这个措辞。"unknown provider"——这是 CPA 返回的错误,不是 Google 返回的错误。请求根本没到 Google 那一层,就被 CPA 拦下来了。

这个认知转折很关键。错误发生在 CPA 层,不是上游层。

第三刀方向就错了。

SSH 连上去看 CPA 日志

既然问题在 CPA,那就得上 VPS 看日志。

连上之后,先看 CPA 的进程状态。健康,正常运行。认证文件也都在。

然后翻日志。CPA 的日志其实很详细,但我之前一直没仔细看过。这次仔细一看,发现了关键信息:

CPA 把 gemini-3.7-flash-high 截断成了 gemini-3.7-flash

等等,什么?

我再看一遍。CPA 有一套后缀剥离逻辑(suffix-stripping)。当你传入 gemini-3.7-flash-high 的时候,CPA 会把 -high 后缀剥掉,只保留 gemini-3.7-flash,然后用这个基础名去查 provider 路由。

这个设计本身是有道理的——上游只认基础模型名,后缀是客户端的配置语义。但问题来了:剥完之后,CPA 去自己的模型注册表里查 gemini-3.7-flash 对应哪个 provider。

查到了。但查错了。

GitHub 上翻 CPA 的模型注册表

我去 CPA 的 GitHub 仓库看了模型注册表。在 v7.2.139 这个版本的更新里,有人把 gemini-3.7-flash 加到了一个错误的 provider 下面。

原来 gemini-3.7-flash 应该走 Antigravity provider(也就是我订阅的那个),但新版本的注册表把它归到了另一个 provider。结果 CPA 拿着模型名去查 provider,查到了一个我没有订阅的、或者根本不对的 provider,然后报了 unknown provider

一个版本更新,一行配置改动,整个路由就废了。

这就是根因:CPA v7.2.139 的模型注册表变更,导致 gemini-3.7-flash 的 provider 路由指向错误。

修复:强制指定 provider 别名

知道了原因,修复就简单了。CPA 支持 oauth-model-alias 配置,可以在本地强制把某个模型名映射到指定的 provider。

我在 CPA 的配置里加了一行:

text 复制代码
oauth-model-alias: gemini-3.7-flash → antigravity

重启 CPA,测试,通了。

以为搞定了?第二颗地雷当场爆炸

curl 测通之后,我以为这事就算彻底翻篇了。

结果把 Hermes Agent 切回默认模型 gemini-3.7-flash-high,准备让它接着跑任务,刚一敲回车,屏幕上直接甩出一行鲜红的报错:

text 复制代码
HTTP 400: reasoning settings conflict for model "gemini-3.7-flash-high": explicit effort "medium" differs from suffix effort "high"

我当时愣在屏幕前,直接给气笑了。

前面我还信誓旦旦写着“第一刀砍向 Hermes 配置——砍错了”,反思自己是不是疑神疑鬼。

合着我当时的第一直觉根本没有砍错

那为什么刚才用 curl 测是通的?

因为前面在排查 CPA 路由时,为了排除干扰,我用的是最干净的最小化 curl 请求:

bash 复制代码
curl -X POST https://ai-gateway.nnnnzs.cn/v1/chat/completions \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{"model": "gemini-3.7-flash-high", "messages": [{"role": "user", "content": "hello"}]}'

这个最小化请求里,根本没有带任何推理思考相关的参数!CPA 配上别名后,这个干净的请求一路绿灯通到了 Antigravity,让我产生了“整条链路已经彻底修好”的假象。

但真实的 Agent 不是裸奔的 curl。Hermes 在它的全局配置里,默认配了 agent.reasoning_effort: medium

这就意味着,只要通过 Hermes 发请求,哪怕你选的是 gemini-3.7-flash-high,Hermes 也会老老实实按标准在请求体里加上 reasoning_effort: "medium"

然后,这个请求被送到了中间层的 New-API。

顺藤摸瓜:第二颗雷来自 New-API 的最新版本

看到这句报错:

explicit effort "medium" differs from suffix effort "high"

很显然,这不是上游报错,也不是 CPA 报错,这是 New-API 网关在卡我

我跑去 New-API 的 GitHub 仓库翻源码,结果彻底破案——这居然也是昨天刚更新出来的“新特性”。

就在昨天,我把 VPC 上的容器顺手升级到了最新版(v1.0.0-rc.31)。而在 9 月 1 日,New-API 刚合入了一个大重构 PR(#7137),在 relaykit/relayconvert/reasoning/intent.go 里加了一段极其严格的硬编码校验:

go 复制代码
if !explicitDisabled && explicit.Effort != "" && suffix.Effort != "" && explicit.Effort != suffix.Effort {
    return Intent{}, fmt.Errorf("%w for model %q: explicit effort %q differs from suffix effort %q", 
        ErrEffortConflict, model, explicit.Effort, suffix.Effort)
}

作者的逻辑是:你模型名后缀带着 -high(说明你想要 high),请求体里又传了 reasoning_effort: "medium",意图冲突了,我不猜你到底听谁的,直接抛 400 掐死。

在升级前的旧版本(v1.0.0-rc.24)里,压根没有这段严格校验,参数要么透传要么忽略,整套链路一直跑得稳稳当当。

巧就巧在,昨天我把两个容器同时升级到了 latest:

  1. CPA 升级到 v7.2.149:踩中了 v7.2.139 新增的模型注册表错位,报 unknown provider(雷一);
  2. New-API 升级到 rc.31:踩中了 PR #7137 的推理参数冲突死锁,报 convert_request_failed(雷二)。

雷一被修好后,极简的 curl 绕过了雷二;一旦回到真机 Agent,雷二被准时引爆。

最终修复

既然找到了第二颗雷,解决思路也就明确了。

既然 New-API 觉得 Body 里的 medium 和模型名里的 -high 打架,而谷歌这个特化模型本身就已经通过名字锁定了 high 强度,那最优雅的做法就是去 Hermes 的 config.yaml 里,把全局的 agent.reasoning_effort 清空为 ''

这样 Hermes 不再向请求体强行注入 medium,New-API 看到模型名带 -high 就会自然当成 high 透传,两边终于不再打架,终端秒回 pong。

终极反转:我去官方仓库提 Issue,被当场抓包“自导自演”

到了今天早上,事情迎来了最具戏剧性的终局。

昨晚为了彻底绕开 New-API rc.31 的 ErrEffortConflict 冲突,我们当时想了一个“一劳永逸”的办法:
既然模型名里的 -high 思考后缀会跟 reasoning_effort 参数打架,那干脆把全链路洗干净,所有配置统统改成 gemini-3.7-flash

改完之后我们信心满满地去调 CPA,结果 CPA 当场甩了我们一个大耳光:

text 复制代码
HTTP 400: unknown provider for model gemini-3.7-flash

当时我们看着这个报错,第一反应是:“好啊,我明明请求的是带有 Antigravity 权限的模型,你 CPA 肯定是自作聪明在中间把连字符后缀给偷偷切掉了,切完之后注册表对不上号,才报了这个错!”

越想越觉得是 CPA 的锅,于是我们义愤填膺地跑到 CPA 的官方 GitHub 仓库(router-for-me/CLIProxyAPI)提了 Issue #5504,言之凿凿地质问开发者:“你们 v7.2.139 引入的模型注册表有问题,收到 gemini-3.7-flash-high 之后把它剥离成了 gemini-3.7-flash,导致 provider 匹配失败!”

结果几个小时后,官方开发者的回复让我们在屏幕前脚趾抠地:

Could not reproduce against current dev.

  1. The routing path has no dash-suffix stripping. thinking.ParseSuffix only strips parenthesized suffixes (model(high)), so gemini-3.7-flash-high is looked up verbatim...
  2. The quoted error text prints the caller-supplied model name verbatim... so on upstream code the base name could only appear there if the request itself used the base name.

人家把源码翻了个底朝天:

  1. CPA 压根就没有任何剥离中划线 -high 后缀的逻辑! 它只剥离括号类型的后缀(如 model(high)),传 -high 就是原样照搬去查路由;
  2. 报错里打印出来的名字,就是调用方自己传进来的名字! 报错显示 gemini-3.7-flash,铁证如山,说明就是我们自己在客户端发了不带后缀的干净名!

这下彻底破案了——
不是 CPA 自作聪明把后缀剥掉了,是我们自己为了治 New-API 的 400,自作主张把后缀给删了,然后跑去冤枉 CPA 把它剥离了! 妥妥的“自导自演”。

盖棺定论:真正的罪魁祸首只有一个

顺着官方开发者的回复,我们把整条五层代理链路从底向上彻底做了一次全量审计,终于看清了所有真相:

  1. 底层 Antigravity(Google OAuth 代理)
    人家官方接口规范就是把思考模式直接编码在模型名里。在 Antigravity 的世界里,这个模型天生就叫 gemini-3.7-flash-high,根本不存在所谓的“干净名 gemini-3.7-flash”。
  2. 中间层 CPA (cli-proxy-api)
    CPA 从头到尾都是忠实的透传者。它收到什么就向上游找什么,既没有乱剥连字符后缀,也没有改坏注册表。
  3. 顶层客户端 Hermes Agent
    Hermes 遵循标准 OpenAI 协议,通过请求体里的 reasoning_effort 表达思考意图,逻辑也很正统。

那到底是谁在犯轴?
真正的始作俑者,从头到尾就只有那一个:New-API v1.0.0-rc.31 那个自作聪明的 PR #7137!

它在网关层横插一脚,搞了一个极其死板的“意图冲突审查”,非要把模型名里的 -high 和请求体里的 medium 判定为不可调和的死罪(ErrEffortConflict)。正是这颗地雷,把我们逼得病急乱投医,以为带后缀是脏数据,一通乱改成干净名,结果在 Antigravity 面前碰壁,反向把锅扣到了无辜的 CPA 头上。

最终优雅解法

想通了这一层,解决方案自然水落石出,甚至比之前加 alias 的方案还要干净纯粹:

  1. New-API 降级回 v1.0.0-rc.30(或者等待官方修复这种粗暴的冲突拦截);
  2. 全链路统一敬畏底层:既然底层 Antigravity 就叫 gemini-3.7-flash-high,那就从 Hermes 到 Cron 任务、再到 New-API 渠道,全量统一使用原生带后缀的模型名;
  3. 撤销一切别名 hack:CPA 侧不需要配任何奇奇怪怪的 oauth-model-alias,原样透传即可。

改完之后,Issue #5504 光速道谢关闭,定时任务与 Agent 会话秒级绿灯,世界终于清静了。

折腾了两天两夜,这篇长文终于迎来了真正的全剧终。还是那句话:在复杂的微服务与代理链条里,不要轻易给任何一个你没看懂的参数做“美化”;有时候你以为在给系统洗脸,其实是把它赖以生存的身份证给擦掉了。

这件事让我想到的

整个排查折腾了一整晚,经历了一次“以为修好 -> 复盘记录 -> 当场打脸 -> 二次破案”的完整闭环。

几个更深层的感想:

多层代理的“连环车祸”,往往是每个人都在各自自作聪明。
回顾整条事故链:

  • 底层 Antigravity:特立独行,非要把动态思考强度拼进模型名里(gemini-3.7-flash-high);
  • New-API:rc.31 搞意图审查,非要硬卡参数与名字的一致性,抛 400 把整条链路掐死;
  • 排障的人(也就是我):病急乱投医,以为带后缀是原罪,擅自给模型名“洗脸”,结果自导自演把无辜透传的 CPA 送上了被告席;
  • CPA:默默承受了这一切,直到官方开发者甩出源码,才沉冤得雪。

每一个中间件的开发者都在做自己认为“合理”的防御和容错,但当它们串联成五层链路时,这几个自以为是的逻辑刚好互相卡脖子,把整条下游链路送上了天。

永远不要相信“最小化 curl 跑通了就等于修好了”。
运维排障时用 curl 做控制变量法是标准动作,但它有个致命盲点:极简请求剥离了生产环境的全部复杂上下文。如果一个问题看似修好了,一定要立刻放回真实的 Agent、真实的业务脚本里跑全流程,否则你以为的万事大吉,可能只是还没触发下一颗雷。

模型注册表应该是可测试的,不应该只是声明式的。
CPA 的模型注册表就是一个静态配置,你没法在部署前跑一个测试说"这个模型能不能路由到正确的 provider"。如果有一个 smoke test,在每次更新后自动验证关键模型的路由,这个 bug 根本不会上线。

/v1/models 返回模型列表不代表你能用这个模型。
这可能是整个排查过程中最让人困惑的一点。你看到模型在列表里,就觉得应该没问题。但实际上这个 API 只是一个目录,不是健康检查。它不验证路由是否正确、provider 是否可用、凭证是否有效。这是一个 UX 问题——用户理所当然地认为"列出来了就能用"。

写在最后

自建 AI 代理栈的好处是灵活、可控、成本低。代价就是你得自己当运维。当五层代理里任何一层出了问题,你得像个侦探一样,从错误信息里倒推到底是哪一层出了问题。

这次的教训是:当你看到一个模型"突然不能用了",先去看最底层代理的日志,而不是从最上层开始猜。 那些看起来"没问题"的中间层,只是告诉你它们知道这个模型的名字,不告诉你它们能不能正确转发。

还有就是,关注你依赖的工具的 changelog。在多层代理架构里,永远尊重最底层的原始接口契约,不要轻易在中间层自作主张做所谓的“标准化清洗”。如果提前看了,可能少踩一半的坑。

加载评论中...