配置
本页说明运行中的网关怎样找到自己的配置:读哪些文件,这些文件放在哪里,以及多个层级都给出了同一个配置文件的路径时,以哪一层为准。本页讲概念;模型注册表的逐字段参考见添加新模型,路由引擎怎样使用这些配置见路由。
发行版 manifest、品牌、UI 模块和后端扩展见发行版定制。
可选腾讯 SES 邮件发送
EMAIL_PROVIDER 默认为 smtp。在 Admin > Configuration 中选择 tencent_ses 可改用腾讯云事务邮件。选择是明确的:SES 请求失败或缺少 SES 模板时不会回退到 SMTP。原有验证邮件、密码重置和通知调用使用所选发送方式。
配置 TENCENT_CLOUD_SECRET_ID、TENCENT_CLOUD_SECRET_KEY、可选的 TENCENT_CLOUD_TOKEN、TENCENT_SES_REGION、TENCENT_SES_FROM_EMAIL 和可选的 TENCENT_SES_FROM_NAME。发件地址必须在所选地域审核通过。秘密值保留在服务端,配置 API 会隐藏这些值。生产凭据和发件身份应保存在部署配置中。
启用 SES 账号邮件前,为 TENCENT_SES_TEMPLATE_VERIFICATION 和 TENCENT_SES_TEMPLATE_PASSWORD_RESET 设置正整数 ID。根据部署使用的通知配置 TENCENT_SES_TEMPLATE_APPROVAL、TENCENT_SES_TEMPLATE_REJECTION、TENCENT_SES_TEMPLATE_ADMIN_SIGNUP 和 TENCENT_SES_TEMPLATE_NOTICE。未设置的 ID 会禁用对应模板。当开放注册要求验证邮箱时,配置健康检查会报告缺失的 SES 账号邮件字段,不再要求 SMTP 凭据。
参考 HTML 位于 config/examples/tencent-ses/。提交供应商审核前,将示例域名替换为部署控制台域名,并选择所需语言和文案。验证与重置模板包含固定的 HTTPS 来源和路径,只有编码后的 token 查询值会变化。其他模板使用 site_name,并按需使用 reason、identifier 或 body。模板链接应与 FRONTEND_URL 保持一致;网关无法检查已审核模板的内容。
SES 接收审核模板和经过转义的标量变量,不接收任意 HTML 变量。序列化后的模板数据限制为 800 个 UTF-8 字节;超长通知及仅有 HTML 的通知会失败,不会被截断。提交成功表示供应商已接受请求,不代表邮件已送达收件箱。请求不会自动重试,因为超时可能发生在请求已被接受之后。需要任意 HTML 邮件或较长通知的部署可以继续使用 SMTP。
网关启动时读什么
类型 |
存放的内容 |
|---|---|
|
模型注册表:网关对外提供的每一个模型 id,以及它背后的上游路由 |
|
可选的部署级设置:健康探测,以及本地/远程之间的权重划分 |
|
告警规则与阈值 |
设置项 |
凭据、SMTP、代理信任、日志、路由调优以及其他所有应用设置项,存放在数据库里,在管理控制台中编辑 |
环境变量 |
数据库连接、上面这些文件的路径,以及容器之间怎样连接 |
要承接流量,必需的只有模型注册表。没有它,网关照样能启动、提供 /health,只是 GET /v1/models 会返回空列表。没有路由配置文件,每条路由保持注册表给它的权重;没有告警配置文件,则使用内置阈值。
有了数据库之后,配置就不只来自文件了。管理控制台会把 provider、key、路由、权重和按模型的覆盖项写进运行数据存储,网关每次启动时都会在加载好的注册表之上重新应用这些状态。这一层见从管理控制台做运行时配置,设置项见存储在数据库中的设置项。
配置放在哪里
config/examples/ 里是网关的参考配置,包括 models.openrouter.yaml 和 routing.minimal.yaml。每个部署把自己的模型注册表、路由规则和告警放在自己的配置目录里,例如 distributions/<name>/config/,再通过显式的环境变量或已启用的发行版 manifest 选用这些文件。manifest 和 overlay 的目录结构见发行版定制。
网关运行期间,也可以在管理控制台里添加 provider、key、路由乃至整个模型。这些状态保存在 Postgres 里,详见下文。
网关如何找到自己的配置
优先级依次是:环境变量、manifest、内置默认值。
每类文件都单独查找,顺序如下:
显式设置的环境变量。
MODELS_CONFIG_PATH、ROUTING_CONFIG_PATH、ALERTS_CONFIG_PATH。(旧名MODELS_CONFIG和ROUTING_CONFIG仍然可用;两者都设置时,以规范名*_CONFIG_PATH为准。)发行版 manifest 的
paths:部分——仅在DISTRIBUTION_CONFIG_MODE=active时生效。默认情况下只检查 manifest,不应用它;见启用 manifest。内置默认值,指向参考示例:
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 (Admin > Configuration)
and restart. /v1/models will stay empty until then, and requests will report the
model as not found.
有数据库时,改为在管理控制台的 Configuration 标签页上设置它(那里会把它标为缺失),然后重启后端。
这是让网关跑上真实流量的最快路径。等你想清楚要提供哪些模型,再用自己的注册表替换这个文件。
模型注册表
字段参考见添加新模型。有三件事放在本页讲,因为它们属于配置加载的行为,而不是某个字段的含义:
路由的 kind: 决定用哪个 adapter;模型的 provider: 是它的默认值。路由省略 kind: 时,使用模型顶层的 provider:;模型完全省略 route: 时,会用顶层的 provider:、base_url 和 api_key 构造出一条路由。provider 同时也是写入 api_logs.provider、并在指标里展示的标签,所以一条路由可以用 provider: 单独覆盖它。
内置的 kind 如下。部署可以通过后端扩展添加更多 kind,查找时会先匹配扩展添加的 kind。
类型 |
adapter |
|---|---|
|
|
|
|
|
|
|
|
|
|
本地推理服务器没有专用 adapter:vllm、sglang 和 ollama 都是 OpenAI 兼容的 kind,区别只在 provider 标签和用量处理上。其他任何 kind 都会让注册表加载失败,报错 ValueError: Unknown adapter kind: <kind>。
注册表里的 ${VAR} 插值只支持整值替换。在 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 共同决定。
所有键及其默认值:
键 |
默认值 |
含义 |
|---|---|---|
|
|
没有用 |
|
|
一次健康探测等待连接的秒数。 |
|
|
两次健康探测之间的秒数; |
|
|
视为本地的端点。 |
|
|
视为远程的端点。 |
|
|
自由格式的映射。 |
每个 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,详见路由。
目前不生效的设置
网关加载配置时接受这些设置,设置了也不会导致启动失败,但目前它们不起任何作用;其中路由配置文件的权重划分,是在有数据库的网关上不起作用。
设置项 |
位置 |
实际效果 |
|---|---|---|
|
路由配置文件 |
只在不带数据库的网关上生效;见路由配置文件 |
|
路由配置文件 |
记入日志,并在 |
|
模型注册表, |
接受并校验,但不会读取 |
|
发行版 manifest |
会出现在 |
|
发行版 manifest |
接受,但没有任何组件读取它 |
|
发行版 manifest |
接受,但不会加载到条款页;请改用 Site UI 模块 |
|
发行版 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 |
|
|
编辑或删除自定义 provider |
Overview → Edit provider |
|
|
添加 provider API key |
Keys → Add a new key |
|
|
禁用、重新启用或删除 key |
Keys |
|
|
把 key 预留给某个层级 |
Keys → Reserved for |
|
|
暂时停用某个 provider |
Availability → Enabled |
|
|
Overview 标签页上的注册表列出了网关能路由到的所有 provider,但只有自定义 provider 能在这里编辑。已经由代码或模型注册表定义的 provider(内置的 adapter kind,以及路由里声明的所有 provider: 标签)在这张表里是只读的;如果数据库里存的定义用了其中某个 slug,启动时会跳过这条定义,不让它顶替原有的 provider。自定义 provider 只能是 openai_compat;通用 adapter 处理不了的协议需要专门的 adapter,这就要改代码了(见编写 provider adapter)。
在这里添加的 key 和注册表里用 ${VAR} 引用的 key 进入同一个池子,一起轮换使用。注册表引用的 key(这个标签页上叫 environment key,它的值来自设置项)在这张表里没有对应的行,所以这个标签页可以禁用它、把它预留给某个层级,但不能删除。要去掉它,就在 Configuration 标签页上清空这个变量,再重启后端。
Routing 标签页
做什么 |
控制台位置 |
端点 |
存在哪张表 |
|---|---|---|---|
创建注册表里没有的模型 |
Create model |
|
|
给已有模型加一条路由 |
Add provider route |
|
|
把注册表里的一条路由指向别处 |
编辑路由的 Target |
|
|
改一条路由的权重 |
每条路由上的权重字段 |
|
|
在 |
策略选择器 |
|
|
为在出站队列中等待过久、或迟迟等不到引擎首个 token 的请求预留一条路由 |
Queue offload |
|
|
为单个模型调整 RouteWise 参数 |
RouteWise 设置 |
|
|
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 |
|
|
让某个模型不受每用户并发上限的限制 |
Model Concurrency Limit |
|
|
两层如何叠加
启动时先加载注册表,再按下面的顺序把已存状态叠加上去;管理员的每次改动在保存时也会立即应用到运行中的 router 上。
自定义 provider 定义;代码或注册表里已有的 slug 会被跳过。
已存的 provider key,加入各个 provider 的 key 池,与环境变量里的 key 并列。
按模型设置的 router 策略覆盖项。
运行时路由。运行时模型只有在创建过程完整结束后才会被恢复;如果一条已存路由对应的模型已经不在注册表里,它会被跳过并记录警告,所以从 YAML 里删掉一个模型,不会因为一行残留数据又把它带回来。
路由覆盖项,把对应的注册表路由改指到新的目标。
key 的层级预留:此时所有 provider 都已加载,所以再读一遍。然后是权重覆盖项、已禁用的 provider、卸载路由和模型可见性,这些会加载到解析器里,供 router 在处理请求时查询。
由这个顺序可以得出两条规则。第一,已存的改动从不修改它覆盖的文件:删掉覆盖项,注册表里的路由、权重或 router: 值就恢复了;运行时添加的候选路由和注册表的路由并存,而不是取代它们。第二,存储里没有记录的内容仍以文件为准,所以要改目录元数据、别名或新增 adapter kind,还是得改注册表再重启。
存储在数据库中的设置项
网关读取的东西里,凡不是上面这些文件、数据库连接或容器连接方式的,都是设置项:provider key、SMTP、Slack webhook、代理信任、浏览器来源(origin)、日志、路由和超时调优、与云端 agent 的约定,以及签发会话、保护 API key 的密钥。有数据库的网关把它们存在 app_config 表里,由管理员在管理控制台的 Configuration 标签页上编辑。DB_ENABLED=false 的网关没有存储的设置项,只读环境变量和内置默认值。
值从哪里来
每个设置项按以下顺序取值:
数据库里它的那一行——哪怕值为空也算,清空一个设置项靠的就是这个;
同名的环境变量;
内置默认值。
环境变量只是起点。每次启动时,凡是在环境变量里有非空值、而数据库里还没有对应行的设置项,都会被复制进数据库,此后以数据库里的行为准。升级时现有的 .env 就是这样延续下来的;也正因为如此,之后再改这个环境变量不会有任何效果:标签页会把它标为 Environment ignored。要改就在标签页上改值,或者用 Reset 重置它,让环境变量、再往后是默认值重新生效。重置是持久的:数据库会记住这次重置,下次启动时不会再把这个变量复制进来,之后对它的修改也会生效。Settings 标签页上的开关(USER_AUTH_ENABLED、SIGNUP_ENABLED 等)也以同样的方式从环境变量复制过去。
环境变量里的值如果本身就不合法(比如无法解析的 CIDR 范围,或者该填数字的地方填的不是数字),就不会被复制进数据库。这时只有数据库里存有该设置项的合法值,后端才会启动,并使用这个值,同时在日志里给出警告;否则后端会停下来,并指出是哪个变量。修正或删除这个变量,或者用 python -m serving.config.manage set 存入一个合法值(见下文 不通过控制台 一节),环境变量无效时这个命令照样能用。数据库连接设置(DB_HOST、DB_PORT 等)是例外:读取数据库需要它们,所以其中任何一个无效,后端都会停下来。
相互依赖的设置项(比如 TRUST_X_REAL_IP 需要 TRUST_PROXY_HEADERS)会放在一起判断,数据库里的值覆盖环境变量里的值。如果一个变量依赖的另一项已经存进了数据库,它会像其他变量一样被复制进去,之后删掉这个变量也不会丢。如果两边合起来仍然违反这类规则,两边都不会被复制,后端会停止并指出是哪条规则,直到其中一边被改正。
第一次连着空数据库启动时,后端还会生成它运行所必需的两个密钥 JWT_SECRET_KEY 和 API_KEY_SECRET,除非环境变量里已经提供。API_KEY_SECRET 和 ERASURE_FENCE_SECRET 一旦设置就不能再改,因为已有的记录依赖它们;见数据库。如果数据库里已经有 API key,后端宁可拒绝启动,也不会为它生成新的 API_KEY_SECRET,因为新密钥会让所有这些 key 失效。
Configuration 标签页
这个标签页把设置项按类别分组,逐个说明用途,并标出每个值的来源。除了网关自己读取的所有设置项,它还列出:
当前模型注册表和路由配置文件引用的每一个
${VAR},列在 Providers 下,并注明用到它的模型;告警规则引用的则列在 Alerts 下;provider key 池里带编号的额外 key,比如
MINIMAX_API_KEY2,只要设置了其中一个就会列出;用 Add variable 添加的变量,用于网关暂时还看不到的
${VAR}引用,比如你即将切换过去的那份注册表里的引用。
密钥只能写不能读:标签页只显示它有没有设置,从不显示它的值,管理审计日志也只记录它被改过。一次保存会作为整体校验,并按保存后实际生效的全部配置来判断,所以相互依赖的设置项(比如 TRUST_CLOUDFLARE_HEADERS 需要 TRUST_PROXY_HEADERS)可以一起修改。如果一次保存或 Reset 会让另一个设置项无法生效,它会被拒绝,并指出是哪一项:例如在 TRUST_X_REAL_IP 开着时关掉 TRUST_PROXY_HEADERS,或者在 TRUST_CLOUDFLARE_HEADERS 开着时清空 Cloudflare 网段。把两项放在同一次保存里修改即可。如果 Reset 后要回落到的环境变量值本身不合法,Reset 也会被拒绝。原本就无效的设置项不会挡住无关的修改。留在环境变量里的名字,比如 DB_PASSWORD 或 MODELS_CONFIG_PATH,不能在这里设置。这个标签页背后是 GET 和 PATCH /admin/config,以及 DELETE /admin/config/{key}。
改动何时生效
大多数设置项一保存就生效:处理这次保存的后端进程立即生效,其他后端进程在十秒内生效。标着 Restart required 的设置项只在启动时读取,进程会一直用启动时的值,直到重启。provider 凭据和注册表引用的每个变量都属于这一类,熔断器和出站并发的调优、告警以及 DB_STORE_FULL_CONTENT 也是。
有这类改动在等待时,标签页会显示 Restart pending 提示和一个 Restart backend 按钮。这个按钮(POST /admin/system/restart)会让后端退出,由它的守护进程重新拉起:Docker 靠仓库自带 Compose 文件里的 restart: unless-stopped,systemd 靠自带 unit 里的 Restart=on-failure。进程内部看不到这项重启策略,所以要由部署来声明:自带的 Compose 文件和 unit 在重启策略旁边设置了 SUPERVISOR_RESTARTS_BACKEND=1;用其他方式启动后端的部署,只有当它的守护进程会在进程退出后把它重新拉起时,才应设置这个变量。没有设置它时,以及后端由多个 worker 进程提供服务时(WEB_CONCURRENCY 或 UVICORN_WORKERS 大于 1,或者用了 uvicorn --workers,这时按钮只能重启其中一个进程),标签页会改为给出一条让你自己运行的命令,比如 docker restart hybridinference-backend。重启会切断正在处理的请求,流式请求也不例外。控制台以 /health 的 instance 字段换成另一个进程作为重启完成的标志,所以后端回来得再快也不会被漏掉。
不通过控制台
某个设置项可能把用来编辑它的控制台本身弄坏——比如一个让登录失效的值。这时可以用 python -m serving.config.manage 在命令行里修改已存储的设置项,校验规则和控制台完全一样:
docker exec -it hybridinference-backend python -m serving.config.manage list
docker exec -it hybridinference-backend python -m serving.config.manage reset JWT_ALGORITHM
list 列出所有设置项,get KEY 查看其中一项,set KEY VALUE 存入一个值(如果它新建的是一个存放密钥的自定义变量,就加上 --secret),reset KEY 则像标签页上的 Reset 一样重置它,让环境变量或默认值重新生效。它只用 DB_* 连接设置直接访问数据库,所以不需要登录,环境变量里别的设置项无效时也照样能运行,而且从不打印密钥的值。后端容器没在运行时,改在源码 checkout 的仓库根目录下运行它(uv run python -m serving.config.manage ...),那里的 .env 可以通过 127.0.0.1:5432 连上 Postgres。正在运行的后端会在大约十秒内读到这个改动;标着 Restart required 的设置项则要等下次重启。重置管理员密码的命令也是同样的用法,见第一个管理员账号。
缺失的设置项
有些设置项是必填的:那两个自动生成的密钥、模型路由加载时需要的每个变量(路由标了 optional: true 的除外),以及公开注册要求邮箱验证时的 SMTP_USER 和 SMTP_PASSWORD。其中任何一个没有值时,控制台会给已登录的用户显示一条横幅。管理员看到的是指向 Configuration 标签页的链接,在那里勾选 Missing only 就能列出要填的项;其他人看到的则是部分功能可能无法使用、请联系管理员。缺少凭据的模型不会被加载,对它的请求会得到一个 404。管理员用自己的 API key 发起请求时,这个 404 会说明部署的配置不完整;其他人得到的则和请求一个部署里根本没有定义的模型时一样,都是“not found”,所以从这个差别里看不出任何超出其可用范围的信息。
如果部署有意不设置某个模型的凭据——比如一条暂时不用的路由,或者在别处才启用的 canary——就应该在模型注册表里给那条路由标上 optional: true。这样这条路由会被悄悄跳过,它的变量也不会被报告为缺失。
警告
设置项以明文存储。任何能读到数据库的人——通过 pg_dump、备份,或者 psql、pgAdmin 会话——都能读到每一个 provider key,解密用户存下的 API key,并签发管理员 token。要像保护这些凭据本身那样,保护数据库的访问权限和备份。
环境变量
环境变量里留下的,是后端连上数据库之前就需要的东西,以及描述容器而不是应用本身的东西:
数据库连接:
DB_HOST、DB_PORT、DB_NAME、DB_USER、DB_PASSWORD、DB_ENABLED;配置文件:
MODELS_CONFIG_PATH、ROUTING_CONFIG_PATH、ALERTS_CONFIG_PATH、DISTRIBUTION_CONFIG_PATH、DISTRIBUTION_CONFIG_MODE;后端进程:
LOG_FILE、BACKEND_EXTENSIONS、GEOIP_COUNTRY_DB、GEOIP_COUNTRY_PROVIDER、WEB_CONCURRENCY、SUPERVISOR_RESTARTS_BACKEND,以及控制台也会读取的REFRESH_TOKEN_COOKIE_NAME;Compose 和控制台:宿主机端口和绑定地址,控制台的构建期取值,以及
AGENT_WEB_INTERNAL_URL这类运行期取值。
网关会读取工作目录下的 .env 文件和进程环境变量;两者冲突时以进程环境变量为准。仓库根目录下的 .env.example 是带注释的变量清单——把它复制成 .env 再改。安装列出了你最可能要设置的设置项。其他设置项如果在环境变量里有值,会按上文所述在启动时导入数据库。永远不要把密钥写进模型注册表或 manifest:在那里用 ${VAR} 引用它。
用你自己的配置运行
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。两个接口的输出都要当作敏感信息。
可选手机号认证
手机号注册、登录和绑定默认关闭。配置好发送服务后,在 Admin > Configuration 中开启 SMS_AUTH_ENABLED 并选择 SMS_PROVIDER=tencent。网关通过 /site-config 发布 features.phone_auth;共享表单仅在功能可用时显示手机号选项。原有邮箱登录和首次设置的管理员登录继续可用。
腾讯适配器需要 TENCENT_CLOUD_SECRET_ID、TENCENT_CLOUD_SECRET_KEY、SMS_REGION、SMS_SDK_APP_ID、SMS_SIGN_NAME 和 SMS_TEMPLATE_ID。临时凭据还需要 TENCENT_CLOUD_TOKEN。审核通过的模板依次接收验证码和有效分钟数两个参数。此适配器目前仅向中国大陆手机号发送短信。真实凭据、已审核签名和模板 ID 应保存在部署配置中,不要提交到源码仓库。
账号标识使用国际格式,例如 +8613812345678。SMS_DEFAULT_COUNTRY_CODE 可为本地格式输入补充国家呼叫码;留空则要求输入国际格式。SMS_ALLOWED_PREFIXES 可通过逗号分隔的前缀限制目的地。使用腾讯国内短信适配器时,如需支持本地输入,将前者设为 86,并将后者设为 +86。号码策略属于部署配置,供应商发送逻辑与账号存储保持分离。
验证码默认 300 秒过期,最多允许猜测五次,且只能使用一次。PostgreSQL 在多个工作进程间串行执行发送和验证配额检查;SMS_OTP_* 设置控制相关限制。Turnstile 与可信代理客户端 IP 处理继续复用注册流程。手机号注册遵循开放注册和审核策略,不会自动合并邮箱账号,也不会创建密码。绑定需要已认证会话及专属于该账号的验证码。
启动时会添加手机号身份字段和持久验证码表,并允许密码哈希为空。开启手机号注册前应先升级全部网关工作进程,并备份数据库。一旦存在仅使用手机号的账号,就不支持回滚到假设每个账号都有密码的旧程序。关闭短信会隐藏手机号登录,但不会删除账号,因此为仅使用手机号的用户关闭该功能前,应先安排其他已验证的登录方式。Site UI v1 文案覆盖仍为可选;发行版可翻译新增手机号文案槽位,无需自行维护认证逻辑。