欢迎光临
我们一直在努力

生产环境怎么选模型:用质量、延迟、成本三维做路由决策

同一个 AI 产品里,用户表面上只看到一个输入框,后面却未必应该永远调用同一个模型。摘要、分类、结构化抽取、复杂分析、带工具的执行、长文改写,对能力和上下文的要求完全不同。把最强模型固定在所有请求上,开发最省事,账单和等待时间通常最先失控;把最便宜模型固定在所有请求上,失败会以“回答似乎还行”的方式积累,直到业务方发现关键结论不可用。

模型路由的目标不是寻找一个永远最优的模型,而是在每个可观察的请求上做受约束的选择:先满足任务质量底线,再满足交互时延目标,最后才在候选中压低成本。这里的“质量”不能只理解为排行榜分数,它必须是某类业务任务的通过率;“延迟”也不只是模型官网标称速度,而是用户从提交到首个可用结果的完整耗时;“成本”则必须同时包括输入、输出、重试和无效调用。本文从一个不依赖特定厂商的最小实现开始,逐步补齐评测、预算、回退和观测。它适合先在一个服务或一条工作流中落地,而不是一上来搭建一个难以维护的“智能路由平台”。

先说清楚边界。本文讨论的是应用层选择模型,不讨论绕过供应商的限流,也不把敏感内容路由到未经批准的第三方。路由器只能在已获安全、合规和数据处理授权的模型集合中选择。若请求包含受限数据,允许模型列表必须在进入评分逻辑之前就被收窄;成本更低不是跨越数据边界的理由。

1. 先把问题拆开:选模型不是只比单价

生产选型经常犯一个直觉错误:先找每百万 token 单价最低的模型,然后用几个难例补救。这个顺序颠倒了。模型单价是静态表格,真实任务却有输入长度、输出上限、失败概率、用户容忍时间和业务风险。对于一次必须正确的合同条款抽取,低单价却频繁人工返工的路线并不便宜;对于按钮点击后立刻显示的简短分类,质量略高但首 token 慢几秒的路线也未必更好。

建议先为每个任务定义三件事:质量门槛、延迟服务目标和允许成本。质量门槛例如“JSON 可解析且字段校验通过”“引用能回指到给定证据”“人工抽检通过”;延迟目标例如“同步界面在目标网络条件下的 P95 首屏时间”;成本则按每次成功业务结果核算,而不是按一次 API 请求核算。没有这些定义,路由器只能用模型名称和主观印象做决定,最终只是把偶然经验写成代码。

下面这张图是最小的决策链路。注意安全策略在最前面,评估器也不与模型调用混在一起:前者决定谁可以被调用,后者为后续选择积累可解释证据。

