我们如何每天路由 100 万+ 次 API 调用、横跨 60 个模型
统一 AI 网关背后的工程实践:渠道注册表、健康检查与故障转移、负载策略、缓存、限流与可观测性,如何稳定支撑每天 100 万+ 次调用。
我们如何每天路由 100 万+ 次 API 调用、横跨 60 个模型
统一 AI API 的正面很简单:一个 Key、一个 base_url、一份价目表、一个余额。而这张脸背后全是难活。
tokenpapa.ai/v1 这个端点背后调用的不是一个模型。它要扇出到十几个相互独立厂商的 65 个在线模型 ID,每一家都有自己的鉴权方式、自己的限流模型、自己对「一个 token 是什么」的定义、自己的报错词汇表,以及自己会出故障的下午。网关的职责,就是让这一堆东西表现得像一个稳定可依赖的服务。
这是一篇讲设计的文章,不是战绩汇报。下面按层拆解一个高并发统一网关需要什么,以及每一层究竟在吸收哪种故障。
| 维度 | 实际意味着什么 |
|---|---|
| 暴露的模型 ID | 65 个在线 ID,覆盖 DeepSeek、OpenAI、Anthropic、Google、阿里、月之暗面、智谱、MiniMax、腾讯、字节与小米 |
| 客户端界面 | 一个 OpenAI 兼容端点:https://tokenpapa.ai/v1 |
| 流量形态 | 突发、流式与非流式混合,各模型之间极不均衡 |
| 故障域 | 任意单个上游都可能变慢、被限流、改价或直接消失 |
| 计费单位 | 按 token、按模型,缓存命中单独计价 |
| 最危险的 bug 类型 | 静默的错误路由——它表现为账单不对,而不是堆栈报错 |
核心洞察:在规模化之后,网关的职责不是转发请求,而是让几十个异构、各自独立会挂的上游,看起来像一个端点、一份价目表、一套可预期的错误契约。
把问题说清楚
服务一个模型是写个代理。服务 60 个,是调度问题加上「事实一致性」问题。
有四条性质让 LLM 路由区别于普通 HTTP 流量路由:
- 单次请求成本不是常数。 打向同一个模型的两个请求,成本可能差三个数量级,取决于输入长度以及模型决定生成多少输出。路由决策就是定价决策。
- 故障是部分的。 上游可能完成连接、返回 HTTP 200,然后一个 token 都不吐;也可能吐了半句话就卡住。只看状态码远远不够。
- 流式是默认形态。 生产流量大多走 SSE,因此失败可能出现在一段 900 token 回答的第 40 个 token 上。
- 消耗的单位是 token,不是调用。 限流、配额和计费全部建立在只有响应结束后才确知的 token 数上。
下面每一层,都是为了吸收上面四条中的某一条。
第一层:渠道注册表
一个渠道就是一份上游凭据,指向一个模型。同一个模型 ID 通常配有多个渠道,而且是刻意为之:两条来自不同通道的 DeepSeek V4 Flash 渠道不会在同一时刻故障,它们的延迟和实际单价也不同。
注册表是路由的唯一事实来源。每个渠道携带这些字段:
| 字段 | 用途 |
|---|---|
| 可服务的模型 ID | 把请求里的 model= 映射到候选渠道 |
| 厂商与区域 | 用于策略分组和延迟假设 |
| 权重与优先级 | 控制同一模型内部的流量切分 |
| 并发与 token 上限 | 本地上游配额视图 |
| 价格覆盖 | 分渠道的输入、输出与缓存单价 |
| 健康状态 | 实时状态:健康、降级、冷却、禁用 |
| 成本档位 | 调用方要求「优先省钱」时使用 |
注册表每个请求都要读,但极少写。这种读写不对称就是整个设计的约束条件:读取必须是一次内存查表,不能是一次数据库查询,否则网关会给它服务的每一次调用都加上延迟。
关键结论:路由表是配置,但它以请求速率被读取。把它放在内存里、做版本管理,并把每次写入当成热重载而不是一次数据迁移。
第二层:健康检查、熔断与故障转移
朴素做法是「返回 5xx 就把渠道标记为不可用」,它在三个关键场景下全部失效:返回 200 却卡住的上游、只是慢而不是坏的上游,以及只对某个模型出故障的上游。
可用性够强的健康模型要看四个信号:
- 存活探测。 每个渠道一个低成本定时探针,并且限定在该渠道服务的模型上。
- 来自真实流量的被动健康。 用生产请求而不是探针来计算滚动成功率、首 token 时间和卡死率。
- 熔断。 连续失败达到阈值后把渠道冷却——先从轮转中摘除一个短窗口,再以很小比例的流量试放,确认后才完全恢复。
- 错误分类。 并非所有 4xx 都是渠道的错。请求格式错误绝不应该触发熔断;401、连续的 429、反复的 5xx 才应该。
故障转移随后分成两种情况,而把它们混为一谈是经典错误:
| 失败位置 | 正确行为 | 原因 |
|---|---|---|
| 首个 token 到达客户端之前 | 在同一模型的第二个健康渠道上重试 | 调用方除了多花一点延迟,观察不到区别 |
| 已经开始流式输出之后 | 关闭流、上报失败,交给客户端决定 | 中途悄悄切换上游会产出拼接感明显的回答 |
| 上游返回 200 但内容为空 | 当作失败处理,重试一次后上报 | 「200 但什么都没有」是最常见的静默故障 |
| 上游限流(429) | 改走其他渠道,而不是立刻退避重试 | 在已经打满的同一渠道上重试是最差的选择 |
第三行才是真正消耗团队排障时间的那一行。一个返回格式完好但内容为空的上游,在所有只统计状态码的指标里看起来都是成功。
第三层:在健康渠道之间做选择
当一个模型有多个可用渠道时,网关需要一套策略。五种策略几乎覆盖全部真实流量:
- 加权轮询 —— 默认选项。摊平负载、让每个健康渠道保持温热,避免过度集中在单一路由。
- 最少在途 —— 发给当前活跃请求最少的渠道。当各上游并发上限不一致时最合适。
- 成本优先 —— 始终选该模型下最便宜的健康渠道。对批处理和时效不敏感的流量是正确的。
- 延迟优先 —— 按近期首 token 时间选择,并加衰减,避免单个快样本主导决策。
- 粘性路由 —— 把某个调用方或会话固定到某渠道,谨慎使用,主要用于提示词缓存亲和。
最要紧的工程细节是:策略选择必须便宜且稳定。一个每次请求都重算全局成本模型的路由器,会先被自己的记账拖垮,而不是被上游拖垮。
核心洞察:成本优先路由是网关的价值所在。根据 TokenPAPA 平台公开单价(2026 年 9 月),DeepSeek V4 Flash 输入为每 100 万 token $0.14,而 GPT-5.6 Sol 为 $13.50——因此,让日常流量留在经济档的路由策略,价值远超任何谈下来的折扣。
第四层:两种形态的缓存
网关层的缓存有两种,经济性完全不同。
精确匹配的响应缓存把「模型 + 消息 + 采样参数」的哈希作为键,存完整的请求响应对。这是最便宜的一笔收益,但只在提示词逐字重复时才成立,而且只在确定性配置下才正确。温度不为 0 时,返回缓存补全会静默改变产品行为。
提示词前缀缓存发生在上游,重要得多。DeepSeek 的自动上下文缓存能在前缀逐字节稳定时把重复输入成本砍掉约 90%:同一个系统提示词、同一批检索上下文、同一组工具定义。网关在这里的贡献是不要破坏它——不要重排 system 消息、不要把时间戳注入提示词、不要每次请求都打乱工具顺序。
这是一条挂着营收的设计规则。系统提示词开头的一个非确定性 token,如果每次请求都重建,就能让整个集群的缓存全部失效。
第五层:限流、配额与公平性
限流有三件不同的活,单个限流器做不完:
| 限流类型 | 作用范围 | 目的 |
|---|---|---|
| 请求速率 | 每 Key、每分钟 | 粗粒度防滥用 |
| token 吞吐 | 每 Key、每分钟,输入加输出 | 真正对应成本的那条限制 |
| 并发数 | 每 Key、每渠道 | 保护慢上游不被压垮 |
| 预算配额 | 每 Key、每天或每月 | 在跑飞的循环变成账单之前拦住它 |
token 吞吐是最重要的一条,也最难做,因为输出 token 只有在流推进过程中才逐步可知。可行做法是在请求时按 max_tokens 预留额度,等流结束后再按实际用量核销。缺少预留这一步,大量长输出请求会同时通过前置检查。
按渠道的限额同样重要。某个客户端拿到 429,不应该是因为另一个客户端把同一条上游路由打满了。因此渠道并发必须与 Key 并发分开统计,并在路由的那一刻平衡两个预算。
第六层:可观测性与计费的事实基础
可观测性层有一条不可妥协的要求:任何请求事后都必须可归因。 一条请求记录至少需要包含:API Key、请求的模型、实际承接的渠道、做出该选择的路由决策、上游请求 ID、输入与输出 token 数、缓存命中 token 数、首 token 时间、总耗时、重试链路,以及计算出的成本。
有了这条记录,三个原本无法回答的问题才能回答:
- 流量没涨,为什么上个月账单高了 30%?
- 凌晨 3 点那次延迟回归,是哪个上游造成的?
- 是模型的输出质量变了,还是流量悄悄换到了另一条渠道?
计费是同一份数据的另一端。每家厂商的上报格式都不一样——有的给 prompt 与 completion token,有的把缓存命中与未命中输入分开,有的在流式响应里干脆不返回用量——所以网关把每个响应归一化成一条内部用量记录,再套一套价目表。正是这一步归一化,才让横跨 65 个模型 ID 的单一余额成为可能。
关键结论:如果你无法从自己的日志里重建单次请求的成本,那你拥有的不是计费系统,而是别人给你的一张账单。
端到端的路由路径
把上面几层拼起来,单个请求的路径是这样的:
# 网关请求路径伪代码 —— 上面各层按顺序落地
def handle_request(request):
key = authenticate(request) # 谁在调用
model = resolve_model_id(request.model) # 展示名 -> 真实 ID
check_key_limits(key, request) # 速率、并发、预算
candidates = registry.channels_for(model) # 第一层
channel = select(candidates, policy=request.policy) # 第三层
reserve_tokens(key, request.max_tokens) # 第五层
with channel.slot(): # 渠道级并发
try:
resp = channel.forward(request)
if resp.is_empty(): # 静默故障场景
raise EmptyCompletion()
except RetryableError:
channel.mark_degraded() # 第二层
channel = select(registry.healthy_for(model), policy=request.policy)
resp = channel.forward(request) # 最后一道故障转移
usage = normalise_usage(resp) # 第六层
reconcile_tokens(key, usage)
record(key, model, channel, resp, usage) # 可观测性
return resp请注意顺序。最容易放错位置的两步是 reserve_tokens(必须在调用上游之前完成,而不是之后)和 channel.slot()(必须持有整个流式输出期间,而不只是建连阶段)。
这些换来的是什么
以上每一层,都为了支撑一个主张:调用方可以把 60 多个模型当成一个服务来用。
| 关注点 | 逐一对接各厂商 | 通过统一网关 |
|---|---|---|
| 凭据 | 每家一个 Key,各自轮换 | 一个 Key,一次轮换 |
| SDK 与约定 | 鉴权头、流式格式、工具调用 schema 各不相同 | 一个 OpenAI 兼容客户端 |
| 故障转移 | 需要你自己逐家实现和维护 | 在路由环节完成 |
| 成本可见性 | 每家一个面板、一张发票 | 一条用量记录、一个余额 |
| 接入门槛 | 若干中国厂商要求中国手机号与本地支付 | 邮箱、Google 或 GitHub,国际信用卡以美元结算 |
| 切换模型 | 一个新的集成项目 | 改一处 model= |
代价也是真实存在的,值得直说:直连让你离厂商最近,新特性当天可用。网关换来的是覆盖面、故障转移和一份统一契约,并在多数模型上收取路由加价。判断时应该看总体拥有成本,而不只是价目表本身。
快速开始
from openai import OpenAI
client = OpenAI(
api_key="your-tokenpapa-key",
base_url="https://tokenpapa.ai/v1",
)
def ask(prompt: str, model: str = "deepseek-v4-flash") -> str:
resp = client.chat.completions.create(
model=model, # 任意在线 ID:gpt-5.6-luna、claude-sonnet-4-6、qwen3.7-plus、kimi-k3
messages=[{"role": "user", "content": prompt}],
max_tokens=600, # 限制输出:输出 token 单价是输入的 3 到 10 倍
)
return resp.choices[0].message.content
print(ask("把这个更新日志总结成 5 条要点。"))两个习惯能让集群从「接通了」变成「可靠」:
- 每次调用都限制输出 token。 上表模型的输出单价是输入的 3 到 10 倍,而不设上限的生成同时也是一次不设上限的并发占用。
- 保持提示词前缀逐字节稳定。 这是解锁自动上下文缓存那约 90% 重复输入节省的前提。
常见问题
Q: 如何在横跨数十个模型的同时不给路由增加延迟?
A: 路由必须与请求准备并行发生,而不是串行排在前面。网关从内存中的渠道注册表把 model ID 解析为具体渠道、选出一个健康上游,并在请求体还在读取时就开始建连。健康路径上的成本只是一次查表加一次连接选择,而不是多一跳独立的调度服务。额外的跳数只出现在故障转移路径上——某次尝试失败后改用第二个渠道。
Q: 上游厂商在请求中途挂掉会怎样?
A: 网关把首个 token 之前发生的失败与已经开始流式输出之后的失败区别处理。如果还没有任何内容返回给客户端,请求可以在同一模型的第二个健康渠道上重试,调用方看到的是一次更慢的成功而不是报错。如果 token 已经在往外推,则干净地关闭流并上报失败——中途悄悄切换上游会产出前后拼接、语义断裂的回答。
Q: 怎么避免 60 个模型变成 60 张账单?
A: 计费在网关层做归一化。每个上游的用量上报方式都不一样,有的只给 prompt 与 completion token,有的把缓存命中与未命中的输入分开统计,因此每个响应都会被映射成一条内部用量记录:模型、输入 token 数、输出 token 数、缓存命中数。之后只用一套价目表结算,调用方无论请求被哪个上游承接,看到的都是同一个余额和同一张账单。
Q: 统一网关为什么可能比直连厂商更贵?
A: 多数模型上网关确实会带来路由加价,因为它要按上游价结算,同时承担基础设施、故障转移容量与支持成本。也有部分模型定价低于官方原价——例如 Kimi K3 在 TokenPAPA 上约比官方低 10%。诚实的比较应该看总体拥有成本:一次集成、一份凭据、一张账单,对比为每一家厂商单独维护 SDK、鉴权流程、重试策略和发票。权威依据是公示价目表,而不是某篇文章。
立即开始
- 注册:前往 tokenpapa.ai/register,支持邮箱、Google 或 GitHub——不需要中国手机号。
- 创建 API Key:在控制台生成 Key,用国际信用卡最低 $10 起充,按量计费、以美元结算。
- 把任何 OpenAI 兼容客户端指向端点,从经济档起步:
from openai import OpenAI
client = OpenAI(api_key="your-key", base_url="https://tokenpapa.ai/v1")
response = client.chat.completions.create(
model="deepseek-v4-flash", # 或 deepseek-v4-pro、gpt-5.6-luna、claude-sonnet-4-6
messages=[{"role": "user", "content": "Hello!"}],
max_tokens=400, # 始终限制输出 token
)
print(response.choices[0].message.content)完整价目表:tokenpapa.ai/pricing。实时模型列表:GET https://tokenpapa.ai/v1/models。
本文讲的是路由架构与设计原则,不涉及内部容量或客户数据。文中模型价格为 2026 年 9 月的 TokenPAPA 平台单价,可能发生变动;在制定预算前请以定价页的实时价格为准。
这篇文档对您有帮助吗?
