
这次我们不聊新的模型权重聊一个更现实的问题LLM 应用上线后到底该用哪个模型处理请求成本才算真正可控大多数团队在选模型时只看单次调用的价格GPT 贵、Claude 贵、国产开源模型便宜。但真正跑过生产环境就会发现便宜模型经常答不对题上层逻辑反复重试把话费、算力、人工审核全赔进去最终单次成功请求的实际成本反而更贵。于是出现了 LLM routing 这个概念在请求进来时自动选择合适的模型而不是把所有流量都打到一个模型上。这个方向最有价值的度量标准不是 cost-per-call而是cost-per-success。换句话说你关心的不应该是“每次调用花多少钱”而是“每得到一个正确结果花多少钱”。这篇文章会用工程视角拆解这套思路LLM 路由的核心逻辑、cost-per-success 的核算公式、最小路由层怎么部署、批量任务怎么设计、接口怎么调用、以及排错时优先看哪些指标。偏实践不会停在概念层面。1. 核心能力速览先给一张速览表后面所有内容都围绕这几项展开。能力项说明核心目标用 LLM 路由把请求分给合适的模型降低单次成功请求的综合成本关键指标cost-per-success统计范围为“成功响应的总成本 / 成功响应数量”对比指标cost-per-call只统计单次调用账单忽略重试、兜底、质量失败带来的隐性成本适用请求类型简单分类、意图识别、关键词抽取、复杂推理、长文本生成、Agent 多轮调用路由策略规则路由、语义路由、质量评分路由、级联降级路由部署方式本地代理层转发模型可走云端 API也可接本地推理服务批量任务可以通过路由层统一排队、限流、重试和失败归档API 能力路由层通常暴露 OpenAI 兼容接口业务侧无需改造硬件要求纯路由层很轻CPU 和少量内存即可如果接本地模型按模型大小另算适合场景生产环境成本优化、多模型切换、批量推理任务、Agent 应用降本这里明确一点路由层本身不是模型它是一个调度服务核心工作是“根据请求特征和模型能力决定这次请求交给谁”。2. 为什么 cost-per-success 比 cost-per-call 更重要很多团队做模型选型时第一步就是对比各家 API 价格表。假设模型 A 单次调用 0.1 元模型 B 单次调用 1 元看起来选 A 能省 90% 成本。跑起来之后发现模型 A 在复杂推理上的准确率只有 40%每次失败都要重试 2 到 3 次甚至还要额外用模型 B 做校验。最终一个成功结果的总花费可能超过直接用模型 B。这就是 cost-per-call 带来的误导。单次调用价格只是表层成本真正的成本由下面几部分构成直接调用费也就是 API 账单或自建推理的算力成本。重试成本失败后重新调用的费用。降级成本从小模型升级到大模型后额外费用。校验成本为了保证输出质量额外调用一个模型打分或抽检。人工成本错误结果流入下游后需要人工修正的时间。这些成本加起来才是 cost-per-success。用公式表达cost-per-success (直接调用成本 重试成本 降级成本 校验成本 人工修正成本) / 成功响应数如果业务侧只统计 cost-per-call那么重试成本会被漏掉如果只统计 API 账单那么人工修正成本会被漏掉。实际生产里被低估的那部分往往比明面上的调用费更吓人。所以引入 LLM routing 的直接动机就是把请求分配给“当前任务下性价比最高”的模型而不是“最便宜”或“最强”的模型。简单任务走便宜模型复杂任务走贵模型介于中间的任务走中间档整个系统的 cost-per-success 才会下降。3. LLM 路由的基本逻辑与模型分级LLM routing 不是玄学本质是给请求做一次分类再按分类结果转发。常见做法是先给模型分级再给任务分级。3.1 模型分级把所有可用的模型按“能力强度”和“单位成本”排一个梯队模型梯队成本能力定位适合任务轻量模型低快速响应能处理简单指令意图分类、实体抽取、格式转换、关键词生成中量模型中综合能力较好摘要、改写、通用对话、结构化 JSON 输出重量模型高推理能力最强复杂代码、数学推理、长链路 Agent、高价值内容生成这里不需要具体到某个模型名称因为各家模型迭代太快实际选型时按自己的任务评测结果填入即可。3.2 任务分级任务分级有两种思路一种是预先知道任务类型另一种是动态判断。第一种是业务侧显式指定。比如接口请求里带一个task_type字段值为simple_classification路由层直接走轻量模型值为complex_reasoning直接走重量模型。这种方式最简单也最容易控制成本缺点是业务侧要对任务类型有清晰认知。第二种是路由层自动识别。用一个嵌入模型或轻量分类器对请求文本打标然后根据打标结果选择模型。这样业务侧不用感知路由逻辑但会引入额外延迟和误判风险。实际生产环境往往混合使用先看有没有显式标签没有再跑语义分类分类不确定时默认走中间档模型。3.3 同层多模型兜底路由层还必须处理模型故障和效果波动。比如轻量模型当前限流就切换同层级的另一个轻量模型如果整个轻量层都失败再降级到重量模型。这里的关键是不要让失败请求直接中断而是沿着“路由 - 重试 - 降级”的路径处理。一个推荐策略是“层级内轮换 跨层级降级”请求进入路由层 - 根据任务类型选择梯队 - 梯队内优先模型调用 - 失败或质量不合格同级换模型重试 - 同级全部失败升级到上一梯队重试 - 全部失败返回明确错误信息进入离线队列这套策略能显著降低重试成本。因为绝大多数轻量请求不需要升级到重量级模型只有轻量模型确实解决不了时才发生升级。4. cost-per-success 的核算口径与计算脚本没有度量就没有优化。部署路由层之前先把 cost-per-success 的核算脚本写好。4.1 核算口径建议每个请求记录以下字段request_id请求唯一 ID。task_type任务类型。routed_model实际路由到的模型。success是否成功。quality_score质量分可选。cost本次实际成本。latency延迟。retry_count重试次数。escalated是否发生升级。按天聚合时核心指标如下总成本 sum(cost) 成功数 count(success true) 失败数 count(success false) cost-per-success 总成本 / 成功数 cost-per-call 总成本 / (成功数 失败数) 失败率 失败数 / (成功数 失败数) 重试放大系数 总调用次数 / 成功数重试放大系数很有意思。如果这个值是 1.8说明每成功一个结果背后实际上产生了 1.8 次模型调用。这时候只看 cost-per-call 一定会低估真实成本。4.2 Python 核算脚本模板用 Python 写一个最小核算脚本输入是请求日志 CSV。import csv import sys from collections import defaultdict def calculate_cost_per_success(log_file): total_cost 0.0 total_calls 0 success_count 0 fail_count 0 task_stats defaultdict(lambda: {cost: 0.0, success: 0, fail: 0, calls: 0}) with open(log_file, newline, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: task_type row.get(task_type, unknown) success row.get(success, ).strip().lower() true cost float(row.get(cost, 0) or 0) total_cost cost total_calls 1 if success: success_count 1 task_stats[task_type][success] 1 else: fail_count 1 task_stats[task_type][fail] 1 task_stats[task_type][cost] cost task_stats[task_type][calls] 1 print(f总调用次数: {total_calls}) print(f成功数: {success_count}) print(f失败数: {fail_count}) print(f失败率: {fail_count / total_calls:.2%} if total_calls else 无请求) print(fcost-per-call: {total_cost / total_calls:.4f} if total_calls else 无请求) print(fcost-per-success: {total_cost / success_count:.4f} if success_count else 无成功请求) print(f重试放大系数: {total_calls / success_count:.2f} if success_count else 无成功请求) print(\n各任务类型统计:) for task_type, s in task_stats.items(): if s[success] s[fail] 0: continue cps s[cost] / s[success] if s[success] else float(inf) print(f {task_type}: 调用{s[calls]}, 成功{s[success]}, f失败{s[fail]}, 成本{s[cost]:.4f}, cost-per-success{cps:.4f}) if __name__ __main__: if len(sys.argv) 1: calculate_cost_per_success(sys.argv[1]) else: print(用法: python calculate_cps.py requests.csv)这个脚本能快速看出两件事哪类任务的成本黑洞最严重哪类任务失败率过高。4.3 判定成功的标准成功不能只靠“模型返回了文本”来判断。建议至少包含一层自动校验必须包含合法 JSON如果能解析成功才算成功。必须包含关键字段比如意图分类要求返回category字段。长度校验生成内容长度要在合理范围内。关键词校验下游要求包含特定标识时校验是否出现。质量校验本身也会产生额外成本所以不用每一条都做重校验可以按比例抽检或者只对高成本模型的结果做全量校验。5. 部署一个最小可用 LLM 路由层路由层可以自己写也可以用开源网关项目。无论哪种方案核心接口设计建议兼容 OpenAI 格式这样业务侧切换成本最低。5.1 最小路由服务设计用 Python FastAPI 写一个最小路由服务负责按任务类型转发请求到不同模型后端。# app.py from fastapi import FastAPI, Request import httpx import os app FastAPI() # 路由配置按实际环境替换 MODEL_ENDPOINTS { light: { url: os.getenv(LIGHT_MODEL_URL, http://127.0.0.1:8001/v1/chat/completions), api_key: os.getenv(LIGHT_MODEL_API_KEY, sk-local-light), model: os.getenv(LIGHT_MODEL_NAME, light-model), }, heavy: { url: os.getenv(HEAVY_MODEL_URL, https://api.example.com/v1/chat/completions), api_key: os.getenv(HEAVY_MODEL_API_KEY, sk-heavy), model: os.getenv(HEAVY_MODEL_NAME, heavy-model), }, } DEFAULT_TIER light def select_tier(payload: dict) - str: # 业务侧显式指定任务类型实际规则可按项目调整 task_type payload.get(task_type, ) if task_type in (complex_reasoning, code_generation, agent_plan): return heavy return light app.post(/v1/chat/completions) async def chat_completions(request: Request): body await request.json() tier select_tier(body) # 去掉路由层自定义字段避免透传到模型 body.pop(task_type, None) endpoint MODEL_ENDPOINTS[tier] headers {Authorization: fBearer {endpoint[api_key]}} body[model] endpoint[model] async with httpx.AsyncClient(timeout120) as client: resp await client.post(endpoint[url], jsonbody, headersheaders) return resp.json(), resp.status_code这个示例只展示了最基本的按任务类型分发实际生产还需要加超时、重试、限流、日志和错误映射。5.2 启动方式pip install fastapi uvicorn httpx uvicorn app:app --host 0.0.0.0 --port 8000启动后业务侧把原来的模型 API 地址替换成http://127.0.0.1:8000/v1/chat/completions模型名随意传一个路由层会忽略并替换成实际模型名。5.3 路由层本身很轻路由层不做生成只做转发所以对硬件要求极低。单台 2C4G 的轻量云主机完全够用。如果加了语义路由需要跑一个嵌入模型那么显存需求取决于嵌入模型大小通常几个 G 显存足够也可以直接用云 API 计算 embedding。6. 功能测试与效果验证部署之后不要急着接生产流量先做一组功能测试验证路由是否按照预期工作。6.1 测试任务类型分发准备两个请求一个标记为简单任务一个标记为复杂任务。curl -s http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: any-model, task_type: simple_classification, messages: [{role: user, content: 把这句话分类今天天气不错}] }curl -s http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: any-model, task_type: complex_reasoning, messages: [{role: user, content: 写一段 Python 代码实现一个带缓存的双向链表}] }判断标准查看路由层日志第一条请求应该落到 light 梯队第二条落到 heavy 梯队。如果两条都落在同一梯队说明select_tier逻辑有问题或者模型后端地址配置错了。6.2 测试失败降级临时把 light 梯队的模型地址改成一个不存在的端口然后发送一个简单分类请求。预期行为路由层尝试调用 light 模型失败后自动切换 heavy 模型重试最终返回成功结果。如果路由层直接抛 500 错误说明缺少降级逻辑需要在路由函数里增加 try/except 和重试循环。6.3 测试质量校验对 JSON 输出类任务加一层 JSON 解析校验。只有返回合法 JSON 且包含category字段的响应才算成功。import json def validate_response(content: str) - bool: try: data json.loads(content) return category in data except json.JSONDecodeError: return False把校验结果写入日志后续核算 cost-per-success 时就能区分“调用成功”和“真正成功”。6.4 测试观察指标验证时重点看每个请求实际路由到哪个模型。延迟是否在可接受范围。失败时是否发生重试重试了几次。是否出现发往同一模型后端的并发堆积。7. 接口 API 与批量任务设计路由层上线后批量任务可以直接复用同一套接口但要注意批量场景和在线对话场景的调度策略不同。7.1 在线接口 vs 批量接口在线接口侧重低延迟批量接口侧重吞吐和成本。建议在路由层区分两条路径路径接口路径特点适用在线同步/v1/chat/completions实时返回超时短对话、Agent 在线推理批量任务/v1/batch异步提交轮询结果离线数据处理、批量打标批量任务的推荐流程是先提交一批请求到队列路由层按优先级和成本预算逐条处理结果写入对象存储或数据库业务侧通过任务 ID 轮询。7.2 批量任务提交示例import requests import time ROUTER_URL http://127.0.0.1:8000 API_KEY sk-router-xxx headers {Authorization: fBearer {API_KEY}} # 提交批量任务 submit_payload { task_name: batch_classify_2025, task_type: simple_classification, inputs: [ {id: 1, text: 用户咨询如何退款}, {id: 2, text: 用户投诉物流太慢}, {id: 3, text: 用户感谢售后不错}, ], routing_hint: light_first, } resp requests.post(f{ROUTER_URL}/v1/batch, jsonsubmit_payload, headersheaders, timeout30) task_id resp.json().get(task_id) print(ftask_id: {task_id}) # 轮询任务状态 while True: status requests.get(f{ROUTER_URL}/v1/batch/{task_id}, headersheaders, timeout30).json() print(status[status]) if status[status] in (completed, failed): break time.sleep(5) # 获取结果 results requests.get(f{ROUTER_URL}/v1/batch/{task_id}/results, headersheaders, timeout30).json() for item in results[items]: print(item)实际接口路径和字段以项目实现为准。7.3 批量任务里的成本控制批量任务最好设置一个全局成本预算。比如这批 10 万条请求预算上限 1000 元。路由层在处理时优先使用轻量模型当累计成本接近预算的 80% 时自动降低升级概率减少 heavy 模型调用。还可以给每个任务设置“最大升级次数”。比如一条请求最多从 light 升级到 heavy 一次如果 heavy 还是失败就不再重试直接标记为人工处理。这样能避免单条异常请求吃掉大量成本。7.4 失败重试建议批量任务的重试策略和在线不同不能无限重试。推荐配置同一模型重试上限2 次。降级到上级模型上限1 次。重试间隔指数退避1s、4s、16s。最终失败进入failed状态保留原始输入方便人工回放。重试日志一定要记录原因比如超时、限流、JSON 解析失败、内容为空、质量分过低。只有知道失败原因才能调整路由规则。8. 资源占用与性能观察路由层的资源占用受两个因素影响是否使用语义路由、是否本地托管嵌入模型。如果没有语义路由纯规则转发那么路由层的 CPU 和内存占用都很低。一台 2 核 4G 的服务器处理几千 QPS 没有问题瓶颈往往在后端模型服务不在路由层。如果加了语义路由每次请求都要先计算 embedding 再走分类器资源占用会明显上升。这里两种做法可以选择本地跑 embedding 模型适合对数据隐私要求高的场景但需要 GPU 显存模型大小和显存占用以实际模型为准。调用云 embedding API无需本地显存但单次请求延迟会多几十毫秒且有额外 API 费用。观察路由层性能时建议打印以下日志字段请求进入时间、路由决策耗时。实际模型调用耗时。响应返回时间。路由到的模型名称。是否发生重试和降级。如果路由决策耗时超过 100ms而规则又是纯判断说明路由层存在异常比如日志写入阻塞、后端健康检查同步调用等。一个容易踩的坑是 Python 服务在处理同步请求时写了大量阻塞日志或调用外部健康检查接口导致路由层本身成为瓶颈。排查时先用cProfile压一下路由决策函数确认耗时分布。9. 常见问题与排查方法下面整理一份排查清单适用于自建 LLM 路由层和接入开源网关的场景。问题现象可能原因排查方式解决方案所有请求都路由到同一个模型路由规则没有生效默认值覆盖了规则检查路由决策日志确认task_type是否正确传递调整任务类型解析逻辑增加规则单元测试请求偶尔超时后端模型服务限流或单条请求过长查看模型服务日志和流量指标路由层增加超时自动降级避免长时间等待cost-per-success 一直很高失败率和重试率高或升级过多按任务类型拆解统计针对性调整模型梯队对失败任务做人工抽检批量任务卡住队列消费线程过少或后端并发受限观察队列长度和后端并发数增加消费者数量限制单任务最大并发模型返回内容格式不对路由时没有做输出校验检查校验逻辑是否被跳过在路由层增加响应校验函数日志字段缺失路由层没有记录完整调用信息检查日志模板补全 request_id、model、retry_count 等字段升级到 heavy 模型后效果仍不好问题不在模型而在于提示词或任务定义抽检输入输出对比人工答案先优化提示词再考虑更换模型API 接口鉴权失效业务侧没有携带有效凭证检查网关鉴权配置统一走 API Key 校验限制内网访问最容易忽视的是“成功”的定义。如果一个请求虽然拿到了 200 响应但内容包含乱码或空字符串不校验会直接污染成本统计。所以路由层一定要在返回前或返回后做一次有效性检查。10. 最佳实践与总结最后给几条基于生产经验的路由层落地建议。第一先跑通核算再做优化。没有 cost-per-success 和重试放大系数后面任何优化都看不到真实收益。第二第一版路由规则尽量简单。先用任务类型显式路由跑一周拿到基线数据再决定要不要上语义路由。直接上复杂分类器会引入新的维护成本。第三把路由规则做成可配置不要硬编码在代码里。模型梯队、重试次数、升级策略、预算上限都应该放在配置文件或管理后台改规则不用重新发版。# routing_config.yaml 示例 tiers: light: models: - name: light-model-1 priority: 1 - name: light-model-2 priority: 2 heavy: models: - name: heavy-model-1 priority: 1 strategy: retry_same_tier: 2 escalation_limit: 1 timeout_seconds: 30 enable_quality_check: true quality_sample_rate: 0.3第四所有模型调用都要有日志至少要记录 request_id、模型名、成本、成功标志、重试次数、升级次数。这些字段是成本追溯的基础。第五涉及人脸、声音、版权素材、用户隐私数据时必须在路由层做数据脱敏和访问控制。本地部署模型时要确认权重来源和许可证商用前做合规检查。第六不要为了降本而牺牲核心体验。重量级任务该用贵模型就用只要 cost-per-success 在下降整体方向就是对的。LLM 路由的本质不是“把请求推到最便宜的模型”而是“把请求推到成本效率最高的模型”。从 cost-per-call 转向 cost-per-success意味着从被动看账单变成主动设计成本结构。建议先拿一周真实请求跑通核算脚本再逐步把路由规则加进去成本优化就有了明确的数据支撑。