参与贡献
HybridInference 采用 MIT 许可(见 LICENSE),欢迎贡献。本页介绍仓库里有哪些内容、改动需要通过哪些检查,以及如何提交改动。
环境准备
git clone <your-fork-url> hybridinference
cd hybridinference
git remote add upstream https://github.com/HarvardMadSys/hybridInference.git
git fetch upstream dev
git switch -c yourname/tests/routing-config-defaults upstream/dev
make setup-dev
把分支名里的 yourname 换成你的 GitHub 用户名。如果想让主 checkout 空出来做别的事,可以改为在单独的 worktree 里创建分支,例如 git worktree add -b <branch> ../hybridinference-<feature> upstream/dev。如果你克隆的是上游仓库而不是 fork,就从 origin/dev 开始,并推送到你 fork 的 remote。
make setup-dev 会用 Python 3.12 创建 .venv,以可编辑方式安装本项目,同步 dev 依赖组,并装上 pre-commit 钩子。完整的前置条件和运行网关的各种方式见安装。
所有涉及 Python 的 make 目标都通过 uv run 执行。如果你的环境需要,可以覆盖这个设置:
make lint UV_RUN="uv run --active"
完成第一次贡献
先在你的 worktree 里把快速开始跑一遍,或者按源码开发环境搭好环境。亲眼看到网关成功响应一次请求,之后遇到失败时就更容易判断是你的改动引起的,还是环境没配好。本地的示例部署就够用了,参与贡献不需要访问维护者的服务器。
可以挑一个能复现的小 bug、一个还没有测试覆盖的配置场景,或者一处让人困惑、你又能自己验证的说明。先搜一下 issue tracker 和现有测试。较大的功能,先提交功能请求,把行为讨论清楚再动手写;仓库提供了 bug 报告和功能请求的表单。
一个具体的入门练习:给路由 YAML 里使用环境变量默认值的端点补一个测试。先读 apps/backend/routing/config.py 里的 _expand_env_value 和 load_routing_config,再看 tests/unit/routing/test_config.py 里相邻的测试。变量没设置时应该用默认 URL,设置了就应该覆盖默认值。下面这个测试通过公开的加载函数把两种情况都测到,并在每个用例结束后恢复环境变量:
@pytest.mark.parametrize("endpoint", [None, "https://override.example"])
def test_endpoint_env_default(tmp_path, monkeypatch, endpoint):
monkeypatch.delenv("TEST_ROUTING_ENDPOINT", raising=False)
if endpoint is not None:
monkeypatch.setenv("TEST_ROUTING_ENDPOINT", endpoint)
path = tmp_path / "routing.yaml"
path.write_text(
"remote_deployment:\n"
" - endpoint: ${TEST_ROUTING_ENDPOINT:-https://default.example}\n"
" models: [example-chat]\n"
)
config = load_routing_config(path)
assert config.remote_deployment[0].endpoint == (endpoint or "https://default.example")
assert config.remote_deployment[0].models == ["example-chat"]
只有这个场景还没被覆盖时,才把它加进那个测试文件。如果加载器本来就行为正确,这就是一次只加测试的贡献。如果是修 bug,先写好回归测试,确认它在原来的代码上会失败,再去改负责这段逻辑的、范围最小的那个函数,直到测试通过。
编辑时运行相关测试:
uv run pytest -q tests/unit/routing/test_config.py
如果改动影响运行时行为,请在本地网关上重放受影响的请求。在快速开始的示例跑着的时候,make build s=backend DISTRIBUTION=example 会重新构建改动过的后端代码;make smoke DISTRIBUTION=example 会检查健康状态、模型列表和一次经过路由的 completion。还要实际验证改动的行为:通用的冒烟测试通过,并不能说明某个具体的 bug 已经修好。如果改动涉及账号或前端,快速开始里介绍了单独的完整控制台检查。
开 PR 之前,运行 make format、make lint 和 make test;如果涉及前端或文档,还要跑相应的检查。看一遍 git diff,确认补丁里只有你打算改的文件。把分支推到你的 fork,向上游的 dev 提 PR,标题类似 test(routing): cover endpoint environment defaults。按现有的 PR 模板写明覆盖了哪些行为、实际跑了哪些检查;如果是修 bug,还要写上复现步骤和修复前后的结果。
仓库结构
apps/
backend/
serving/ FastAPI gateway: HTTP surface, SSE streaming, provider
adapters, auth, storage, observability, admin API
routing/ routing engine: routers, strategies, endpoint health,
circuit breaker
frontend/ Next.js web and admin console
config/
examples/ reference model registry and routing config; also the
built-in fallback a checkout with no overlay resolves to
distributions/ deployment overlays, one directory each: manifest, config,
branding, deploy env files. `example/` is the runnable
example the Quickstart uses
deploy/
docker/ Dockerfiles and docker-compose.yml
systemd/ unit files for host-level deployments
docs/
developer/ this guide (MyST Markdown, built with Sphinx)
agents/ design specs and plans
ops/ maintenance and CI helper scripts (admin, ci, db)
tests/ see the test tiers below
benchmark/ benchmarking scripts
后端有两件事很容易搞错:
apps/backend/routing/executor.py只是一个向后兼容的 shim,把FixedRouter以RouteExecutor的名字重新导出。要改请改apps/backend/routing/routers.py。后端的包是
serving和routing,根目录在apps/backend。make setup-dev会把它们装进.venv,所以通过uv run或激活后的.venv,在任何目录下都能 import 它们;在这个环境之外,需要设置PYTHONPATH=apps/backend。
质量门禁
命令 |
执行什么 |
|---|---|
|
|
|
|
|
|
|
|
|
|
make all 不做类型检查。mypy 在 dev 依赖组里,可以手动运行,但 pyproject.toml 里没有 [tool.mypy] 配置段,也没有哪个 target 会调用它,所以类型检查不是强制的。
代码风格由工具强制,而不是靠评审:
ruff,行长 100,target 为
py310,启用E/W/F/I/UP/B/C4/SIM/TCH/RUF(见pyproject.toml的[tool.ruff])。pydocstyle,采用 Google 风格。它会跳过
tests、.venv、node_modules、apps/frontend、ops和docs,所以apps/backend里必须写 docstring,并且会强制检查。
pre-commit 钩子在 commit 时运行,由 make setup-dev 安装:
pre-commit run --all-files # run them over the whole tree
这些钩子包括 ruff(版本与 CI 保持一致)、pydocstyle、gitleaks 密钥扫描、常规的空白字符/YAML/JSON/TOML 检查,以及 apps/frontend 的 eslint + prettier。
开 PR 之前,先跑 make format,并确认 make test 通过。
测试
测试分层以 marker 为准,目录只是惯例。pyproject.toml 里声明的 marker 有 unit、integration、slow、perf、external 和 dbtest;由于开启了 --strict-markers,使用未声明的 marker 会直接报错。
层级 |
位置 |
marker |
在 |
|---|---|---|---|
单元测试 |
|
— |
是 |
API 接口 |
|
— |
是 |
服务器 / 可观测性 |
|
— |
是 |
需要能连上的 Postgres |
主要在 |
|
否 |
会打到真实的外部服务器 |
|
|
否 |
testpaths 是 ["tests", "distributions"],因此 overlay 自带的测试会和 tests/ 一起在默认测试集中运行。
make test # the default suite
make test-verbose # same selection, serial, -vv
make test-cov # same selection, with coverage
make test-db # only -m dbtest
make test-all # everything except -m external
make test-external # only -m external
uv run pytest tests/unit/routing/test_manager.py # one file
uv run pytest -m dbtest tests/integration/ # one tier
dbtest 这一层需要能连上的 PostgreSQL。测试会读取 TEST_DB_HOST、TEST_DB_PORT、TEST_DB_USER、TEST_DB_PASSWORD(默认是 localhost:5432 和 postgres/postgres),有些还会读取完整的 TEST_PG_DSN。只启动数据库容器就够了:
docker compose -f deploy/docker/docker-compose.yml --env-file .env up -d postgres
make test 用 pytest-xdist 以文件为粒度并行。调查某个具体失败时可以串行跑,但怀疑是环境变量泄漏引起的失败,还要在 -n auto 下再查一遍——跨文件的环境泄漏只有在并行运行时才会暴露。
前端
控制台的门禁与 Python 那套是分开的:
make frontend-install # npm ci
make frontend-lint # eslint, --max-warnings 0
make frontend-type-check # tsc --noEmit
make frontend-test # vitest run
make frontend-check # all three
make check-all # backend lint + test, then frontend-check
CI 还会在 apps/frontend 里额外运行 npm run format:check(prettier)和 npm audit --omit=dev --audit-level=high。
文档
这些页面用 MyST Markdown 编写,由 Sphinx 从 docs/developer/ 编译,根 toctree 是 docs/developer/index.rst。在仓库根目录下构建文档,并运行 CI 会跑的检查:
make docs-verify
它会把警告当作错误来构建英文和中文站点,检查每个 Markdown 文件里的本地链接,并检查有没有哪段译文悄悄回退成了英文。交叉引用断了、或者某个页面没加进 toctree,它都会失败,所以推送之前先跑一遍。Sphinx、myst-parser 和 sphinx-rtd-theme 都来自 dev 依赖组,所以执行过 make setup-dev 就能构建。make docs 只构建站点。
新写一个页面,还要把它加进 docs/developer/index.rst;没加进去的孤立文件会产生警告,而 CI 会把警告当作错误。
撰写页面
这些页面随源码一起发布,大多数读者是从搜索结果或侧边栏进来的,心里只想着一件要办的事。写的时候要想着这样的读者:
说明页面写给谁、读完之后能做什么,放在第一段。
先给出操作步骤或答案。例外和边界情况放在后面,篇幅长的话放进 note;小节开头也不要先讲软件缺少什么。
只有读者会打开或编辑某个文件时,才写出它的文件名。不要为了证明某个说法去引用函数或行号;那是测试和评审的事。
描述软件现在的样子。改了什么、以前是怎么工作的、你是怎么验证的,这些该写进 PR、发布说明或
docs/reviews/。不要写某个具体部署的细节——它的主机、模型 id、账号和测量数据。这些属于那个发行版自己的文档。
用术语表里的词;要用一个新术语,先把它加进术语表。
一个段落只讲一件事。条件一多,就改成列表或表格。
如果某个设置的实际作用和它的名字对不上,用一句话说明,把它加进目前不生效的设置,并提一个 issue,而不是长篇解释内部实现。
翻译
站点同时发布英文版和中文版。译文是挂在英文段落上的,改动英文段落,挂在上面的译文就对不上了。请在同一个 PR 里更新中文,具体做法见翻译文档。
提出改动
从
dev开分支,而不是main。PR 提向dev;针对main和dev的 PR 都会跑 CI。分支命名为
<user>/<scope>/<feature-name>,例如jane/routing/weighted-fallback。commit 和 PR 的标题写成 conventional commits 的形式——
type(scope): summary,例如fix(routing): keep the fallback route on 429。dev上的历史按每个 PR 压成一个 commit,所以你写的 PR 标题就是最终的 commit 标题,格式参照git log --oneline即可。一个 PR 只做一个功能或一处修复,文档在同一个 PR 里一起更新,并为新行为补上测试。
在 PR 中明确说明 API 行为、配置、默认值或数据库 schema 的变化,并提供迁移步骤和回滚限制。参见发布与升级。
不要直接向
main或dev提交。
CI 会跑什么
CI 这个 workflow(.github/workflows/ci.yml)会先判断 PR 改动了哪些路径,再只运行相关的 job:
作业 |
作用 |
|---|---|
Backend Quality |
|
Frontend Quality |
prettier, eslint, |
Site UI Containers |
分别构建不带模块和带示例 Site UI 模块的前端镜像,确认缺少上下文的模块构建会失败,并检查运行中的示例如何提供它的静态资源 |
Docs Build |
|
|
分四个分片跑 pytest,带 PostgreSQL 服务,参数为 |
Security Scan |
对整棵代码树跑 |
|
构建受影响的镜像,再启动可运行的示例并做冒烟测试 |
Tutorial E2E |
运行快速开始里的 |
CI Gate |
把上面各个 job 的结果汇总成一个检查项 |
因为 CI 包含 dbtest,改动存储或认证时,可能本地全绿而 CI 报红。改动 apps/backend/serving/storage/ 或认证相关接口时,要对着本地 Postgres 跑一遍 make test-db。
如果一个 PR 一次 CI 运行都没有,先看它是不是有合并冲突——有冲突的 PR 不会触发 pull_request workflow。