配置

本页说明运行中的网关怎样找到自己的配置:读哪些文件,这些文件放在哪里,以及多个层级都给出了同一个配置文件的路径时,以哪一层为准。本页讲概念;模型注册表的逐字段参考见添加新模型,路由引擎怎样使用这些配置见路由。

发行版 manifest、品牌、UI 模块和后端扩展见发行版定制。

网关启动时读什么

类型

存放的内容

models

模型注册表:网关对外提供的每一个模型 id,以及它背后的上游路由

routing

可选的部署级设置:健康探测,以及本地/远程之间的权重划分

alerts

告警规则与阈值

环境变量

所有敏感信息和与主机相关的设置:凭据、数据库连接、功能开关

要承接流量,必需的只有模型注册表。没有它,网关照样能启动、提供 /health,只是 GET /v1/models 会返回空列表。没有路由配置文件,每条路由保持注册表给它的权重;没有告警配置文件,则使用内置阈值。

有了数据库之后,配置就不只来自文件了。管理控制台会把 provider、key、路由、权重和按模型的覆盖项写进运行数据存储,网关每次启动时都会在加载好的注册表之上重新应用这些状态。这一层见从管理控制台做运行时配置。

配置放在哪里

config/examples/ 里是网关的参考配置,包括 models.openrouter.yaml 和 routing.minimal.yaml。每个部署把自己的模型注册表、路由规则和告警放在自己的配置目录里,例如 distributions/<name>/config/,再通过显式的环境变量或已启用的发行版 manifest 选用这些文件。manifest 和 overlay 的目录结构见发行版定制。

网关运行期间,也可以在管理控制台里添加 provider、key、路由乃至整个模型。这些状态保存在 Postgres 里,详见下文。

网关如何找到自己的配置

优先级依次是:环境变量、manifest、内置默认值。

每类文件都单独查找,顺序如下:

  1. 显式设置的环境变量。MODELS_CONFIG_PATH、ROUTING_CONFIG_PATH、ALERTS_CONFIG_PATH。(旧名 MODELS_CONFIG 和 ROUTING_CONFIG 仍然可用;两者都设置时,以规范名 *_CONFIG_PATH 为准。)

  2. 发行版 manifest 的 paths: 部分——仅在 DISTRIBUTION_CONFIG_MODE=active 时生效。默认情况下只检查 manifest,不应用它;见启用 manifest。

  3. 内置默认值,指向参考示例:config/examples/models.openrouter.yaml 和 config/examples/routing.minimal.yaml。alerts 的默认值是 config/alerts.yaml,本仓库并不带这个文件——缺少告警配置文件就表示「使用内置阈值」。

这意味着两件事:

  • 环境变量优先于 manifest。如果部署已经有 manifest,你又设置了 MODELS_CONFIG_PATH,manifest 里的 models: 路径就会被忽略,网关会记一条 explicit env override ... wins over manifest value ... 日志。两种方式选一种就好。

  • 来自环境变量的路径,相对于进程的工作目录解析;manifest 里的相对路径,则相对于 manifest 文件自己所在的目录解析。

如果第 1 层或第 2 层给出的路径实际不存在,网关只会发出警告,不会启动失败:它记录 Models config not found: <path> (source=env),然后在一个模型都没注册的状态下继续运行。

全新克隆、完全没有配置时会怎样

在一个干净的仓库里启动网关,不设 MODELS_CONFIG_PATH,也没有 manifest 和 overlay,这时用的是内置默认值:网关加载 config/examples/models.openrouter.yaml。这个文件注册了三个模型:两个直接走 OpenRouter,另一个优先用本地的 OpenAI 兼容服务器,失败时自动回退到 OpenRouter。三个模型都只需要同一个凭据。

export OPENROUTER_API_KEY=sk-or-...
uv run uvicorn serving.servers.app:app --no-proxy-headers --port 8080

