发行版定制

发行版就是某个部署自己的那套文件:名称和品牌、模型与路由配置,以及功能开关。本页写给搭建发行版的运维人员,内容包括:只改配置能改哪些东西、manifest 怎么工作,以及怎么升级。

发行版可以直接用 HybridInference 的标准网站,也可以套用自己的品牌和运营策略,还可以自己开发公开页面、同时继续共用控制台和认证逻辑。选能满足需求的最小接口即可;不同方案的构建和升级成本不一样。

标准应用提供默认的首页、账号页和条款页,以及仪表盘、API key 管理、Playground、请求历史和管理控制台。认证、持久化、邮件、模型 provider 和可选服务,仍需按各自的文档完成部署配置。快速开始会在本地运行一个示例,从推理一路走到 Web 控制台和管理控制台。

可以定制什么

下面的文件名沿用示例的目录布局;manifest 中的路径也可以指向你发行版里的其他文件。

可以定制什么

文件或接口

是否需要重新构建

站点名称、公开 URL、支持邮箱和组织信息

distribution.yaml,加上 site.branding 指定的 branding.yaml

无需重新构建前端;修改挂载文件后重启后端

Logo/favicon、链接、API 示例、团队/赞助商和数据政策提示

branding.yaml;可选挂载的 site-assets/ 图片,通过 /site-assets/* 提供

挂载的配置和资源不用重新构建前端;打包进镜像的资源改了,就要重新构建

完整首页 /

模块的 client.tsx → Landing,配合 styles.css 和模块资源

构建包含模块的前端镜像

/login、/signup、/forgot-password、/reset-password、/verify-email 的外观和接口支持的文案

模块的 client.tsx → AuthFrame / fieldLayout / authMessages,配合 styles.css 和共享表单钩子

构建包含模块的前端镜像;认证控制器仍共用

/terms 的法律文本和注册时要求的确认项

模块的 client.tsx → 同时提供 TermsFrame、TermsContent 和 consentItems,保留必需的条款/隐私锚点

构建包含模块的前端镜像

公开页面的标题、描述及其文档语言

模块的 server.ts → metaMessages 和 locale

构建包含模块的前端镜像

现有控制台的站点信息和功能设置

branding.yaml、distribution.yaml 及受支持的环境变量/管理设置

无需重新构建前端;Site UI 不能替换仪表盘、管理后台、Playground、/chat、/team 或 /authorize 的布局

模型、端点、别名、权重及受支持的 router 设置

config/models.yaml、config/routing.yaml、环境变量及受支持的管理 API

改配置不用重新构建代码;改文件要重启后端,通过管理接口修改则运行时生效

公开注册和已配置的 RAG 策略

distribution.yaml → features,以及各功能的环境变量/管理设置

无需重新构建前端;文件/环境变量变更后需重启/重新创建服务

已有的 agent 链接/代理集成

前端容器环境变量:AGENT_PUBLIC_URL、AGENT_WEB_INTERNAL_URL、AGENT_CONTROL_PLANE_INTERNAL_URL

运行时环境变量变更后重新创建前端容器;/agents 不是 Site UI 扩展点

可信 adapter、agent 访问规则和配额上报

后端环境变量 BACKEND_EXTENSIONS,加上可导入的扩展 .py 模块(使用文档中列出的钩子)

代码已挂载时重启即可;代码打包在后端镜像中时需要重新构建

额外路由、不同的控制台布局或新的认证流程

应用源码,例如 apps/frontend/src/app/;改在上游,或改在你明确自行维护的 fork 里

重新构建受影响的应用镜像;这些不属于 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 用它做什么

schema_version

品牌文档版本;当前为 1

app_description, site_host

站点描述和展示的主机名

organization.name, .url, .tagline

组织信息、链接和标语

links.docs_url, .status_url, .github_url

文档、状态页和仓库链接;空字符串会隐藏可选链接

links.nav

额外的 HTTPS 页头链接,每项包含 label 和 url,按列表顺序展示;不会替换现有导航

example.api_base, .api_key_env_var, .model

公开的快速开始示例;API base 为空时不显示。这里填的是环境变量名,只是示例,不是 API key 的值

analytics.statcounter_project_id, .statcounter_security_key

可选的客户端统计分析所用的公开标识;留空即不启用

signup.turnstile_site_key, .fast_track_domain, .fast_track_org

公开的 widget key,以及关联组织在注册页上的展示方式;不是服务端密钥,也不是授权规则

storage_key_prefix

浏览器存储的命名空间;修改后不会迁移已存储的偏好设置

data_policy_notice

页面上显示的数据政策提示;不影响后端如何处理数据

assets.logo_url, .favicon_url

品牌图片,通过允许的 HTTPS URL 或 /site-assets/* 提供

team

/team 的人物资料:name、affiliations,以及可选的 badge、image、website

sponsors

默认首页的赞助商图片:name、alt、src、class_name、width、height

允许哪些值,以模板和 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

  1. 按路由和行为盘点本地修改,区分品牌值、公开页面展示、共享控制台修改和后端逻辑。

  2. 能迁的就迁:配置值迁到运行时配置,公开页面的展示迁到 Site UI 模块。迁移不是必须的;也可以继续保留 fork,但要一直承担合并和验证的工作。用 UI 模块时,认证控制器仍然是共用的。

  3. 把剩下的源码改动明确列出来。重新设计控制台、增加路由、新的认证行为,不会因为文件挪进了模块目录,就变成受支持的模块功能。

  4. 在单独的分支里合并要升级到的上游版本,按你需要的行为解决冲突,然后构建出要发布的那一对前端/后端。

  5. 上线前,先在 staging 环境验证发行版。合并成功、上游 CI 通过,都不能说明 fork 的页面展示和流程没问题。

改用 Site UI 可以减少以后的冲突,但它不会自动迁移已有的 fork,也不会让上游任意一条源码路径变成稳定接口。

上线前

  • 锁定上游版本;用了自定义 UI 的,还要锁定模块版本和构建出的镜像摘要(digest)。确保与之匹配的配置和资源版本都能找回来。

  • 阅读配置和 API 的迁移说明,并针对选定的版本做校验。遇到不受支持的模块 API 版本,构建必须中止。

  • 检查实际镜像中的 /app/site-ui-manifest.json,确认构建结果使用的是预期的标准 UI 或自定义 UI;通过 HTTP 测试打包资源和挂载资源。

  • 检查公开路由、配置的链接、窄屏/宽屏布局、键盘操作、表单校验/加载/错误状态、登录/退出和注册策略。

  • 在部署好的候选版本上,检查登录后的控制台、用户和管理员权限,并发一次推理请求;fork 里的本地行为也要覆盖到。

  • 只上线测试过的那组镜像和配置,并保留上一组,以便回滚。