
上个月月底我照例打开后台的用量统计页面准备看一眼这个月 LLM 相关功能的账单。结果差点把咖啡喷在屏幕上——账单金额比上个月涨了接近一倍业务量的增长却不到 20%。我第一反应是有人把 API Key 泄露了快速检查了一圈之后发现没有。问题出在一个更隐蔽的地方我们把所有请求都默认送给了同一个旗舰模型。这个场景估计很多团队都不陌生。聊天助手要接文档摘要要接内容分类要接后台的自动标签功能也想接。每个功能单独看都不贵但全部叠在一起再乘以调用次数和用户量账单就开始失控。更扎心的是多数请求本质上是简单任务用不到旗舰模型的深度推理能力。我们最后的解法是搭了一个模型路由层把请求分门别类地送往不同规模的模型总成本砍掉了 94%。回看整个过程真正值得分享的不是这个数字本身而是我们想明白的一件事成本问题的根源不在模型单价而在于大量请求跑在了能力严重过剩的模型上。1. 先算一笔账账单暴增不是“单价贵”而是“平均智商太高了”1.1 为什么团队会不自觉地把所有请求堆到最强的模型上说实话这种选择并不是某个人拍板决定的而是开发路径自然形成的。一开始接 LLM 功能团队都会选那个效果最稳、能力最强的旗舰模型。好处非常明显开发方便业务方只需要接一个 API不用在多个模型之间来回切换通用性强不管用户问什么都能给出一个看起来还不错的回答试错成本低产品团队可以直接用一套提示词模板跑完所有场景。但问题会在调用量上来之后慢慢暴露。当功能从一个变成五个从每天几千次变成几十万次你会发现成本并不是线性上涨而是指数级的。因为除了单次调用价格还要考虑输入侧的系统提示词、注入的上下文、多轮对话的历史记录以及失败重试带来的额外 token。这些都是容易被忽略的“成本放大器”。一句话总结不是模型单价欺骗了你而是所有请求都享受了最高配待遇。1.2 成本的真实构成单价只是冰山一角我习惯把一次 LLM 调用的总成本拆成三个乘数模型单价、单次 token 量、调用次数。多数人死盯着第一个实际上真正膨胀的往往是后两个。假设一次简单分类请求只需要 200 个 token 输入、50 个 token 输出但你的系统提示词写了 2000 词再把整个文档都塞进上下文一次调用的成本就是原来的十几倍。这个账很难被发现因为后台报表里只有一个总量很多人不是在“看账单”而是在“猜账单”。还有一个更容易被忽略的问题能力过剩。我们当时做了个抽样统计把一段时间的请求人工打标发现大约八成请求属于信息抽取、格式化输出、简单分类、关键词提取约一成半左右属于摘要、改写、普通对话真正需要深度推理、多步规划、复杂代码生成的不到百分之五。1.3 能力过剩的成本到底有多大以我们当时使用的估算方式来看如果旗舰模型单次成本记为 100一个足以应对八成任务的轻量模型通常只需要 1 到 3 个成本单位中端模型大概在 10 到 30 个成本单位之间。也就是说每把一条简单请求从旗舰模型迁移到轻量模型省下的不是 10%而是 90% 以上。这个数字很反直觉。很多人以为省钱是把所有请求都换到一个便宜的模型上那样省不了多少因为你的复杂请求照样要去贵模型。真正科学的省钱是让每个请求匹配对应难度的模型而不是“一刀切”。2. 模型路由器的核心思路不是“选便宜”而是“让请求匹配模型”2.1 路由器到底是个什么角色模型路由器可以理解成站在业务系统和多个 LLM 后端之间的一层基础设施。它不负责生成内容也不修改你的提示词只做一件事决定每个请求应该交给哪个模型执行。它和网关、负载均衡有相似之处但判断逻辑完全不同。负载均衡默认后端都是同质的谁空谁上模型路由器的后端能力、价格、速度和稳定性都不一样路由器必须根据请求特征做出差异化的决策。也可以拿快递分拣来类比不是所有包裹都需要在 24 小时内空运到达有些走陆运就够了。路由器的价值不是让所有包裹变快或变慢而是让时效、成本和需求互相匹配。2.2 三种基本路由策略我把常见策略分成三类硬路由、语义路由、自适应路由。策略实现成本准确率适合场景主要风险规则路由低中低请求模式固定、类型有限规则维护量随场景扩张语义路由中中高请求多样但意图清晰冷启动需标注样本自适应路由高高质量敏感、有反馈闭环系统复杂度高规则路由是最快的完全基于关键词、正则、指令特征做判断。比如请求里出现“总结”“摘要”就归到摘要类出现“JSON”“格式化”就归到结构化输出类。好处是零推理成本坏处是场景一多规则之间会打架维护成本迅速上升。语义路由是把请求文本转成向量再和预定义类别的原型向量做相似度计算。这种方法有更好的泛化能力即使请求表达方式变了只要语义接近还是能分到正确的类里。自适应路由更复杂它会根据模型输出的质量评分、用户反馈、人工抽样结果来动态调整路由规则。适合质量敏感的业务但需要很强的工程能力和反馈闭环。2.3 为什么最终能省下 94%回到开头的问题为什么能达到 94% 的降幅关键在请求分布。我们前面那个“八成简单、一成半中等、不到半成复杂”的分布虽然是个估算但它暗示了一个非常有效的优化空间。如果简单请求全部走轻量模型中等请求走中端模型只有真正的复杂请求才走旗舰模型那么理论上整体成本会被拉低一个数量级以上。用估算法来验证假设 100 个请求旗舰模型单次成本为 100 单位轻量模型为 2 单位中端模型为 20 单位。全部请求都走旗舰模型总成本是 10000 单位。路由之后80 个简单请求走轻量15 个中等请求走中端5 个复杂请求走旗舰总成本是 80×2 15×20 5×100 160 300 500 960 单位。降幅约 90%。如果我们再叠加缓存、提示词精简和批处理94% 是完全可以达到的量级。当然这组数字只是用来解释机制。不同团队的请求分布、模型价格、功能复杂度都不同实际降幅需要根据自己的数据来测算。3. 三层路由架构入口采集、路由引擎与回退池3.1 整体链路我们在原有业务和模型调用之间加了一层“路由网关”整条链路可以这样看业务请求 ↓ 入口采集请求文本、用户身份、任务类型、上下文长度、参数约束 ↓ 路由引擎规则 → 语义 → 小模型分级判断 ↓ 模型池简单 → 轻量模型中等 → 中端模型复杂 → 旗舰模型 ↓ 回退池失败升级、低质量升级、超时重试 ↓ 统一日志与成本归因这一层对上层业务是无感知的。业务方仍然只面对一个 API 入口只是内部实现变了。3.2 入口采集先要把请求的特征拿全路由做得好不好很大程度取决于入口信息采集得够不够全。每个请求进来我们会记录这些信息请求文本和系统提示词。任务类型标签如果业务方显式传了的话。上下文长度和最大输出 token 限制。用户身份和功能来源比如是客服助手还是文档摘要。是否多模态输入、是否要求 JSON 输出、是否有超时约束。这些信息不一定都能进入路由判断但它们是后续做成本归因和问题排查的基础。没有这些字段遇到账单异常的时候只能靠猜。3.3 路由引擎三层判断逐步过滤我们的路由引擎不是单一策略而是三种策略的叠加第一层是快速规则。用正则和关键词做初筛速度最快。比如请求字数小于 50、没有复杂指令、不涉及代码和多模态直接标记为轻量模型候选。第二层是语义判断。对未能在第一层得出结论的请求生成 embedding与路由表中的类别向量比较。相似度高于阈值的按类别路由低于阈值的不敢乱判直接走默认策略。第三层是小模型兜底。对语义边界模糊的请求用一个非常便宜的小模型做分类让它只输出一个短标签“simple”、“medium”或“complex”。这一步会增加几十到几百毫秒的延迟但整体收益远大于成本。这里要提醒一点路由不是非要达到 100% 的准确率。只要把“高置信的简单请求”准确识别出来让它们走轻量模型就能省下大部分成本。至于模棱两可的请求全部交给中高端模型处理损失并不大。3.4 回退池宁可升级不能失败回退池是整个架构里最容易被新手团队忽略的部分。路由做得再好也会遇到模型本身不稳定、服务商限流、返回格式错误、超时等情况。我们的回退策略是逐级升级轻量模型出错或超时 → 自动重试一次仍失败则升级到中端模型。中端模型出错或质量不达标 → 升级到旗舰模型。旗舰模型也失败 → 返回统一错误同时触发告警。给一个简化的 python 伪代码示例MODEL_POOL { simple: [lite-model-a, mid-model-b], medium: [mid-model-b, flagship-model-c], complex: [flagship-model-c], } def call_with_fallback(payload, route_label): candidates MODEL_POOL[route_label] last_error None for model in candidates: try: response call_llm(model, payload) if quality_check(response): return response except Exception as e: last_error e continue raise last_error or RuntimeError(all models failed)这里的关键是quality_check不能只看“有没有返回”还要看返回内容是否合理。比如要求 JSON 输出结果返回了一段自然语言这不能算成功。4. 路由判断的三种方式规则、语义向量与小模型分类4.1 规则路由最短路径但维护量不小规则路由最适合请求模式非常固定的场景。举个例子团队内部有一个“情感打标”功能输入的是用户反馈输出只有正面、中性、负面三个标签。这个任务完全可以用规则路由单独拎出来。规则的核心不是“猜意图”而是“看边界”。比如请求文本长度小于等于 50 字且没有代码块、没有图片输入、没有复杂工具调用 → 轻量模型候选。提示词中包含“列出所有步骤”“分析以下代码”等强指令 → 中高端模型候选。多模态输入、上下文超长、要求严格 JSON Schema → 中高端模型候选。需要注意的是规则越多冲突越多。今天加一条新规则可能让昨天正常的一条请求跑到错误的模型上。所以规则要尽量少并且每条规则都要能追溯到业务方需求。4.2 语义向量路由让分类更有泛化能力我们平时说的“某个请求比较复杂”其实不是一句能穷举的话。你很难写规则来覆盖“帮我对比一下这两个方案的优劣”“这个 bug 可能出在哪里”这类请求。这时候可以用 embedding 做语义分类。基本思路是把这三种模型等级分别定义成几个“典型查询”转成 embedding 后存起来。新请求进来计算它和每个典型查询的相似度选最高的。import numpy as np def cosine_similarity(a: np.ndarray, b: np.ndarray) - float: return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) 1e-12)) def semantic_route(query_emb, route_prototypes, threshold0.78): best_label, best_score default-complex, 0.0 for label, proto_embeds in route_prototypes.items(): for proto in proto_embeds: score cosine_similarity(query_emb, proto) if score best_score: best_label, best_score label, score if best_score threshold: return default-complex return best_label这里有几个参数要解释threshold是置信度阈值。设高了更多请求会落到“default-complex”成本降得少但更稳妥设低了省得更多但误判风险上升。route_prototypes里的类目向量需要定期校准至少每两周评估一次发现分类偏差就补充或替换原型样本。默认值 0.78 只是一个经验参考范围实际要结合你自己的 embedding 模型和请求分布来调。从工程经验看语义路由的准确率通常在 70% 到 95% 之间取决于样本质量和类别边界是否清晰。边界越清晰效果越好类别之间语义重叠很大效果就会明显下降。4.3 小模型做预分类先用便宜的分类器把“简单”和“复杂”分开第三种方式是调用一个非常便宜的小模型让它只做分类不做生成。这种方法适合业务场景比较开放、规则和 embedding 都不太够用的场景。给一个示例 prompt 结构你是请求分类器。只输出一个词simple、medium、complex。 - simple: 简单抽取、格式化、关键词提取、翻译短语。 - medium: 摘要、改写、普通问答、代码解释。 - complex: 多步推理、复杂代码生成、安全分析、跨领域规划。 请求内容 {query}小模型分类的成本很低但要注意几个点小模型对指令理解和格式遵循能力相对有限所以要给它非常清晰的边界输出要强制 JSON 或单一标签方便程序解析分类不准时不要尝试修复模型而是调整 prompt 中的任务定义。4.4 三种方式怎么选我的建议是从规则开始先跑一个月。在这一个月里抽样本看有多少请求被规则漏掉再决定要上语义路由还是小模型分类。如果你的请求类型本身就不多规则维护起来不累