企业接入大模型的第一阶段,往往只是把一个模型 API 包装成聊天窗口;进入生产环境后,问题却迅速从“能不能调用”变成“成本是否可控、故障是否可切换、质量是否可度量”。如果分类、抽取、改写、复杂推理和视频生成都被送到同一个高价模型,企业买到的不是智能,而是一条缺少调度能力的昂贵单链路。反过来,如果只追求最低单价,把所有请求都交给轻量模型,错误答案、人工复核和客户流失又会吞掉节省的推理费用。
在这里插入图片描述

真正可持续的企业级 AI 基础设施,需要在请求进入模型之前做一次工程决策:识别任务意图与复杂度,在质量、成本、时延、可用性和合规约束之间求解,并在上游限流或故障时自动降级。Model Routing(模型路由)不是简单的 if/else,而是一个具备模型目录、策略评估、在线反馈、熔断器、预算控制和可观测性的决策系统。本文给出一套可落地的方法:以统一的大模型网关连接多供应商与多模态模型,通过 RouteLLM 类路由思想、成本—质量评分和 Fallback Mechanism,构建面向高可用 SLA 的调度中台。

一、企业级 AI 的“算力黑洞”:单一模型为何同时拖垮成本与稳定性

单模型方案最大的误区,是把“调用成功率”误当成“业务成功率”。供应商返回 HTTP 200,只能说明接口完成了响应;它无法证明答案正确、费用合理、尾延迟可接受,也无法证明下一分钟仍然可用。生产系统至少要同时管理四种不确定性:请求难度不确定、模型质量不确定、供应商容量不确定、业务损失不确定。

先看请求分布。多数企业工作负载不是均匀的:大量任务是分类、字段抽取、格式转换和固定模板摘要,少量任务才需要长上下文、多步推理或高精度代码生成。如果所有任务都走高能力模型,相当于用数据库主库执行每一条日志查询。高价模型的推理能力被浪费在低熵任务上,而月账单会随 Token 数量线性增长。

可以把单次请求的真实成本写成:

Creal=Cinput+Coutput+Cretry+Creview+Perror×Lbusiness C_{real}=C_{input}+C_{output}+C_{retry}+C_{review}+P_{error}\times L_{business} Creal=Cinput+Coutput+Cretry+Creview+Perror×Lbusiness

其中,输入与输出 Token 费用只是显性成本;重试、人工复核、错误概率与业务损失才是经常被忽略的部分。廉价模型不一定带来低成本,因为一次错误抽取可能触发人工工单;顶级模型也不一定带来高收益,因为简单分类的质量提升可能只有小数点后的差异。成本优化的对象应当是“完成一个合格业务结果的期望成本”,而不是模型价格表上的每百万 Token 单价。

请求类型 常见占比 主要约束 不做路由的浪费 更合理的目标
标签分类、意图识别 25%–40% 低时延、结构稳定 高能力模型过度推理 小模型或规则优先
字段抽取、格式化 15%–25% JSON 合规率 为简单模式匹配支付高价 轻量模型加 Schema 校验
客服问答、RAG 20%–35% 事实性、引用完整 上下文过长推高输入成本 检索压缩后按置信度路由
代码与复杂分析 5%–15% 推理质量、可验证性 轻量模型返工率高 强模型加测试或评审
图像、音频、视频 通常低于 10% 队列、显存、文件传输 同步阻塞并挤占连接 异步任务与专用算力池

再看稳定性。把业务绑定到一个供应商、一个区域或一个模型 ID,会形成三个单点故障(SPOF):供应商控制面故障会让鉴权或模型目录不可用;数据面拥塞会产生 429、超时和 P99 抖动;模型升级或下线会让固定 ID 突然失效。即使供应商全年可用性达到 99.9%,每月仍可能有约 43 分钟不可用。若企业目标是 99.99%,单靠同一上游的自动重试无法补齐数量级差异。在这里插入图片描述

重试甚至可能放大故障。上游开始限流后,如果每个实例立即重试三次,原始 1000 QPS 会瞬间变成接近 4000 次尝试,形成 retry storm。正确做法是设置总重试预算、指数退避与随机抖动,并将请求切到独立故障域,而不是在同一个故障节点上反复撞击。

所谓“大模型 API 费用爆炸”,本质上通常不是某一家模型单价突然失控,而是高并发 API 频率限制、无界重试、长上下文冗余和高低难度任务混跑共同造成的结果。因此,企业网关需要管理四本账:按团队、应用和租户统计 Token 的财务账;记录模型质量与人工纠错的质量账;记录可用率、P99 和错误预算的 SLA 账;记录数据去向、权限与审计链路的合规账。Cost Optimization(成本优化)只有同时连接这四本账,才真正具备 CTO 和架构师所关心的 ROI 意义。

二、动态模型路由原理:从意图分类到成本—质量 Pareto 最优