不设这个变量,文件里的每个模型都会被跳过(每条路由唯一的 API key 展开后是空的),网关在开始服务之前就会给出提示:

No models are available: every model in config/examples/models.openrouter.yaml was
skipped because its credential is unset. Set OPENROUTER_API_KEY and restart.
/v1/models will stay empty until then, and requests will report the model as not found.

这是让网关跑上真实流量的最快路径。等你想清楚要提供哪些模型,再用自己的注册表替换这个文件。

模型注册表

字段参考见添加新模型。有三件事放在本页讲,因为它们属于配置加载的行为,而不是某个字段的含义:

路由的 kind: 决定用哪个 adapter;模型的 provider: 是它的默认值。路由省略 kind: 时,使用模型顶层的 provider:;模型完全省略 route: 时,会用顶层的 provider:、base_url 和 api_key 构造出一条路由。provider 同时也是写入 api_logs.provider、并在指标里展示的标签,所以一条路由可以用 provider: 单独覆盖它。

内置的 kind 如下。部署可以通过后端扩展添加更多 kind,查找时会先匹配扩展添加的 kind。

类型

adapter

openai_compat, staging, vllm, sglang, ollama, chutes, featherless, cliproxy, deepseek, zai, kimi, minimax

OpenAICompatAdapter

openrouter, openrouter[<slug>]

OpenRouterAdapter

claude

ClaudeAdapter

gemini

GeminiAdapter

anthropic

AnthropicAdapter

本地推理服务器没有专用 adapter:vllm、sglang 和 ollama 都是 OpenAI 兼容的 kind,区别只在 provider 标签和用量处理上。其他任何 kind 都会让注册表加载失败,报错 ValueError: Unknown adapter kind: <kind>。

注册表里的环境变量插值只支持整值替换。在 models.yaml 里,只有 base_url、api_key、api_keys、provider_model_id 和路由的 embeddings_path 会被展开,而且只在整个值恰好是 ${VAR} 时才展开。不支持 ${VAR:-default},也不支持嵌在字符串中间的替换:

