hugpy-router
One OpenAI-compatible address in front of every box's hugpy-wrapper front door. Per call it picks the box, proxies the call — streaming, uploads and binary answers pass through as they arrive — and when a box refuses before the first byte it tries the next one. Every call is one row in the router's own log, linked to the box's row.
client ──> hugpy-router ──> hugpy-wrapper (box A) ──> engine
└────────> hugpy-wrapper (box B) … (bounce on refusal)
How a box is chosen
- Pin —
{"alloc": {"worker": "<box>"}}in the body: that box or a refusal. Never rerouted. - Eligible — online, the call's pool, not blocked, not already tried for this call.
- Serving first — a box where the model is loaded, healthy, not loading, and running the same
settings (
model@options— the options the router last sent that box for that model). A loaded model with other settings is not a match: the box would reload it. Idle before busy, then most recently used. - Load — boxes whose catalog holds the model, most free GPU first, then least recently picked.
- Refusal — none qualifies:
503 no_boxwith every box's reason.
A box that answers load_error, insufficient_resources, profile_materializing, model not found,
502/503/504, or is unreachable — before the first byte — is skipped and the next one tried (at most 3
boxes per call). Other errors (e.g. a 400) go back to the client exactly as the box sent them.
Run
pip install hugpy-router
hugpy-router boxes add --name ae --url http://127.0.0.1:40717 --key-file /etc/hugpy/wrapper.env \
[--store /var/lib/hugpy-wrapper/fitevict.sqlite3]
hugpy-router serve --port <n|auto> [--host 127.0.0.1]
No port is assumed. --store (optional): when the box's store file is readable on this machine
(hugpy-wrapper --system makes it readable by group hugpy), the router reads the box's state
straight from it, re-reading only when SQLite reports a commit. Otherwise it follows the box with a
long-poll on GET /v1/telemetry/state?wait=30&since=<n> (hugpy-wrapper ≥ 0.1.52). Either way the
box's /health decides online / offline.
The boxes file (HUGPY_ROUTER_BOXES, default <config dir>/router/boxes.json, 0600) holds each box's
key; clients never see box keys.
Routes
| route | |
|---|---|
POST /v1/chat/completions, /v1/completions, /v1/embeddings, /v1/audio/*, /v1/images/*, /v1/videos/generations, /v1/summarize, /v1/keywords |
proxied |
GET /v1/models |
every box's catalog, with boxes and resident_on per model |
GET /health |
liveness |
GET /v1/router/state |
admin: each box's link, online, residents, catalog size, room; the settings sent |
GET /v1/telemetry?since=<id>&limit=<n> |
admin: the router's call log (cursor) |
GET/POST /v1/keys, POST /v1/keys/<id>/revoke |
admin: API keys |
Responses carry X-Hugpy-Box and X-Hugpy-Router-Request-Id; the box's own call row has that id as
its parent.
Keys
The same scheme as hugpy-wrapper (hpk_<id>_<secret>, Authorization: Bearer), with the router's
own settings: HUGPY_ROUTER_KEY (its admin key), HUGPY_ROUTER_REQUIRE_KEY (outside by default —
no key from loopback and private networks), HUGPY_ROUTER_TRUSTED_NETS,
HUGPY_ROUTER_TRUSTED_PROXIES (forwarded requests count as outside unless their proxy is listed).
Admin routes need an admin key except from loopback.
hugpy-router keys create --name laptop [--scope use|admin] --router-port <n>
Store
HUGPY_ROUTER_HOME, else systemd's state directory, else <data dir>/router: one SQLite file
(hugpy-wrapper's store machinery — WAL, migrations, integrity check): route_log (one row per call:
decision, attempts, box, timings) and api_keys.
Layout
hugpy_router/
core/ types + decide (pure, stdlib only)
links/ box links (local store file / remote long-poll) + the boxes file
serve/ aiohttp app: proxy (hot path), management API
observability/ the router's store + schema/
cli/ hugpy-router serve / boxes / keys
imports/ constants (every env name), functions, classes
0.0.1 is phase 1 of the design (one or many boxes, local or remote links). Next: an installer on the
hugpy layout (user hugpy-router, /etc/hugpy/router.env, unit), and hugpy-core configuring the
router (verdicts, pools, blocks, pair settings) and reading through it.
Metadata
Release files for hugpy-router 0.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hugpy_router-0.0.1.tar.gz | 23.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hugpy_router-0.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 48.9 kB
Release files / hugpy_router-0.0.1.tar.gz
| Download URL | hugpy_router-0.0.1.tar.gz |
|---|---|
| Size | 23.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
048419d6851ca98874dc6b43ab0116d97d3184d274f26ca4519f12e891f44b58
|
|
BLAKE2b-256 checksum How to use checksums |
fb4c504c6503dad1c1bc7808caf629c9030dac1bd7ea778d8c8ba5a6e5c67484
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / hugpy_router-0.0.1-py3-none-any.whl
| Download URL | hugpy_router-0.0.1-py3-none-any.whl |
|---|---|
| Size | 25.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b3aa6eef2263c93bc84efa2d6a312e2643181229a00c0e998fce4b57bf81b675
|
|
BLAKE2b-256 checksum How to use checksums |
026aa9a13571fe48898e9ba5c2c1056ebc6ec31a29a0ade386a10dfd375c9c29
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|