安装

本页介绍怎样把 HybridInference 网关跑起来:用 Docker 服务栈,或者直接从源码运行、方便自己修改。

如果想先什么都不配、看网关实际应答一次请求,那就先看快速开始。它用一个固定回复的假 provider,不需要 provider 账号,不需要 API key,连 .env 都不用。本页是下一步:用你自己的 provider,搭你自己的部署。

需要准备什么

运行方式

要求

Docker 服务栈

Docker Engine 24+ 与 Docker Compose v2+

从源码运行

Python 3.10–3.13(推荐 3.12,以 pyproject.toml 为准)与 uv

在 Docker 之外运行控制台

Node.js 22(CI 安装的版本)

Linux 或 macOS。

用 Docker 运行

git clone https://github.com/HarvardMadSys/hybridInference.git hybridinference
cd hybridinference
cp .env.example .env

第一次 make up 之前,在 .env 里填好数据库登录信息:DB_USER 和 DB_PASSWORD 默认是空的,DB_NAME 已经设成了 hybridinference。三个都有值之前,Compose 拒绝启动。.env 里别的都不是必填的。网关的其他设置项——provider key、SMTP、代理信任等——都存在它的数据库里,第一次启动后在控制台里填写。

然后启动这套服务栈:

make up     # creates the external volume, then brings up Compose
make ps     # show the services and their health
curl -s http://localhost:8080/health

第一次 make up 还会创建存放数据库的 Docker 卷 hybridinference_postgres_data。docker compose down -v 不会删除它;如果需要干净的数据库,见重置这套服务栈。

.env 里只保留数据库连接和容器相关的设置。之后如果改了其中某项,要重新运行 make up,而不是 make restart:容器只在创建时读取 .env,所以重启后用的还是旧值。

会启动三个容器:

服务

宿主机地址

说明

backend

127.0.0.1:8080

FastAPI 网关;用 BACKEND_HOST / BACKEND_PORT 覆盖监听地址

frontend

127.0.0.1:3001

Next.js 控制台;用 FRONTEND_HOST / FRONTEND_PORT 覆盖

postgres

127.0.0.1:5432

用 DB_PORT 覆盖

这些端口默认可从宿主机访问。同机运行的反向代理可以使用回环地址;从其他机器访问则需要显式覆盖监听地址。控制台也会转发 API 请求,公开它之前请先阅读部署指南。

Compose 文件中也包含 pgAdmin,通过可选的 admin profile 启动。

首次运行设置

第一次连着空数据库启动时,后端会生成它运行所必需的两个密钥——签发登录 token 的 JWT_SECRET_KEY,以及保护 API key 的 API_KEY_SECRET——并把它们存进数据库。然后它等待创建管理员:生成一个一次性的设置码,保存在数据库里,并打印在日志里:

docker logs hybridinference-backend 2>&1 | grep 'setup code'

打开控制台 http://localhost:3001,它会把你带到设置页面,然后:

  1. 输入设置码。在设置完成之前它保持不变,不管 LOG_LEVEL 设成什么,后端每次启动都会再把它记进日志。设置完成之前,后端也拒绝一切注册,所以别人抢不走第一个账号。

  2. 创建管理员:一个用户名和一个密码。这个账号没有邮箱地址,用用户名登录。选一个难猜的用户名,不要用 admin:登录对每个用户名每 15 分钟只允许尝试五次,而且在检查密码之前就计数,所以知道用户名的人就能把这个账号锁住。

  3. 填写页面列出的设置项。默认情况下,网关提供 config/examples/models.openrouter.yaml 里的三个示例模型,它们都走 OpenRouter(其中一个会先尝试本地的 Ollama 服务器),所以 OPENROUTER_API_KEY 会被列为缺失:设置好它,这些模型就能用。要换成你自己的模型,见添加新模型。

  4. 完成。模型注册表在后端启动时读取 provider 凭据,所以只要你保存的内容需要重启,最后一步就会提供 Restart backend。

这里设置的所有内容,之后都可以在管理控制台的 Configuration 标签页上修改,做法见存储在数据库中的设置项。创建管理员的其他方式,以及怎样重置它的密码,见第一个管理员账号。

备注

密钥和数据库要放在一起保管。API_KEY_SECRET 一旦设置就不能再改,因为新值会让现有的所有 API key 失效;改 JWT_SECRET_KEY 则会让所有用户退出登录。保护已删除账号记录的 ERASURE_FENCE_SECRET,除非你在第一次启动前就在 .env 里设置它,否则会沿用 API_KEY_SECRET 的值,而且之后同样不能再改;见数据库。数据库的 dump 里带着这些密钥和每一个 provider key。