动态 AI 模型路由的输入不应只有一段 Prompt。一个生产请求至少包含文本、模态、租户、数据等级、最大预算、时延目标、输出 Schema、历史轮次和工具权限。路由器先从这些信息中提取特征,再判断候选模型是否满足硬约束,最后才在可用候选集内做软评分。

一组实用的路由特征可以分为五类:

  1. 规模特征:输入 Token、期望输出长度、附件数量、图像分辨率、视频时长。
  2. 语义特征:分类、抽取、翻译、代码、数学、多步分析、图像生成或视频生成。
  3. 难度特征:约束数量、推理词密度、代码块数量、跨文档引用、工具调用需求。
  4. 业务特征:租户等级、最大成本、SLA、数据驻留要求、是否允许跨境处理。
  5. 运行特征:模型近五分钟错误率、并发占用、P95/P99、Rate Limit 剩余额度和熔断状态。

意图分类器可以由规则、小型 BERT、轻量 LLM 或 Embedding 近邻共同完成。规则适合识别 JSON Schema、文件类型和显式命令;小模型负责语义类别;对于边界请求,可再计算它与历史高难度样本的向量相似度。这里的关键不是让分类器“永不出错”,而是让它能输出置信度。当置信度低于阈值时,路由器应进入拒绝决策:升级到更可靠的模型,或者执行一次小规模探测,而不是自信地走最便宜路径。

对候选模型 mmm,可以定义归一化效用函数:

U(m∣x)=wqQ(m,x)−wcC^(m,x)−wlL^(m,x)−wrR(m)+waA(m) U(m|x)=w_qQ(m,x)-w_c\hat C(m,x)-w_l\hat L(m,x)-w_rR(m)+w_aA(m) U(mx)=wqQ(m,x)wcC^(m,x)wlL^(m,x)wrR(m)+waA(m)

其中,QQQ 是任务质量预测,C^\hat CC^ 是预估费用,L^\hat LL^ 是预测时延,RRR 是近期故障风险,AAA 是区域、权限和模态适配度。各项都应归一化到相近区间,否则价格或时延的数量级会压过其他指标。路由决策是在满足硬约束的集合中选择效用最大的模型:

m∗=arg⁡max⁡m∈MvalidU(m∣x) m^*=\arg\max_{m\in M_{valid}}U(m|x) m=argmMvalidmaxU(mx)

硬约束包括数据地域、最大上下文、输出格式、工具调用能力、内容安全等级和租户白名单。它们不能靠权重抵消。例如,某模型不允许处理机密数据,即使价格再低、速度再快,也不应进入候选集。软约束才适合放进评分函数,例如希望成本更低、响应更快或质量更高。
在这里插入图片描述

质量分数不能由品牌印象给出,而应来自任务级评测。分类任务使用 F1,抽取任务使用字段准确率,代码任务使用测试通过率,RAG 使用事实一致性与引用命中率,图像生成使用人工偏好或业务审核通过率。每个模型对不同任务会形成不同的质量向量,不存在一个在所有维度都最优的“万能模型”。这也是成本—质量 Pareto 前沿的意义:如果模型 A 比模型 B 更贵且质量不更好,A 在该任务上就应被支配并移出候选集。

一个可解释的路由流程如下:

业务请求
   │
   ├─ 解析元数据:租户 / 模态 / 数据等级 / SLA / 预算
   │
   ├─ 意图分类:规则 + 轻量分类器 + 语义相似度
   │
   ├─ 难度评估:长度、约束、多步推理、工具调用、历史失败率
   │
   ├─ 硬约束过滤:权限、区域、上下文、Schema、模型健康度
   │
   ├─ Pareto 候选集:剔除高成本低质量的被支配模型
   │
   ├─ 效用评分:质量 - 成本 - 时延 - 风险 + 适配度
   │
   └─ 选择主模型 → 质量闸门 → 通过则返回
                         │
                         └─ 失败/低置信度 → Fallback 候选链

RouteLLM 类方案通常用历史偏好数据学习“某个请求是否值得升级到强模型”。在企业环境中,不能把训练完成的路由器当成静态黑盒。模型价格会变、版本会更新、业务分布会漂移,路由阈值必须通过在线反馈校准。建议保留 1%–5% 的受控探索流量,对同一类任务抽样比较强弱模型,再用质量差、成本差和人工复核结果更新策略。探索必须受预算与数据权限约束,不能把敏感请求随机发送给未经批准的供应商。

语义路由匹配度(Semantic Routing Score)也不能直接等同于“答案质量”。它只表示当前请求与某类历史样本或路由原型的接近程度。离线阶段可按业务类别绘制分数分布,寻找误路由成本最低的阈值;上线后再按周检查分数校准曲线:例如所有预测为 0.8 的样本中,是否真有约 80% 适合轻量模型。若高分样本的实际通过率持续下降,说明任务分布、Prompt 模板或模型版本发生漂移,应降低自动路由比例并重新标注,而不是简单提高强模型权重掩盖问题。

