把额度耗尽当路由信号:多模型串联的切换与降级规则

我们见过的多数模型路由代码,切换条件写得很粗:请求失败就换下一个。这个写法在真实环境里会出两类事故——额度用完之后一路疯狂重试,或者网络抖一下就把流量悄悄切到更贵的通道上。有一份开源语音输入工具的实现把这件事做得少见地干净:它把多个识别模型串成一条链,只在一种明确的返回码上切换,并给耗尽的模型设冷却期,全部耗尽就提示用户,同时把实时模式降级成分段模式。规则一共四条,但它给的是一个完整样本:配额可以当成路由信号来用,前提是你先把它和故障分开。
一、先把”用完了”和”坏了”拆成两件事
按该项目的做法,它接的是一个模型服务平台,控制台侧有两步前置:创建密钥,以及在额度页面打开”免费额度用完即停”。第二步容易被当成计费设置跳过去,但它其实是路由逻辑能不能工作的前提——不打开,超出部分直接开始计费,程序永远收不到一个可以辨识的”耗尽”信号,后面写的切换分支形同虚设。
这个设计值得琢磨的地方是:它把”配额”从一个账务概念变成一个可观测的事件。事件要有触发条件、有后果、有恢复时间。触发条件是明确的返回内容,后果是从候选链上摘掉这个模型,恢复时间是冷却期结束。三件事齐了,路由才有确定性;缺一条,就退化成”看到报错就乱撞”。
反过来讲,也不该把所有异常都升级成路由事件。该项目明确写了不触发切换的三种情况:网络错误、密钥无效、限流。这三类各有正确处置:等一等、停下来告警、按退避重试。把它们统统当”额度用完”,就会发生一件最贵的事——把本该重试的请求换到别的通道上跑,账单涨、问题还在。
二、核心交付物之一:五类错误的处置策略表
我们在自己项目里把这张表当成路由模块的输入契约。分类不是为了打标,是为了每一类对应一个不同的控制分支和一种不同的用户可见结果。
| 错误类别 | 识别依据 | 控制分支 | 是否计入切换次数 | 用户侧表现 |
|---|---|---|---|---|
| 额度耗尽 | 返回中明确的耗尽标识或对应错误码 | 摘掉该候选,进冷却期,取下一个候选 | 计入 | 说明已换通道,必要时说明能力变化 |
| 限流 | 频率类返回码与重试建议头 | 原地退避重试,不换候选 | 不计入 | 短暂等待提示,不改模式 |
| 鉴权失败 | 密钥类返回码 | 停止整条链,告警并要求人工处理 | 不计入 | 明确报错,不许静默换密钥 |
| 网络超时 | 连接层错误、无响应 | 按链路降级或重试,不换候选 | 不计入 | 说明当前不可用,给重进方式 |
| 模型不可用 | 服务侧下线或该请求形态不支持 | 跳过该候选,取下一个,记待清理 | 计入 | 不解释内部候选,只说结果可用与否 |
表格之外还有一条纪律:分类要在客户端做,不要等返回文本给人读。错误码与耗尽标识要写成常量并集中维护,供应商改了返回结构时,改动只落在一处。
三、候选链的编排规则
据该项目文档,串联规则四点:同一供应商的候选数量上限为 8 个模型;只有明确返回额度耗尽才切换;耗尽的模型进入 24 小时冷却;全部耗尽则向用户提示额度已用完。
这四点的工程含量比看起来高。
- 数量上限是对”链太长”的保护。候选越多,一次失败请求要试的分支越多,最坏情况下用户等到的是串行的多次超时。上限也逼着人做取舍:链上只放行为可预期的模型。
- 冷却期是对”反复唤醒”的保护。没有冷却,请求量少时会发生一件事:刚耗尽的模型被下一个请求再撞一次,撞完又失败,链上时间全花在无效试探上。冷却期内不许被唤醒,这一点必须写成硬规则,而不是”优先级降一降”。
- 全部耗尽要有终态。很多实现写到”取下一个候选”就停了,没写”没有下一个怎么办”。该项目给了终态:提示用户。终态要能操作,不能只是一个错误码。
- 跨供应商排序要留探测窗口。多个来源时,判断一个候选是否可用不能靠单次请求,也不能靠无限重试;要定义”探测几次、间隔多久、多久判死”。
另有一处容易漏:候选模型的行为并不等价。该项目按模型名把能力分成三类——边录边出的实时识别、有单次时长上限的分段识别(据该文档约四分钟)、以及只能做文件转写的类型,后者直接从听写列表里隐藏。把不等价的模型放在同一条链上时,链的排序要和能力类别绑定,不能只按额度剩余排。
四、降级是能力变化,不是失败,必须让用户看见
该项目四条规则里最见功夫的是最后一条的副作用处理:当实时模型都耗尽、链降到分段类型时,当前这一次会话会被降级为分段模式,实时预览不再可用。
这件事的界面含义经常被写成一句话报错。正确的做法是把”现在是什么模式”当成一个可见状态:
- 界面显示当前运行形态,而不是只显示”正在识别”。
- 说清为什么变了:是额度耗尽,还是能力本身不支持连续输入。
- 说清什么时候恢复:跟随候选的冷却与补充,不许承诺没有依据的时间。
- 不许静默改变行为。同一段话在实时形态和分段形态下结果不同,用户不知道形态变了,就会把差异当成质量不稳定。
我们把它写成一条通用交付规则:任何由配额或错误分类触发的能力变化,都要在界面上留下可读的痕迹;只在日志里记的那次叫故障,界面上记了的那次才叫降级。
五、密钥、发送范围与对账
一条链跑起来之后,第二个容易被忽略的面是安全与计量。
密钥侧,该项目的做法是存进操作系统的安全存储(凭据管理器、钥匙串一类),而不是配置文件。这一点在客户端工具上尤其重要:配置文件会随项目一起被拷走、进版本库、被打包。另外接口地址按业务空间与区域分不同域名,说明”密钥”与”接入点”是两件事,换区域时两者都要改,只改一处会出现看起来像鉴权失败的错误。
发送范围侧,该项目记录了两条值得抄的纪律:润色环节只送识别出的文本而不送音频,并且对部分模型默认关闭思考模式以避免延迟;同时它会一起发送当前应用名,而这个项可以在设置里关掉。”额外发送什么”要有一份能关闭、能查证的清单,日志里则不许出现密钥与应用名这类可识别上下文。
对账侧,我们的做法是把每个候选的用量、单位成本、耗尽时刻与切换次数按候选维度落表。月底要能回答两个问题:这条链替我们避开了多少超额计费,以及哪几个候选从来没被耗尽过(那是可以下掉的冗余)。该项目也记录了几个已知限制,比如需要按住组合键触发、未签名安装包会被系统拦截、没有移动端版本,这些不影响路由规则本身,但影响这条规则能被部署到哪些环境。
六、上线前的三条验收
这三条是我认为配额路由唯一值得过的验收,缺一条就别上线:
| 验收项 | 制造方式 | 通过判据 |
|---|---|---|
| 只切该通道 | 人为把一个候选跑到耗尽状态 | 只有该候选进冷却,其余候选不受影响,计费不越界 |
| 不误判为耗尽 | 断开网络、用无效密钥各试一次 | 不进切换分支,分别走重试与停止告警 |
| 全耗尽有终态 | 把链上候选全部置为耗尽 | 给出可读提示与恢复说明,界面模式标注同步变化 |
配额这件事在工程上还有一种更长期的风险:额度政策会变、价格会变、单个模型会下线。这三种变更下的退出顺序要提前写在配置里——先摘掉行为不再符合预期的候选,再调整冷却与排序,最后才是重新评测。洹拓在给客户做类似链路时,会把这三步写成变更单,而不是运维当场拍。因为一旦允许”随手摘一个模型”,这条链的可观测性就没了,月底对账时谁也对不上为什么这笔用量出现在这里。