#mermaid-svg-TEpFjxVc6iim4buN{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TEpFjxVc6iim4buN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TEpFjxVc6iim4buN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TEpFjxVc6iim4buN .error-icon{fill:#552222;}#mermaid-svg-TEpFjxVc6iim4buN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TEpFjxVc6iim4buN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TEpFjxVc6iim4buN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TEpFjxVc6iim4buN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TEpFjxVc6iim4buN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TEpFjxVc6iim4buN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TEpFjxVc6iim4buN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TEpFjxVc6iim4buN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TEpFjxVc6iim4buN .marker.cross{stroke:#333333;}#mermaid-svg-TEpFjxVc6iim4buN svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TEpFjxVc6iim4buN p{margin:0;}#mermaid-svg-TEpFjxVc6iim4buN .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-TEpFjxVc6iim4buN .cluster-label text{fill:#333;}#mermaid-svg-TEpFjxVc6iim4buN .cluster-label span{color:#333;}#mermaid-svg-TEpFjxVc6iim4buN .cluster-label span p{background-color:transparent;}#mermaid-svg-TEpFjxVc6iim4buN .label text,#mermaid-svg-TEpFjxVc6iim4buN span{fill:#333;color:#333;}#mermaid-svg-TEpFjxVc6iim4buN .node rect,#mermaid-svg-TEpFjxVc6iim4buN .node circle,#mermaid-svg-TEpFjxVc6iim4buN .node ellipse,#mermaid-svg-TEpFjxVc6iim4buN .node polygon,#mermaid-svg-TEpFjxVc6iim4buN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TEpFjxVc6iim4buN .rough-node .label text,#mermaid-svg-TEpFjxVc6iim4buN .node .label text,#mermaid-svg-TEpFjxVc6iim4buN .image-shape .label,#mermaid-svg-TEpFjxVc6iim4buN .icon-shape .label{text-anchor:middle;}#mermaid-svg-TEpFjxVc6iim4buN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TEpFjxVc6iim4buN .rough-node .label,#mermaid-svg-TEpFjxVc6iim4buN .node .label,#mermaid-svg-TEpFjxVc6iim4buN .image-shape .label,#mermaid-svg-TEpFjxVc6iim4buN .icon-shape .label{text-align:center;}#mermaid-svg-TEpFjxVc6iim4buN .node.clickable{cursor:pointer;}#mermaid-svg-TEpFjxVc6iim4buN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TEpFjxVc6iim4buN .arrowheadPath{fill:#333333;}#mermaid-svg-TEpFjxVc6iim4buN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TEpFjxVc6iim4buN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TEpFjxVc6iim4buN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TEpFjxVc6iim4buN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TEpFjxVc6iim4buN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TEpFjxVc6iim4buN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TEpFjxVc6iim4buN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TEpFjxVc6iim4buN .cluster text{fill:#333;}#mermaid-svg-TEpFjxVc6iim4buN .cluster span{color:#333;}#mermaid-svg-TEpFjxVc6iim4buN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TEpFjxVc6iim4buN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TEpFjxVc6iim4buN rect.text{fill:none;stroke-width:0;}#mermaid-svg-TEpFjxVc6iim4buN .icon-shape,#mermaid-svg-TEpFjxVc6iim4buN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TEpFjxVc6iim4buN .icon-shape p,#mermaid-svg-TEpFjxVc6iim4buN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TEpFjxVc6iim4buN .icon-shape .label rect,#mermaid-svg-TEpFjxVc6iim4buN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TEpFjxVc6iim4buN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TEpFjxVc6iim4buN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TEpFjxVc6iim4buN :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

失败且可安全重试

请求与租户上下文

数据分类与允许模型

任务分类和输入估算

过滤质量不达标候选

按延迟和成本排序

调用选中模型

结构校验与业务检查

日志: 模型 令牌 延迟 结果

受限回退模型

这里有一个很重要的取舍:不要把“自动判断任务难度”的提示词当作路由的唯一依据。模型可以帮助分类,但它本身也会产生延迟、成本和误判。第一版优先复用已有的产品信号,例如接口路径、工作流节点、用户选择的模式、请求要求的 JSON schema、输入 token 估算和租户等级。只有当这些信号覆盖不了任务时,才考虑用一个小模型做额外分类,并把分类结果记录下来检查。

2. 用真实业务题集建立质量底线

公开基准适合了解能力边界,却不能替代你的验收。一个客服系统关心是否遵守商品政策,一个数据助手关心 SQL 是否只读且返回正确字段,一个报告生成器关心事实、格式和引用。把这些任务混成“总体准确率”,会让模型在最重要的失败类型上被平均分掩盖。

建立题集时不需要从几千条开始。先收集已经发生过的典型请求、人工修改过的输出、明确的失败工单,以及少量专门构造的边界例子。每条样本应写清任务类型、可公开的输入或脱敏输入、预期检查、风险等级和版本。答案是开放文本时,尽量拆成可客观检查的规则:必须包含哪些要点、不得出现哪些断言、是否只能依据上下文回答。让另一个模型做“裁判”可以提高覆盖率,但高风险样本仍需人工抽检;裁判模型也会偏向写得流畅的错误答案。

