专栏 Minis 拆解

模型路由:组、优先级与降级

同一个会话,上午回答得细致,下午变得简短,连标点都少了。

模型路由:组、优先级与降级

同一个会话,上午回答得细致,下午变得简短,连标点都少了。

没有换模型,也没有改设置。是模型组在背后换了一个成员。

一、单点撑不住

一个模型能提供的稳定性,取决于它背后那家服务商当天的心情:限流、排队、区域故障、额度用完、临时维护。

对聊天机器人来说,这最多是一次报错。对 Agent 来说,代价要大得多——一轮任务可能已经跑了十几次工具调用,模型在中途挂掉,整个上下文都卡在一个无法继续的状态。

所以需要备胎。不是一个,是一组。

二、组是候选列表加一条规则

模型组的结构很朴素:一个成员列表,加一个选择策略。

策略 行为 适用
fallback 按顺序用,失败才换下一个 稳定优先,行为可预测
loadBalance 在成员间分摊 吞吐优先,但不同模型的输出风格会飘

大多数场景用 fallback。不是因为它更先进,是因为它可预测:正常情况下永远用第一个,出问题才换。用户得到的回答风格稳定,排查也简单——看到异常,先看是不是降级了。

负载均衡看起来更”高级”,但它把不确定性引进了输出。同一个问题,两次回答可能来自两个不同模型,质量差异会被误认为是系统不稳定。

三、什么时候该降级

不是所有错误都值得换一个模型重试。

该换的:429 限流、5xx 服务端错误、连接超时、首包迟迟不来。这些是”这个成员现在不行”,换一个大概率能过。

不该换的:401/403 凭据错误、400 请求格式错误、上下文超长。这些是”你发的东西有问题”,换谁都会失败,只是多烧一次额度。

实现上通常给一个策略开关:只在限流时降级,或者任何错误都降级。

选后者看起来更”稳”,其实更糟。一个参数拼错的请求,会在整条候选链上依次失败,最后报的还是同一个原因,只是慢了五倍。

降级只能解决”节点不可用”,不能解决”请求不正确”。 分不清这两者,备胎就是纯粹的浪费。

四、走一遍候选链

降级的过程比想象中朴素:从当前成员的下一个开始,沿着列表往下找,绕一圈回到自己为止。

找的时候要跳过两类成员:

  • 被禁用的。用户可能临时关掉了某个服务商,它不该在降级时被重新捡起来。
  • 没有凭据的。配置里留着条目但没填 key,选它等于选了一个必然失败的选项。

跳过之后还没有候选,就老实报错,不要硬凑。

这两个跳过条件看起来是细节,但它们决定了一条原则:候选链的质量取决于排除了多少无效项,而不是包含了多少项。一个配置错误的成员混在里面,会让”降级”变成”多失败一次”。

五、降级之后,绑定关系怎么办

这里有个容易搞混的地方:会话绑定的是组,不是组里的某个成员。

所以降级不该改变绑定的组,只改变这一次请求实际用的模型。下一次请求如果第一个成员恢复了,应该自动回到它。

但用户手动选了一个具体模型,情况就不同了——那是显式意图,不能被自动降级覆盖。否则用户会发现”我明明选了 A,怎么变成 B 了”,而且找不到地方改回来。

区分”临时故障切换”和”用户主动选择”,是路由逻辑里最容易出错的一处。

六、组内成员要能力对等

还有一个隐藏的约束:同一个组里的模型,上下文窗口不能差太多。

如果主力是 200K 窗口,备胎只有 32K,那么长对话一旦降级,历史直接装不下,请求被拒。这时候降级不但没救场,还把一个”暂时不可用”变成了”这个会话废了”。

所以组不是随便凑的。成员之间至少要在三项上接近:窗口大小、工具调用能力、多模态支持。差得太远,降级就是换一个问题。

这也解释了为什么很多产品把”便宜模型”和”贵模型”分开组:不是按价格分,是按能力边界分。

七、用户需要知道发生了什么

最后一个问题不是技术问题,是信任问题。

降级发生时,界面上如果什么都不说,用户只会觉得”今天这个 AI 有点笨”。他不会想到是备用模型,更不会想到去检查主模型的状态。

所以要有明确的标记:这次回答由哪个模型产生,是否发生了降级。

这条信息不占多少空间,但它把一次”莫名其妙的质量波动”变成了一次”可以理解的服务切换”。

小结

模型路由听起来像基础设施,其实它处理的是一个很具体的问题:当一部分世界不可靠时,怎么让整体还能用。

答案不复杂:准备候选、按顺序试、跳过无效的、别覆盖用户的显式选择、并且告诉他发生了什么。

难的是每一条都要想清楚边界——哪些错误该换、哪些不该换,哪些状态该保留、哪些该重置。这些边界画错,备胎就会变成新的故障源。