路由效果至少用六项指标衡量:路由准确率、升级率、合格答案成本、质量闸门拦截率、Fallback 成功率和策略后悔值。所谓后悔值,是当前选择与事后最优模型之间的效用差。只看“有多少流量走了便宜模型”会诱导策略过度降级;只看平均质量则会掩盖高风险长尾。正确目标是在业务质量下限和 SLA 约束内最小化期望成本。

三、高可用架构:用多模型聚合中台消除 Vendor Lock-in

聚合中台的价值不只是把不同厂商包装成一个 Base URL。真正的大模型网关(LLM Gateway)需要把控制面与数据面分离:控制面维护模型目录、价格、权限、策略、评测结果和密钥;数据面执行鉴权、限流、路由、协议转换、流式转发、熔断和审计。这样即使管理后台短暂不可用,已经下发到边缘节点的策略仍可继续处理请求。
在这里插入图片描述

对于标称聚合 500+ 模型或节点的平台,企业不应把“目录数量”直接等同于“生产可用模型数量”。同一基础模型可能因区域、版本、上下文或供应商不同形成多个条目。接入验收必须通过模型目录接口和真实请求验证以下信息:稳定的模型 ID、上下文上限、模态、工具调用能力、数据区域、计费单位、并发限制、错误码语义和退出策略。Nano Banana 类全模态聚合基础设施的意义,在于提供更大的候选池;是否能降低 Vendor Lock-in,则取决于企业是否保留自己的模型别名、评测集、审计数据和可迁移协议。

Nano Banana 企业级接入还应增加一次协议一致性验收:分别测试非流式、SSE 流式、工具调用、结构化输出、文件上传和异步回调,确认错误码、用量字段与取消语义能够被统一解析。对目录中的候选模型执行小规模金丝雀请求,只有健康检查、质量样本、预算上限和数据区域全部通过后,才允许进入生产路由池;其余条目只保留在实验池。这样可以防止“目录很大”反而扩大不可控的行为差异。

推荐架构如下:

┌──────────────────── 业务与终端 ────────────────────┐
│ Web / App / Agent / RAG / 批处理 / 图像与视频工作流 │
└────────────────────────┬───────────────────────────┘
                         │ OpenAI 兼容协议 / 异步任务协议
┌────────────────────────▼───────────────────────────┐
│ 统一 API 网关:鉴权、租户限流、Schema 校验、幂等键  │
└────────────────────────┬───────────────────────────┘
                         │
┌────────────────────────▼───────────────────────────┐
│ 智能路由层:意图分类、复杂度、预算、质量与时延评分   │
│ 模型注册表:别名、能力、价格、区域、健康度、版本     │
└───────────────┬───────────────────────┬─────────────┘
                │                       │
┌───────────────▼────────────┐  ┌──────▼─────────────┐
│ 熔断 / 限速 / 重试预算      │  │ 质量闸门 / 输出校验 │
│ Fallback / 灰度 / 流量影子  │  │ 引用 / JSON / 安全   │
└───────────────┬────────────┘  └──────┬─────────────┘
                └──────────────┬────────┘
                               │
      ┌────────────────────────▼──────────────────────┐
      │ 多供应商、多区域、文本/图像/音频/视频模型池    │
      │ OpenAI / Gemini / DeepSeek / Flux / Kling / Wan│
      └────────────────────────┬──────────────────────┘
                               │
      ┌────────────────────────▼──────────────────────┐
      │ 指标、Trace、成本账本、审计日志、离线评测仓库   │
      └───────────────────────────────────────────────┘

模型注册表是整套系统的核心资产。业务代码不应直接写死供应商型号,而应调用 text-lowtext-balancedreasoning-highimage-standard 等企业别名。别名指向经过评测的具体模型版本,变更时通过灰度发布逐步切流。PRD 中常见的 Gemini 1.5 Flash、Gemini 1.5 Pro 等名称只适合作为历史架构示例;生产配置应从实时模型目录读取当前在役 ID,避免型号退役后整条链路失效。

高可用不能只靠一条线性的模型列表。Fallback 候选必须跨故障域:主模型和备模型若共享同一供应商、区域或底层容量,它们很可能同时故障。每个候选应标记 providerregionfailure_domain,路由器优先选择质量接近但故障域独立的备选。对于 429,应参考 Retry-After 并转移容量;对于 5xx 和连接超时,可以在剩余时间预算内切换;对于 400、内容违规和权限错误,盲目换模型通常无效,应直接进入修复或拒绝流程。

熔断器需要三个状态:关闭时正常放量;错误率超过阈值后打开,暂时移除节点;冷却期结束进入半开,只放少量探测请求。熔断统计应区分模型问题和客户端问题,不能把无效 Prompt 导致的 400 计入上游故障率。流式响应还要区分首 Token 前失败与输出中断:前者可安全切换,后者如果已经向用户发送部分内容,再切模型可能产生语义重复,需要由协议层发出中断标记或重建完整响应。