base_url: ${LOCAL_BASE_URL}                  # expanded
base_url: ${LOCAL_BASE_URL:-http://x/v1}     # NOT expanded — treated as a var
                                             #   named "LOCAL_BASE_URL:-http://x/v1",
                                             #   resolves empty, model is skipped
base_url: http://${LOCAL_HOST}/v1            # NOT expanded — the literal string,
                                             #   including "${LOCAL_HOST}", is used

路由配置文件和告警配置文件的展开规则更宽松(见下文),所以别把一种文件里的写法习惯带到另一种文件里。

路由配置文件

routing.yaml 是可选的,大多数部署都可以不要它。依赖它之前先读完这一节:在带数据库的网关上(标准 Docker 服务栈就是这种),它的权重划分不起作用,它的健康探测也从不改变路由。流量去哪里,由模型注册表里的权重、在管理控制台里设置的权重覆盖,以及路由里介绍的 router 共同决定。

所有键及其默认值:

键

默认值

含义

default_router

fixed

没有用 router: 指定 router 的模型,使用这个 router。下面的权重划分也只在它为 fixed 时才执行。

timeout

2

一次健康探测等待连接的秒数。

health_check

0

两次健康探测之间的秒数;0 表示关闭探测。

local_deployment

[]

视为本地的端点。

remote_deployment

[]

视为远程的端点。

logging

{}

自由格式的映射。

每个 deployment 条目都必须同时有 endpoint:(必须以 http:// 或 https:// 开头)和非空的 models: 列表;models: 为空的条目会让整个文件校验失败,网关会记录 RoutingManager failed to initialize,然后继续使用注册表自己的权重。而 endpoint: 展开为空的条目只会被单独丢弃,同时发出一条警告,列出因此失去归属的模型。这样,某个变量没设置也不会导致其余端点全部失效。

default_router: fixed
timeout: 2
health_check: 30

local_deployment:
  - endpoint: ${LOCAL_BASE_URL:-http://localhost:8000}
    models: [<model-id>]

remote_deployment:
  - endpoint: https://api.your-provider.example/v1
    models: [<model-id>]

权重划分只对不带数据库的网关生效。启动时,网关把每个模型的路由分成本地和远程两组:只有路由的 base_url 与某个条目的 endpoint: 完全一致,并且模型也列在这个条目的 models: 里,这条路由才归入对应的组。然后网关给两组各分一半流量(除非已废弃的 routing_parameter.local_fraction 设置了别的比例),在组内平均分摊,再把每个模型的权重缩放到总和为 1.0。如果某个模型有一组为空,全部流量归另一组。什么都匹配不上的模型,保留它 route: 列表里的权重。在带数据库的网关上,路由读的是注册表里的权重加上管理员设置的覆盖值,根本不会用到这次划分。

健康探测只针对 local_deployment 里的端点:远程 provider 没有网关要探测的 /health 路径,探测的话会被误判为不健康。每次探测都是向端点 origin 的根路径加 /health 发一个 GET。

探测只报告结果,不会改变路由。即使某个端点每次探测都失败,它也保有原来的权重,照样接收流量。每次状态变化都会记一条 WARNING 日志,GET /routing 在 manager_status.endpoint_health 下展示最新结果,旁边是 endpoint_health_enforced: false。真正把故障端点暂时移出的是熔断器。

和模型注册表不同,这个文件的变量展开支持 ${VAR}、${VAR:-default} 和嵌在长字符串里的变量,而且在任意嵌套层级都有效。

routing_strategy: 和 routing_parameter: 分别是 default_router: 和按模型配置的 router_params: 的旧写法,已废弃。它们仍然能加载,但会记录一条废弃警告。按模型选择 router(模型注册表里的 router: / router_params:)会覆盖 default_router,详见路由。

目前不生效的设置

网关加载配置时接受这些设置,设置了也不会导致启动失败,但目前它们不起任何作用;其中路由配置文件的权重划分,是在有数据库的网关上不起作用。

设置项

位置

实际效果

local_deployment / remote_deployment 权重划分

路由配置文件

只在不带数据库的网关上生效;见路由配置文件

health_check 的探测结果

路由配置文件

记入日志,并在 GET /routing 中报告;从不用于路由决策

router_params.local_fraction

模型注册表,router: fixed

接受并校验,但不会读取

features.routers

发行版 manifest

会出现在 /site-config 里,但不会选择或限制 router

paths.mcp

发行版 manifest

接受,但没有任何组件读取它

site.terms_document, site.privacy_document

发行版 manifest

接受,但不会加载到条款页;请改用 Site UI 模块

deployment.target

发行版 manifest

只是个标签;不会选择主机,也不会构建任何东西

从管理控制台做运行时配置

上面这些文件只在启动时读一次。运维人员对路由的其他改动,都经由管理控制台(或者它背后的 /admin/* 端点)写进运行数据存储,所以不用重启就能生效,重启后也不会丢。如果某个模型、provider 或 key 需要马上在运行中的网关上可用,又不想改 overlay、重新部署,就用这一层。

这一层需要数据库。DB_ENABLED 默认为 true(连接配置见数据库);设成 DB_ENABLED=false 就没有运行数据存储,下面所有端点都返回 500 Database not configured,调用这些端点的控制台标签页也就无处可写。所有端点都要求在 Authorization: Bearer ... 头里带上管理员的 JWT 或 ADMIN_TOKEN。

Providers 标签页

做什么

控制台位置

端点

存在哪张表

添加自定义的 OpenAI 兼容 provider,同时添加它的第一个 key

Overview → Add provider

POST /admin/provider-definitions;POST /admin/provider-definitions/verify 先探测上游,不保存

provider_definitions

编辑或删除自定义 provider

Overview → Edit provider

PATCH / DELETE /admin/provider-definitions/{provider}

provider_definitions

添加 provider API key

Keys → Add a new key

POST /admin/provider-keys;POST /admin/provider-keys/verify 先校验它

provider_api_keys

禁用、重新启用或删除 key

Keys

POST /admin/provider-keys/{key_id}/disable、.../enable、DELETE /admin/provider-keys/{key_id};来自环境变量的 key 用 POST /admin/provider-keys/disable-env 和 .../enable-env

provider_api_keys, disabled_provider_env_keys

把 key 预留给某个层级

Keys → Reserved for

POST /admin/provider-keys/{key_id}/min-role、.../min-role-env——见为某个层级预留上游 key

provider_api_keys.min_role, provider_env_key_min_roles

暂时停用某个 provider

Availability → Enabled

PATCH /admin/providers/{provider}/disabled;GET /admin/providers/routable 列出当前路由表里的 provider

disabled_providers

Overview 标签页上的注册表列出了网关能路由到的所有 provider,但只有自定义 provider 能在这里编辑。已经由代码或模型注册表定义的 provider(内置的 adapter kind,以及路由里声明的所有 provider: 标签)在这张表里是只读的;如果数据库里存的定义用了其中某个 slug,启动时会跳过这条定义,不让它顶替原有的 provider。自定义 provider 只能是 openai_compat;通用 adapter 处理不了的协议需要专门的 adapter,这就要改代码了(见编写 provider adapter)。

在这里添加的 key 和注册表里用 ${VAR} 引用的 key 进入同一个池子,一起轮换使用。来自环境变量的 key 在数据库里没有对应的行,所以控制台可以禁用它、把它预留给某个层级,但不能删除;要删除,就去掉这个环境变量再重启。

Routing 标签页

做什么

控制台位置

端点

存在哪张表

创建注册表里没有的模型

Create model

POST /admin/routing/provider-route-models;POST /admin/routing/provider-route-model-verifications 试跑这条路由但不注册

provider_route_candidates,外加 site_settings 里的策略和 required-role 标记

给已有模型加一条路由

Add provider route

POST /admin/routing/provider-route-candidates/{model_id};先用 .../provider-route-candidate-verifications/{model_id} 检查;PATCH / DELETE /admin/routing/provider-route-candidates/{model_id}/{route_id}

provider_route_candidates

把注册表里的一条路由指向别处

编辑路由的 Target

PUT /admin/routing/provider-routes/{model_id}/{route_id};先用 .../provider-route-verifications/{model_id}/{route_id} 检查;DELETE 删除覆盖项并恢复 YAML 路由

provider_route_configs

改一条路由的权重

每条路由上的权重字段

PUT / DELETE /admin/routing/weights/{model_id}/{endpoint_id};GET 显示 YAML 值、覆盖值和生效值

provider_weight_overrides

在 fixed 和 routewise 之间切换模型的 router

策略选择器

PATCH /admin/routing/provider-route-strategies/{model_id}

site_settings

为在出站队列中等待过久、或迟迟等不到引擎首个 token 的请求预留一条路由

Queue offload

PUT / DELETE /admin/routing/offload-routes/{model_id};GET /admin/routing/offload-routes 列出每个模型的卸载路由,以及它是否已在路由中生效——见排队等待卸载

site_settings

为单个模型调整 RouteWise 参数

RouteWise 设置

/admin/routewise/model-settings——见 RouteWise

site_settings

GET /admin/routing/provider-routes(或 .../{model_id})返回网关当前在用的每一条路由,每条都标着 source: yaml、override 或 runtime;想看两层叠加后的结果,这是最快的办法。

这个标签页上的 provider 选择器会列出所有自定义 provider,以及注册表、当前路由表或已配置凭据中已经出现的内置 kind。provider 能以哪种路由类型添加(on_demand、quota 或 concurrency),取决于部署和这家供应商的约定,用 PROVIDER_ROUTE_TYPES 声明(见环境变量);没有列出的 provider 三种都可以用。

在这里创建的模型是运行时模型:它只存在于数据库里,包含 id、第一条路由、定价表、router 策略和 required_role(默认 admin,所以在你调低之前,普通用户看不到这个新模型),每次启动时都会恢复。它没有注册表条目里声明的目录元数据(上下文长度、模态、支持的参数、别名),这些都取 ModelConfig 的默认值:context_length 8192,max_output_length 4096,只支持文本输入输出,不支持工具调用。如果模型需要其中任何一项,就把它写进注册表。

Settings 标签页

做什么

控制台位置

端点

存在哪张表

修改模型对谁可见

Model Visibility

PATCH /admin/models/{model_id}/visibility 设置或清除 required_role 覆盖项;GET /admin/models/visibility 列出基线值、覆盖值和生效值

model_visibility_overrides

让某个模型不受每用户并发上限的限制

Model Concurrency Limit

PATCH /admin/models/{model_id}/concurrency

model_concurrency_exemptions

两层如何叠加

启动时先加载注册表,再按下面的顺序把已存状态叠加上去;管理员的每次改动在保存时也会立即应用到运行中的 router 上。

  1. 自定义 provider 定义;代码或注册表里已有的 slug 会被跳过。

  2. 已存的 provider key,加入各个 provider 的 key 池,与环境变量里的 key 并列。

  3. 按模型设置的 router 策略覆盖项。

  4. 运行时路由。运行时模型只有在创建过程完整结束后才会被恢复;如果一条已存路由对应的模型已经不在注册表里,它会被跳过并记录警告,所以从 YAML 里删掉一个模型,不会因为一行残留数据又把它带回来。

  5. 路由覆盖项,把对应的注册表路由改指到新的目标。

  6. key 的层级预留:此时所有 provider 都已加载,所以再读一遍。然后是权重覆盖项、已禁用的 provider、卸载路由和模型可见性,这些会加载到解析器里,供 router 在处理请求时查询。

由这个顺序可以得出两条规则。第一,已存的改动从不修改它覆盖的文件:删掉覆盖项,注册表里的路由、权重或 router: 值就恢复了;运行时添加的候选路由和注册表的路由并存,而不是取代它们。第二,存储里没有记录的内容仍以文件为准,所以要改目录元数据、别名或新增 adapter kind,还是得改注册表再重启。

环境变量

网关会读取工作目录下的 .env 文件和进程环境变量,不区分大小写;两者冲突时以进程环境变量为准。仓库根目录下的 .env.example 是带注释的变量清单——把它复制成 .env 再改。安装列出了你最可能要设置的变量。密钥只能放在这里:不要放进模型注册表(在那里用 ${VAR} 引用),也不要放进 manifest。

用你自己的配置运行

uv run uvicorn serving.servers.app:app --no-proxy-headers --port 8080

然后检查实际加载了什么:

curl -s localhost:8080/health           # includes routes_configured
curl -s localhost:8080/v1/models        # generated from the registered adapters

启动日志会告诉你加载了什么。Registered N routes from <path> 指出实际使用的是哪个注册表文件;[distribution dark mode] ... 这几行显示 manifest 启用后会改变什么;Skipping model '<id>' ... after env expansion 逐个列出凭据没有设置的模型。

警告

GET /routing 不需要任何凭据,会返回每条已发布路由的上游 base_url、provider 标签和权重——也就是你完整的上游拓扑,包括 base URL 里的主机和端口。如果网关能从公网访问,而你又不打算公开这份拓扑,就在反向代理上屏蔽 /routing。GET /admin/routing 还会返回未发布的路由,需要在 Authorization: Bearer ... 头里带上管理员的 JWT 或 ADMIN_TOKEN。两个接口的输出都要当作敏感信息。