添加新模型

本指南写给想让网关多提供一个模型的运维人员。如果网关已经支持这个 provider(任何 OpenAI 兼容 API、OpenRouter、Anthropic、Vertex 上的 Claude 或 Gemini),只需要改配置:在模型注册表里加一个条目,配好凭据,然后重启。

  • 如果是你自己用 vLLM、SGLang 或 Ollama 部署的模型,见添加新的本地模型。

  • 如果 provider 有自己的一套非 OpenAI API,需要先写一个 adapter;见编写 provider adapter。

  • 配置了数据库时,也可以在网关运行期间从管理控制台添加模型、路由或 key;见从管理控制台做运行时配置。不过在控制台创建的模型没有模型目录所需的元数据,所以需要设置上下文长度、模态或别名的模型,还是要写进注册表。

模型注册表在哪里

网关使用 MODELS_CONFIG_PATH 指定的文件;没设置时,用已启用的发行版 manifest 指定的文件;两者都没有时,用自带的示例 config/examples/models.openrouter.yaml。详见网关如何找到自己的配置。

因此,要使用自己的注册表,有两种做法:

  • 指向一个文件。注册表放在任意位置,设置 MODELS_CONFIG_PATH=/path/to/models.yaml 即可。README.md 里的快速开始就是这么做的。

  • 建一个发行版。创建 distributions/<name>/,里面放一份 distribution.yaml manifest 和一份 config/models.yaml,然后设置 DISTRIBUTION_CONFIG_PATH=distributions/<name>/distribution.yaml 和 DISTRIBUTION_CONFIG_MODE=active。distributions/example/ 是一个可以直接复制的可用发行版;config/examples/distribution.example.yaml 是一份带注释的 manifest。

本指南中所说的「你的模型注册表」,指的就是上述解析过程最终选中的那个文件。

如果解析出的路径上没有注册表,后端会记一条错误日志,/v1/models 返回空列表,请求任何模型都会报 not found。

添加模型

  1. 在模型注册表中添加一条模型条目:

models:
  - id: your-model-id
    name: Your Model Display Name
    provider: existing_provider  # e.g. "gemini", "deepseek"
    provider_model_id: "actual-provider-model-id"
    base_url: ${PROVIDER_BASE_URL}
    api_key: ${PROVIDER_API_KEY}
    quantization: "bf16"
    input_modalities: ["text"]
    output_modalities: ["text"]
    context_length: 8192
    max_output_length: 4096
    supports_tools: true
    supports_structured_output: true
    supported_params: [temperature, top_p, max_tokens, stop]
    pricing:
      prompt: "0"
      completion: "0"
      image: "0"
      request: "0"
      input_cache_reads: "0"
      input_cache_writes: "0"
    route:
      - kind: existing_provider
        weight: 1.0

路由会继承模型的 base_url 和 api_key,所以路由上只需写不一样的部分。如果第二条路由指向别的地址,就在那条路由上把这两项再写一遍。

  1. 在仓库根目录的 .env 里设置环境变量(后端的配置加载器读的就是这个文件):

PROVIDER_BASE_URL=https://api.provider.example/v1
PROVIDER_API_KEY=your-api-key

如果 api_key、api_keys 或 base_url 用到的某个 ${VAR} 没有设置或为空,网关会跳过整个模型,并在日志里写明缺的是哪个变量。给路由加上 optional: true,就只跳过那一条路由。

  1. 重启后端以加载新模型。

  2. 按下文的方法验证它。

关于别名:如果想让客户端也能用另一个名字调用这个模型,比如 OpenRouter 风格的厂商 slug,或者你的推理运行时使用的原始模型路径,就把这些名字列进 aliases。它们会解析到同一组路由。

通过网关验证

启动后端。直接从源码运行时:

MODELS_CONFIG_PATH=/path/to/your/models.yaml \
  uv run uvicorn serving.servers.app:app --no-proxy-headers --port 8080

如果用仓库自带的 Docker Compose,则改用 make build s=backend 重新构建并重启后端。

GET /v1/models 接受匿名请求——它解析 API key 只是为了决定要不要包含仅管理员可见的条目:

curl -s http://localhost:8080/v1/models | jq

POST /v1/chat/completions 没有有效的网关 API key 就会返回 401,除非后端以 USER_AUTH_ENABLED=false 运行。请求时要带上你在自己网关上签发的 key(是网关的 key,不是上游 provider 的):

export GATEWAY_API_KEY=<your gateway API key>

curl -s -X POST http://localhost:8080/v1/chat/completions \
  -H "Authorization: Bearer $GATEWAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "your-model-id",
    "messages": [{"role": "user", "content": "Hello!"}]
  }' | jq

流式测试:

curl -N -s -X POST http://localhost:8080/v1/chat/completions \
  -H "Authorization: Bearer $GATEWAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "your-model-id",
    "messages": [{"role": "user", "content": "Stream test"}],
    "stream": true,
    "max_tokens": 64
  }'

配置参考

模型字段

这些键可以写在模型条目上。其中大多数也可以写在路由条目上,此时以路由上的值为准;id 和 name 标识的是模型本身,只在模型层级读取。

