登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

多模型路由怎么按失败类型切换:超时、限流和内容拒答的分流规则

来源:17golang原创

时间:2026-08-25 06:04:55 398浏览 收藏

线上同时接入高延迟低吞吐的强模型和低延迟高吞吐的快模型后,最容易踩坑的部分从来不是多候选模型列表的配置,而是请求异常之后的跳转逻辑。一次请求超时、触发上游限流、服务临时不可用,和模型明确返回内容拒答,对应的处理逻辑差异极大。如果不加区分全部写死成“请求失败就自动切下一个模型”,不仅会把本可以快速重试的请求拖成严重长尾,还可能把不该绕过的安全拒答逻辑变成无休止的跨模型轮询,平白浪费大量算力。

要点速览
  • 先把所有失败场景归类为可重试、可降级、需人工处理、不可绕过四个大类,再判断要不要切换后续模型。
  • 每一次发往模型的请求都要附带完整的路由尝试记录、剩余重试预算和最终状态标记,避免跨模型重复扣费。
  • 超时和限流场景要做有界重试,内容拒答场景必须保留原始返回原因,不能通过换上游模型的方式绕过内容安全策略。
  • 上线验收不能只看接口成功率指标,还要核对尾延迟占比、重复请求率、降级请求比例和不同模型拒答规则的一致性。

先把“失败”拆成四种路由信号

假设业务有两个候选:fast-chat 用于低延迟问答,deep-chat 用于需要更长上下文的任务。请求进入路由器后,不要只保存一个布尔值 success,而要记录能决定下一步的状态。

异常信号典型表现默认处理动作
超时连接建立阶段或者响应等待时长超出预设预算短退避后执行有限次数重试,重试失败再触发降级逻辑
限流上游服务返回额度不足或者并发数已达上限优先读取响应携带的等待提示,无提示时切到候选队列或者排队等待
服务异常网关错误、连接直接中断、返回响应格式损坏直接更换下一个候选模型,同时记录当前服务的故障时间窗口
内容拒答模型明确返回安全规则或者业务策略拒绝生成停止自动轮询后续模型,直接向用户说明当前服务的可提供范围

这张表的关键不在名称,而在“默认动作”。路由器可以把上游状态映射成内部枚举,例如 RETRYABLE_TIMEOUTRATE_LIMITEDUPSTREAM_UNHEALTHYPOLICY_REFUSAL。业务层只消费内部状态,不直接根据某个供应商的文字错误做判断。

多模型路由把超时、限流、服务异常和内容拒答分流到不同处理路径的二维工程示意图

为一次请求建立可追踪的路由状态

路由状态结构体至少要覆盖请求唯一标识、候选模型优先级顺序、已经尝试过的模型列表、单次请求等待预算和全链路总耗时预算。下面给出的是示意结构,字段名可以根据自己服务的开发语言灵活调整:

{
  "request_id": "req-7f21",
  "candidates": ["fast-chat", "deep-chat"],
  "attempts": [],
  "retry_budget": 2,
  "deadline_ms": 8000,
  "final_state": "PENDING"
}

每次尝试结束后追加一条记录,而不是覆盖上一条。记录里保留 modelstarted_atelapsed_msstate 和上游请求是否已经接受。这样可以区分“请求根本没发出去”“上游已接收但响应丢失”和“模型正常返回拒答”。

单次超时和总超时必须分开

单次超时只限制当前候选,deadline_ms 则限制整个用户请求。比如总预算是 8 秒,第一次候选已经用掉 6 秒,第二次就不能再拿一个完整的 8 秒窗口。路由器应把剩余时间传给下一次尝试,剩余时间不足时直接返回可解释的降级状态。

同一请求不要重复消耗结果

上游服务在响应完整返回前主动断开连接时,客户端侧根本不知道模型后台是否已经完成生成动作。这时候盲目切换其他模型重试,很可能生成两份结果,甚至让后续的扣费和审计记录完全对不上。对支持幂等键的上游服务,重试时复用同一个幂等键;对不支持幂等机制的上游,至少要在本地标记“结果未知”状态,交给上层业务逻辑判断是否接受这次可能产生重复结果的尝试。

不同失败信号应该走不同的处理链

实现上可以把分类、动作和终态分开。分类只回答“发生了什么”,动作回答“下一步允许做什么”,终态回答“这次请求最终如何结束”。这三个概念混在一个 if error != nil 里,后期很难加监控和回归测试。

