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

多模型网关怎么做请求降级:超时预算、备用模型与结果标记

来源:17golang原创

时间:2026-08-25 17:58:46 102浏览 收藏

接入多个大模型后,最容易被忽略的就是“失败之后还剩多少可用时间”。如果主模型已经花了 2.4 秒,网关还不带任何限制就把请求转给备用模型,用户最后看到的大概率不是更可靠的答案,而是一次注定超时的无效重试。更稳妥的做法是先给整条请求设定总耗时预算,再为每个模型分配可动态回收的时间,并且把最终返回的结果标记清楚。

多模型降级的核心不是“主模型失败就立刻重试”,而是“在总超时预算范围内,只针对可恢复的失败做一次切换,同时让调用方能明确知道结果来自哪条处理路径”。

实践要点:
  • 总预算优先级高于单模型超时,备用模型只能使用剩余时间。
  • 仅针对超时、限流和临时网络错误做降级,不重试参数或鉴权错误。
  • 备用模型最多只尝试一次,并在响应中保留 route、degraded 和失败分类。

先把一次调用拆成三段时间

假设客户端给网关的超时时间是 3 秒,网关不能把这 3 秒全部交给主模型。还要预留出连接建立、序列化、日志打印和响应写回的开销。一个简单的分配方案是:总预算 3000 毫秒,网关自身开销预留 300 毫秒,主模型最多分配 1800 毫秒,备用模型最多分配 900 毫秒。

这里的数字不是固定标准,核心逻辑是把“剩余预算”作为切换备用模型的判断条件。主模型提前返回的话,备用模型就能拿到更多可用时间;主模型把分配的预算耗尽之后,备用模型只能使用剩下的时间,不能重新申请一整轮 3 秒的完整时长。

totalBudget := 3000 * time.Millisecond
reserve := 300 * time.Millisecond
primaryLimit := 1800 * time.Millisecond

deadline := time.Now().Add(totalBudget - reserve)
primaryCtx, cancel := context.WithDeadline(ctx, minDeadline(deadline, primaryLimit))
defer cancel()

验收的时候不要只盯着平均延迟。至少要记录 p95、p99、主模型耗时、备用模型耗时和剩余预算几个指标,否则降级链路偶尔变慢的时候,很难判断是模型本身响应变慢,还是网关重复等待导致的问题。

主模型消耗部分超时预算后切换备用模型,剩余预算决定备用模型等待时间

哪些失败值得切换,哪些失败应该立即返回

降级条件要按照错误的可恢复性划分。主模型返回 429、网关连接超时、上游临时的 502 这类情况,通常可以在剩余预算足够的时候做切换。参数校验失败、鉴权失败、请求体过大、内容安全策略拒绝这类问题,就不应该换一个模型再发一次,因为备用模型也没办法修复请求本身的错误。

建议在网关内部使用统一稳定的失败分类,不要把上游厂商返回的原始错误字符串直接作为业务判断依据:

type FailureKind string

const (
    RetryableTimeout FailureKind = "retryable_timeout"
    RetryableRate    FailureKind = "retryable_rate_limit"
    RetryableUpstream FailureKind = "retryable_upstream"
    InvalidRequest   FailureKind = "invalid_request"
    AuthDenied       FailureKind = "auth_denied"
)

func canFallback(kind FailureKind, remaining time.Duration) bool {
    return remaining > 450*time.Millisecond &&
        (kind == RetryableTimeout || kind == RetryableRate || kind == RetryableUpstream)
}

450 毫秒只是示例阈值。备用模型需要的最小可用时间,取决于网络状况、输出长度和服务端排队情况。线上可以给每个模型单独维护一个最小可用预算,并用近期的 p95 耗时作为动态调整的参考。

备用模型只承担一次机会

一个常见的错误实现是主模型失败之后依次尝试两个备用模型,最后把三次请求的耗时直接相加。这么做看起来提高了成功率,实际上会把尾延迟和上游调用成本一起放大。更可控的策略是每个请求最多做一次切换:主模型失败后直接调用预先配置好的备用模型,备用模型也失败的话就直接结束流程。

result, failure := callModel(primary, primaryCtx)
if failure == nil {
    return response(result, "primary", false)
}

remaining := time.Until(deadline)
if !canFallback(failure.kind, remaining) {
    return errorResponse(failure.kind, "primary_failed", remaining)
}

fallbackCtx, cancel := context.WithDeadline(ctx, deadline)
defer cancel()
backup, backupFailure := callModel(backupModel, fallbackCtx)
if backupFailure != nil {
    return errorResponse(backupFailure.kind, "fallback_failed", time.Until(deadline))
}
return response(backup, "fallback", true)

备用模型的选择也应该是预先确定的配置,不要每次失败的时候临时随机选。可以按照任务类型做配置,比如摘要类任务切换到小模型,结构化抽取任务切换到同样支持 JSON 输出的模型;不要把不支持相同输出协议的模型放到同一条降级链路里。

多模型网关把主路径、降级路径和失败原因转换为统一结果标记

把降级事实放进响应和日志

调用方通常不需要知道上游完整的错误细节,但必须能区分“主模型正常返回”和“备用模型兜底成功”两种情况。推荐在内部日志和对外响应里分别保留一组稳定的字段:

{
  "route": "fallback",
  "provider": "backup-provider",
  "degraded": true,
  "failure_kind": "retryable_timeout",
  "latency_ms": 2478,
  "request_id": "req_7f3a"
}

对外是否暴露 provider 要看产品的边界定义,但 degraded、request_id 和可归类的错误码都非常实用。监控系统可以直接统计降级率、备用模型成功率、备用模型 p95 耗时,以及按 failure_kind 切分的失败量。如果降级率持续走高,优先检查主模型的健康度和限流配置,不要第一时间就想着扩容备用模型池。

上线前用四组场景验收

压测脚本至少要覆盖四组场景:主模型正常返回、主模型在预算内超时、主模型返回不可恢复错误、主模型失败后备用模型也超时。每组场景都要核对 HTTP 状态、degraded 字段、route 值、request_id 是否完整贯穿全链路日志,同时总耗时不能超过客户端设置的预算。

还要专门验证取消传播逻辑:客户端已经断开连接的时候,主模型和备用模型的请求都要同步收到取消信号。否则网关虽然提前给用户返回了错误,上游的请求还在悄悄消耗并发额度,积累一段时间之后很容易演变成连接池耗尽的故障。

最后检查:总预算是否只有一个统一来源;降级逻辑是否最多触发一次;不可恢复错误会不会触发重试;响应能不能识别出降级状态;主备请求能不能被同步取消;监控能不能按失败分类定位问题。

相关问题

备用模型一定要比主模型更快吗?

不一定,但它必须在剩余预算内有很高的完成概率。如果备用模型本身响应更慢,就应该把它限制在更短的输出长度场景下,或者只用来处理短任务。

429 和 5xx 都应该切换吗?

不能只看状态码做判断。429 通常适合切换,但要结合 Retry-After 和剩余时间综合判断;5xx 也有可能是请求本身触发的上游错误,需要先按错误类型做区分。

降级成功后要不要自动重写答案?

不要在同一个请求里再开启一轮不受预算控制的改写操作。如果需要对结果做二次质量处理,应该放到异步任务里或者单独开一个接口,并且明确这个二次处理不占用本次请求的成功耗时预算。

多模型网关的可靠性来自清晰的边界定义:总预算控制最大等待上限,失败分类控制是否允许切换,一次切换机会控制调用成本,结果标记则让质量和稳定性问题完全可追踪。先把这四件事做扎实,再去讨论更复杂的智能路由策略。

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