没有数据库的网关(DB_ENABLED=false)没有设置步骤,也没有存储的设置项:它从环境变量读取所有设置项;打开用户认证时,JWT_SECRET_KEY 和 API_KEY_SECRET 也要放在环境变量里。只有既没有数据库、也没有账号的网关才能不要它们——即 DB_ENABLED=false 并且 USER_AUTH_ENABLED=false,就像快速开始的阶段 1 那样。

本地开发(不用 Docker)

git clone https://github.com/HarvardMadSys/hybridInference.git hybridinference
cd hybridinference

make setup-dev

make setup-dev 会用 Python 3.12 创建 .venv(如果已有的 .venv 是别的次版本,它会拒绝继续),以可编辑方式安装本项目,同步 dev 依赖组,并装好 pre-commit hooks。手动来做的话:

uv venv -p 3.12
source .venv/bin/activate
uv sync --group dev

请在仓库根目录下运行网关,因为默认的配置路径都相对于仓库根目录:

cp .env.example .env    # edit as above; a process started here reads it
uv run uvicorn serving.servers.app:app --no-proxy-headers --host 127.0.0.1 --port 8080

有数据库时,首次运行设置里说的设置码会出现在这个终端的日志里。

在另一个终端里运行控制台:

cd apps/frontend
npm ci
BACKEND_INTERNAL_URL=http://127.0.0.1:8080 npm run dev -- --hostname 127.0.0.1

打开 http://localhost:3001。控制台会把 API 路径转发给网关,BACKEND_INTERNAL_URL 告诉它网关在哪。这个变量的默认值 http://backend:8080 只在 Docker 内部有效,而控制台又不读仓库里的 .env,所以每次启动控制台都要设置它。用 curl http://localhost:3001/health 检查整条链路。

如果想连着 Postgres 开发、又不想启动整套服务栈,可以只起数据库容器:

make docker-volumes
docker compose -f deploy/docker/docker-compose.yml --env-file .env up -d postgres

如果本地网关不需要账号或数据库,在 .env 中同时设置 DB_ENABLED=false 和 USER_AUTH_ENABLED=false。它仍能路由请求,/health 会报告 "database_configured": false。账号、API key、请求历史以及存储在数据库中的设置项,都需要数据库。

配置

环境变量

仓库根目录的 .env 里放的是后端连上数据库之前就需要的东西,以及描述容器而不是应用本身的东西:数据库连接(DB_*)、宿主机端口和绑定地址、配置文件路径,以及少量进程和控制台的取值。.env.example 列出了它们。从仓库根目录启动后端时,后端会读取 .env;Compose 也会把它传给容器。

其他所有设置项都存在数据库里,在管理控制台的 Configuration 标签页上编辑,那里对每一项都有说明;USER_AUTH_ENABLED 这类运行时开关在 Settings 标签页上。环境变量里的值只是起点:启动时,如果数据库里还没有这个值,后端会把它复制进去,此后以存储的值为准。见存储在数据库中的设置项。

你最可能要用到的设置项:

设置项

作用

USER_AUTH_ENABLED

1(默认)要求推理请求携带用户 API key;0 允许匿名推理,但不会关闭账号登录或管理员鉴权。位于 Settings 标签页

ADMIN_TOKEN