超时 -> 检查剩余预算 -> 短退避 -> 同模型或候选模型
限流 -> 读取等待提示 -> 预算允许则排队/换候选 -> 否则降级
服务异常 -> 熔断计数 -> 选择健康候选 -> 失败则返回暂时不可用
内容拒答 -> 保留拒答原因 -> 停止绕路 -> 返回安全范围说明

超时是否换模型取决于请求类型。短问答可以优先换到 fast-chat,长上下文任务如果换到不支持同等上下文的候选,应返回能力不匹配,而不是假装成功。限流则要关注等待提示,固定每次等待相同时间会在高峰期制造新的拥塞。

内容拒答是最容易被误处理的一类。它可能意味着输入或请求方式超出了允许范围,换一个模型并不会改变业务责任。路由层可以把拒答原因映射成统一的 POLICY_REFUSAL,但不要把它当成上游故障,也不要自动继续轮询所有候选。

把重试和降级边界写成可验收规则

重试不等于不加限制地“多试几次”。建议先给每一类失败场景单独定义重试预算,再把最终状态的流转逻辑固定下来:

  • 超时:最多允许一次短退避重试,且重试后的总等待时长不能超过预设的全链路截止时间。
  • 限流:优先遵守上游返回的等待提示参数;没有携带提示参数时使用带随机扰动的短退避策略。
  • 服务异常:同一个候选模型连续失败次数达到预设阈值后临时熔断,避免后续请求继续撞向故障实例。
  • 内容拒答:不做重试、不切换其他模型绕过;直接返回安全的替代说明,或者引导用户改写当前的请求内容。

降级也要有能力声明。比如 deep-chat 失败后切到 fast-chat,结果中应带上 degraded=true 和能力差异,而不是让调用方以为拿到了完全等价的回答。对于结构化输出,还应重新校验必填字段;模型切换后格式约束可能并不一致。

多模型路由在总时间预算内处理重试、候选切换、降级和最终状态的二维工程示意图

用四组实验验证路由不会失控

测试环节不要只简单模拟一个 500 状态码返回。把当前请求参数完全固定,给每一个候选模型注入可复现的指定响应,然后核对路由日志和最终返回结果。

  1. 让第一个候选的响应等待时长超过单次请求预算,确认路由只会触发允许次数内的重试,转发到第二个候选时拿到的是扣除已耗时时长后的剩余时间。
  2. 让第一个候选直接返回限流提示,确认路由不会立刻高频重发请求,同时完整记录下本次等待的触发原因。
  3. 让第一个候选返回格式完全损坏的异常响应,确认切换到下一个候选后依然会完整校验返回结果的结构化字段。
  4. 让候选直接返回内容拒答,确认路由不会发起第二次模型请求,且最终返回状态里完整保留拒答对应的分类标记。

监控上至少看四个指标:按失败类别统计的请求数、同一 request_id 的尝试次数、降级请求占比和端到端尾延迟。成功率上升但尝试次数和尾延迟同时上升,通常不是路由变好了,而是重试预算过宽。

常见问题

超时后一定要换到更强的模型吗?

不一定。需要先判断剩余可用耗时、任务上下文长度和后续候选的健康状态。短文本问答场景更适合切到低延迟候选模型,长上下文任务则可能需要直接返回服务暂时不可用,避免降级到小模型后丢失用户上传的关键上下文信息。

限流和服务异常为什么要分开?

限流场景通常意味着短时等待或者更换容量充足的候选即可解决,服务异常场景则可能需要触发熔断和运维告警。把两类场景混为同一异常处理,会让系统在上游服务完全故障时依旧持续无效重试,也会错过限流响应里携带的官方等待提示参数。

内容拒答能不能交给另一个模型判断?

可以把拒答结果转发给业务层做友好解释或者生成改写建议,但不能把其他模型当成绕过内容策略的通道。路由层要直接停止自动轮询逻辑,同时保留原始拒答的类别标记,后续用于安全审计和产品策略优化。

把路由器当成有边界的状态机

多模型路由真正要解决的核心问题是异常场景下的可解释性:清晰知道为什么要切换候选模型、还剩多少可用请求预算、当前结果是否属于降级返回、哪一类失败场景绝对不能继续尝试。把失败分类规则、尝试记录、全链路总截止时间和最终状态流转逻辑固化到协议里,再用超时、限流、格式损坏、内容拒答四组场景做完整验收,路由组件才不会从预期的容错机制变成隐形的重复调用生成器。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>