99.99% 是端到端目标,不是网关单组件目标。每月约 4.3 分钟的错误预算要分配给鉴权、路由、上游、缓存和网络。网关自身应多可用区部署、无状态化、策略本地缓存,并为模型目录配置最后已知可用快照。审计与计费写入不能阻塞主请求,可通过消息队列异步落盘;但关键的预算扣减需要原子预留,避免高并发时产生超额消费。

最后必须准备退出演练。每季度选择一个主模型,把 10% 影子流量切到替代供应商,比较质量、成本和 P99;验证密钥轮换、别名更新、数据导出与账单对账。没有经过演练的“零 Vendor Lock-in”只是架构图上的愿望。

四、Python 实战:实现复杂度评分、动态分发、熔断与 Fallback

下面给出一个可运行的最小路由器。它不依赖特定供应商,使用 OpenAI 兼容的 /v1/chat/completions 协议;模型名称通过环境变量配置。默认 --dry-run 模式不发送网络请求,适合先检查复杂度评分与候选顺序。执行真实调用前安装依赖 pip install requests,再配置网关地址与密钥。

from __future__ import annotations

import argparse
import os
import random
import re
import time
import uuid
from dataclasses import dataclass, field
from typing import Any

import requests


RETRYABLE_STATUS = {408, 429, 500, 502, 503, 504}


@dataclass(frozen=True)
class ModelCandidate:
    """模型注册表中的一条可路由记录。"""

    name: str
    tier: str
    provider: str
    failure_domain: str
    quality: float       # 离线评测质量,归一化到 0~1
    cost: float          # 相对成本,不绑定容易变化的公开价格
    latency_ms: int      # 近期 P95,用于路由预测


@dataclass
class CircuitState:
    failures: int = 0
    opened_at: float | None = None


@dataclass
class RouteDecision:
    complexity: str
    score: int
    ordered_models: list[str]
    reasons: list[str] = field(default_factory=list)


class GatewayError(RuntimeError):
    def __init__(self, message: str, retryable: bool):
        super().__init__(message)
        self.retryable = retryable