下面代码没有调用任何模型。它演示如何把候选输出交给可重复的业务检查:先验证 JSON,再验证必填字段和引用范围。与其把一次模型输出的“感觉不错”写入数据库,不如把这类确定性检查变成路由质量指标的一部分。

from __future__ import annotations

from dataclasses import dataclass
import json
from typing import Any

@dataclass(frozen=True)
class CheckResult:
passed: bool
reason: str

def check_extraction(text: str, allowed_sources: set[str]) > CheckResult:
try:
payload: dict[str, Any] = json.loads(text)
except json.JSONDecodeError as exc:
return CheckResult(False, f"不是合法 JSON: {exc.msg}")
if not isinstance(payload.get("summary"), str) or not payload["summary"].strip():
return CheckResult(False, "缺少非空 summary")
citations = payload.get("citations")
if not isinstance(citations, list) or not all(isinstance(x, str) for x in citations):
return CheckResult(False, "citations 必须是字符串列表")
unknown = set(citations) allowed_sources
if unknown:
return CheckResult(False, f"出现未授权引用: {sorted(unknown)}")
return CheckResult(True, "结构和引用均通过")

def demo() > None:
good = '{"summary":"库存不足,建议补货","citations":["inventory-12"]}'
assert check_extraction(good, {"inventory-12"}).passed
assert not check_extraction("not-json", {"inventory-12"}).passed

if __name__ == "__main__":
demo()

质量数据还必须分版本保存。提示词、工具定义、检索索引、系统指令和模型版本中任一项改变,都可能改变结果。若只记“某模型通过率 92%”,几周后这个数字无法解释,也不能支撑回滚。最实用的字段包括:task_type、model_id、prompt_version、dataset_version、checker_version、输入/输出 token、调用结果以及失败原因。输入正文可能涉及隐私,日志中可保留哈希、长度、风险标签和经脱敏的错误样本,原文存储则按数据政策处理。

3. 延迟要区分排队、首 token 与总耗时

流式对话里,用户往往先感受首 token,而不是总生成时长;批量离线任务则更关心吞吐和最终完成时间。把两类请求放进同一个“平均响应时间”会得到一个没有行动价值的数。至少分开记录排队时间、连接时间、首 token 时间、模型生成时间和重试耗时。只有拆开,才知道该换模型、缩短上下文、提高并发,还是只是下游网络抖动。

路由器不应根据一次调用的耗时做选择。单次延迟很容易受冷启动、网络和共享限额影响,应该用滑动窗口内按任务类型统计的 P50/P95,并在样本数不足时回到保守默认值。并且,延迟需要有“超时后的业务行为”:允许后台继续的报告任务可以转异步;必须同步响应的任务应在明确的时间预算内回退为简短确认、检索结果或人工入口,不能无限重试。

对于长输入,token 估算本身也会影响选择。精确分词需要对应模型的 tokenizer,但第一版不必为了估算新引入一个巨大依赖。可以用请求字节数或字符数只做粗过滤,真正计费仍以供应商返回的 usage 为准。估算的职责只是避免明显不可能放进上下文窗口的请求进入候选队列;一旦接近窗口边缘,应使用官方 tokenizer 或在调用前做可解释的截断、摘要、分块处理。

4. 成本按“完成一次业务结果”算,而不是按请求算

调用账单中的输入 token、输出 token 很容易取得,因此许多系统只统计它们。可用户的成本还包括无效重试、格式错误后的二次修复、工具失败后的重复规划、缓存未命中的重复上下文以及人工兜底。若低价模型把 15% 的请求送进昂贵回退,路由器至少需要把这条回退链路的成本归因给初始选择,才不会被表面单价误导。

成本模型应配置在代码外或通过安全的部署配置下发,不能让前端请求携带单价。因为价格、上下文窗口和供应商模型标识都会变化,静态值需要定期核对官方价格页;本文不写死任何厂商数字。下面的实现把模型的能力、风险许可和每 token 费用放在一个只读目录中,并计算预估上界。预估值用于候选排序和预算拦截,结算值仍应取调用响应的实际 usage。

