2024 年我做一个 AI 功能,要在产品里大量调用大模型:有的地方是帮用户改写一句话,有的地方是把一段文本分个类,也有的地方是让模型写一段复杂的分析。第一版我做得很省事:既然要用大模型,那就选当时最强、最贵的那个模型,所有请求,不管难易,统统打给它。本地一测——效果真好:不管什么任务,模型都答得又准又漂亮。我心里很踏实:"用大模型嘛,选最强的那个,所有请求都打给它,质量肯定最好。"可等它真正上线、扛着真实的流量,一串问题冒了出来。第一种最先把我打懵:月底账单直接翻了十倍——我盯着账单想不通,后来才发现,大量"把文本分个类""调整一下格式"这种极简单的请求,也都在用最贵的模型在算。第二种:用户开始抱怨慢——最强的模型延迟也最高,一个本该秒回的简单任务,也要让用户干等好几秒。第三种最吓人:某天模型厂商那边限流,我的强模型请求大面积 429,而我没有任何后备,整个 AI 功能直接瘫痪。第四种:我想省钱,一刀切把模型全换成最便宜的小模型,结果那些复杂的分析任务,质量瞬间崩了。我盯着这一连串问题想了很久才彻底想明白,第一版错在一个根本的认知上:我以为"用大模型,就是选最强的那个,所有请求都打给它"。这句话把"用好大模型"和"用最贵的大模型"当成了一回事。可它不是。不同档位的模型,价格能差几十倍、延迟能差好几倍;而真实流量里,绝大多数请求其实是简单的——简单请求用最贵的模型,就是在拿大炮打蚊子。真正用好大模型,核心不是"选最强的那个",而是理解任务有难有易、模型有贵有贱,并搭一套"按任务复杂度,把请求路由到合适档位模型"的机制。这篇文章就把大模型的成本优化和模型路由梳理一遍:为什么"全打最强模型"是错的、怎么给请求分级、规则路由怎么做、怎么让一个小模型来决定路由、强模型不可用时怎么降级,以及路由开销、灰度对比、质量监控这些把模型路由真正做对要避开的坑。
问题背景
先把那串问题的现象和我的误判讲清楚,后面所有的设计都是冲着纠正这个误判去的。
现象:把所有请求都打给最强最贵的模型之后,上线冒出一串问题:账单翻了十倍(大量简单请求也在用最贵的模型);用户抱怨慢(强模型延迟高,简单任务也要久等);模型厂商一限流,整个功能就瘫痪(没有任何后备模型);想省钱一刀切换成小模型,复杂任务质量又崩了。
我当时的错误认知:"用大模型,就是选最强最贵的那个,所有请求都打给它,质量最有保障。"
真相:大模型不是只有一个,而是一个有档位的家族:有又贵又强又慢的旗舰模型,也有又便宜又快、能力够用的小模型,它们的每 token 价格能差几十倍,延迟能差好几倍。而你的真实流量,从来不是均质的——里面混着大量极简单的请求(分类、提取、格式调整)和少量真正复杂的请求(多步推理、长文分析)。把简单请求也打给旗舰模型,既烧钱又拖慢;把复杂请求一刀切给小模型,又会让质量崩盘。可靠的做法,是给请求分级,按复杂度把它们路由到不同档位的模型,再配上降级链,让强模型挂了也有后备。
要把模型路由做对,需要几块认知:
- 为什么"全打最强模型"是错的——模型分档,价格和延迟差距巨大;
- 请求分级——任务有难有易,要先能区分它们;
- 规则路由——用启发式规则做第一道、近乎零成本的分流;
- 模型判别路由——规则搞不定时,让一个便宜的小模型来决定路由;
- 降级链、路由开销、灰度对比、质量监控这些工程坑怎么处理。
一、为什么"全打最强模型"是错的
先把这件最根本的事钉死:大模型市场,从来不是"一个模型"在卖,而是一整排不同档位的模型在卖。它们之间的差距,大到超出很多人的直觉:旗舰模型和入门小模型,每百万 token 的价格可能差几十倍;响应延迟,旗舰模型常常是小模型的好几倍。而你做的产品,流量里绝大部分请求——根据我后来的统计,常常超过七成——是"判断这句话是不是投诉""把这段话改得更礼貌""提取出其中的日期"这种简单活儿。这种活儿,一个便宜的小模型就能干得又快又好。你却让最贵的旗舰模型去做,每一次,都是在为你根本用不到的那部分能力,付全价。
下面这段代码,就是我那个"账单翻十倍"的第一版:
# 反面教材:不管什么请求,统统打给最强最贵的模型
from openai import OpenAI
client = OpenAI()
def naive_complete(prompt):
resp = client.chat.completions.create(
model="gpt-4o", # 破绽:旗舰模型,所有请求一律用它
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
# 破绽一:"这句话是不是投诉" 这种简单分类,也在烧旗舰模型的钱。
# 破绽二:旗舰模型延迟高,简单任务也要让用户等好几秒。
# 破绽三:gpt-4o 一旦限流或故障,这里没有任何后备,直接全挂。
这段代码在本地测试时表现完美,因为我本地只测了几十次,账单可以忽略不计,延迟我也不在意。它的问题不在代码本身,而在一个被忽略的前提:它默认"所有请求都值得用最贵的模型"。可这把"用大模型"简化成了"用一个大模型"。于是那串问题就有了解释:账单翻十倍,是因为七成简单请求付了旗舰模型的全价;慢,是因为简单任务也被塞进了高延迟的旗舰模型;一限流就全瘫,是因为把所有鸡蛋都放进了一个篮子。问题的根子清楚了:用好大模型的工程量,全在"承认任务有难有易、模型有贵有贱"之后——你不为这种差异做分流,就只能一直付最贵的价、担最大的险。先从"怎么区分难易"说起。
二、给请求分级:任务有难有易
要做路由,第一步不是写路由代码,而是先把你的模型和任务,都摆清楚。一边,是你能用的几档模型,各自的价格和定位;另一边,是你的业务里有哪几类任务,各自有多难。先把模型档位显式地定义出来:
# 把可用模型按档位列清楚:每档的定位、单价(每百万 token,美元,示意值)
MODEL_TIERS = {
"cheap": {
"name": "gpt-4o-mini",
"price_per_mtok": 0.15, # 便宜、快,适合简单任务
},
"standard": {
"name": "gpt-4o",
"price_per_mtok": 2.50, # 中档,适合大多数常规任务
},
"strong": {
"name": "o1",
"price_per_mtok": 15.00, # 旗舰,只留给真正复杂的推理
},
}
# 一眼能看出:cheap 和 strong 的单价差了 100 倍。
# 这就是为什么"简单请求别打 strong"能省下惊人的成本。
有了档位表,就能把"省了多少钱"算成一笔明账——这笔账,是说服自己和团队做路由的最有力依据:
def estimate_cost(tier, tokens):
"""估算某一档模型处理指定 token 量的成本。"""
price = MODEL_TIERS[tier]["price_per_mtok"]
return tokens / 1_000_000 * price
# 假设一天 100 万次请求,每次平均 1000 token
daily_tokens = 1_000_000 * 1000
# 全打旗舰模型
all_strong = estimate_cost("strong", daily_tokens)
# 路由后:70% 走 cheap,25% 走 standard,5% 走 strong
routed = (estimate_cost("cheap", daily_tokens * 0.70)
+ estimate_cost("standard", daily_tokens * 0.25)
+ estimate_cost("strong", daily_tokens * 0.05))
print(f"全打旗舰:每天 ${all_strong:,.0f}")
print(f"路由之后:每天 ${routed:,.0f}")
print(f"省下:{(1 - routed / all_strong) * 100:.0f}%")
这段估算跑出来的结果,往往很惊人——把简单请求分流到小模型,整体成本常常能砍掉七成以上。这里的认知要点是:模型路由的本质,是把"请求的复杂度"和"模型的档位"对齐——让简单的请求,落在便宜的模型上。对齐之前,你得先有能力判断一个请求到底属于哪一档。判断的办法有两种,先说最便宜的一种:规则路由。
三、规则路由:用启发式规则做第一道分流
很多时候,一个请求是难是易,用几条简单的规则就能八九不离十地判断出来,根本不需要再调一次模型。这种规则路由,是第一道、也是成本最低(几乎为零)的分流。常见的规则维度有:输入长度(短文本通常简单)、关键词(出现"分析""推理""为什么"往往复杂)、任务类型(分类、提取这类有明确标签的任务通常简单):
def route_by_rules(prompt, task_type=None):
"""用启发式规则给请求定档,几乎零成本、零延迟。"""
# 规则一:任务类型已知且属于简单类,直接走最便宜的档
simple_tasks = {"classify", "extract", "format", "translate"}
if task_type in simple_tasks:
return "cheap"
# 规则二:输入很短,大概率是简单请求
if len(prompt) < 200:
return "cheap"
# 规则三:命中"需要深度推理"的关键词,走旗舰档
hard_signals = ["分析", "推理", "为什么", "设计方案", "权衡"]
if any(sig in prompt for sig in hard_signals):
return "strong"
# 规则四:其余的,走中档作为安全默认
return "standard"
规则路由的优点是极致的便宜和快——它只是几个字符串判断,不花一分钱、不增加任何可感知的延迟。它的缺点也很明显:规则是死的,语言是活的。一个请求没命中任何"难"关键词,内容却很复杂;或者一个很长的请求,其实只是啰嗦,本质很简单——规则会判错。所以规则路由适合做第一道粗筛:把那些一眼就能定性的请求快速分流掉。对于规则拿不准的那部分,需要一种更聪明、但也要花一点钱的办法:模型判别路由。
四、模型判别路由:让小模型来决定路由
当规则判断不了一个请求的难易时,有一个看似矛盾、实则很妙的办法:再调用一次模型,专门让它来判断"这个请求该用哪一档"。这里的关键,也是我一开始差点踩错的地方:做这个"判断"的模型,必须是那个最便宜的小模型——你绝不能用旗舰模型去判断"该不该用旗舰模型",那等于为了省钱先花一笔大钱,整个路由就失去意义了。判别任务本身很简单(就是个分类),小模型完全能胜任:
def route_by_classifier(prompt):
"""规则拿不准时,用最便宜的小模型来给请求定档。"""
judge_prompt = (
"判断下面这个用户请求的复杂度,只回答一个词:\n"
"simple(简单:分类、提取、改写)\n"
"medium(中等:常规问答、总结)\n"
"hard(复杂:多步推理、深度分析、方案设计)\n\n"
f"用户请求:{prompt}\n\n复杂度:"
)
resp = client.chat.completions.create(
# 关键:判别用最便宜的模型,绝不能用旗舰模型来做这个判断
model=MODEL_TIERS["cheap"]["name"],
messages=[{"role": "user", "content": judge_prompt}],
max_tokens=4, # 只要一个词,限制输出省钱
temperature=0, # 判别要稳定
)
level = resp.choices[0].message.content.strip().lower()
# 把判别结果映射到模型档位
return {"simple": "cheap", "medium": "standard",
"hard": "strong"}.get(level, "standard")
把规则路由和模型判别路由串起来,就是一个分层的路由器:先用零成本的规则做粗筛,规则能定的直接定;规则拿不准的,才花一点小钱让小模型来判别:
def route(prompt, task_type=None):
"""两层路由:规则先粗筛,拿不准时再交给小模型判别。"""
# 第一层:规则路由,零成本
tier = route_by_rules(prompt, task_type)
# 只有规则给出"standard"(也就是规则其实没把握)时,
# 才升级到第二层,花一点钱让小模型仔细判别
if tier == "standard" and task_type is None:
tier = route_by_classifier(prompt)
return tier
这里的认知要点是:路由本身也是有成本的,所以路由要分层——先用免费的规则挡掉大部分,只在真正需要时,才动用"花钱的判别";而判别用的模型,永远是最便宜的那一档。路由解决了"正常情况下该用哪个模型",可还有一个绕不开的问题:选中的那个模型,如果它挂了怎么办?
五、降级链:强模型不可用时怎么办
开头那个"模型厂商一限流,整个功能就瘫痪",根子是我把命运完全押在了单个模型上。模型 API 是会限流(429)、会超时、会偶发故障的外部依赖。一个健壮的系统,必须为"首选模型不可用"准备后备——这就是降级链(fallback chain):首选模型失败时,自动退到下一个备选模型,哪怕备选模型档次低一点,能给用户一个能用的结果,也远胜于直接报错:
import time
# 每一档的降级链:首选失败,就依次尝试后面的备选
FALLBACK_CHAIN = {
"strong": ["o1", "gpt-4o", "gpt-4o-mini"],
"standard": ["gpt-4o", "gpt-4o-mini"],
"cheap": ["gpt-4o-mini", "gpt-4o"],
}
def call_with_fallback(prompt, tier):
"""按降级链依次尝试;首选模型挂了,就退到下一个。"""
last_error = None
for model_name in FALLBACK_CHAIN[tier]:
try:
resp = client.chat.completions.create(
model=model_name,
messages=[{"role": "user", "content": prompt}],
timeout=20, # 必须设超时,别让一个慢请求拖垮整体
)
return resp.choices[0].message.content
except Exception as e:
# 这个模型不可用(限流/超时/故障),记下来,试下一个
last_error = e
time.sleep(0.5) # 略等一下再退,避免瞬间风暴
continue
# 整条链都失败了,才真正抛错
raise RuntimeError(f"降级链全部失败,最后错误:{last_error}")
这个 call_with_fallback 的精髓,在于它把"调用模型"从"一锤子买卖"变成了"有退路的尝试"。注意降级链的方向是"往便宜往小退"——因为越小的模型,通常越不容易被限流、越稳。下面这张图,把一次完整的"路由 + 降级"流程串起来:
六、工程坑:路由开销、灰度与质量监控
五块设计之外,还有几个工程坑,不处理就会让模型路由省了钱却埋了雷。坑 1:别让路由的开销,吃掉路由省下的钱。模型判别路由每次要多调一次小模型,这是有成本、有延迟的。如果你每个请求都走判别,省下的钱可能还不够付判别的开销。所以一定要像第四节那样分层:让绝大多数请求走零成本的规则路由,只让一小部分走判别。坑 2:重复请求要先查缓存,再谈路由。路由决定"用哪个模型",但最省钱的请求,是那个根本不用发出去的请求。对完全相同的输入,应该先查缓存,命中就直接返回,连路由都不必做:
import hashlib
_cache = {}
def cache_key(prompt):
return hashlib.sha256(prompt.encode("utf-8")).hexdigest()
def smart_complete(prompt, task_type=None):
"""完整管线:缓存 -> 路由 -> 带降级的调用 -> 回填缓存。"""
key = cache_key(prompt)
# 第一步:查缓存。命中就直接返回,连模型都不用碰
if key in _cache:
return _cache[key]
# 第二步:路由,决定用哪一档模型
tier = route(prompt, task_type)
# 第三步:带降级链地调用
result = call_with_fallback(prompt, tier)
# 第四步:回填缓存,让下一次相同请求直接命中
_cache[key] = result
return result
坑 3:换便宜模型,必须用灰度和抽样对比来验证质量,不能凭感觉。把某类请求从旗舰模型降到小模型,到底会不会掉质量?这个问题不能拍脑袋。正确做法是灰度:先只让一小部分流量走新路由,同时抽样把同一批请求也发给旗舰模型,把两边的结果存下来人工对比或打分。确认质量没有明显下降,再逐步放大灰度比例:
import random
# 灰度比例:先让 10% 的流量走新的路由策略
ROUTED_TRAFFIC_RATIO = 0.10
def complete_with_canary(prompt, task_type=None):
"""灰度发布:小比例走路由,其余仍走旧策略,并抽样对比。"""
if random.random() < ROUTED_TRAFFIC_RATIO:
result = smart_complete(prompt, task_type)
# 抽样:1% 的灰度请求,同时也发给旗舰模型留底对比
if random.random() < 0.01:
baseline = call_with_fallback(prompt, "strong")
log_comparison(prompt, result, baseline) # 存起来供后续打分
return result
# 灰度之外的流量,继续走原来的稳妥策略
return call_with_fallback(prompt, "strong")
坑 4:每一档模型的质量和用量,都要持续监控。路由上线后,你要盯住几件事:每一档模型分到了多少流量(用来核对省钱效果)、降级链触发的频率(频繁降级说明首选模型不稳)、以及每一档的输出质量打分(防止为省钱牺牲了核心体验)。坑 5:核心场景要敢于"不省钱"。路由的目标是省下不该花的钱,不是把所有钱都省掉。对那些直接影响营收、影响用户核心体验的关键请求,该用旗舰模型就用旗舰模型——在这些地方省钱,省下的是小钱,赔进去的是大钱。一个好的路由策略,分得清哪里该省、哪里该花。
关键概念速查
| 概念 / 手段 | 说明 |
|---|---|
| 模型路由 | 按请求复杂度,把它分发到不同档位的模型 |
| 模型档位 | cheap / standard / strong,价格和延迟差距可达几十倍 |
| 请求分级 | 区分简单任务和复杂任务,是路由的前提 |
| 规则路由 | 用长度、关键词、任务类型做零成本的第一道分流 |
| 模型判别路由 | 规则拿不准时,用最便宜的小模型来判断该用哪一档 |
| 分层路由 | 规则先粗筛,只对拿不准的请求才动用花钱的判别 |
| 降级链 | 首选模型失败时,自动退到下一个备选模型 |
| 缓存优先 | 相同请求先查缓存,最省钱的是不发出去的请求 |
| 灰度与抽样 | 小比例试新路由,抽样和旗舰模型对比验证质量 |
| 核心场景不省钱 | 影响营收和核心体验的关键请求,该用旗舰就用旗舰 |
避坑清单
- 别把所有请求都打给最强模型,简单请求用旗舰就是大炮打蚊子。
- 不同档位模型价格差几十倍,先把档位和单价列成明账。
- 真实流量大多是简单请求,路由后整体成本常能砍掉七成以上。
- 规则路由用长度、关键词、任务类型做粗筛,零成本零延迟。
- 规则是死的会判错,拿不准的请求才升级到模型判别路由。
- 判别路由必须用最便宜的小模型,绝不能用旗舰判断该不该用旗舰。
- 给每一档配降级链,首选模型限流故障时自动退到备选。
- 调用必设超时,降级方向往便宜往小退,小模型通常更稳。
- 相同请求先查缓存,最省钱的请求是根本不用发出去的请求。
- 换便宜模型要灰度加抽样对比验证质量,核心场景敢于不省钱。
总结
回头看那串"账单翻十倍、响应慢、一限流就瘫、一刀切又崩"的问题,以及我后来在模型路由上接连踩的坑,最该记住的不是某一个模型的名字,而是我动手前那个想当然的判断——"用大模型,就是选最强的那个,所有请求都打给它"。这句话错在它把"用好大模型"和"用最贵的大模型"画上了等号。我以为用最强的模型,就是对质量最负责。可我忽略了一件事:大模型不是一个东西,而是一排有贵有贱、有快有慢的东西;而我的请求也不是一种东西,而是一堆有难有易的东西。用最贵的模型去做最简单的事,不是对质量负责,而是对成本、对延迟、对稳定性的三重不负责。
所以做大模型的成本优化,真正的工程量不在"选哪个模型最强"这一个选择上。那个选择,谁都会做。真正的工程量,在于你要承认"任务的复杂度是分布的、模型的档位是分布的",并搭起一套把这两个分布对齐的机制:它简单,你就用规则路由零成本地把它分到小模型;规则拿不准,你就用一个便宜的小模型去判别它的难易;选中的模型挂了,你就用降级链退到后备;相同的请求,你就用缓存让它根本不必再发;要换便宜模型,你就用灰度和抽样盯住质量别滑坡。这篇文章的几节,其实就是顺着这条路由链展开的:先想清楚"全打最强模型"为什么错,再讲怎么给请求分级、规则路由怎么做粗筛、模型判别怎么补规则的不足、降级链怎么兜住故障,最后是路由开销、灰度、监控这几个把模型路由做扎实的工程细节。
你会发现,给大模型请求做路由,和现实里"一个公司怎么分派任务给不同级别的员工"完全相通。一个不会管理的老板,会怎么做?他不管什么活,大事小事,全都丢给那位最资深、薪水最高的专家(这就是所有请求都打旗舰模型)。专家当然每件都能做好,可让他去复印文件、填表格(这就是简单请求),是对最贵人力的巨大浪费;而且万一这位专家请假了,整个公司就转不动了(这就是没有降级链)。而一个真懂管理的老板怎么做?他先把任务分轻重(这就是请求分级),简单的活派给成本低的新人(这就是路由到小模型),遇到拿不准的活,先花几分钟让一个助理快速评估一下难度(这就是判别路由),关键人物不在时,有明确的替补顶上(这就是降级链),而且对那些真正决定公司生死的大事,他绝不吝啬,一定请最强的专家出手(这就是核心场景不省钱)。
最后想说,模型路由做没做对,差距永远不会在"本地测几十次都挺好"时暴露——本地你的调用量小到账单可以忽略、延迟你也不在意、模型也恰好没限流,你会觉得"选个最强的模型、所有请求都打给它"已经是全部。它只在真实的、有海量请求、有成本压力、模型偶尔就要限流一次的线上环境里才显形。那时候它会用最直接的方式给你结账:做不好,你会像我一样,盯着月底那张翻了十倍的账单发懵,看着用户因为简单任务也要久等而流失,在模型厂商一次限流里眼睁睁看着整个功能瘫痪;而做对了,你的 AI 功能会又快又省又稳:简单请求被小模型秒回、几乎不花钱,复杂请求仍由旗舰模型保质保量,某个模型挂了降级链悄悄顶上、用户毫无感知。所以别等"那张翻了十倍的账单"找上门,在你写下那行 model="gpt-4o" 的那一刻就该想清楚:我面对的不是一个模型,而是一排有贵有贱的模型;我接住的不是一种请求,而是一堆有难有易的请求——把它们对齐的这套路由,分级、规则、判别、降级,我是不是每一环都搭上了?这些问题有了答案,你拿到的才不只是一个"能用、但烧钱"的 AI 功能,而是一个又快、又省、又扛得住故障的可靠系统。
—— 别看了 · 2026