发行版定制
发行版就是某个部署自己的那套文件:名称和品牌、模型与路由配置,以及功能开关。本页写给搭建发行版的运维人员,内容包括:只改配置能改哪些东西、manifest 怎么工作,以及怎么升级。
发行版可以直接用 HybridInference 的标准网站,也可以套用自己的品牌和运营策略,还可以自己开发公开页面、同时继续共用控制台和认证逻辑。选能满足需求的最小接口即可;不同方案的构建和升级成本不一样。
标准应用提供默认的首页、账号页和条款页,以及仪表盘、API key 管理、Playground、请求历史和管理控制台。认证、持久化、邮件、模型 provider 和可选服务,仍需按各自的文档完成部署配置。快速开始会在本地运行一个示例,从推理一路走到 Web 控制台和管理控制台。
可以定制什么
下面的文件名沿用示例的目录布局;manifest 中的路径也可以指向你发行版里的其他文件。
可以定制什么 |
文件或接口 |
是否需要重新构建 |
|---|---|---|
站点名称、公开 URL、支持邮箱和组织信息 |
|
无需重新构建前端;修改挂载文件后重启后端 |
Logo/favicon、链接、API 示例、团队/赞助商和数据政策提示 |
|
挂载的配置和资源不用重新构建前端;打包进镜像的资源改了,就要重新构建 |
完整首页 |
模块的 |
构建包含模块的前端镜像 |
|
模块的 |
构建包含模块的前端镜像;认证控制器仍共用 |
|
模块的 |
构建包含模块的前端镜像 |
公开页面的标题、描述及其文档语言 |
模块的 |
构建包含模块的前端镜像 |
现有控制台的站点信息和功能设置 |
|
无需重新构建前端;Site UI 不能替换仪表盘、管理后台、Playground、 |
模型、端点、别名、权重及受支持的 router 设置 |
|
改配置不用重新构建代码;改文件要重启后端,通过管理接口修改则运行时生效 |
公开注册和已配置的 RAG 策略 |
|
无需重新构建前端;文件/环境变量变更后需重启/重新创建服务 |
已有的 agent 链接/代理集成 |
前端容器环境变量: |
运行时环境变量变更后重新创建前端容器; |
可信 adapter、agent 访问规则和配额上报 |
后端环境变量 |
代码已挂载时重启即可;代码打包在后端镜像中时需要重新构建 |
额外路由、不同的控制台布局或新的认证流程 |
应用源码,例如 |
重新构建受影响的应用镜像;这些不属于 Site UI API v1 |
配置无法替换首页的 hero 区块,也无法替换控制台的主题、侧边栏或布局;添加导航链接也不会生成它所指向的页面。Site UI 模块只翻译它自己提供的公开页面;见文档语言与页面标题。
只改配置
先从带注释的 config/examples/distribution.example.yaml 和 config/examples/branding.example.yaml 入手。属于部署的文件放在发行版仓库或 overlay 里,凭据放在环境变量里。想保留默认的页面布局,就用标准前端镜像,不选任何 Site UI 模块。复用这个镜像有个前提:镜像编译时确定的后端网络地址要和你的部署一致;见变更何时生效。
文件放在哪里
真实部署的配置放在 发行版 overlay 里:distributions/ 下的一个目录,装着该部署的 manifest、配置文件、Compose/env 输入和品牌信息。
distributions/<name>/
├── distribution.yaml # the manifest: identity + where the config files are
├── config/
│ ├── models.yaml
│ └── routing.yaml
└── deploy/
├── backend.env # any *.env here; Compose reads them all
└── docker-compose.yml # optional overlay on deploy/docker/docker-compose.yml
这样拆分,是为了让源码树和容器镜像保持中立。模型 id、上游 base URL、端口、站点信息和品牌,都属于某一个部署,而不属于项目本身;把它们放进 overlay,镜像就可以只构建一次,再把某个部署的 overlay 挂载进去(deploy/docker/docker-compose.yml 以只读方式挂载 overlay,而不是把它打包进镜像)。这也意味着,从本仓库克隆下来的代码不会带上任何人的主机地址。
配置路径的优先级和管理覆盖设置见配置。
发行版 manifest
manifest 是一份带版本号的 YAML 文档,它给部署命名,并告诉网关这个部署的配置文件在哪里。config/examples/distribution.example.yaml 是带注释的参考示例。下面的示例和该文件里的示例一旦启用,都会关闭公开注册;如果这个部署需要通过公开注册 API 接受新账号,请把 public_signup 改成 true 或 null:
schema_version: 1
distribution:
id: example
display_name: Example Router
release: "1.0.0"
site:
public_base_url: https://your-gateway.example
support_email: [email protected]
branding: branding.yaml # copy branding.example.yaml beside this manifest
features:
routers: [fixed]
public_signup: false
rag: false
paths:
models: config/models.yaml # relative to this file's directory
routing: config/routing.yaml
deployment:
target: local
schema_version 必须为 1。distribution.id 标识发行版,display_name 是公开名称,release 记录版本。deployment.target 记录 local 或 staging 等运维标签;它不会选择主机、构建镜像或部署服务。
paths.models、paths.routing 和 paths.alerts 指定网关使用的配置文件。相对路径以 manifest 所在目录为基准;如果显式设置了网关的环境变量,仍以环境变量为准。paths.mcp 和法律文档相关字段可以写,但有下文说明的限制。
用两个环境变量把网关指向它:
export DISTRIBUTION_CONFIG_PATH=distributions/<name>/distribution.yaml
export DISTRIBUTION_CONFIG_MODE=active
公开注册。manifest 已启用时,features.public_signup: false 会彻底关闭注册:注册页面消失,POST /auth/signup 返回 403,不会创建账号。无论是 SIGNUP_ENABLED=true 还是管理控制台里的注册开关,都无法重新打开注册,管理 API 也会以 400 拒绝这项修改。要开放注册,把该字段设为 true 或删掉它,重启后端,并确认注册开关是打开的。
当 manifest 允许注册,或者没有启用 manifest 时,管理控制台里的开关优先于 SIGNUP_ENABLED(默认 true)。GET /site-config 通过 features.public_signup 返回最终结果,控制台以它为准。邮箱验证、按域名审批和配额,对每个新账号依然照常生效。
如果网关在一秒内没能从数据库读到注册开关,它会把注册当作关闭处理,而不是改用 SIGNUP_ENABLED,并在五秒后再试。在管理控制台里保存一次这个开关,错误会立即消除。
公开用量统计。features.public_stats: true 会在 /stats 发布汇总数据:已批准的账户数和等候名单人数、每日处理的 token 数、国家和地区、用户书写所用的语言,以及调用 API 的智能体客户端。该页面读取 GET /public-stats,它返回 public_stats_snapshots 表中最新的快照;开关关闭或未设置时返回 404。快照由你每天调度一次的任务写入,例如通过 cron 或 systemd timer:
docker exec <backend-container> python -m serving.analytics.public_stats
加上 --dry-run 只打印快照而不写入,--weeks N 可修改默认 26 周的时间窗口。该任务读取 api_logs、每小时的国家汇总表和账户状态,只存储汇总数据。低于 3 个账户的单项计数不会公开。语言部分需要消息原文,因此只有在 DB_STORE_FULL_CONTENT=true 时才会生成。在繁忙的网关上统计 10 周数据约需一分钟。任务只保留最新的 30 个快照。
启用 manifest
只设置 DISTRIBUTION_CONFIG_PATH 不会改变任何东西。网关默认只做试运行:加载并校验 manifest,在日志里记下它会改动什么,但继续沿用原来的配置。控制这一点的设置是 DISTRIBUTION_CONFIG_MODE,它的默认值 dark 就表示试运行:
[distribution dark mode] models config stays config/examples/models.openrouter.yaml
(source=default, sha256=51fd6cde50fd); manifest would use
/srv/app/distributions/example/config/models.yaml (sha256=6117799cd0bf) — DIFFERENT
先看这几行日志,再设置 DISTRIBUTION_CONFIG_MODE=active 让 manifest 生效。已启用的 manifest 如果加载失败(比如挂载缺失、YAML 写错),网关会记一条 CRITICAL 日志,然后在没有任何模型的状态下运行,而不会拿一份不是你指定的模型注册表来提供服务。试运行时,同样的失败只会记进日志。
模式要么不设,要么严格设为 dark 或 active。任何其他值、空值,或者设了 active 却没有给出 manifest 路径,都会被当作配置损坏:注册关闭(403),/site-config 返回 503,文档助手的端点也返回 503。这条警告只会在启动日志里出现一次,所以看到这些错误时,请检查 DISTRIBUTION_CONFIG_MODE 和 DISTRIBUTION_CONFIG_PATH,改正后重启后端。
启用或升级 manifest 之前,先检查它。顶层和 features 下的未知键会被拒绝,所以拼错的功能名(publicSignup、public-signup)或错放到顶层的功能会直接报错。运维人员自己的备注请另放一个文件。把 DISTRIBUTION_CONFIG_PATH 设为你要部署的 manifest,然后用即将部署的那个版本运行:
uv run python - <<'PY'
import os
from pathlib import Path
from serving.config.distribution import load_distribution_config
load_distribution_config(Path(os.environ["DISTRIBUTION_CONFIG_PATH"]))
print("Manifest valid.")
PY
paths、site、distribution 和 deployment 这几节仍会忽略不认识的键,所以拼错的路径键会悄悄回退到默认的模型注册表。启用之后,请确认启动日志里 Registered N routes from <path> 这一行指向的是你想要的文件。
站点信息,以及 manifest 里不能放什么
manifest 的 site: 和 features: 两节是公开的:manifest 启用后,GET /site-config 会把它们返回给每一位访客。站点名称和地址还会出现在网关发出的邮件里,以及它发给 OpenRouter 的请求头里;取值顺序是:先用已设置的 SITE_NAME、SITE_PUBLIC_BASE_URL、SITE_DOCS_URL 和 SITE_SUPPORT_EMAIL,其次是已启用的 manifest,最后是中立的默认值。你不设置时,标准 Compose 文件会把 SITE_NAME 设为 HybridInference,所以请把它设成你的站点名称,或者设为空值,改用 manifest 里的名称。
manifest 里绝不能放密钥。凭据留在环境变量里,而且 schema_version: 1 特意不支持在 manifest 的值里插入环境变量。
运行时品牌配置
把完整的 config/examples/branding.example.yaml 复制一份,填好其中的公开值,再让 site.branding 指向它。这个路径相对于 manifest 所在的目录。站点名称在 distribution.display_name 里设置;但如果没有品牌文档,只设显示名称并不会替换前端的默认名称。品牌文档在后端启动时校验,并通过 /site-config 公开,所以里面的每个值都必须是可以给访客看的。
品牌字段 |
默认 UI 用它做什么 |
|---|---|
|
品牌文档版本;当前为 |
|
站点描述和展示的主机名 |
|
组织信息、链接和标语 |
|
文档、状态页和仓库链接;空字符串会隐藏可选链接 |
|
额外的 HTTPS 页头链接,每项包含 |
|
公开的快速开始示例;API base 为空时不显示。这里填的是环境变量名,只是示例,不是 API key 的值 |
|
可选的客户端统计分析所用的公开标识;留空即不启用 |
|
公开的 widget key,以及关联组织在注册页上的展示方式;不是服务端密钥,也不是授权规则 |
|
浏览器存储的命名空间;修改后不会迁移已存储的偏好设置 |
|
页面上显示的数据政策提示;不影响后端如何处理数据 |
|
品牌图片,通过允许的 HTTPS URL 或 |
|
|
|
默认首页的赞助商图片: |
允许哪些值,以模板和 serving/config/branding.py 里的 schema 为准。品牌配置会拒绝未知键;必填字段不能直接省略。可选内容不需要时,按文档写空字符串或空列表。赞助商的 class_name 只接受 schema 支持的图片尺寸类名,不能写任意 CSS。没有哪个品牌字段能设置全局配色、字体或页面布局。自定义页面组件自己决定渲染哪些运行时品牌值。
本地的品牌图片放在挂载的 site-assets 目录里,通过 /site-assets/* 访问;也可以用允许的公开资源 URL。用 HTTP 实际请求一下这些 URL 来确认。如果 /site-config 请求失败或返回无效内容,页面会报配置错误,而不会悄悄换一套默认值,显示一个看似正常的站点。
功能设置及其限制
在已启用的 manifest 里设置
features.rag: false,会同时关掉 RAG 的界面和后端访问。开启 RAG 仍然需要把服务本身配置好;见文档 RAG 助手。AGENT_PUBLIC_URL,或者内部 agent 服务的目标地址,用来配置现有的 agent 链接/代理集成;它们不会替你安装 agent 服务。见公开路径表。features.routers、paths.mcp、site.terms_document、site.privacy_document和deployment.target可以写,但目前还不起作用;见目前不生效的设置。要发布自己的条款,请使用 Site UI 模块。
在公开的品牌配置里加个提示、或者把导航项藏起来,都代替不了后端授权。用了自定义 UI 模块,上面的注册规则照样生效。
通过受支持的管理 API 修改模型或 provider,会在运行中的网关上生效,并保存到它的数据库里。这些修改不会改写 YAML;每次启动时,保存下来的覆盖设置会重新叠加到 YAML 之上。排查配置不一致时,这两层都要看。见运行时配置。
变更何时生效
改了挂载的 manifest、品牌、模型/路由或告警文件,要重启后端。刷新页面看展示效果;后端提供的是启动时加载的那份品牌配置快照。
替换挂载的 site-assets 文件不需要重新构建镜像。这个路由允许浏览器缓存五分钟,所以要直接请求实际的 URL 来确认,并把浏览器和代理的缓存考虑进去。改
SITE_ASSETS_DIR本身则需要重新创建前端容器。改了运行时环境变量,要重新创建受影响的容器;只重启容器的话,用的还是原来的环境变量。只在服务端使用的 agent 目标地址也属于运行时值。
支持在线修改的设置,通过管理控制台或 API 来改,然后确认实际生效的值,以及这个功能有没有自己的缓存。
如果改的是真正只在构建时生效的
NEXT_PUBLIC_*值,或者由BACKEND_INTERNAL_URL编译进去的后端网络地址,就要重新构建前端。这些值和运行时品牌配置不同,重启后端不会改变它们。
站点信息请用运行时品牌配置来设置,不要再引入新的构建时品牌变量。具体的 Compose 命令见部署。配置或资源如果是打包进镜像、而不是挂载进去的,改了之后要重新构建对应的镜像。
示例发行版
distributions/example/ 是一个完整、可运行的发行版,作为示例留在仓库里:一份 manifest、一份只含单个模型的注册表(指向仓库自带、无需凭据的假 provider)、Compose overlay,以及冒烟脚本。可以跟着快速开始走一遍:
make up DISTRIBUTION=example
make smoke DISTRIBUTION=example
它带着一个标记文件 EXAMPLE_OVERLAY,只要这个文件在,它就不会被自动发现选中。Makefile 挑选发行版的规则是这样的:
根据
distributions/*/deploy/*.env发现 overlay,但排除所有含有EXAMPLE_OVERLAY的目录。恰好一个候选:自动选中它,
make会打印出它要编译进去的是哪个站点的信息。有多个候选:
make失败,并要求你指定其中一个。一个都没有(刚克隆本仓库时就是这个状态):不选任何 overlay,整套服务保持中立。
DISTRIBUTION=<name> 按名字选择一个,示例发行版也可以这样选,而且只能这样选。DISTRIBUTION=none 表示不选任何发行版。
自定义公开页面和后端代码
配置能覆盖的是站点信息、品牌、模型和功能开关。超出这个范围,还有两个接口可用,各自有额外的构建或部署成本:
Site UI 模块用发行版自己的 React 组件替换公开页面(首页、账号页面的外壳、条款页),这些组件会编译进发行版的控制台镜像。
后端扩展在启动时加载可信的 Python 代码,用来添加 adapter、云端 Agent 访问规则或配额来源。
升级检查清单
本仓库维护应用本身,以及它对外发布的接口:配置、Site UI 模块和后端扩展。每个发行版自己维护自己的配置、模块、部署以及可能有的 fork,并自己决定什么时候升级。
已有的源码 fork
按路由和行为盘点本地修改,区分品牌值、公开页面展示、共享控制台修改和后端逻辑。
能迁的就迁:配置值迁到运行时配置,公开页面的展示迁到 Site UI 模块。迁移不是必须的;也可以继续保留 fork,但要一直承担合并和验证的工作。用 UI 模块时,认证控制器仍然是共用的。
把剩下的源码改动明确列出来。重新设计控制台、增加路由、新的认证行为,不会因为文件挪进了模块目录,就变成受支持的模块功能。
在单独的分支里合并要升级到的上游版本,按你需要的行为解决冲突,然后构建出要发布的那一对前端/后端。
上线前,先在 staging 环境验证发行版。合并成功、上游 CI 通过,都不能说明 fork 的页面展示和流程没问题。
改用 Site UI 可以减少以后的冲突,但它不会自动迁移已有的 fork,也不会让上游任意一条源码路径变成稳定接口。
上线前
锁定上游版本;用了自定义 UI 的,还要锁定模块版本和构建出的镜像摘要(digest)。确保与之匹配的配置和资源版本都能找回来。
阅读配置和 API 的迁移说明,并针对选定的版本做校验。遇到不受支持的模块 API 版本,构建必须中止。
检查实际镜像中的
/app/site-ui-manifest.json,确认构建结果使用的是预期的标准 UI 或自定义 UI;通过 HTTP 测试打包资源和挂载资源。检查公开路由、配置的链接、窄屏/宽屏布局、键盘操作、表单校验/加载/错误状态、登录/退出和注册策略。
在部署好的候选版本上,检查登录后的控制台、用户和管理员权限,并发一次推理请求;fork 里的本地行为也要覆盖到。
只上线测试过的那组镜像和配置,并保留上一组,以便回滚。