/admin/* 端点使用的可选旧式 bearer token;留空只会禁用这一访问方式

PROVIDER_ROUTE_TYPES

管理控制台可以按 provider 添加的路由类型,写成逗号分隔的 provider=type[|type] 条目(例如 chutes=quota,openrouter=concurrency|on_demand);留空则每个 provider 都不受限

DB_ENABLED

false 时网关不用数据库运行,所有设置项都从环境变量读取。只能放在环境变量里

DB_STORE_FULL_CONTENT

false(默认)完全不存储 prompt 和响应;true 则完整存储。见请求日志与隐私

FRONTEND_URL

用户在验证邮件和重置邮件里点击的绝对 URL

BASE_URL

本网关自己对外的 origin,用来拼装注册和密码重置邮件里的绝对链接。请设置它:服务器不解析 X-Forwarded-*,所以留空的话,即便在 TLS 反代后面,也会从请求里推导出 http://…。见可信代理与客户端 IP

LOG_LEVEL, LOG_FORMAT

日志详细程度,以及 json/纯文本输出

ALERTS_ENABLED, SLACK_ALERTS_WEBHOOK_URL

进程内告警,默认关闭

TRUST_PROXY_HEADERS, TRUSTED_PROXIES

信任 TRUSTED_PROXIES 中所列代理发来的 X-Forwarded-For;下面的 Cloudflare 设置也需要打开这个开关

TRUSTED_DIRECT_CLIENT_NETWORKS

可选,直接从私有网络连入的客户端所在网段(CIDR);不会因此信任转发头

TRUST_CLOUDFLARE_HEADERS, TRUSTED_CLOUDFLARE_NETWORKS

信任所列对端发来的 Cloudflare CF-Connecting-IP;还需要同时设置 TRUST_PROXY_HEADERS=1

按你的部署方式该设上面哪几项代理设置,见可信代理与客户端 IP。

Provider 凭据

模型注册表自己指定要用哪些凭据。它的 api_key、api_keys、base_url 和 provider_model_id 字段都可以写成 ${VAR},在加载注册表时解析:先取数据库里存储的同名设置项,没有再用环境变量。所以一个部署需要哪些 provider key,就看它的注册表写了哪些变量,变量名随你起。

route:
  - kind: openrouter
    base_url: https://openrouter.ai/api/v1
    api_keys:
      - ${OPENROUTER_API_KEY}

只有整个值才会被替换:${VAR:-default},以及嵌在更长字符串里的 ${VAR},都不会被替换。详见配置。

Configuration 标签页会在 Providers 下列出当前注册表引用的每个变量,标出哪些是模型加载必不可少的,并保存它们的值;自带的默认注册表只需要 OPENROUTER_API_KEY。标签页还没列出的名字(比如你即将切换过去的那份注册表要用的)可以用 Add variable 添加。注册表在启动时读取这些值,所以改了之后要重启后端。没有数据库的网关则从 .env 读取它们。

也可以在管理控制台(Providers → Keys)里添加 key,无需重启;这些 key 和注册表里指定的 key 放在同一个池子里。见从管理控制台做运行时配置。

配置文件在哪里

网关通过环境变量或发行版的 manifest 找到自己的模型注册表、路由配置文件和告警规则;两者都没有指定时,就回退到 config/examples/ 下的示例。具体规则见网关如何找到自己的配置,文件里该写什么,那一页后面都有讲。

Compose 文件把 config/ 和 distributions/ 都以只读方式挂载进后端,所以在宿主机上改了其中任何一个,重启后端就行——不用重新构建镜像。

备注

直接跑在宿主机上的后端,可以通过 localhost 访问宿主机上的推理服务器。跑在 Docker 里的后端则必须用 Docker 内能访问到的地址,例如 host.docker.internal,并且要在模型注册表里显式写出来;HybridInference 不会替你改写 provider 的 URL。

验证安装

对着运行中的网关执行:

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

curl -s http://localhost:8080/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer ${HYBRIDINFERENCE_API_KEY}" \
  -d '{"model":"<model-id>","messages":[{"role":"user","content":"Say hello."}]}'

如果设了 USER_AUTH_ENABLED=0,就去掉 Authorization 头。模型 id 要用 /v1/models 真的列出来的那些。

同一个调用,用 OpenAI 的 Python SDK 来发——网关就是那个 base_url,现有客户端别的什么都不用改:

from openai import OpenAI

client = OpenAI(api_key="<your-api-key>", base_url="http://localhost:8080/v1")

response = client.chat.completions.create(
    model="<model-id>",
    messages=[{"role": "user", "content": "Say hello."}],
)
print(response.choices[0].message.content)

本地开发时,make check 会运行 linter 和默认测试套件;其他检查见贡献指南。

故障排查

required variable DB_USER is missing a value: DB_USER must be set in .env file——Compose 还没启动任何东西就停了。按上文的做法,在 .env 里填上 DB_USER 和 DB_PASSWORD。

启动时出现 Authentication configuration incomplete: set JWT_SECRET_KEY, API_KEY_SECRET——后端没能从数据库读到它的设置项,退回到了环境变量,而环境变量里没有密钥。更早的一行 Configuration load failed after 3 attempts 会说明原因;应该修好数据库连接,而不是往 .env 里加密钥。

设置页面不接受设置码——从 docker logs 命令输出的最后一行重新复制;更早的行可能属于你后来重置掉的数据库。输错的设置码会按客户端地址限流,但正确的设置码总会被接受。如果日志里根本没有设置码,说明设置已经完成;直接登录,或者按第一个管理员账号里的做法重置管理员密码。

缺少必填的设置项——已登录的用户会看到一条横幅,它指的那些设置项在 Configuration 标签页上标着 Missing。在那里把它们填上;provider 凭据要在 Restart backend 之后才生效。如果部署有意不设置某个模型的凭据,就改为在模型注册表里给那条路由标上 optional: true,这样这个变量就不再是必填的。

改了设置项之后登录失败——用命令行把这个设置项改回来,这不需要登录;见不通过控制台。

env file ... .env not found——在 make up 之前先用 cp .env.example .env 创建它。

从源码运行时报导入错误——运行 make setup-dev(或 uv sync --group dev),把后端装进 .venv,然后用 uv run 启动网关,或者先激活 .venv 再启动。

所有请求都 404,且 /v1/models 为空——说明注册表什么都没加载进来。启动时后端要么打印 Registered N routes from <path>,要么打印一条错误,指出它找不到的注册表路径;把这个路径和配置里的优先级列表对照一下。

端口已被占用——在 .env 里覆盖 BACKEND_PORT、FRONTEND_PORT 或 DB_PORT。