线上故障复盘:Google API密钥已失效但健康检查仅验证key存在,导致发票识别接口返回虚假成功响应。
把一份扫描发票发送到文档 API,会返回供应商、总金额和到期日期。有一种情况是,回答问题的模型根本没有看到过那张发票。回复看起来和正确答案一模一样:相同的结构、相同的字段、标记成功、被扣费,因为接口是在调用成功时才计费的。
我们的路由离产生那种结果只差一个"善意的改动"。
三个模型提供商共处一个接口,请求中的模型名称决定由哪一个来服务。每个提供商都有一个健康检查,检查逻辑是:密钥是否存在,以及它是否不以 PLACEHOLDER 开头。
在 2026-08-16,那次检查报告一切正常,而公开 API 已经宕机了。那个共享的 Google 凭证已经连同它所属的云项目一起在 2026-08-01 被撤销了。变量仍然被设置着,所以读取起来仍然正常。用生产环境中的确切值对各个提供商进行测试:Google 密钥返回 400 API_KEY_INVALID,Anthropic 密钥返回 200,OpenAI 密钥返回 200。发票预设指定了一个 Google 模型,所以 /v1/invoice 返回了 503,日志行显示提供商是 "stub"、模型是 gemini-2.5-pro,而另外两个可用的提供商就躺在旁边无人问津。
看一个密钥无法知道它是否已被撤销。只有真正发起调用才能发现。所以路由器不再信任"存在性",而是在一个提供商真正失败时尝试下一个——这才是 failover 的本意。

四小时后,用一个附件测量:一通带 PDF 的调用跳转到了 Anthropic,然后到 OpenAI,两次真正的提供商往返分别是 175ms 和 148ms,最终落在 OpenAI 上。
这两个驱动都没有看过那个附件。它们都只根据提示词和消息历史来构建请求。文档被无声无息地丢弃了,模型被要求"提取附带的发票"——却什么也没给它看。用有效的密钥跑一遍会返回成功,而成功正是触发扣费的那个东西。在付费接口上一个自信的虚构比它替换掉的错误更糟糕,因为错误是可见的,而虚构不是。
第一次修复把"有附件就意味着只能用 Google 或干脆不用"硬编码进了路由器。这条规则大概维持了一周,原因只有一个:Google 的驱动是唯一一个会读取文件的。活下来的那个版本改为询问提供商本身。
// claude.ts — every attachment must be a format this rail reads
supportsFiles(files: InferenceFile[]): boolean {
return files.every(
(f) => DOCUMENT_MIMES.has(f.mimeType) || IMAGE_MIMES.has(f.mimeType),
);
}
// the router: a candidate must be able to SERVE the call, not merely hold a key
const usable = ordered.filter(
(d) => d.isConfigured() && (!files?.length || d.supportsFiles(files)),
);
它接收文件而不是回答是/否的标志,因为读取能力是按格式区分的,不是按提供商区分的。
Anthropic 的驱动随后学会了发送原生文档和图片块,所以客户 PDF 第一次有了真正的第二选择。
替换的 Google 凭证是一个免费层级的 AI Studio 密钥,而 Google 的免费层级的内容会被用来改进他们的产品。ParseRail 的安全页面说,你发送的文档"不会被保留为训练数据或用于我们自己的用途"。一份发票经过那条轨道一次就足以让那个公开的声明变成假的,所以这个产品根本不使用 Google 凭证。
这就让音频无处可去了。/v1/transcribe 现在在接口目录中携带了 unavailable: { since: "2026-08-19", reason },请求处理器在做任何工作之前先读取同一条目,然后将原因原样返回,接口浏览器的徽章给它标记为 Paused。
这是我们构建 ParseRail 的方式:https://parserail.kynth.studio/?utm_source=kynth-devto&utm_medium=social&utm_campaign=kynth
每月一次,拆解一个已上线的产品。它做什么、构建它的成本是多少、背后的流水线长什么样、以及那些数字是多少——从仓库和实时站点上读出来的,不是凭记忆写的。订阅加入列表。