class SmartAIRouter:
    def __init__(
        self,
        endpoint: str,
        api_key: str,
        candidates: list[ModelCandidate],
        timeout_s: float = 25.0,
        failure_threshold: int = 3,
        cooldown_s: float = 30.0,
    ) -> None:
        self.endpoint = endpoint.rstrip("/")
        self.api_key = api_key
        self.candidates = candidates
        self.timeout_s = timeout_s
        self.failure_threshold = failure_threshold
        self.cooldown_s = cooldown_s
        self.circuits = {item.name: CircuitState() for item in candidates}

    def evaluate_complexity(self, prompt: str) -> tuple[str, int, list[str]]:
        """用可解释特征估算难度;生产环境可替换为训练后的分类器。"""
        score = 0
        reasons: list[str] = []
        length = len(prompt)

        if length > 4000:
            score += 3
            reasons.append("长上下文")
        elif length > 1200:
            score += 2
            reasons.append("中等上下文")

        feature_rules = [
            (r"证明|推导|多步|权衡|根因|架构设计", 2, "多步推理"),
            (r"\x60{3}|class\s|def\s|SELECT\s|异常栈", 2, "代码任务"),
            (r"JSON|Schema|严格格式|字段校验", 1, "结构化约束"),
            (r"工具调用|function calling|检索|数据库", 2, "外部工具"),
        ]
        for pattern, value, reason in feature_rules:
            if re.search(pattern, prompt, re.IGNORECASE):
                score += value
                reasons.append(reason)

        if score <= 2:
            return "LOW", score, reasons or ["短文本或低约束任务"]
        if score <= 5:
            return "MEDIUM", score, reasons
        return "HIGH", score, reasons

    def _circuit_available(self, model: str) -> bool:
        state = self.circuits[model]
        if state.opened_at is None:
            return True
        if time.monotonic() - state.opened_at >= self.cooldown_s:
            # 进入半开状态:允许一个探测请求。
            state.opened_at = None
            state.failures = self.failure_threshold - 1
            return True
        return False

    def _utility(self, item: ModelCandidate, complexity: str) -> float:
        """质量、成本、时延的简化效用函数。"""
        quality_weight = {"LOW": 0.45, "MEDIUM": 0.62, "HIGH": 0.82}[complexity]
        cost_weight = {"LOW": 0.42, "MEDIUM": 0.25, "HIGH": 0.08}[complexity]
        latency_penalty = min(item.latency_ms / 10000.0, 1.0)
        tier_bonus = 0.0
        if complexity == "LOW" and item.tier == "low":
            tier_bonus = 0.12
        elif complexity == "MEDIUM" and item.tier == "balanced":
            tier_bonus = 0.12
        elif complexity == "HIGH" and item.tier == "high":
            tier_bonus = 0.18
        return (
            quality_weight * item.quality
            - cost_weight * item.cost
            - 0.12 * latency_penalty
            + tier_bonus
        )

    def make_decision(self, prompt: str) -> RouteDecision:
        complexity, score, reasons = self.evaluate_complexity(prompt)
        healthy = [item for item in self.candidates if self._circuit_available(item.name)]
        if not healthy:
            raise RuntimeError("所有候选模型均处于熔断状态")

        # 先按效用排序,再尽量让相邻候选来自不同故障域。
        ranked = sorted(healthy, key=lambda x: self._utility(x, complexity), reverse=True)
        ordered: list[ModelCandidate] = []
        remaining = ranked[:]
        while remaining:
            previous_domain = ordered[-1].failure_domain if ordered else None
            index = next(
                (i for i, item in enumerate(remaining)
                 if item.failure_domain != previous_domain),
                0,
            )
            ordered.append(remaining.pop(index))

        return RouteDecision(
            complexity=complexity,
            score=score,
            ordered_models=[item.name for item in ordered],
            reasons=reasons,
        )

    def _mark_success(self, model: str) -> None:
        self.circuits[model] = CircuitState()

    def _mark_failure(self, model: str) -> None:
        state = self.circuits[model]
        state.failures += 1
        if state.failures >= self.failure_threshold:
            state.opened_at = time.monotonic()

    def _request_once(
        self,
        model: str,
        prompt: str,
        trace_id: str,
        timeout_s: float,
    ) -> dict[str, Any]:
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "X-Trace-Id": trace_id,
            "Idempotency-Key": trace_id,
        }
        payload = {
            "model": model,
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0.2,
            "stream": False,
        }
        try:
            response = requests.post(
                f"{self.endpoint}/v1/chat/completions",
                headers=headers,
                json=payload,
                timeout=(3.0, timeout_s),
            )
        except (requests.Timeout, requests.ConnectionError) as exc:
            raise GatewayError(str(exc), retryable=True) from exc

        if response.status_code >= 400:
            detail = response.text[:300]
            raise GatewayError(
                f"HTTP {response.status_code}: {detail}",
                retryable=response.status_code in RETRYABLE_STATUS,
            )
        return response.json()

    def route_request(self, prompt: str, dry_run: bool = True) -> dict[str, Any]:
        decision = self.make_decision(prompt)
        if dry_run:
            return {"dry_run": True, "decision": decision.__dict__}

        if not self.api_key:
            raise RuntimeError("真实调用前必须设置 AI_GATEWAY_API_KEY")

        trace_id = str(uuid.uuid4())
        started = time.monotonic()
        errors: list[dict[str, str]] = []

        for attempt, model in enumerate(decision.ordered_models, start=1):
            elapsed = time.monotonic() - started
            remaining = self.timeout_s - elapsed
            if remaining <= 1.0:
                break
            try:
                result = self._request_once(model, prompt, trace_id, remaining)
                self._mark_success(model)
                return {
                    "trace_id": trace_id,
                    "selected_model": model,
                    "attempt": attempt,
                    "decision": decision.__dict__,
                    "response": result,
                }
            except GatewayError as exc:
                errors.append({"model": model, "error": str(exc)})
                if not exc.retryable:
                    # 参数、权限等 4xx 错误切模型通常无效,立即暴露问题。
                    raise
                self._mark_failure(model)
                # 退避受总时限约束;抖动可避免多个实例同时重试。
                delay = min(0.25 * (2 ** (attempt - 1)) + random.random() * 0.2, 1.5)
                if time.monotonic() - started + delay < self.timeout_s:
                    time.sleep(delay)

        raise RuntimeError({"trace_id": trace_id, "errors": errors})


def build_router() -> SmartAIRouter:
    # 业务只依赖稳定别名;具体供应商型号由模型注册表或环境变量注入。
    low = os.getenv("MODEL_LOW", "text-low")
    balanced = os.getenv("MODEL_BALANCED", "text-balanced")
    high = os.getenv("MODEL_HIGH", "reasoning-high")
    candidates = [
        ModelCandidate(low, "low", "provider-a", "domain-a", 0.78, 0.08, 650),
        ModelCandidate(balanced, "balanced", "provider-b", "domain-b", 0.88, 0.35, 1100),
        ModelCandidate(high, "high", "provider-c", "domain-c", 0.96, 1.00, 2200),
    ]
    return SmartAIRouter(
        endpoint=os.getenv("AI_GATEWAY_URL", "http://127.0.0.1:8000"),
        api_key=os.getenv("AI_GATEWAY_API_KEY", ""),
        candidates=candidates,
    )


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="动态模型路由示例")
    parser.add_argument("prompt", nargs="?", default="把这段客服文本分类为售前或售后")
    parser.add_argument("--execute", action="store_true", help="发送真实 API 请求")
    args = parser.parse_args()
    output = build_router().route_request(args.prompt, dry_run=not args.execute)
    print(output)