字段

类型

必填

说明

id

string

是

唯一的模型标识;客户端发送的就是这个名字

name

string

是

展示名称

provider

string

是

provider/adapter 的 kind;没有写 route: 列表时,它也是 route[].kind 的默认值。注意这里有个同名的坑:模型层级的 provider: 用来选 adapter,而路由上的 provider: 只是统计用的标签——见路由字段表

base_url

string

是,写在这里或每条路由上

API 端点的 base URL

api_key

string

否

API 鉴权密钥

provider_model_id

string

否

provider 那边的模型标识(实际发给上游时用它代替 id)

model_type

string

否

"chat"(默认)或 "embedding"。也可以写成 type:,两者等价;embedding 模型不走加权 router,而是按顺序把自己的路由当作回退链

aliases

list[string]

否

可用于路由的其他名称

quantization

string

否

量化格式(默认:"bf16")

input_modalities

list[string]

否

输入类型:"text"、"image"

output_modalities

list[string]

否

输出类型:"text"

context_length

int

否

最大上下文窗口(默认:8192)

max_output_length

int

否

最大输出 token 数(默认:4096);max_tokens 超过它时会被压到这个值

supports_tools

bool

否

是否支持 function calling(默认:false)

supports_structured_output

bool

否

是否支持 JSON mode(默认:false)

supported_params

list[string]

否

允许的参数名(默认:temperature、top_p、max_tokens)

reasoning_efforts

list[string]

否

这个模型的 reasoning_effort 接受哪些取值。只有当 reasoning_effort 在 supported_params 里时才有意义;为空表示「不提供」。它没有默认值,因为各模型接受的取值不同

on_demand

bool

否

模型在共享 GPU 上按需加载(首次请求时启动,空闲时停止)。这个标记会在 /v1/models 中返回,RouteWise 的后台延迟探测器也从不探测这类端点(默认:false)

processor

string

否

为 OpenAICompatAdapter 指定输出处理器,绕过按模型 id 的自动识别。可取值:"default"、"glm"、"qwen_coder"、"think_block"

extra_body

dict

否

合并进 OpenAI 兼容上游请求体的默认字段。核心字段和已校验的客户端参数优先

priority_scheduling

bool

否

该端点上的 sglang 服务器是带 --enable-priority-scheduling 启动的;见在 sglang 路由上优先调度 decode。通常按路由设置,而不是按模型设置

route_metadata

dict

否

每条路由上的自由格式元数据,供路由策略读取

pricing

dict

否

基础价格。prompt、completion、input_cache_reads 和 input_cache_writes 的单位是美元/100 万 token;request 的单位是美元/次请求,image 是美元/张图片

pricing_schedule

dict

否

一个生效时间(只支持 UTC),加上每天循环的价格时段:effective_at 之前一直用 pricing;之后,不在任何左闭右开的 [start, end) 时段内时用 default,落在某个时段内时,用该时段自己的 pricing 覆盖基础字段

模型条目还接受 route(见下文)、router 和 router_params(见为每个模型选择 router),以及 required_role——能看到并调用该模型的最低用户角色(admin_only: true 是 required_role: admin 的旧写法)。

路由配置

路由让一个模型拥有多个端点,并按权重分配流量:

route:
  # Local vLLM deployment
  - kind: vllm
    weight: 0.7  # 70% of traffic
    base_url: http://localhost:8000
    provider_model_id: "/models/local-model"

  # Remote API fallback
  - kind: your_provider
    weight: 0.3  # 30% of traffic
    base_url: https://api.provider.example
    api_key: ${API_KEY}

除上面的模型字段之外,路由条目还接受:

字段

类型

说明

kind

string

adapter 的 kind(见下)。默认取模型的 provider

weight

float

流量的相对占比(默认 1.0)。0 表示保留这条路由的配置但不使用:连回退时都不会尝试它

api_keys

list[string]

这个端点的 key 池,用来替代 api_key。两者同时设置会报错。在延迟统计和配额记账上,整个池算作一个端点;真正互相独立的资源要拆成不同的路由

embeddings_path

string

拼在该路由 base_url 后面的 OpenAI 兼容 embeddings 路径;开头的斜杠可有可无。不写、写 null 或空字符串时,仍按默认规则推导 URL

optional

bool

如果 key、base_url 或 embeddings_path 引用的 ${VAR} 未设置或解析为空白,就跳过这条路由并记一条警告

provider / provider_display_name

string

只覆盖分析统计里的标签——它重命名的是仪表盘里的那一行,不会选择 adapter,选 adapter 的是 kind。见在仪表盘中给路由命名

provider_type

string

RouteWise 的成本类别:on_demand、quota 或 concurrency

routewise_pool, quota_pool, concurrency_pool, quota_source, quota, concurrency

—

RouteWise 的资源池与预算元数据

一条路由用自己的 kind 作为 provider 标签上报——这个值记录在 api_logs.provider 里,所有按 provider 划分的管理视图都以它分组。因此同一个 kind 的两条路由会共用仪表盘上的同一行;用 provider: 才能把它们分开。

自定义 embeddings 路径