from __future__ import annotations

from dataclasses import dataclass

@dataclass(frozen=True)
class ModelProfile:
name: str
quality_by_task: dict[str, float]
p95_ms_by_task: dict[str, int]
input_cost_per_million: float
output_cost_per_million: float
allowed_risk: frozenset[str]

def estimated_cost(profile: ModelProfile, input_tokens: int, max_output_tokens: int) > float:
if min(input_tokens, max_output_tokens) < 0:
raise ValueError("token 数不能为负")
return (
input_tokens * profile.input_cost_per_million
+ max_output_tokens * profile.output_cost_per_million
) / 1_000_000

def choose_model(
profiles: list[ModelProfile], *, task: str, risk: str, input_tokens: int,
max_output_tokens: int, min_quality: float, latency_budget_ms: int,
max_estimated_cost: float,
) > ModelProfile:
candidates = [
p for p in profiles
if risk in p.allowed_risk
and p.quality_by_task.get(task, 0.0) >= min_quality
and p.p95_ms_by_task.get(task, 10**9) <= latency_budget_ms
and estimated_cost(p, input_tokens, max_output_tokens) <= max_estimated_cost
]
if not candidates:
raise LookupError("没有同时满足质量、延迟、风险和预算的模型")
return min(candidates, key=lambda p: estimated_cost(p, input_tokens, max_output_tokens))

def demo() > None:
profiles = [
ModelProfile("fast", {"classify": 0.93}, {"classify": 800}, 1.0, 2.0, frozenset({"internal"})),
ModelProfile("strong", {"classify": 0.97}, {"classify": 1700}, 4.0, 8.0, frozenset({"internal", "restricted"})),
]
selected = choose_model(profiles, task="classify", risk="internal", input_tokens=300,
max_output_tokens=100, min_quality=0.90, latency_budget_ms=1000, max_estimated_cost=0.01)
assert selected.name == "fast"

if __name__ == "__main__":
demo()

代码故意没有使用“质量 × 价格 ÷ 延迟”的单一加权分数。这样的公式看似方便,实际上会把不可妥协的条件变成可交易的分数:一个不允许处理受限数据的模型,可能因为便宜而胜出;一个未达到最低质量的模型,可能因为快而胜出。更稳妥的顺序是先过滤硬约束,再对剩余候选按成本排序。业务确实需要在质量和成本之间做连续折中时,也应以任务级策略明确表达,而不是藏在一个没人能解释的全局权重里。

5. 回退不是“失败后随便换一个更强模型”

回退通常被理解为可靠性保障,但不受约束的回退会把成本和风险扩大。一个格式校验失败的请求,可能适合在同一模型上加一次更严格的 schema 提示;一个工具调用被拒绝的请求,应该显示权限不足,而不是换更强模型继续尝试;一个疑似提示注入的请求,则应该被安全策略拦截,不能被“再试一次”覆盖。

因此需要为失败原因建立小而清晰的表。可重试的短暂网络错误和限流错误需要指数退避及上限;输出截断可能在预算内提高输出上限或要求更短答案;JSON 解析失败可以进行一次受限修复;安全检查、权限拒绝、输入超过最大上下文、业务校验不通过,通常应直接返回可理解的错误或交给人工。每一次回退都应携带前一次的结果码,避免循环。

路由决策也不应由客户端决定。客户端可以声明交互模式和是否接受异步,但模型目录、风险标签、预算、回退链和最终模型名都应在服务端完成。否则用户只需伪造一个“低风险”字段就可能让数据流向不该调用的模型,或绕开租户预算。对多租户产品,预算要同时有单请求上限、用户或工作区周期上限,以及系统级熔断,避免突发流量拖垮所有人。

6. 观测:让每一次选择能回答“为什么是它”

