一、引言
当你的应用同时用 OpenAI、Anthropic、Google 的模型,或者在「贵模型质量高、便宜模型够用」之间做选择时,直接在各处代码里写死供应商 SDK 会很快失控:每次切换供应商要改一堆代码、某个供应商挂了没有兜底、月底账单不知道花在哪了。
AI 网关(AI Gateway)解决的是「多模型调用」的工程问题:一个统一入口,对外给业务一个稳定接口,对内做路由、fallback、限流、缓存、成本核算。本文拆解 AI 网关的架构、路由策略、fallback 机制、限流与成本控制,并给出在 Vercel / Cloudflare 上的落地代码。
二、为什么需要 AI 网关
2.1 直接调多个 SDK 的问题
问题一:代码耦合——换供应商要改业务代码
问题二:无兜底——某供应商故障,功能直接不可用
问题三:无观测——每笔调用成本、延迟、成功率看不见
问题四:无统一限流——各模型各自为政,无法全局配额
2.2 网关的定位
业务代码 → AI 网关(统一入口)
├── 路由:按任务/成本/延迟选模型
├── fallback:主模型失败自动切换
├── 限流:按租户/密钥/模型配额
├── 缓存:同 prompt 结果缓存降成本
└── 观测:token/成本/延迟/成功率
→ OpenAI / Anthropic / Google / 开源模型
心法:AI 网关不是「又一个代理」,而是把 LLM 供应商当成「可插拔的算力池」——业务侧只认一个接口,供应商怎么换、怎么降级,全部收敛在网关里。
三、统一入口:多 Provider 抽象
3.1 统一接口契约
给业务侧一个稳定的「chat 完成」接口,网关内部适配各家 SDK:
// 统一请求/响应类型
type ChatRequest = {
model: string // 业务侧逻辑模型名,如 'smart' | 'fast'
messages: Array<{ role: string; content: string }>
temperature?: number
maxTokens?: number
}
type ChatResponse = {
content: string
usage: { inputTokens: number; outputTokens: number }
provider: string
model: string
}
3.2 Provider 适配层
// provider.ts:各家供应商统一适配
const providers = {
openai: {
async chat(req: ChatRequest) {
const res = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.OPENAI_KEY}` },
body: JSON.stringify({ model: 'gpt-4o', ...req }),
})
const data = await res.json()
return {
content: data.choices[0].message.content,
usage: { inputTokens: data.usage.prompt_tokens, outputTokens: data.usage.completion_tokens },
provider: 'openai',
model: 'gpt-4o',
}
},
},
anthropic: {
async chat(req: ChatRequest) {
const res = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': process.env.ANTHROPIC_KEY,
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({ model: 'claude-sonnet', max_tokens: req.maxTokens ?? 1024, messages: req.messages }),
})
const data = await res.json()
return {
content: data.content[0].text,
usage: { inputTokens: data.usage.input_tokens, outputTokens: data.usage.output_tokens },
provider: 'anthropic',
model: 'claude-sonnet',
}
},
},
}
3.3 模型映射表
// model-mapping.ts:逻辑模型 → 各供应商实际模型
const modelMap = {
fast: { openai: 'gpt-4o-mini', anthropic: 'claude-haiku', google: 'gemini-1.5-flash' },
smart: { openai: 'gpt-4o', anthropic: 'claude-sonnet', google: 'gemini-1.5-pro' },
}
export function resolveModel(logical: string, provider: string): string {
return modelMap[logical]?.[provider]
}
细节:给业务侧暴露「逻辑模型名」(
fast/smart)而不是供应商真名——底层换模型、换供应商,业务代码零改动。
四、路由策略:按任务、成本与延迟选模型
4.1 路由维度
按任务复杂度:摘要/分类 → fast;推理/创作 → smart
按成本预算:单次调用预算低 → fast
按延迟要求:实时聊天 → 低延迟模型;后台批量 → 成本优先
按租户等级:免费用户 → fast;付费用户 → smart
4.2 实现一个路由函数
// router.ts:基于任务的策略路由
type TaskKind = 'classification' | 'summarize' | 'creative' | 'reasoning'
function routeFor(task: TaskKind): string {
switch (task) {
case 'classification': return 'fast' // 便宜、快
case 'summarize': return 'fast'
case 'creative': return 'smart'
case 'reasoning': return 'smart' // 推理要强
}
}
// 网关主入口
export default async function gateway(req: ChatRequest & { task?: TaskKind }) {
const logical = routeFor(req.task ?? 'reasoning')
const provider = pickPrimaryProvider() // 默认主供应商
return callProvider({ ...req, model: logical }, provider)
}
4.3 动态路由:按响应质量/失败率自适应
// 失败率驱动的自适应:连续失败就切更稳的供应商
const health = { openai: 0, anthropic: 0 } // 滑动窗口失败率
function pickPrimaryProvider(): string {
if (health.openai > 0.1 && health.anthropic < 0.05) return 'anthropic'
return 'openai'
}
五、Fallback:主模型挂了怎么办
5.1 两级 fallback 模型
第一级:同供应商换模型(gpt-4o → gpt-4o-mini)
第二级:换供应商(openai → anthropic → google)
// fallback.ts:供应商链 + 重试
const FALLBACK_ORDER = ['openai', 'anthropic', 'google']
async function callWithFallback(req: ChatRequest): Promise<ChatResponse> {
const errors: string[] = []
for (const provider of FALLBACK_ORDER) {
try {
return await providers[provider].chat(req)
} catch (err) {
errors.push(`${provider}: ${err.message}`)
// 达到最大重试次数则继续下一个供应商
continue
}
}
throw new Error(`all providers failed: ${errors.join(' | ')}`)
}
5.2 超时与重试语义
// 每次调用带超时,超时视为失败 → 触发 fallback
async function callWithTimeout(req: ChatRequest, provider: string, ms = 15000): Promise<ChatResponse> {
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), ms)
try {
return await providers[provider].chat(req, { signal: controller.signal })
} finally {
clearTimeout(timer)
}
}
铁律:fallback 只对「可重试」的请求生效。幂等、可重复的请求(摘要、分类、改写)放心 fallback;涉及计费/扣费的请求要先明确「重试是否会重复扣费」,必要时落记录再重试。
5.3 Fallback 的观测与告警
每次 fallback 打日志:from / to / reason / 耗时
统计 fallback 率:> 5% 说明主供应商长期不稳
fallback 消耗的成本单独核算,避免「隐藏成本」
六、限流与配额管理
6.1 限流维度
按租户:每个 API key / 用户一个配额
按模型:贵模型单独限流(防止被刷爆成本)
按速率:RPM(每分钟请求)与 TPM(每分钟 token)双维度
6.2 用 KV/Redis 实现令牌桶
// rate-limit.ts:基于 KV 的滑动窗口限流
async function checkLimit(env: { KV: KVNamespace }, tenantId: string, limit = 60): Promise<boolean> {
const now = Date.now()
const windowMs = 60_000
const key = `ratelimit:${tenantId}:${Math.floor(now / windowMs)}`
const count = Number((await env.KV.get(key)) ?? '0')
if (count >= limit) return false
await env.KV.put(key, String(count + 1), { expirationTtl: 120 })
return true
}
export default async function gateway(req: Request, env: { KV: KVNamespace }) {
const tenant = req.headers.get('x-api-key') ?? 'anonymous'
const allowed = await checkLimit(env, tenant, 60)
if (!allowed) return Response.json({ error: 'rate_limited' }, { status: 429 })
// ... 继续路由
}
6.3 配额与告警
- 配额用尽:返回 429 + Retry-After
- 接近配额(80%):预警告警
- 超预算:自动降级到便宜模型(fast 兜底),而不是直接拒绝
七、成本控制与观测
7.1 成本核算
// 单价表($/1M token,示例值)
const PRICING = {
'gpt-4o': { input: 2.5, output: 10 },
'gpt-4o-mini': { input: 0.15, output: 0.6 },
'claude-sonnet': { input: 3, output: 15 },
'claude-haiku': { input: 0.25, output: 1.25 },
}
function estimateCost(provider: string, model: string, usage: { inputTokens: number; outputTokens: number }): number {
const p = PRICING[model]
return ((usage.inputTokens / 1_000_000) * p.input + (usage.outputTokens / 1_000_000) * p.output)
}
// 每笔调用后记录成本
const cost = estimateCost(res.provider, res.model, res.usage)
await logUsage({ tenant, task, model, tokens, cost, latencyMs })
7.2 缓存:同 prompt 结果复用
// 缓存 key:模型 + 消息(仅对可缓存任务生效)
async function cachedChat(env: { KV: KVNamespace }, req: ChatRequest): Promise<ChatResponse> {
const cacheKey = `ai:${req.model}:${JSON.stringify(req.messages).slice(0, 200)}`
const hit = await env.KV.get(cacheKey)
if (hit) return JSON.parse(hit)
const res = await callWithFallback(req)
await env.KV.put(cacheKey, JSON.stringify(res), { expirationTtl: 3600 })
return res
}
心法:缓存只对「确定性任务」(分类、摘要、翻译、关键词提取)安全,创作类任务别缓存。缓存命中率直接等于成本节省率。
7.3 观测面板
核心指标:
每笔调用成本(分模型 / 分租户 / 分任务)
成功率 / fallback 率 / 平均延迟 / token 消耗
预算实时进度条(当月已花 / 预算)
输出:
结构化日志 + 指标(配合 OTel 体系)
完整的观测闭环可参考 可观测性与错误追踪 的方法论,AI 网关尤其要把「成本」做成一等指标。
八、总结
AI 网关与模型路由的核心要点:
- 统一入口是基础:业务只认一个稳定接口,供应商差异收敛在网关适配层。
- 逻辑模型名隔离:暴露
fast/smart而非供应商真名,底层换模型零改动。 - 路由要按任务/成本/租户:分类摘要走便宜模型,推理创作走强模型,免费用户降级。
- fallback 必须有链:同供应商换模型 → 换供应商,超时视同失败,幂等请求才可重试。
- 限流双维度:按租户 RPM/TPM,贵模型单独配额,超预算自动降级而非拒绝。
- 成本是一等指标:每笔调用记账,缓存确定性任务,fallback 消耗单列。
- 观测与预算闭环:成功率、延迟、成本、预算进度全部可视化,超阈值告警。
模型能力会快速迭代,但「网关 + 路由 + fallback + 限流 + 成本」这套工程骨架是稳定资产。把它搭好,你的 AI 应用就能随时接最强的模型、随时切换供应商、账单永远可控。边缘侧可以再叠加 AI 应用部署 的推理路径与 边缘缓存策略 的缓存分层,让 AI 调用既省又稳。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。