运行 python smart_router.py 会输出复杂度、评分理由和候选顺序;运行 python smart_router.py "分析这段代码的并发根因" --execute 才会发起真实请求。示例刻意使用相对成本和企业别名,因为价格与在役型号会变化,路由器不应把易变信息编译进业务代码。在这里插入图片描述

这段代码覆盖了路由主链路,但生产化还需补齐五类能力。第一,模型目录应来自配置中心并进行签名、版本化和热更新。第二,质量分数应按任务拆分,不能让通用平均分覆盖关键场景。第三,熔断状态需要放入共享或分片状态存储,避免每个实例各自判断。第四,响应应通过 JSON Schema、事实校验或代码测试进入质量闸门。第五,日志只记录必要元数据,对 Prompt、密钥和个人信息做脱敏,同时保留 trace_id、策略版本、候选集、选中原因和实际费用,以便回放。

还要警惕 Fallback 的质量语义。主模型失败后,备模型未必支持相同工具、上下文或输出格式。注册表应明确能力矩阵,并在切换前重新检查硬约束。对于扣款、发信、下单等有副作用的 Agent 工具调用,必须使用业务幂等键;否则首次请求虽然在客户端超时,却可能已经执行成功,第二个模型再次调用工具会造成重复操作。

五、全模态扩展:从 Token 路由到 GB 级图像与视频负载均衡

文本模型的请求体通常以 KB 或 MB 计,图像和视频任务则可能达到 GB 级。把大文件直接以 Base64 塞进同步 JSON,会同时放大网关内存、网络带宽和日志风险。全模态路由的第一原则是“控制流与数据流分离”:API 只传对象地址、内容摘要和任务参数,原始文件通过预签名地址进入对象存储;工作节点按需拉取,输出也写回对象存储。在这里插入图片描述

一个文本到图像再到视频的工作流,不应被视为一次超长模型调用,而应拆成有状态 DAG:

用户需求
  │
  ├─ 文本规划:生成脚本、镜头表与安全标签
  │      └─ 质量闸门:结构、长度、敏感内容
  │
  ├─ 图像生成:按分辨率、风格、队列和单价路由
  │      └─ 结果评估:清晰度、主体一致性、审核
  │
  ├─ 视频生成:按时长、帧率、运动强度和区域路由
  │      └─ 异步轮询或 Webhook 回调
  │
  └─ 合成与交付:转码、字幕、对象存储、生命周期清理

不同模态需要不同的成本单位。文本按输入与输出 Token 估算,Embedding 按输入量,图像按张数、分辨率和采样步数,视频按秒数、分辨率、帧率或 GPU 时间。统一成本函数必须先把这些单位换算为企业内部的“成本点”,再与租户预算比较。如果只统计文本 Token,视频生成会成为新的算力黑洞。

维度 文本请求 图像请求 视频请求 路由关注点
载荷 Prompt 与上下文 参考图、遮罩、提示词 素材、首尾帧、音轨 对象存储与带宽
执行方式 多为同步或流式 同步与异步并存 以异步任务为主 队列和状态机
主要成本 输入/输出 Token 分辨率、步数、张数 时长、分辨率、帧率 统一成本点
质量指标 正确率、事实性 主体一致、审美、合规 时序一致、运动稳定 模态专属评测
失败恢复 重试或换模型 保留种子重建 从镜头或分段续跑 幂等与检查点

异步任务需要明确状态机,例如 QUEUED → RUNNING → SUCCEEDED/FAILED/CANCELLED。客户端提交任务后获得 job_id,网关把任务放入按模态、优先级和租户隔离的队列。Webhook 必须包含时间戳、签名和事件 ID,接收端验证签名并按事件 ID 去重。若回调失败,平台按退避策略重发;客户端也可用 job_id 查询状态,但轮询频率要受限,避免状态接口成为新的热点。

全模态负载均衡不能只看节点空闲数。视频任务占用 GPU 时间长,采用普通轮询会让短任务排在长任务后面。更合理的方法是加权最短预计完成时间:综合当前队列长度、模型冷启动、预计 GPU 秒数、区域带宽和缓存命中率。对于交互式预览,可先路由到低分辨率快速模型;用户确认构图后,再把确定的种子和参数提交给高质量渲染池。这种两阶段策略往往比直接生成最终分辨率更省成本。

多模态 Fallback 也有特殊约束。文本模型切换后通常还能重新生成;视频模型切换可能改变画面风格、角色一致性和随机种子。模型注册表应记录是否支持种子、首尾帧、参考角色、区域重绘和续帧。备选模型只有在语义能力兼容时才可自动切换,否则应停在可恢复检查点,让上层决定降分辨率、缩短时长还是更换风格。