有些 OpenAI 兼容网关的 API 前缀不是 /v1。在对应的路由上设置 embeddings_path,就不会被多拼上一段 /v1:

models:
  - id: text-embedding-example
    name: Example embeddings
    provider: openai_compat
    model_type: embedding
    route:
      - kind: openai_compat
        base_url: https://gateway.example/api/v2
        api_key: ${EMBEDDING_API_KEY}
        embeddings_path: /embeddings

这样请求会发送到 https://gateway.example/api/v2/embeddings。这个覆盖只属于这一条路由,不影响它的回退路由。不设置时,以 /v1 结尾的 base URL 追加 /embeddings,其他 base URL 追加 /v1/embeddings。拼接路径前会先去掉 base URL 末尾的斜杠。

embeddings_path 只能写在 route: 里。写在模型层级会被拒绝,没有路由列表的简写模型也一样。路径前后的空白会被去掉,但纯空白字符串、除 null 以外的非字符串值,以及父级路径段(..)都会被拒绝。显式写 null 或 "" 时,仍按默认规则推导 URL。

整个值写成 ${VAR} 时,它必须解析成非空白的路径;变量缺失并不意味着改用默认值。如果路由不是可选的,变量未设置或为空白都会导致启动失败。设置了 optional: true 时,只跳过这一条路由,并记一条写明模型、路由和变量名的警告。

其他任何非法的 embeddings_path 都会导致启动失败,可选路由也不例外,免得网关带着残缺的注册表启动。

支持的 adapter kind

路由条目里的 kind 字段决定用哪个 adapter。所有标为OpenAI 兼容的 kind 共用同一个 OpenAICompatAdapter 实现,并自动套用各 provider 专属的 profile。

kind

类别

备注

openai_compat

OpenAI 兼容

通用的 OpenAI 兼容端点;没有更贴切的 kind 时用它

staging

OpenAI 兼容

openai_compat 的克隆,但有自己的 provider 标签,便于在指标里单独跟踪第二个通用端点

vllm

OpenAI 兼容

本地 vLLM 推理服务器

sglang

OpenAI 兼容

本地 SGLang 推理服务器

ollama

OpenAI 兼容

本地或远程的 Ollama 服务器

chutes

OpenAI 兼容

Chutes.ai 托管推理

featherless

OpenAI 兼容

Featherless.ai 托管推理

cliproxy

OpenAI 兼容

面向 OpenAI 兼容模型的 CLI 代理端点

deepseek

OpenAI 兼容

DeepSeek API(套用 DeepSeek 用量 profile)

kimi

OpenAI 兼容

Moonshot/Kimi 位于 /v1 下的按 token 计费 API(套用 Kimi 用量 profile)

zai

OpenAI 兼容

Z.AI 位于 /api/paas/v4/ 下的普通 API;Z.AI profile 追加 /chat/completions,不再添加版本段

minimax

OpenAI 兼容

MiniMax API(套用 MiniMax 用量 profile)

openrouter

自定义

OpenRouter 聚合器。用方括号形式 openrouter[<slug>] 锁定某个 sub-provider

gemini

自定义

Google Gemini API(需要转换消息格式)

claude

自定义

经 Google Vertex 访问的 Anthropic Claude

anthropic

自定义

直连 Anthropic Messages API 的客户端

其他 kind 需要由后端扩展注册,否则加载注册表时会报 ValueError: Unknown adapter kind。

路由之间的权重

一个模型的各条路由按注册表里写的权重分摊它的流量;管理控制台可以覆盖某个权重,而不必改文件。路由配置文件也可以在本地路由和远程路由之间调整权重,但只在没有数据库的网关上生效;见路由配置文件。每个请求如何选定路由,见路由。

故障排查

模型没有出现在 /v1/models 里

  • 确认后端实际加载的是哪份注册表。它在启动时会记录 Registered N routes from <path>;如果那个路径上没有注册表,则会记录一条写明路径的错误日志。

  • 检查 models: 下的 YAML 语法和缩进。

  • 检查有没有模型被跳过:如果某条路由的 key 或 base_url 引用的 ${VAR} 解析为空,整个模型都会被丢弃,日志里会写明模型名和未设置的变量。

  • 如果用了 aliases,确认规范的 id 只出现一次,且别名没有和别的模型撞名。重复的别名会解析到最后加载的那个模型,并记录一条警告。

鉴权失败

  • 网关返回的 401 表示你的网关 API key 缺失或无效,或者鉴权已开启而你没有发送 Authorization 头。

  • 从路由返回的 401 可能说明上游的 key 不对。不过有些网关对匹配不到的路径也返回 401,所以在换掉一个其实有效的 key 之前,先检查请求 URL 和 embeddings_path。对话接口的路由层把这类失败记为 upstream_auth_misconfig;embeddings 接口则记一条 Embedding request failed for model=...,并附上上游的状态码和错误信息。

  • 确认 ${ENV_VAR} 展开成功:只有恰好是 ${NAME} 这种形式的取值才会被展开,而且只对 base_url、api_key、api_keys 和 provider_model_id,以及路由级的 embeddings_path 生效。

另见