大模型应用做久了,你会发现一个特别尴尬的现象:明明接入的是同一个顶配模型,同一个API网关,线上效果却总是忽好忽坏——有些请求被大模型杀鸡用牛刀,账单高得吓人;有些请求却被小模型草率处理,用户一眼看出答案在敷衍。问题往往不在模型本身,而在于缺少一层“智能路由”。我先说清楚,这里讨论的是大模型应用层的请求调度,不是网络层的IP路由。所谓智能路由,简单讲就是在大模型前面加一个“调度大脑”,让每个请求找到最合适的模型、最合适的上下文深度、最合适的处理链路。这篇文章我会从问题出发,讲清楚路由决策的依据、网关层的落地实现、数据校准方式,以及我实际踩过的几个坑,适合正在做大模型应用集成、本地部署、移动端接入的同学参考。
1. 大模型应用的真实困境:同一条链路同时杀死成本和体验
1.1 全量切大模型,账单先撑不住
我们团队早期做AI问答助手的时候,方案非常简单粗暴:所有用户请求统一打到云端一个70B级别的旗舰大模型上。效果确实稳,什么问题都能接住,技术评审会上没人挑得出毛病。但到了月底一看账单,整个人是麻的。对话类应用的平均请求量比我预想的大得多,尤其移动端用户经常在短时间内连续追问,上下文窗口又长,token消耗几乎是翻着倍涨,一个月光模型调用费就烧掉了小几万。
这个数字看上去还能接受,但业务一旦放量,单位成本就变得极其敏感。假设一次普通问答平均消耗800个token,100万次请求就是8亿token,按市场价算,大规模商用场景单月成本直逼六位数。而真正需要这种旗舰模型深度推理的请求,占比其实不到两成。剩下八成的问题,比如“你们的退货政策是什么”“帮我总结一下这段话”“这道题选A还是B”,用一个小参数模型完全能接住,甚至更快。
1.2 全量切小模型,质量洼地处处漏风
被账单教育之后,我们的第一反应是降级:全部切到7B级别的开源模型,用vLLM在内部GPU服务器上部署,成本确实断崖式下跌,首token延迟也从900ms降到了200ms左右。但新的问题紧随而来——小模型不是万能的,碰到多步推理、复杂代码生成、长文档理解,它就开始一本正经地胡说八道。
有一次用户问了一个涉及多个条件判断的税务计算问题,7B模型给出了一个逻辑上完全自洽但前提全错的答案。用户拿着这个答案去办事,回来投诉说我们误导人。那一刻我意识到,模型选择不是“选个最好的”或“选个最便宜的”,而是要在合适的时候用合适的模型。一刀切的做法本质上是在赌用户的请求复杂度分布,赌输了就要付出产品口碑和客诉成本的代价。
1.3 智能路由的本质:把“调用模型”变成“调度模型”
后来我们开始系统性地研究路由方案,才发现业界已经有比较成熟的做法——根据请求特征动态分派模型。核心思路就一句话:先判断,再生成。请求进入系统后,路由层先做“体检”,识别出任务类型、复杂度、需要的上下文深度、可接受的延迟上限,然后决定交给哪个后端模型去处理。
我把这种调度拆成三个层面来理解:
- 模型路由(Model Routing):决定请求给7B、14B、70B还是闭源旗舰模型。比如简单问答走7B,代码生成走70B,涉及多工具调用的复杂任务走旗舰模型。
- 上下文路由(Context Routing):决定给模型喂多少信息。有些请求只需要最近三条历史消息,有些请求却要把整个知识库的检索结果都塞进去。上下文预算分配不合理,再强的模型也白搭。
- 链路路由(Pipeline Routing):决定要不要走重链路。有的请求直接生成答案就够,有的请求需要先规划、再检索、再生成,甚至要调用外部工具。
三层路由合在一起,才构成完整的智能路由体系。而整个体系的收益模型其实是一个三角平衡:成本、延迟、质量。全量大模型保证了质量但牺牲了成本和延迟;全量小模型保证了成本和延迟但牺牲了质量;智能路由就是尽可能在这三个维度之间找到动态最优解。
2. 路由决策看什么:任务类型、上下文预算与端云边界
2.1 任务类型是第一个路由键
路由决策的第一步永远是识别“用户到底想干什么”。我在工程上习惯先构造一个粗粒度的任务分类器,不需要很复杂,几个典型类别就够用:
| 任务类别 | 典型请求 | 推荐模型档位 | 原因 |
|---|---|---|---|
| 事实问答 | 你们公司地址在哪 | 7B或本地GGUF | 检索命中后直接抽取,低延迟完成 |
| 内容总结 | 帮我总结这份周报 | 7B~14B | 对指令跟随要求中等 |
| 代码生成 | 写一个Python爬虫 | 70B或旗舰模型 | 需要强逻辑与多轮修正能力 |
| 多步推理 | 分析这个方案的风险点 | 70B或旗舰模型 | 需要链式思维,小模型容易断 |
| 创意写作 | 帮我写一段宣传文案 | 14B~70B | 对风格控制要求较高 |
| 闲聊陪伴 | 今天心情不好 | 7B | 不需要高精度,响应快更重要 |
实际工程中,这个分类可以通过三选一的方式实现:关键词规则、微调的小分类模型、或者让路由模型打分。我建议从第一条开始,因为规则的方法最快、最透明、最容易调试。比如请求里出现“怎么实现”“请写一段代码”,直接归类为代码生成;出现“帮我总结”“概括一下”,归类为内容总结。规则覆盖不到的,再落到分类模型或打分模型上。
2.2 上下文预算决定要不要走“重链路”
很多团队做路由只看模型规模,忽略了上下文管理,这是个很大的误区。同样一个“帮我分析一下”的请求,如果只带一条用户消息,14B模型就能干得很漂亮;如果带了20轮对话加上3份文档内容,上下文超过8000 token,模型的选择逻辑就完全不同——此时多模态理解、长上下文压缩能力比参数规模更重要。
所以我在路由层单独设计了一个“上下文预算评估器”,它看三件事:
- 当前会话消息长度:超过一定阈值,说明这是一个深度对话场景,要按长上下文模型的标准路由。
- 外部检索内容量:如果系统决定注入RAG检索结果,路由必须知道这些内容有多长、结构是否复杂,避免把3万字的检索内容硬塞给一个上下文窗口只有8K的小模型。
- 回答期望粒度:用户问“简单说结论”还是“按一二三四详细拆解”,对生成质量的要求完全不同。这个可以通过提示词里直接声明,也可以从历史行为里学。
决策逻辑可以做成很直白的伪代码:
def route_with_context(request): task_type = classify_task(request.text) context_len = estimate_context_tokens(request) if task_type == "codegen": return ROUTE_LARGE_MODEL if context_len > 8000: # 长上下文优先保证窗口充足,避免截断 return ROUTE_LONG_CONTEXT_MODEL if task_type in ("faq", "chat"): return ROUTE_SMALL_LOCAL_MODEL # 其他情况走默认的规则路由 return router_model.score(request)这个预算评估器不需要真的是一个大模型,一个统计模型加几条阈值规则就够了。关键是让路由决策具备“上下文感知”能力,而不是只看消息第一句。
2.3 端云边界:GGUF本地模型与云端大模型的互补
结合本地部署的热潮,我觉得智能路由还有一个特别落地的场景——端云协同。移动端APP可以通过llama.cpp等框架加载量化后的GGUF格式小模型,比如7B Q4量化版本,在手机本地跑推理。好处是零网络延迟、零调用费用、数据不出设备,坏处是能力天花板低。
我做过一个实际方案:Android端先加载一个GGUF小模型处理轻量请求,比如天气查询、简单计算、常用知识问答。当本地模型对某个请求计算出的置信度低于阈值时,客户端发起请求到云端,云端再接上vLLM部署的中型模型或闭源旗舰模型。整套流程在路由层体现为一个“端云分派器”,它接收的信息不止是文本本身,还包括端侧模型的特征:
def route_device_or_cloud(request, device_info): if device_info["local_model_loaded"]: score = device_info["local_confidence"] if score > 0.85 and not request.requires_reasoning: # 本地直接出结果,不涉及网络请求 return ROUTE_LOCAL_GGUF if score < 0.6: # 置信度太低,立即上云 return ROUTE_CLOUD_VLLM # 中间地带可以放一个异步上云复核 return ROUTE_CLOUD_AFTER_LOCAL_FALLBACK return ROUTE_CLOUD_VLLM这种路由设计有一个隐藏收益:把大部分请求从云端卸载之后,云端GPU压力骤减,那么高峰期的旗舰模型配额可以用来处理真正复杂的请求。移动端用户感知最明显的变化是:以前一句“查一下今天天气”要转圈1.5秒,现在本地模型200毫秒直接给答案。
3. 网关层的智能路由:多后端接入、流式转发与降级策略
3.1 路由层应该放在调用链的哪个位置
很多人觉得路由逻辑写在业务代码里就行,每个接口单独判断一下调哪个模型。这种方式在一两个接口时没问题,一旦接口数量多起来,路由规则会散落得到处都是,改一条策略可能要动十几个地方。
我的建议是把路由做成一个独立的网关服务,放在业务后端和模型后端之间。业务后端不需要关心最终由哪个模型解答,它只负责把请求发给网关,网关根据预设策略路由到不同的模型服务提供商,再把结果流式回传。
这样做的好处有三个:
- 策略集中管理:所有路由规则在一个服务里维护,发布时只需重启网关,不用改业务代码。
- 后端可灰度切换:不同模型服务可以按百分比切流量,比如先让10%的请求走新接入的模型,观测质量稳定后再放开。
- 故障隔离:某个模型服务挂了,网关可以自动把流量切到其他后端,业务层完全无感知。
3.2 路由即服务:一份最小可运行的Python路由网关
我画一下我们生产环境里网关的最小结构(这里展示核心逻辑,非完整代码)。网关接收请求后,用H2章节里说的流程计算路由决策,然后调用目标后端的接口。关键点是目标后端的处理必须支持SSE流式输出,网关只做透传,不要在中间缓冲攒数据。
# gateway.py 简化示例 from fastapi import FastAPI, Request from fastapi.responses import StreamingResponse app = FastAPI() BACKENDS = { "local_7b": {"url": "http://localhost:8001/v1/completions"}, "cloud_70b": {"url": "https://llm-api.example.com/v1/completions"}, "flagship": {"url": "https://flagship.example.com/v1/chat"}, } def decide_backend(req): # ... 结合任务分类、上下文预算、端云信息 ... if req["task"] == "faq": return "local_7b" if req["context_tokens"] > 8000: return "cloud_70b" return "flagship" @app.post("/route") async def route_chat(request: Request): payload = await request.json() backend = decide_backend(payload) target_url = BACKENDS[backend]["url"] async def sse_stream(): # 这里通过 httpx 或自定义 SSE 客户端向后端发起请求 async for chunk in call_backend_sse(target_url, payload): yield chunk return StreamingResponse(sse_stream(), media_type="text/event-stream")这里有几个工程细节值得展开说。第一,路由决策的耗时必须在毫秒级,不能为了做判断先调用一次大模型,那还不如不做路由。规则和统计模型在这个场景下比“用大模型路由”更实用。第二,网关必须记录每个请求的路由决策结果,包括分流到哪个模型、决策依据是什么、后端响应时长是多少。这些日志是后续校准策略的唯一数据来源,后面第4章还会细讲。
3.3 SSE流式转发与AbortController协同的细节
智能路由网关一旦介入,流式输出的复杂度会上升一个量级。最典型的问题是“用户点击停止生成”时,前端会把请求中断,此时网关必须同时断开与后端模型服务的连接,否则流还会在网关上继续跑,白白消耗模型资源计费时长。
前端侧如果是浏览器环境,一般用AbortController来控制fetch的取消:
const controller = new AbortController(); const response = await fetch('/route', { method: 'POST', body: JSON.stringify({ text: inputText }), signal: controller.signal, }); // 用户点击停止按钮 stopBtn.onclick = () => controller.abort();网关侧收到客户端的连接中断后,要在上下文里传递取消信号,主动关闭上游连接。我见过团队把这一步漏掉,结果用户每取消一次,网关和模型后端之间的连接还会继续把整段内容生成完,白烧几万token。所以网关服务里,每一个代理出去的请求都要绑定一个asyncio.Task,客户端断开时立刻取消这个task,再关闭上游连接。
具体实现时可以给每个请求加一个影子ID,前端在EventSource或fetch里把这个ID传过来,网关把它映射到上游请求句柄上。取消时前端发一个/cancel?id=xxx请求,网关同步取消对应的上游SSE流。Web原生SSE不支持自定义取消,这也是为什么很多生产项目自定义SSE协议而不是直接用EventSource的原因之一。
3.4 超时熔断与优雅降级
接入多个模型后端之后,会面临一个很现实的问题:不同后端的稳定性完全不一样。开源自部署的7B模型只要GPU没炸,延迟相对稳定;云端API却可能因为限流或区域网络波动,偶尔出现几十秒不返回首token的情况。
网关要有三层超时策略,而不是统一设一个固定值。客户端等待首token的超时时间应该比网关内部的长,否则用户那边已经报错了,网关还在傻等上游。我这边常用的配置逻辑如下:
- 客户端超时:30秒,主要兜底用户网络问题。
- 网关到后端首token超时:15秒,超过则切换备胎后端。
- 单条流式消息的最大空闲时间:3秒,超过说明流已经断了,触发自动重连或降级。
降级不是简单换个模型重复请求一遍,要考虑累积成本。比如一个请求先派给了本地小模型,生成到一半发现质量明显不对,网关可以快速中断,转投云端大模型重新生成。这种“小模型预检、大模型兜底”的模式在客服场景特别好用:本地模型先秒回一个初步答案,如果答案置信度低,用户会看到一个智能化的提示“正在为您切换高精度引擎”,体验上完全说得通。
4. 用数据校准路由:质量评估、成本核算与A/B验证
4.1 立好评估集,先有标尺再谈路由
路由策略上线之前,我最担心的事情是“拍脑袋定阈值”。比如我说“低于0.6置信度就上云”,那这个0.6是怎么来的?不能拍脑袋。我们的做法是先建立一个小型评估集,把历史请求按任务类型、上下文长度分布抽样,差不多500条左右,每一条都人为标注好“标准答案”。
然后让不同的路由策略在这500条数据上跑一遍,统计每条请求用了哪个模型、花了多少token、生成结果和标准答案的接近程度如何。这里要注意,不同模型对同一请求的答案质量很难自动打分,所以早期我们是让三个人分别打,取平均分。后期引入了“另一个大模型当裁判”做评估,效果也不错,但要注意裁判模型的偏好偏差。
有了评估集之后,路由策略就不是玄学了。比如我们调整“小模型置信度阈值”时,可以很快看到一组数据:阈值0.7时,小模型承担了63%的流量,整体质量评分4.2;阈值0.9时,小模型只承担38%的流量,整体质量评分提到4.6。根据业务对质量和成本的不同容忍度,选择就变得很清晰。
4.2 成本核算模型:不看单次调用,要看一个月
智能路由省钱的核心点在于“高价值请求才走贵模型”。但很多团队算账时错在只看单次调用,比如“7B模型一次调用省3分钱”,觉得没什么了不起,却没有算总量。
我提供一个比较简单的估算模型。假设日活1万,平均每人每天20次对话请求,每条请求的平均输入输出token加起来800,那一个月总token消耗大约48亿。用旗舰模型,假设百万token价格大概几十到一百元级别,一个月就是好几万甚至上十万。如果智能路由能把60%的流量分给自部署的7B模型,这部分token基本只花电费和维护费,云端只承担剩下40%的旗舰调用,月成本可能直接降到原来的三成到五成。
但这里有个反向坑:本地部署的GPU成本还没算进去。一台能稳定跑7B模型并发的GPU服务器,月租加运维摊下来也是一笔钱。所以路由策略不一定只把请求分给自部署模型就“省钱”,还要对比云端便宜档位API。路由决策本质是一个收益函数,每次转发都要按实时成本做归一化判断,简单的伪代码逻辑是:
def cost_aware_route(req): local_cost_estimate = estimate_gpu_cost_per_token() + electricity cloud_small_cost = cloud_small_price_per_million_tokens cloud_large_cost = cloud_large_price_per_million_tokens # 根据模型预计算分数和单次成本 best_value = argmin( local_7b: (quality_loss, local_cost_estimate), cloud_small: (quality_loss, cloud_small_cost), cloud_large: (0, cloud_large_cost) )在实际系统里,我不会对每次请求都做复杂的成本计算,而是每隔几分钟同步一次各后端的单位价格数据,路由决策时直接查表。只要保证“决策开销”远小于“省下的成本”,这个路由就是划算的。
4.3 线上验证方式:影子路由与渐进切流
评估集验证完,不代表策略可以全量上线。因为评估集是离线构造的,真实的用户请求分布一定会有出入。我推荐两种线上验证方式,胆子小的时候用影子路由,胆子大了用渐进切流。
影子路由指的是:请求真实流量仍然正常发给原来的模型,但同时在路由层“模拟”跑一遍新策略,计算“如果这个请求按新策略走,会分给谁,会产生多少成本”。影子路由不改变线上行为,只负责记录。连续跑一周后,把影子日志和真实日志对比,能看出新策略的流量分布、质量变化、成本变化。这套方式的优点是零风险,缺点是样本代表性可能不够,毕竟真实用户不会受影子路由影响。
渐进切流则是把新策略的生效比例从5%、20%、50%、100%逐步上调。每一步都要盯两个核心指标:用户满意度(比如点赞点踩率、平均对话轮数)和业务指标(比如转化率、客诉量)。切流的每一步都留至少24小时观察期,因为很多质量问题要隔天才能从用户反馈中浮现出来。
4.4 路由策略本身也要“微调”
智能路由上线以后不是一劳永逸的。用户的问题分布会随着产品迭代、季节活动、新功能上线而变化。比如我们上线了一个“法律条文检索”功能后,需要走长上下文路由的请求占比明显变多,原来在规则里写的8K上下文阈值就显得不合理。
这个场景下,我对路由策略的调优走的是类似“微调”的节奏,只不过微调的对象不是大模型权重,而是策略参数和规则库:
- 每周拉取路由日志,分析各任务类型占比、分流模型占比、平均成本、超时率。
- 对每个超出预期的指标,定位到具体路由决策点和特征维度。
- 调整阈值或增加规则,先在评估集上回归,再灰度发布。
如果规则路由已经没法精确表达业务逻辑,才考虑用一个小模型来做路由打分。小模型的输入是请求文本加上一些上下文统计特征,输出是各候选模型的推荐分。这类模型不需要特别大的参数量,7B级别在CPU上也能跑得动,甚至可以量化后放在和业务服务同一台机器上。业界的一些开源路由项目比如RouteLLM走的也是类似路线,核心思路是训练一个轻量的router模型,结合胜率或成本偏好做决策。我们在项目中也参考了这个思路,但前期没有直接上模型,因为规则在大部分场景已经足够,模型路由是用来兜住规则照顾不到的边界情况的。
5. 上线之后才会遇到的坑:路由误判、流式中断与超时叠加
5.1 路由误判比模型答错更隐蔽
路由方案落地后,最常见的线上问题是“路由误判”。这里不是说模型答错,而是请求被分到了一个错误的后端,导致模型完全不理解用户意图。举例来说,用户问“这段代码为什么报错”,分类器把它识别成“报错信息分析”,结果路由到了一个专门优化文案创意的模型,模型给出的回答完全围绕“如何美化错误提示信息”,没有一句对症的。这种误判对体验的破坏力比模型直接答错更隐蔽,因为表面看回答是流畅的、有条理的,但完全没解决问题。
我排查这类问题的方法是给网关加上“路由可解释性”日志——每一次路由决策除了记录最终结果,还要记录参与决策的关键特征值,比如分类标签、置信度、上下文token数量、命中哪条规则。用户在反馈中心点“答案不满意”时,我们可以一键关联到这次请求的路由日志,快速判断是分类错了,还是模型本身能力不足。没有这层日志,排查路由误判只能靠瞎猜。
5.2 决策必须抢在第一个token之前完成
很多朋友可能没有意识到,路由决策一旦晚了,哪怕只晚半秒钟,就会造成体验上的“风格撕裂”。因为SSE流一旦开始往客户端推数据,用户就已经在阅读第一个token了,此时路由器如果决定切换后端,用户会明显感觉到前半段和后半段的口径、措辞、质量变了。
我给网关定了一条铁律:路由决策必须在连接建立后200毫秒内做出,并且决策完成前不允许向后端发送任何生成请求。如果某类请求确实需要更长时间分析,宁可先返回一个“正在分析”的占位提示,也不要贸然开流。为了做到这一点,任务分类器不能完全依赖规则,还要有一个极快的轻量分类模型,推理时间控制在10毫秒以内。这类模型不需要很大,几百兆参数的量化版本就足够。
前端配合上,我会建议在SSE协议里加一个元信息事件,首个事件专门返回路由决策结果,类似:
{"event": "routing", "data": {"backend": "cloud_70b", "reason": "task_type=codegen, confidence=0.93"}}前端拿到这个事件后,可以选择展示一个“正在匹配高精度引擎”的过渡状态。既解决了决策延迟的感知问题,也让用户对系统能力更有信心。
5.3 超时时间不要叠加,要分层
多跳链路里,超时配置是重灾区。一个请求从客户端到网关到模型后端,每一跳都有超时时间,如果每一跳都是20秒,那整体最坏情况不是20秒,而是可能叠加成60秒——客户端已经把请求放弃了,网关上层的连接还悬着,底层模型还在生成,最后生成结果发回一个没人接收的socket。
我们的处理原则是“内层超时要小于外层超时”。客户端有一个总超时,网关内部每一跳的超时都比总超时小。以一个30秒的客户端总超时为例,网关到后端的首token超时设15秒,后端收到请求后如果15秒没返回,网关立刻熔断切到备胎;备胎请求的超时再减5秒。这样最坏情况下,用户等待不超过30秒,并且每一次等待都有意义。
5.4 缓存与路由的冲突:不能只缓存“答案”
做了路由之后,一定会有同学想顺手做个结果缓存,这是好事,但很快会发现一个问题:如果路由策略本身在变,缓存命中旧模型的答案可能会把错误答案重新发出去。比如路由策略更新后,某个请求从前一天开始就走70B旗舰模型了,可因为命中缓存,用户拿到的还是三天前7B模型生成的低质量答案。
解决思路是给缓存键加几个路由相关的维度:任务类型、路由策略版本、上下文摘要哈希。只要路由策略版本变化,缓存命中率会暂时下降,但能保证不会把旧策略的答案错发给新策略的用户。另外,缓存分层也要做,简单高频请求可以长期缓存,涉及复杂推理的请求干脆不缓存,因为这类答案对时效性和上下文敏感度极高。
5.5 别忘了“人肉路由”:给用户留一个人工通道
最后一个坑不在技术层面,而在产品层面。智能路由再聪明,也总有判断不了边界情况的时候。尤其是客服、法律咨询、医疗建议这些高价值场景,用户如果连续两次对AI答案表达了不满,继续在模型间来回路由已经没有意义。这时候路由应该把请求直接升级到人工座席,而不是再做第三轮模型打分。
我们最后在路由链路上加了一个“人工接管”出口:如果模型置信度连续低,或者用户连续点踩两次,网关直接给该会话打上“需要人工”标签,通知后台客服系统接入。后来复盘发现,这个设计本身也在反向优化我们的路由——人工座席的聊天记录比普通用户反馈更有价值,它们是最贴近用户真实意图的训练数据来源,后续用来校准路由模型和评估集,提升非常明显。
6. 我的落地顺序与最终心得
如果让我给一套可复制的落地路径,我会按下面的顺序推进:
- 先完善观测。在现有架构里记录下每个请求由哪个模型处理、耗时、成本、用户反馈。没有这些数据,所有优化都是空谈。
- 启用简单规则路由。按任务类型关键词、上下文长度、用户端类型等最容易获取的特征,先做出第一个路由版本。哪怕只有三条规则,也比全量一个模型好。
- 建立离线评估集。花一周时间人工标注几百条请求,给路由版本做回归测试,找到规则里的明显问题。
- 接入数据校准。把成本数据、质量评分、超时率拉通,调整阈值和分类逻辑。
- 加上模型路由。只在规则不够用的边界,引入轻量router模型做打分,同时把它当做一个可替换的组件,随时可以回退到规则版。
- 沉淀人工反馈闭环。让每次用户点踩、每次人工座席接管,都变成下一次路由决策改进的养料。
我个人实际做下来,最深的体会是:智能路由不是一个一次性开发完成的系统,它是一种持续演进的产品能力。模型在更新、用户在变、成本在波动,路由策略必须像对待一个独立产品版本一样来管理和迭代。最后多说一句,如果你打算在自己的项目里做这件事,不要一开始追求“用模型来做路由”,先把规则和数据跑通,收益就已经超过你的预期了。