恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
AI推理网关路由架构与策略实践:应对多模型调用混乱
首页
资讯中心
/
AI推理网关路由架构与策略实践:应对多模型调用混乱
AI推理网关路由架构与策略实践:应对多模型调用混乱
发布时间:2026/9/24 21:09:07
做AI推理网关这件事说白了就是一句话当你的大模型后端从一两个变成七八个调用入口必须有一个统一的路由架构把流量按策略分到最合适的推理服务上。这篇是“大模型推理优化系列”的第一篇我会把AI推理网关的路由架构和策略实践拆开讲包括怎么分层、怎么设计策略、怎么处理流式中断这些细节适合正在做模型治理、推理成本控制或者打算从单模型直连走向多模型统一入口的团队参考。去年上半年我们线上模型只有两个一个自建的vLLM服务一个第三方的接口。业务代码直接调SDK倒也不乱。到年底模型服务涨到八个Ollama上还跑着qwen2.5-7b这类本地小模型调用层就彻底失控了有些请求明明该走便宜模型却走了贵的有些模型升级了业务方还在用旧接口有些后端无响应时所有调用一起超时。最终我决定在业务和推理服务之间加一层AI推理网关把路由决策从业务代码里捞出来统一管理。这篇文章记录的就是这个过程。1. 推理网关要解决的真实问题模型变多之后调用层先乱了1.1 从“两个模型”到“八个模型”的失控过程一开始真没想过要引入网关。一个vLLM集群管住了大部分线上对话一个外部API负责高难度任务业务方在自己的服务里写死两套调用逻辑顶多封装一个公共类看起来挺干净。转折点出现在“性价比”三个字上。有人开始接轻量模型处理简单问答有人把文本分类任务切到本地Ollama上的开源模型再后来多模态、长上下文、代码生成各自找到了专属后端。线上模型服务从两个变成八个每个服务的调用方式、鉴权方式、超时要求都不一样。团队里每个人都在自己的代码里维护一份模型清单你去问某个模型现在能不能用没有人能立刻答上来。失控的典型表现是A服务通过vLLM的Python SDK调用模型B服务直接用Ollama的HTTP接口C服务又接了一版厂商的官方SDK。三个服务各自封装没有一个统一的地方能看清所有模型服务的健康状态和调用量。新模型上线、旧模型下线变成一次高危变更因为影响面根本理不清。1.2 散落在业务代码里的路由逻辑就是技术债我见过最典型的“路由代码”长这样def chat(messages): last messages[-1][content] if 复杂 in last or 推理 in last: return call_vllm(messages) elif 简单 in last or 闲聊 in last: return call_ollama(messages) else: return call_api(messages)这段代码看着能跑实际上问题很大。判断依据是拍脑袋写出来的关键词模型稍微升级一下这个规则就废了但谁也不知道该什么时候改、改成什么。今天新接入一个模型所有相关服务都要跟着改一遍路由逻辑从一份复制成了三四份改漏一个就出事。更麻烦的是没法观测。每个模型每天处理多少请求、花了多少token、失败率多高这些数据散落在各个服务的日志里想统计得人肉捞日志。等到月底算推理成本的时候财务拿着账单过来我们连哪部分钱花在哪个模型上都说不清楚。1.3 推理网关的定位入口、策略执行点、观测点给业务和推理服务之间加一层网关核心不是“转发”这个动作而是把三件事集中在一层解决流量入口所有模型调用统一走一个地址、一套协议业务方不用关心后端是vLLM还是API。策略执行点路由规则、限流、鉴权、熔断、降级全部下沉到网关业务代码里不再有路由判断。观测点每个请求经过网关时统一记录耗时、token数、成功失败状态成本和性能数据一口出。一个比较贴近的类比是以前每个业务方自己开车去不同的车站现在统一到一个枢纽站换乘。多跳一跳但是每个站点的状态、每辆车的班次都被集中管理起来。引入网关初期会有少量转发开销但从治理角度看这笔开销非常值得。2. 路由网关的分层架构设计接入、策略、转发各管一段2.1 接入层要做的协议归一化网关接入层最核心的一件事是统一协议。目前几乎所有的推理服务都兼容OpenAI的Chat Completions格式vLLM、Ollama、各类外部API全都能接收这种格式的请求所以直接把OpenAI兼容格式作为网关的内部标准格式是成本最低的选择。接入层要做的事情包括鉴权校验调用方的API Key或内部token区分租户和业务线。参数校验检查必填字段、约束temperature范围、确认max_tokens不超过上限。模型名映射业务方传过来的model名字是逻辑名网关映射成实际后端业务方不需要知道后端地址。比如业务方传modelchat-smart网关映射到vllm-main传modelchat-cheap映射到ollama-local。额外字段剥离比如内部用的trace_id、租户id不能透传给外部API需要在接入层剥掉。2.2 策略层要组织的是“规则候选集打分”不是if-else很多人实现路由时第一反应就是写if-else这是最容易被代码腐化的做法。网关策略层应该围绕“规则、候选集、选择器”三个概念组织match描述什么请求命中这条路由规则比如路径前缀、模型名、请求头里的租户标识。candidates命中规则后有哪些后端可以作为候选。selector从候选集中挑出最终目标的方式可以是加权随机、最小负载、分数排序。一套典型的YAML路由配置长这样routes: - name: chat_default match: path_prefix: /v1/chat/completions candidates: - upstream: vllm-main weight: 80 - upstream: public-api weight: 20 selector: weighted_random这套配置的逻辑是所有Chat Completion请求80%流量走自建vLLM20%流量走外部API。路由规则和代码分离改权重不需要改代码热更新配置就能生效。规则一多还可以加优先级字段从上到下匹配第一条命中就先走第一条。2.3 转发层与连接管理转发层承担的是“把请求送到后端”的脏活每个后端类型要有对应的适配器。vLLM适配器要处理好max_model_len和stream参数Ollama适配器要注意本地接口和OpenAI兼容接口的差异外部API适配器要关注上游限流头、账户余额这类因素。连接管理是转发层比较容易忽略的点。如果每次请求都新建一条TCP连接再做TLS握手在高并发下延迟会明显抬高。网关内部一定要给每个后端维护连接池复用HTTP长连接。实测下来连接复用能降低30%左右的请求延迟同时对后端的连接数也更稳定不容易把推理服务打挂。2.4 一次请求在网关里的完整生命周期阶段做什么关键点1接收HTTP请求网关入口统一地址2鉴权和限流校验调用方身份和配额3参数校验和归一化模型名映射、字段规整4路由匹配找到命中的路由规则5候选打分选出最终目标后端6转发上游通过适配器和连接池发送请求7响应处理透传SSE流或聚合完整响应8指标记录记录耗时、token、失败原因9返回业务统一响应格式返回给调用方整个过程对业务方是透明的业务方只需要和一个域名交互。3. 路由策略的核心维度能力、成本、延迟、优先级怎么组合3.1 能力约束先行模型路由本质上是一个多目标决策。能力是硬约束成本、延迟、负载是软目标。如果一个请求要求function calling而候选模型根本不支持那不管它多便宜、多快都不能选。给每个上游维护一份能力元数据至少包含以下字段supports_tool_calling是否支持函数调用supports_vision是否支持图片输入max_context_length最大上下文长度output_limit最大输出长度quantization量化精度比如FP16、INT8、AWQ路由开始之前先根据元数据把不满足硬约束的候选过滤掉再进入后续打分。这一步没做好的话后面所有优化都是空中楼阁。3.2 成本与负载怎么平衡过滤完能力约束之后如果候选集里还剩多个后端就需要权衡成本和负载。自建vLLM集群的边际成本很低GPU已经买了电费和运维都是固定的外部API按token计费每千token一个价流量大了之后账单很吓人本地Ollama模型几乎零边际成本但能力弱一些适合低优先级的简单任务。动态权重是更细致的做法。网关不停统计每个后端的队列长度、每分钟token消耗负载高的后端自动降低权重负载低的后端多分流量。外部API还可以做配额管理设置每分钟token上限和月度预算超过限额之后直接把流量全切到内部模型避免账单失控。3.3 延迟敏感度决定路由偏好同一个模型服务交互式对话和离线批处理对延迟的要求完全不同。网页对话框用户等着回复要求首token延迟尽量低TTFT首token时间最好控制在800ms以内。离线推理任务凌晨跑一批文本分类延迟不是首要指标成本才是。数据标注要求模型输出质量稳定延迟可以放宽。所以在路由策略里应该带上场景标签。偏重延迟的场景优先选快的后端偏重成本或质量的场景优先选对应的后端。我们线上就把请求头里增加了一个X-Scene字段网关根据场景做不同维度的打分效果比单一策略好很多。3.4 多维度打分路由的实现思路多维度打分是目前比较通用的做法核心思路是先做硬性过滤再做加权求和。一个简化的打分函数def score_candidate(candidate, ctx): # 硬性过滤 if ctx.need_tool_calling and not candidate.supports_tool_calling: return -1 if candidate.health down: return -1 if ctx.max_context_length candidate.max_context_length: return -1 # 软目标打分 score 0.0 score candidate.quality_rank * 10 # 质量分越高越好 score (100 - candidate.current_load) * 0.2 # 负载越低越好 if candidate.cost_class ctx.max_cost_class: score 15 # 满足成本约束加分 if ctx.latency_sensitive and candidate.ttft_p95 1200: score - 30 # 延迟超标扣分 return score选出分数最高的候选作为转发目标。注意分数本身没有绝对意义只有排序价值。权重不要硬编码在代码里应该做成配置项最好还能通过AB实验来调否则调参一次发版一次维护成本也很高。4. 流式输出与中断处理SSE 和 abort 在网关层远比想象中麻烦4.1 网关转发SSE的技术要点大模型对话普遍用SSEServer-Sent Events做流式输出。一个典型的SSE事件长这样data: {id:chatcmpl-1,object:chat.completion.chunk,choices:[{delta:{content:你好}}]} data: [DONE]每个事件以data:开头以空行结尾最后发一个[DONE]表示结束。网关转发SSE时必须做到边收边推绝不能等上游全部生成完再一次性返回否则用户感知的延迟会非常差。还有几个细节容易踩坑不要把整个SSE流缓存在内存里再转发模型输出可能很长内存会扛不住。有些后端会周期性发心跳注释行keep-alive网关要么透传要么过滤但不要影响事件解析。网关配置超时必须区分读超时和写超时SSE场景下写超时要宽松很多因为连接虽然空闲但用户还在等数据。4.2 客户端断开后必须终止上游请求这是流式场景里代价最大的坑。用户在网页上点了一个“停止生成”前端调用AbortController.abort()连接断开了。但如果网关没有把断开信号传递给上游上游大模型会继续生成tokenGPU继续算token继续烧月底账单上的钱就是这么流走的。网关在转发流式响应时必须持续检测客户端连接状态一旦发现客户端断开要立刻向上游发起终止请求。用Python写转发逻辑时需要在一个循环里不断检查request.is_disconnected()app.post(/v1/chat/completions) async def relay_chat_completion(request: Request): payload await request.json() upstream route_to_upstream(payload) async with httpx.AsyncClient(timeout600) as client: async with client.stream(POST, upstream, jsonpayload) as resp: async for line in resp.aiter_lines(): if await request.is_disconnected(): await resp.aclose() break yield line \n\n注意不要在检测到客户端断开后直接什么都不做就break最好先关闭上游响应流让上游感知到连接终止。如果你的网关用的是其他语言核心逻辑是一样的收到abort信号时向上游请求对象调用cancel或者close而不是听之任之。4.3 流式限流与重试边界流式请求的限流策略和非流式不一样。流式请求一旦发出用户已经看到前半段内容了这时候重试会导致内容重复体验极其糟糕。所以网关对流式请求的重试必须严格限制在“首包之前”。也就是说只有连接建立阶段失败、或者超过首包超时时间还没有收到第一个事件才允许重试一旦收到第一个数据块后面任何失败都只能中断连接不能重试。限流维度上除了常规的QPS还需要关注并发连接数和token速率。大模型推理服务的并发能力非常有限网关需要给每个后端设置并发上限超出上限的请求排队或直接快速失败。流式连接断开的一瞬间如果有大量客户端同时abort还会形成一波“断开风暴”打到上游网关最好加个保护控制同一瞬间向上游发终止请求的数量。5. 故障转移与熔断降级后端集群不健康时的兜底方案5.1 健康检查的层次网关路由的前提是知道哪些后端是健康的。健康检查不能只停留在“端口通”这个层面。层次检查方式能发现的问题不能发现的问题L4 TCP端口连通性进程挂掉服务假死、死锁L7 HTTPGET /health 返回200应用层是否存活模型是否能实际推理业务级发一个极小推理请求模型是否可推理显存OOM、队列堆积最怕的就是服务进程还在健康检查也返回200但实际推理时因为显存不足或者并发打满每个请求都超时。这种情况下只做TCP检查基本等于没有防护。建议至少做到HTTP层每个后端定期探活关键后端可以加业务级探测用一个极短的prompt周期性验证模型能正常返回。5.2 熔断器三态及其参数设定熔断器是故障转移里最经典的机制。它的状态有三个状态含义网关行为closed正常状态请求正常转发open熔断状态不再转发到该后端直接走降级half-open试探状态放少量请求进去验证后端是否恢复参数上我一般先给一组推荐值再根据实际压测调整连续失败3次进入open状态open状态持续30秒30秒后进入half-openhalf-open状态放2个试探请求试探成功则回到closed失败则重新进入open。这组参数不是银弹如果你的后端启动恢复特别慢open窗口要加长如果失败信号非常可靠阈值可以调小一些。5.3 超时分级与降级策略超时如果不分级所有请求卡在同一个超时值上网关很容易把自己拖死。推荐分成三档连接超时2-3秒连不上的后端直接跳过。首包超时5-10秒大模型加载prompt可能比较慢但首次数据迟迟不来多半有问题。整体空闲超时流式过程中超过30-60秒没有新数据主动断开避免僵尸连接一直占着资源。降级策略上比较实用的是语义降级。主模型失败后根据业务允许的程度降级到本地小模型或者外部兜底API。降级不是返回一个错误而是返回一个尽量合理的结果同时在响应头里加一个X-Fallback: true标记业务方可以根据这个标记判断本次回复质量。比如vLLM集群挂了低优先级的闲聊请求直接切到Ollama本地模型用户几乎无感知。5.4 故障场景速查表故障场景网关行为业务方感知上游连接超时尝试下一个候选后端可能感觉稍慢上游返回5xx熔断计数切换备胎基本无感客户端中途断开终止上游流式请求无感知所有候选都不可用返回503并附带说明明确失败外部API配额耗尽全部切到内部模型可能质量下降这张表可以直接拿去做联调时的测试用例让每个故障场景真的走一遍比看文档有效得多。6. 一次真实的路由切换落地配置、指标与踩坑6.1 初始架构和改造目标我们当时的后端组合很有代表性一个自建的vLLM集群作为主力跑qwen2.5-7b一个Ollama本地服务跑轻量模型一个外部API兜底处理复杂任务。改造前的痛点是三个后端分别被不同服务用不同方式调用没有一个统一入口。改造目标定得很朴素业务方只认网关一个地址路由策略要可配置、可热更新任何单一后端故障都要自动切走不让业务方感知。6.2 路由配置示例这是当时简化后的核心配置upstreams: vllm-main: type: vllm endpoint: http://vllm-cluster:8000 capability: max_context: 32768 supports_tool_calling: true health: kind: http url: /health interval: 10s ollama-local: type: ollama endpoint: http://ollama:11434 capability: max_context: 8192 supports_tool_calling: false health: kind: http url: /health interval: 15s public-api: type: openai endpoint: https://api.example.com/v1 quota: tokens_per_minute: 100000 monthly_budget_usd: 2000 routes: - name: chat_main match: path_prefix: /v1/chat/completions candidates: - vllm-main - ollama-local - public-api selector: score_based fallback: public-api这个配置表达的信息是所有Chat Completion请求都进这条路由候选集是三个后端选择器用分数排序如果所有候选都失败最后兜底走到public-api。6.3 灰度切流过程网关上线不能一把梭需要分步走。我们分了四步旁路模式网关先开启但不接管真实流量。复制线上请求打到网关观察路由策略命中的结果是不是符合预期。这一步能发现大部分路由规则配错的问题。小流量验证把5%-10%的流量切到网关重点观察流式转发、abort处理是否正常。按业务线切换优先切延迟不敏感的业务线交互类业务放后面。每条业务线独立回滚。全量切换所有流量统一走网关业务方的模型调用代码收敛为一个HTTP请求。灰度过程中的监控面板必须包含三类指标网关本身的错误率、各后端健康状况、token消耗成本趋势。没有这些指标灰度就是在盲飞。6.4 效果指标与经典踩坑记录切换完成后的体感变化很明显路由规则变更从几天一次发版变成秒级热更新后端故障转移从人工介入的分钟级变成自动处理的秒级成本也因为低优先级任务被分流到本地模型而明显下降。踩过的坑也值得记一笔坑后果解决方案网关超时设得比上游短上游还在算网关先断了返回空响应网关超时要大于上游最长输出时间健康检查只做TCP后端假死照样路由过去请求全超时增加HTTP和业务级探测流式请求做了重试用户看到两段重复内容只在首包前重试客户端abort没有传给上游前端断开了GPU还在烧token检测断开立即终止上游流我自己的一个习惯是网关上线后必须安排专人盯一周指标重点看路由命中分布和失败原因。不要以为加上网关就万事大吉路由策略要基于真实流量持续调尤其是权重和熔断参数前两周基本每天都要微调一次。网关只是推理优化的第一站后续还有prompt缓存、动态批处理、模型并行这些更深的优化可以做但干净统一的路由架构是所有优化能够落地的地基。如果你们也正在做类似的改造我建议先从小范围灰度开始把策略维度想清楚再动手不要一上来就堆一堆功能。