模型路由最怕黑箱。发生投诉时,工程师至少要在一条关联日志中看到:请求属于哪个任务、命中了哪条策略、候选为什么被排除、最终模型、提示词和工具版本、输入/输出 token、首 token 与总耗时、校验是否通过、是否回退以及最终成本。不要记录完整敏感提示词来换取排障便利;为字段分级、脱敏和最短保留期,比“日志越全越好”更符合生产要求。

仪表盘不要一开始堆几十个图。先固定四张:按任务的质量通过率;按模型和任务的 P95 首 token/总耗时;按模型、租户和路由策略的实际成本;回退率及失败原因分布。任何一个图出现异常,都能指向具体操作。例如质量下降先比对题集和提示词版本;成本上升先看输出 token 与回退率;延迟上升则先区分供应商响应与本地排队。

上线方式同样要克制。先以 shadow 模式记录“当前模型结果”和“建议模型结果”,但不要把后者返回给用户;在脱敏且允许的样本上比较检查结果和成本。然后选一个低风险任务做小比例灰度,预设质量、延迟、成本和投诉率的回滚阈值。没有基线就没有“优化”,只有换了模型后的主观感受。

7. 一份可执行的落地顺序

第一周先选一个任务而非全站,例如“从固定模板中提取字段”。定义一个 schema、收集一小组脱敏样本、实现确定性检查,并记录当前模型的 token 与延迟。第二周挑一个同样获准处理该数据的候选模型,在离线题集上跑相同提示词,确认质量门槛不降。第三周把本文的硬约束过滤和一次受限回退接入服务端,影子记录后再灰度。这个顺序的好处是每一步都有独立价值:即便最终不需要动态路由,你仍得到一套任务评测和可观测数据。

不要过早把路由做成自我学习系统。请求量小、模型只有两三个、任务边界清楚时,一张版本化策略表和几十行纯函数比复杂的 bandit 或强化学习更可靠。只有当策略已经积累大量稳定标注、任务分布变化明显且人工规则维护成本真正超过收益时,才考虑更复杂的方法;届时也仍需保留硬约束、离线回放和一键回滚。

8. 常见误区与最终检查

最常见的误区有四个:用供应商通用榜单代替业务验收;用平均延迟代替用户感知的分位数;只记录单次 API 单价而不归因重试和回退;让模型根据一句提示词自己决定是否应该调用更强模型。它们都不是不能用,而是缺少可审计的边界。生产系统真正需要的是知道一个选择何时有效、何时必须停下。

提交前可以逐项检查:模型目录是否标有数据风险许可;每个任务是否有可运行的质量检查;策略是否先过滤硬约束;超时和回退是否有次数上限;日志是否避免写入不该保存的原文;模型、提示词、题集和策略是否都有版本;是否能在不改业务代码的情况下关闭新路由。完成这些,再去追求更精细的成本优化,顺序才不会反过来。

9. 从任务画像开始,而不是从模型名字开始

一张模型清单很容易演变成“大家各自凭印象挑一个”。更好的做法是先给任务建画像。画像不需要复杂的特征工程,通常写下输入是否包含当前私有事实、输出是否要求严格结构、是否允许异步、错误的业务后果、预期输入和输出长度、是否会调用工具、是否必须引用来源、以及能够处理的风险等级就够了。它让产品、算法和运维讨论的是同一个对象:不是“这个模型厉不厉害”,而是“它是否适合这条可观察的业务链路”。

例如,短文本意图分类往往输入短、输出固定、可用确定性检查,适合先选择延迟稳定且成本低的候选;文档问答依赖当前资料,应将检索、引用和权限视为质量前提,模型能力只是一部分;生成面向外部客户的正式回复,可能需要较高的风格与安全门槛,但通常允许在后台生成;涉及工具执行的请求,质量指标不能只看语言,而要看计划是否可执行、参数是否合法、是否正确拒绝越权操作。把任务混在一起统计,会让强模型在高风险任务的价值被低风险流量稀释,也会让便宜模型的适用范围被夸大。