安全与合规必须进入路由前后两端。输入文件先做类型校验、恶意内容扫描和元数据清理,路由时根据数据等级限制区域与供应商,输出再做版权、隐私和内容安全审核。对象存储地址应短期有效,日志不得保存完整预签名 URL;任务完成后按生命周期策略删除中间帧与临时音轨。所谓“全模态聚合”,只有把文件、队列、回调、成本和安全同时纳入治理,才不是简单的模型列表扩充。

六、生产压测与基准评估:拆开测量网关开销、P99 与测试资源

压测模型网关时,最常见的错误是只看端到端平均延迟。平均值会掩盖排队、限流和冷启动造成的长尾,而把上游生成时间混入网关耗时,又无法判断路由层是否达标。建议至少拆成 DNS/连接、网关鉴权、特征提取、路由决策、上游排队、首 Token、完整生成和回调交付八个阶段,并为每段写入 Trace Span。
在这里插入图片描述

对同步文本链路,路由网关的内部 P99 目标可设在 20ms 以内,但它不包含公网连接和模型生成。这个目标需要通过本地策略缓存、预编译规则、批量指标上报和连接池实现。复杂语义路由如果需要再调用一个模型,开销可能达到数百毫秒,应将轻量分类器本地化,或只在低置信度请求上启用二级判断。

在搭建多模型路由策略的验证期,为了对上游 500+ 节点的握手延迟和降级逻辑进行基准测试,开发者无需先扩大生产算力池,可以接入统一测试节点 https://178.nz/bo 建立隔离的调试会话。初始化接入端点时会自动签发 2 个测试路由积分(Routing Credits),可用于完成一次从简单文本分类到图像渲染候选的 Fallback 链路校验。测试前仍应以端点实时返回的模型目录、可用区域和计费字段为准,并使用脱敏样本,不能把测试额度与生产容量承诺混为一谈。

一套可信的压测应包含四组实验:

  1. 基线实验:关闭语义路由,只做鉴权和固定转发,测量网关基础开销。
  2. 策略实验:打开复杂度计算与候选排序,对比路由决策增加的 P50、P95 和 P99。
  3. 故障注入:人为注入 429、5xx、连接超时、慢响应和错误 JSON,验证熔断与 Fallback。
  4. 容量实验:按阶梯提升 QPS,观察 CPU、内存、连接池、队列和错误率拐点。

流量模型必须接近生产分布。若线上 70% 是短文本、20% 是 RAG 长上下文、8% 是图像、2% 是视频,压测也应保留这种混合比例;全部使用“你好”只会得到虚假的高吞吐。还要模拟租户倾斜:一个大客户可能占据 40% 流量,限流器必须证明它不会挤压其他租户。对于流式响应,同时记录首 Token 延迟 TTFT、Tokens/Sec 和完整响应时间;三者分别反映交互感受、生成吞吐与连接占用。

下表是一组用于验收设计的示例数据,不是任何供应商的公开性能承诺:

场景 QPS 网关 P50 网关 P99 端到端 P99 Fallback 成功率 观察结果
固定转发基线 200 4ms 11ms 2.8s 不适用 连接池稳定
动态文本路由 200 7ms 18ms 3.0s 不适用 满足 20ms 目标
10% 上游 429 200 8ms 19ms 3.6s 98.7% 退避后跨域切换
主节点 5xx 200 9ms 20ms 4.1s 99.2% 熔断在阈值后生效
混合全模态提交 80 10ms 23ms 异步统计 97.9% 对象存储签名成为热点

当样本量不足时,不要轻易宣称 P99。若只发 100 个请求,P99 几乎等于最慢的一个样本,统计波动极大。每个稳定阶段至少保留数万次请求,并给出窗口长度、并发数、载荷分布、失败定义和置信区间。视频异步任务则应报告排队 P99、开始执行 P99、完成 P99 和超时率,不能与文本接口的毫秒级网关延迟放在同一列比较。

故障演练的验收条件也应量化:熔断发现时间小于多少秒,切换期间丢失多少请求,是否超过总超时预算,恢复后半开放量是否导致二次雪崩,账单是否出现重复扣费。只有把“正常时够快”和“异常时可控”同时测出来,高可用 SLA 才有证据支撑。

七、降本案例:SaaS 团队如何在质量红线内节省 70% 算力开销

下面使用一个脱敏的 SaaS 演算案例说明路由收益。团队每月处理约 825 万次 AI 请求,早期为了保证效果,所有文本任务都走高能力模型,图像任务也固定在同一高质量档。财务侧只看到总账单,研发侧无法按业务归因;遇到 429 时,应用实例各自重试,既放大峰值又产生重复费用。

改造前,团队先做两周影子评测,把请求划分为分类抽取、客服 FAQ、RAG 总结、复杂分析和图像任务,并为每类定义质量红线。随后只将低风险流量逐步切到轻量模型:第一周 5%,第二周 20%,达到质量阈值后扩大到 70%。复杂分析仍使用高能力模型,低置信度答案由质量闸门自动升级。为了避免价格变化干扰,表中按团队合同和月度用量折算为“万元等价成本”,数据用于展示计算方法而非承诺固定价格。

