一个中型 AI 应用,后端常常同时挂着 OpenAI、Anthropic、自研微调模型、自托管 vLLM 四五个来源。业务代码里散落着各家 SDK、各自的密钥、各自的限流处理——改一个模型要动十几个文件,某个供应商挂了才发现没有兜底。LLM 网关(Model Gateway) 的作用就是把这一切收敛到一个统一入口:业务只对着一个 API 说话,网关负责协议适配、路由、限流、故障转移与成本核算。本文讲清网关该做什么、怎么路由、怎么不成为新的故障点。
前置:LLM 推理服务架构设计 、LLM 服务可观测性 、推理引擎终极对比 。
一、网关的职责边界
先明确网关该管和不该管的事:
| 职责 | 说明 | 归属 |
|---|---|---|
| 协议适配 | OpenAI/Anthropic/Gemini 格式互转 | 网关 |
| 路由分流 | 按策略选后端模型 | 网关 |
| 鉴权与配额 | 密钥、租户、限流 | 网关 |
| 故障转移 | 重试、降级、熔断 | 网关 |
| 成本归因 | token 计费、按租户分摊 | 网关 |
| 缓存 | 精确/语义缓存命中 | 网关(或旁路) |
| 模型推理 | 真正的计算 | ❌ 后端引擎 |
| 业务逻辑 | 提示词组装、结果解析 | ❌ 应用层 |
网关不做的事(避免「胖网关」反模式):
□ 不做提示词工程(那是应用的职责)
□ 不做模型推理(只转发到后端引擎)
□ 不做重业务逻辑(会变成难维护的中间层)
□ 尽量无状态(状态外置到 Redis 等)
工程要点:网关的价值在于**「收敛横切关注点」**——鉴权、限流、路由、计费这些「每个请求都要做一遍」的事,与其在每个业务服务里重复实现,不如收敛到网关一处。但别让它膨胀成「什么都管」的胖网关:提示词组装、结果后处理这类业务逻辑必须留在应用层,否则网关会成为迭代瓶颈与故障放大器。
二、四类路由策略
路由是网关的核心。不同业务场景需要不同的分流依据:
2.1 静态路由(按模型名/规则)
最基础:请求里指定模型名,网关映射到具体后端。
# 路由配置示例
routes:
- match: { model: "gpt-4o" }
backend: openai
upstream: "gpt-4o-2024-08-06"
- match: { model: "fast" }
backend: self-hosted
upstream: "http://vllm-fast:8000"
- match: { model: "claude-*" }
backend: anthropic
2.2 成本/性能路由
按「便宜优先」「快优先」自动选型,把简单请求导向小模型:
成本路由示例:
□ 输入 < 500 token 且无工具调用 → 小模型(7B)
□ 需要长上下文(> 32K) → 长上下文模型
□ 命中缓存 → 直接返回,不走模型
□ 其余 → 主力大模型
性能路由示例:
□ 延迟敏感(交互)→ 低 TTFT 后端
□ 吞吐敏感(批处理)→ 高吞吐后端
□ 无 SLA 约束 → 排队到最空闲的副本
2.3 语义路由(Semantic Routing)
用嵌入(Embedding)判断请求意图,再分流到最合适的模型或提示词模板:
# 概念示意:用 embedding 相似度选路由
routes = {
"code": embed("写代码、调试、编程问题"),
"math": embed("数学计算、证明、公式"),
"chat": embed("闲聊、问答、通用对话"),
}
def route(query):
q = embed(query)
best = max(routes, key=lambda k: cosine(q, routes[k]))
return model_for(best) # code→强代码模型,chat→小模型
语义路由能显著降本:把大量简单闲聊请求拦在便宜模型上,只在真正需要时才路由到大模型。
2.4 负载/健康路由
在多个等价副本间做负载均衡,并剔除不健康实例:
负载策略:
□ 轮询 / 加权轮询:简单,适合同构副本
□ 最少连接:适合长流式请求(避免某副本堆积)
□ 一致性哈希:带会话亲和(多轮对话复用 KV cache)
□ 优先级:付费租户优先
健康检查:
□ 主动探活(/health)→ 剔除故障副本
□ 被动熔断(错误率/超时率超阈值)→ 临时摘除
□ 半开恢复(试探性放量)→ 恢复后回归
路由决策的组合顺序(典型):
1. 鉴权 + 配额检查(超限直接拒)
2. 缓存命中检查(命中直接返回)
3. 显式模型指定?(有 → 静态路由)
4. 策略路由(成本/语义/性能)
5. 负载均衡选副本(含健康过滤)
6. 转发 + 记录成本与指标
工程要点:路由策略要**「可配置、可观测、可回滚」**。别把路由逻辑硬编码进代码——配置化(YAML/DB)才能让运营侧快速调整。每条路由决策都应记录「为什么选了这个后端」,否则线上成本异常时无从追查。相关成本治理思路见 LLM 成本优化 与 FinOps 成本智能 。
三、统一 API 与协议适配
网关对外暴露一套统一 API(通常对齐 OpenAI 的 /v1/chat/completions),对内适配各家差异。
3.1 差异适配表
| 差异点 | OpenAI | Anthropic | 自托管 vLLM |
|---|---|---|---|
| 请求格式 | messages | messages + system 独立 | 兼容 OpenAI |
| 系统提示 | role: system | 顶层 system 字段 | role: system |
| 流式格式 | SSE data: | SSE event: 类型 | SSE data: |
| 工具调用 | tools/tool_calls | tools/tool_use | tools |
| 停止条件 | stop | stop_sequences | stop |
| Token 计数 | usage | usage | usage |
# 网关内部:把统一请求翻译成各家格式
def to_anthropic(req):
system = [m["content"] for m in req["messages"] if m["role"] == "system"]
msgs = [m for m in req["messages"] if m["role"] != "system"]
return {
"model": req["model"],
"system": system[0] if system else None,
"messages": msgs,
"max_tokens": req.get("max_tokens", 1024),
"stop_sequences": req.get("stop", []),
}
3.2 流式转发的坑
流式(SSE)转发比非流式难得多:
流式转发的关键点:
□ 逐块转发,别缓冲整个响应(否则失去流式意义)
□ 统一 SSE 事件格式(各家 event 名不同)
□ 客户端断连时及时取消上游请求(省算力)
□ 计费:流式下 token 数从 usage 或累计块推算
□ 超时:流式请求的超时应是「首块超时 + 空闲超时」双阈值
工程要点:统一 API 的难点不在「转发」,而在**「语义对齐」**——工具调用、流式事件、停止条件这些细节各家不一致,适配层要仔细处理边界。流式转发尤其要防「缓冲放大」:网关一旦把整个响应缓起来再发,就退化成非流式了。客户端断连要能及时取消上游,否则白烧 GPU。
四、限流、配额与成本归因
网关是执行「谁用多少、花多少」的天然位置。
4.1 多级限流
限流维度(从粗到细):
□ 全局:保护整个网关不被打爆
□ 租户/API Key:防单租户挤占
□ 模型:某模型的总并发上限
□ 请求级:单请求 max_tokens 上限(防超长生成)
限流算法:
□ 令牌桶:允许突发,适合交互
□ 漏桶:平滑输出,适合保护后端
□ 滑动窗口:精确计数,适合配额
□ 并发信号量:限制同时在飞请求数(LLM 最常用)
# 配额配置示例
tenants:
- id: team-a
rpm: 600 # 每分钟请求
tpm: 200000 # 每分钟 token
concurrent: 32 # 最大并发
models: ["gpt-4o", "fast"]
monthly_budget_usd: 2000
4.2 成本归因
网关天然掌握「谁、用了哪个模型、多少 token」,是成本归因的最佳采集点:
| 归因维度 | 用途 |
|---|---|
| 按租户/团队 | 内部结算、预算控制 |
| 按模型 | 选型评估、路由优化 |
| 按接口/功能 | 找出「烧钱大户」 |
| 按缓存命中 | 量化缓存节省 |
成本计算(网关侧):
cost = input_tokens × 单价_in + output_tokens × 单价_out
按租户累加 → 与预算对比 → 超限告警/限流
→ 单位经济学($/1K 请求、$/用户)可反推定价
工程要点:限流要**「多级 + 按并发」**。LLM 请求耗时长、并发数比 QPS 更能反映后端压力,所以「并发信号量」是最有效的限流手段。成本归因要在网关做,因为只有这里同时知道「调用方」和「token 消耗」;缺少这层,成本永远是笔糊涂账。
五、高可用与故障转移
网关本身不能成为单点。它承载着「所有 AI 流量」,挂了就是全站 AI 不可用。
高可用设计要点:
□ 网关无状态化 → 多副本水平扩展 + 负载均衡
□ 配置外置(DB/配置中心)→ 副本共享路由规则
□ 优雅重启 → 在飞请求不中断
□ 依赖隔离 → 缓存/计费挂了不阻塞主链路
故障转移策略:
□ 重试:同后端换副本重试(幂等前提下)
□ 降级:大模型不可用 → 路由到小模型兜底
□ 熔断:错误率超阈值 → 摘除该后端,定期半开试探
□ 超时分层:连接超时 / 首块超时 / 总超时
# 熔断器概念示意
class Breaker:
def __init__(self, threshold=0.5, window=60):
self.fail, self.total, self.state = 0, 0, "closed"
def call(self, fn):
if self.state == "open":
raise BackendUnavailable()
try:
r = fn(); self._ok(); return r
except Exception:
self._fail()
if self.fail / max(self.total, 1) > self.threshold:
self.state = "open" # 熔断,走降级
raise
降级链示例:
主模型(GPT-4o) → 备用(Claude) → 小模型(本地 7B) → 静态兜底话术
每一级都设超时与熔断,避免「降级也超时」拖垮整体
工程要点:故障转移的核心是**「预设降级链 + 快速熔断」**。别指望「重试就能解决」——供应商整体故障时重试只是浪费超时时间。熔断要「快」(几秒内摘除)+「会恢复」(半开试探)。降级链要提前配置并演练,线上第一次遇到故障才现想降级方案必然手忙脚乱。
六、可观测性与灰度
网关是所有 AI 请求的必经之路,天然是观测的最佳埋点位置。
网关侧必采指标:
□ 请求量 / 错误率 / 延迟(按模型、租户、路由规则分维度)
□ 各后端健康度(成功率、P99 延迟、熔断状态)
□ 缓存命中率(精确 + 语义)
□ token 消耗与成本(按租户/模型)
□ 路由分布(各后端承接了多少流量)
□ 限流触发次数(谁被限了、限了多少)
灰度发布:
□ 新模型/新提示词按流量比例灰度(如 5%)
□ 对比新旧版本的质量与成本指标
□ 支持按租户/用户定向放量
□ 出问题一键回滚(路由配置切回旧版本)
这些指标应接入统一的 LLM 服务可观测性 体系,与后端引擎的 GPU 指标形成端到端视图。网关的多租户与配额设计,与 多 LoRA 推理服务 的多租户隔离思路一致。
七、速查表与一句话记忆
| 问题 | 一句话答案 |
|---|---|
| 网关做什么 | 收敛鉴权/路由/限流/计费/故障转移等横切关注点 |
| 网关不做什么 | 提示词工程、模型推理、重业务逻辑 |
| 路由策略 | 静态 / 成本性能 / 语义 / 负载健康,四类组合 |
| 统一 API | 对齐 OpenAI 格式,适配层处理各家差异 |
| 流式难点 | 逐块转发、统一事件格式、断连取消上游 |
| 限流 | 多级 + 按并发(并发信号量最有效) |
| 成本归因 | 网关采集(同时知道调用方与 token 消耗) |
| 高可用 | 无状态多副本 + 配置外置 + 优雅重启 |
| 故障转移 | 重试 → 降级链 → 快速熔断 + 半开恢复 |
| 观测埋点 | 网关是所有 AI 流量的必经之路 |
一句话记忆:LLM 网关 = 统一入口(协议适配)+ 四类路由(静态/成本/语义/负载)+ 多级限流与成本归因 + 降级链与熔断 + 全程可观测——「收敛横切关注点,别做成胖网关」。
延伸阅读
- LLM 推理服务架构设计
- LLM 服务可观测性
- 推理引擎终极对比
- 多 LoRA 推理服务
- LLM 网关与多模型路由(应用层)
- 模型路由与选型策略
- LLM 成本优化 — 单位经济学与降本手段
- FinOps 成本智能 — 成本归因与预算治理
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。