画像还应区分“质量不足”和“质量不确定”。当一个模型在验证集上明显不达标,可以直接排除;当样本少、输入分布变化大或检查器覆盖不足时,不应把它当作通过,而应路由到保守路径并持续采样。很多事故不是模型突然退化,而是系统把从未验证过的新语言、新格式、新业务线悄悄归入了旧任务。为任务保留一个未知或高风险分支,比强行分类更诚实。

10. 策略的所有权与变更方式

路由策略会影响质量、成本和数据流向,因此它不能散落在多个业务 if 语句和前端配置里。第一版可以是一份服务端受版本控制的 YAML、数据库表或代码常量,关键是有明确所有者、评审边界和变更记录。安全允许列表应由安全或数据治理规则控制;任务门槛由业务验收数据支撑;价格和上下文限制由平台维护;产品只能选择已批准策略,不应能直接指定任意模型标识。

策略变更也需要与普通配置一样可审计。一次调整若只为降低成本,必须说明影响哪些任务、最低质量是否保持、预算超限时会发生什么;一次加入新模型,先要完成数据处理、能力、限流与异常行为验证。不要把实时价格抓取后自动改写生产策略,价格页更新、币种、地区、缓存和计费口径都可能造成误判。定期人工核对官方文档并在下一次版本发布时更新,通常比“全自动最优”更安全。

多环境尤其容易被忽略。开发环境的模型别名、密钥和限额不应被当作生产画像;压测流量也不应记入真实成本基线。测试环境可以使用合成或脱敏样本验证策略函数的边界:风险不匹配必须拒绝,样本不足必须走默认,超出预算必须可解释地降级或排队。策略代码最好保持纯函数,输入输出可序列化;这样离线回放一条历史请求时,才能知道当时为什么选中那个模型。

11. 预算不是简单的拒绝开关

成本控制常常被实现为“超过额度就报错”,结果用户在最需要帮助时得到一个模糊失败。更好的预算设计分层:单请求上限阻止异常长上下文或无限输出;工作区周期预算让管理员了解消耗;全局保护防止供应商故障或攻击流量拖垮服务。不同层的动作也不同:单请求可以要求缩短内容、改为摘要或转异步;工作区可提示剩余额度并允许管理员调整;系统级异常则应限流、排队或临时关闭非关键功能。

预算策略不应该让低优先级请求抢占高优先级请求。一个可选的报告润色任务和一个生产告警解释任务,即使 token 相同,业务优先级也不同。可把优先级作为排队和允许模型的条件,而不是让所有人竞争同一份“最强模型池”。但优先级本身也要由服务端可信业务状态决定,不能由客户端随意传入。对于异步任务,提交时记录预估上限,执行时以实际用量结算,并在超出预算前停止继续扩展上下文或工具循环。

12. 如何判断路由器本身是否值得存在

不是所有应用都需要动态路由。如果只有一个模型获准处理数据、任务类型单一、每月请求量很低,或模型切换并不能改善质量/延迟/成本,固定一个模型加基本限流和观测反而更稳。路由器增加了配置、监控、测试和故障面,不能只因为“多模型”听起来先进就引入。

判断方法很简单:用历史或影子请求比较固定方案与候选策略,计算在不降低质量和安全的前提下,是否有足够多请求能走更快或更省的路径;再估算策略维护、评测、异常处理的工程成本。如果收益只存在于少量偶发请求,先做人工选择或按接口路径静态选择即可。只有重复模式明显、候选模型差异可测、且能持续维护题集时,动态路由才是比固定规则更小的长期成本。

无论最终选择动态还是静态,都应把“模型选择”当作可测试的产品决策。路由器不需要神秘,它只需在明确边界内稳定地做同一件事:把对的任务交给已经验证且被允许的模型,并在证据不足时选择保守路径。

13. 验收一次策略发布

