new-api:把多个 LLM API 收进一个网关,顺便把计费也做了
posts posts 2026-04-14T20:30:00+08:00new-api 是一个统一 OpenAI 兼容入口背后的 LLM 网关,做了三件事:把各厂模型互转成 OpenAI/Claude/Gemini 兼容格式、按渠道和模型做加权分发与重试降级、用额度系统做用户计费。本文拆开这三条线,跟一个真实请求走完整流程,最后给出该用和不该用的判断。技术笔记LLM, API网关, Claude, OpenAI, GPTnew-api:把多个 LLM API 收进一个网关,顺便把计费也做了
如果你同时接了 OpenAI、Claude 和 Gemini 的 API,每切一次模型就要改客户端、各自管 Key、分别对账。这件事的麻烦不在开发量,而在运维和成本两个维度持续累积。new-api 在这层麻烦上盖一个统一入口:所有模型对外暴露 OpenAI 兼容格式,请求进来后由网关决定发给谁、怎么计费、额度还剩多少。
new-api(QuantumNous/new-api)是开源网关,AGPL-3.0,Go 后端 + React 管理台,2026 年中约 4 万 + star,支持把各家模型互转成 OpenAI / Claude / Gemini 兼容格式,并提供渠道分组、加权分发、自动重试、令牌与额度管理。它不是"又一个 OpenAI 代理",而是把模型访问变成可治理的资源池。
这篇文章不重抄 README 的功能清单。下面把三条主线拆开,跟一个真实请求走完整流程,最后给"什么时候该用、什么时候别用"的判断。
三条主线:网关到底做了什么
new-api 对外是兼容 OpenAI /v1/chat/completions 的 HTTP 服务,对内可以拆成三条独立主线:
客户端 (OpenAI SDK / curl / LangChain)
│
▼
┌──────────────────────────────────────────┐
│ new-api 网关 │
│ │
│ ① 格式转换:OpenAI 请求 ↔ 各厂格式 │
│ ② 渠道路由:按模型/渠道/权重选上游 │
│ ③ 额度计费:预检额度 → 转发 → 按用量结算 │
│ │
└──────┬──────────┬──────────┬─────────────┘
▼ ▼ ▼
OpenAI Claude Gemini ...三条线职责分明:
| 主线 | 负责什么 | 不负责什么 |
|---|---|---|
| 格式转换 | 把某家原生格式翻译成 OpenAI / Claude / Gemini 兼容格式 | 不做语义理解、不改写 prompt、不缓存流式内容 |
| 渠道路由 | 按模型名匹配渠道集,按权重加权分发,处理失败重试与熔断降级 | 不做跨渠道 session 亲和、不做全局负载感知 |
| 额度计费 | 请求前做额度预检,请求后按实际 token 用量结算、记流水 | 不管真实货币支付、不做跨模型预算优化 |
三条线几乎正交——可以只开格式转换而关掉计费,或只做路由不开格式转换。下面跟一个真实请求看它们怎么协作。
一次请求的完整路径
假设团队后端用 openai Python 库发请求,但这次想打 Claude:
import openai
client = openai.OpenAI(
base_url="http://new-api:3000/v1",
api_key="sk-user-abc123"
)
response = client.chat.completions.create(
model="claude-3-5-sonnet",
messages=[{"role": "user", "content": "解释一下 B+ 树的写入路径"}]
)请求进入 new-api 后依次经过:
- 鉴权:
sk-user-abc123解析到对应令牌,查出它的模型权限和剩余额度。 - 额度预检:检查令牌对该模型的剩余额度是否大于零。不足则直接返回 4xx,不把请求转发到上游——这一步把成本风险挡在转发之前,而不是先消耗再对账。
- 渠道匹配:
claude-3-5-sonnet落到 Claude 渠道集。如果配了多个同款渠道(比如不同账号的 Key),池内按权重选一个。 - 格式转换:OpenAI 格式的请求体被译成 Anthropic 原生格式(system 提到顶层、
role映射、content 结构调整),temperature、max_tokens等参数做字段名和取值范围的换算。 - 转发并等待:请求发到上游。若超时或返回 5xx,按重试配置换渠道重试或直接报错。
- 响应回译:Anthropic 返回的
content数组(可能含text和tool_use两种 block)重组回 OpenAI 的choices[0].message.content。 - 额度结算:按响应里的
usage(输入/输出 token 数)和该模型倍率扣减额度,写进流水。
整个过程对调用方透明——代码里只改了 base_url 和 model。正是这 7 步,把"聚合多模型"从客户端胶水收进了网关层。
格式转换:不只是字段重命名
格式转换常被当成"把 messages 换个字段名",实际要处理的比想象中多。
请求体结构差异
OpenAI 的 chat completion 结构最"扁平"——参数全在顶层、messages 是统一数组。Claude 把 system 提到顶层字段,Gemini 把整个请求包进 contents。转换器面对的不只 1:1 映射,而是结构的拆解重组:
| 请求元素 | OpenAI | Claude | Gemini |
|---|---|---|---|
| system prompt | messages[0].role="system" | 顶层 system 字段 | systemInstruction 字段 |
| user/assistant | messages[*].role | messages[*].role | contents[*].role |
| tool calling | tools 数组 | tools 数组(字段名不同) | tools + functionDeclarations |
| 图片输入 | content: [{type:"image_url",...}] | content: [{type:"image",source:{...}}] | parts: [{inlineData:{...}}] |
| 流式 | stream: true | stream: true | stream: true(SSE 事件格式不同) |
流式(streaming)最容易出问题。三家都用 SSE,但事件名、data 字段结构、finish_reason 的枚举值不一样。转换器必须在首字节到达后就逐事件翻译,不能等整个响应收完再处理,否则流式的实时性就丢了。
功能子集的取舍
不是所有 OpenAI 参数都有下游对应物。response_format: {type: "json_object"} 在 Claude 里要靠 prompt 约束实现,logprobs 在 Gemini 里没有等价物,seed 各厂支持程度不一。转换器的策略是"能转就转,转不了就静默丢弃并记日志",而不是硬造一个看起来能用的假参数——后者会在生产里制造难排查的行为差异。
渠道路由:配置在管理台,行为在四层
路由配置不在某个 config.yaml 的路由块里,而是在管理后台维护"渠道 + 分组"。渠道存上游的 Key、模型列表、权重、限流值和健康状态;分组决定不同令牌可用的渠道集合。一次请求的路由决策实际涉及四层信息:
- 模型 → 渠道集:请求里的
model匹配到支持的渠道。同一模型配了多个渠道(比如不同账号的同一厂商 Key),全部入选候选池。 - 渠道健康:每个渠道维护可用/不可用标记。连续失败会被自动熔断,熔断期不参与权重分配,冷却后再试探恢复。
- 权重分发:在健康候选池里按权重做加权随机。两个权重 50 的渠道,各约一半概率被选中——这是"把同一份流量摊到不同账号"的典型做法。
- 重试与降级:选中的渠道返回 429 或 5xx,按配置换渠道重试;降级方向一般是"同模型其他渠道 → 同厂商功能接近的模型 → 报错"。
值得留意:这里是无状态路由——连续两次请求即使模型相同也可能落到不同渠道。需要同一会话始终走同一渠道的(session 亲和),得在网关上层(负载均衡或客户端)做 sticky session,new-api 自身不提供。
限流保护
new-api 在渠道层面支持 QPS(每秒请求)和 TPM(每分钟 token)限制。这两个值配在渠道上,不是从上游 rate-limit 响应头里动态解析的。所以配置时要把值设得比上游实际限制略低,给突发流量留缓冲。
部署:一个 docker-compose up,但要关注三件事
基础部署
git clone https://github.com/QuantumNous/new-api.git
cd new-api
# 按需调整 docker-compose.yml:端口、数据库、时区
docker-compose up -d网关默认监听 3000 端口,访问 <主机>:3000 进入管理台。仓库自带 docker-compose 编排,默认拉起 MySQL 作为主存储(轻量场景也可换用 SQLite 单文件)。官方文档统一见 https://docs.newapi.pro。
生产环境要额外处理的三件事
数据库持久化:网关的用户、令牌、渠道、额度、流水都落在数据库里。容器默认的数据卷要挂到宿主机的持久化目录(-v ./data:/data),或直接用外部数据库——重装容器就丢数据,不是可选优化,是上线前提。
反向代理:网关前的 Nginx/Caddy 至少做两件事。一是 HTTPS 终结,令牌不能明文过公网;二是把请求体大小上限调大,因为图片输入(vision 场景)的请求体可能超过 Nginx 默认的 1MB。
日志与监控:new-api 日志默认打 stdout,可用 Docker 日志驱动采集。但成本分布更适合直接看数据——每次请求的模型、token 用量、扣费金额、状态码都会记流水,管理员按用户和模型聚合就能看到成本构成。
计费系统:额度模型比价格表更重要
new-api 不接支付宝或 Stripe,也不是支付系统——它做的是额度管理 + 在线充值入口。
额度模型
额度用内部"额度单位(quota)“计量,价格通过模型倍率换算:用量 = (token 数 / 1000) × 模型倍率 × 常量。倍率 1.0 大致对应某型号每千 token 的基准价,管理员按上游报价给每个模型设倍率。每个令牌可配独立总额度和可用模型集,管理员在后台充值。
这个模型简单,但有几个生产里容易踩的点:
- 用整数额度单位:网关内部按整数 quota 存储和扣减,把价格换算成固定整数倍率,能避免大量小额调用后浮点误差累积——这属于部署时按自身计价需求落实的运维细节。
- 预检与实扣分离:请求发出前没法知道实际会用多少 token,所以先做额度预检(大于零才转发),跑完再按实际
usage结算。一个只剩一点额度的用户仍可能打出一个消耗较多 token 的长回复——网关不在请求中途掐断。 - 共享额度:默认一个令牌的额度是对其可用模型共享的。想让不同模型有独立上限,需要另配模型级限额。
用量与流水
管理员后台可按用户、模型、时间范围查调用流水,也能后台直接查数据库做聚合。要出成本报表,优先基于流水表聚合,而不是依赖接口的分页列表——数据量上去后,分页接口不是为报表设计的。
什么时候用,什么时候不用
明确该用的场景
- 团队里 3 个以上开发者调不同厂商 API。统一入口的收益不是少写几行代码,而是 Key 不再散落各人
.env、调用量不用手动汇总、换模型不用改客户端。 - 要给不同用户 / 项目分配不同额度。内部平台对外提供 AI 能力、各租户有独立预算,额度 + 令牌模型直接对得上。
- 要屏蔽上游 API 变动。厂商改了 SDK 接口,客户端不动,网关层兼容即可。
不该用或要慎重的场景
- 只有一家厂商、一个模型。网关层多一跳网络和序列化开销,直接用官方 SDK 更快更少错。
- 对流式延迟极度敏感(如实时语音对话)。格式转换和额度记录都是额外开销,虽然通常只在几十毫秒内,但每一跳都要算。
- 要靠网关保持会话状态。new-api 不维护 conversation state,系统提示词和对话历史由客户端管。产品逻辑强依赖服务端多轮状态,网关够不到这一层。
- 要面向 C 端做真实收费。new-api 只管额度不管钱,真收费还要独立的支付和订单系统。
如果决定上车,建议的顺序
- 开发环境
docker-compose up,配一个 OpenAI 渠道,确认调用链路通。 - 加上第二个厂商(如 Claude),验证格式转换在流式和非流式下都正常。
- 接现有用户体系——令牌识别决定额度管理能否落地。
- 配额度与模型倍率,先用小额额度验证结算,确认流水记录无误再放开。
- 上线前补反向代理与数据库持久化。
相关资源
| 资源 | 链接 |
|---|---|
| GitHub 仓库 | github.com/QuantumNous/new-api |
| 官方文档 | docs.newapi.pro |
参与讨论
使用 GitHub 登录。欢迎补充事实、异议与实践。
讨论暂时无法加载。