工作负载 月请求量 改造前 路由后 关键策略
分类与字段抽取 420 万 12.6 万元 0.8 万元 轻量模型、Schema 校验、失败升级
客服 FAQ 260 万 10.4 万元 1.8 万元 缓存、RAG 压缩、置信度路由
RAG 总结 110 万 8.8 万元 2.5 万元 按上下文和引用完整性选择档位
复杂分析与代码 30 万 4.5 万元 4.0 万元 保留强模型,减少无效长上下文
图像任务 5 万 1.2 万元 1.1 万元 预览与最终渲染分层
网关、评测与观测 0 1.0 万元 路由服务、影子评测、日志存储
合计 825 万 37.5 万元 11.2 万元 下降约 70.1%

节省并非主要来自“找到一个更便宜的模型”,而是来自工作负载分层。分类抽取占请求量一半以上,却不需要复杂推理;客服 FAQ 通过缓存和检索上下文压缩同时减少了调用次数与输入 Token;复杂分析没有激进降级,因此守住了客户报告的质量。新增的网关、评测和日志成本被明确计入,没有把基础设施开销藏在节省数字之外。

质量侧设置了三条红线:字段抽取准确率不得低于 98.5%,FAQ 事实一致性不得低于原基线 0.5 个百分点,复杂分析人工通过率不得下降。上线后,低成本模型的原始回答占比达到 76%,但约 6% 因 Schema 失败或置信度不足被升级;最终合格率与基线持平。这个“6% 升级流量”不是失败,而是路由器用少量高价调用守住质量边界的必要成本。

稳定性收益也要独立计算。改造前某一上游限流时,P99 一度超过 30 秒;改造后,通过租户限流、单请求重试预算和跨故障域 Fallback,故障窗口内 98% 以上的合格请求仍在业务超时前完成。需要强调的是,70% 是特定请求分布下的结果,不是所有企业都能复制的固定比例。如果高难度推理占比很高,合理节省可能只有 20%;如果大量是分类和模板抽取,节省可能更高。

项目 ROI 应按下面的方式计算:

ROI=原推理成本−新推理成本−网关与评测成本−迁移成本迁移成本+持续治理成本 ROI=\frac{\text{原推理成本}-\text{新推理成本}-\text{网关与评测成本}-\text{迁移成本}}{\text{迁移成本}+\text{持续治理成本}} ROI=迁移成本+持续治理成本原推理成本新推理成本网关与评测成本迁移成本

迁移成本包括评测集建设、策略开发、观测改造和团队培训。建议先选择一个调用量大、风险低、输出可自动校验的场景试点,例如分类或结构化抽取。没有质量闸门的全面切流,很容易把 API 账单下降变成客服与人工复核成本上升。

八、结语:下一代 AI 基础设施比拼的不是模型大小,而是调度精度

企业 AI 进入规模化阶段后,模型本身会越来越像可替换的计算资源。真正形成壁垒的,是企业掌握的任务评测集、模型注册表、路由策略、质量反馈、成本账本和故障演练能力。它们决定一个请求是否被送到正确的模型,也决定供应商变化时业务能否平稳迁移。在这里插入图片描述

一套成熟的动态模型路由中台应遵循六条原则:先用硬约束过滤,再做成本—质量评分;以业务别名隔离具体型号;把 Fallback 设计成跨故障域切换;用总超时和重试预算阻止故障放大;按任务而不是按品牌评估质量;用 Trace、成本归因和离线回放形成反馈闭环。全模态场景还要进一步分离控制流与数据流,把对象存储、队列、Webhook、内容安全和中间产物治理纳入统一架构。

落地顺序不必一步到位。第一阶段先统一入口和成本归因,让每次调用可观测;第二阶段上线规则路由与模型别名,处理最清晰的低风险任务;第三阶段加入质量闸门、熔断和跨供应商降级;第四阶段再用历史数据训练语义路由器,并通过受控探索持续校准。每一步都应有可回滚策略和量化验收指标。

所谓百倍降本,不应成为脱离工作负载的口号;所谓 99.99% 高可用,也不能只存在于架构图。前者需要用“每个合格结果的真实成本”证明,后者需要用错误预算、故障注入和跨域切换证明。未来的 AI 基础设施不会只比谁接入的模型更多,而会比谁能在每一次请求到来时,更准确地判断应该用哪个模型、花多少钱、承担多大风险,以及失败后如何恢复。

当团队开始建设模型网关时,最值得先问的不是“支持多少模型”,而是三个问题:目前月均大模型 API 支出中,有多少流量属于低复杂度任务?最严重的单点故障发生在哪个故障域?当主模型不可用时,业务是否拥有一条经过真实演练、质量可验证、成本可核算的替代路径?这三份答案,就是动态模型路由项目最可靠的起点。在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