策略发布前至少准备三组回放:典型成功请求证明没有无谓升级;高风险请求证明不会选到未批准模型;接近预算、延迟或上下文上限的请求证明会走预期降级。运行后比较选中模型、排除理由、估算与实际 token、检查器结果和回退次数。若任一硬约束被突破,停止发布而不是调大权重掩盖问题。将这组回放和策略版本一同保存,下一次价格或模型快照变化时可以重复执行。

还应覆盖候选为空的场景:所有模型因风险、质量、预算或延迟被过滤时,服务要返回明确的业务降级,而不是偷偷选择一个“最接近”的模型。可选择缩短任务、转异步、提示用户缩小范围或进入人工流程,具体方式由产品决定。测试中断开一个候选模型、模拟超时和超预算,确保没有隐藏的默认管理员路径。对路由器来说,安全地不做决定也是正确结果。

线上灰度期间,选择结果还应与人工或旧策略建立对照。若新策略把请求改派到更低成本模型,却让结构校验、引用校验或转人工率变差,节省不是净收益。把停用开关放在路由入口,而不是散落在每个业务调用点;这样出现异常时可以立即回到已知路径。路由策略只有可验证、可回退,才配得上进入生产链路。

每次回滚后也应保留新策略的影子评测,而不是立刻删除。这样可以区分配置错误、阈值不合适和候选模型不稳定,并让下一轮改动只解决已知缺口。没有证据表明动态选择带来净收益时,固定模型就是更简单、更可靠的生产策略。

将这条原则写进运行手册:策略异常时先关闭动态分流,保留追踪,再离线分析;禁止临时扩大模型允许范围或取消质量检查。紧急操作越少越明确,恢复路径越可靠。

运行手册还应写明谁有权修改策略、谁负责确认质量回归、谁通知受影响工作区,以及恢复后何时重新启用灰度。没有这些职责,技术开关存在也可能在事故中无人敢动。用演练确认联系人、权限和步骤实际可用,避免把重要恢复路径只留在个人记忆里。

这样,策略优化才不会反过来增加运行风险。

14. 模型名不要散落在业务代码里

如果 controller、Prompt、任务节点里到处写具体模型 ID,后面做路由、灰度和回滚会非常痛苦。更好的方式是业务只请求一个能力档:

fast_structured
balanced_qa
strong_reasoning
restricted_data_allowed

平台层再把能力档映射到当前批准的模型版本。这样模型更换不会迫使业务仓库大面积修改,也能在同一能力档下做 shadow 和灰度。

能力档不是为了隐藏真实模型。Trace 里仍然必须记录最终 model_snapshot,否则出了问题无法复现。它只是把“业务需求”与“供应商模型名称”解耦。

15. 路由器也要防止反馈回路

如果系统根据最近 10 分钟的成功率自动把更多流量切给“当前最优模型”,短期噪声可能导致流量越切越偏,最终让另一个模型样本越来越少,数据也越来越不可信。

第一版更适合固定窗口、最小样本量和人工审核后的策略更新。即便后面做自适应路由,也保留探索流量、硬约束和一键回退,不要让一个瞬时指标自动改写数据边界或安全允许列表。

16. 模型选型报告最后应该长什么样

不要交一张“模型 A 9.2 分、模型 B 8.7 分”的表。更有用的是按任务给出决策:

任务质量门槛候选P95单成功任务成本结论
意图分类 schema+准确率 A/B 默认 A,失败不回退
RAG 问答 引用正确率 B/C 默认 B,高风险走 C
报告生成 人工通过率 C 异步 C

数据必须来自自己的题集和调用日志。没有真实结果的格子就写“待测”,不要拿供应商宣传数据补齐。这样的选型文档半年后还能解释为什么当时做这个决定。

参考资料

  • OpenAI API 文档:模型与用量
  • OpenAI API 文档:结构化输出
  • NIST AI Risk Management Framework
  • OpenTelemetry:语义约定与可观测性
赞(0)
未经允许不得转载:171主机测评 » 生产环境怎么选模型:用质量、延迟、成本三维做路由决策
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址