polyglotmesh
A fast, queue-based Rust LLM router. You register multiple OpenAI-compatible and Anthropic-compatible upstreams; the router exposes a single base URL for each, with priority-based load balancing, per-key limits, health checks, and live queue stats. Speaks every LLM dialect in the room — hence polyglot — and weaves them into a single mesh — hence mesh.
A fast, queue-based Rust LLM router. You register multiple OpenAI-compatible and Anthropic-compatible upstreams; the router exposes a single base URL for each, with priority-based load balancing, per-key limits, health checks, and live queue stats.
┌──────────┐ ┌───────────────────┐ ┌──────────┐
client ─┤ OpenAI │───▶│ polyglotmesh │───▶│ upstream │
│ Anthropic│ │ (single baseurl, │ │ A │
└──────────┘ │ one self-issued │ │ B │
│ API key) │ │ C │
└───────────────────┘ └──────────┘
Documentation map:
docs/ARCHITECTURE.md— module layout, request lifecycle, queue internals, auth/limits pipeline, state machine.docs/FEATURES.md— every CLI subcommand, every HTTP endpoint, every config field, with examples.examples/config.sample.toml— the annotated reference config.scripts/install.sh— build + install the binary, copy the sample config into~/.polyglotmesh/.
Quick start
# Build + install
# → drops ~/.local/bin/polyglotmesh AND ~/.polyglotmesh/config.sample.toml
# Initialize (generates a fresh API key)
# → prints the OpenAI + Anthropic base URLs and your Bearer token
# Add 3-4 OpenAI upstreams (one CLI call each)
# Add 1-4 Anthropic upstreams
# Generate an admin token
# Run
The config file to edit
After init, the active config lives at:
$ polyglotmesh where
config: /home/you/.polyglotmesh/config.toml
That single file controls everything: server bind address, every
upstream's base URL and key, every virtual key with its
rpm_limit / tpm_limit / max_budget / models / expires, model
aliases, queue tuning, health-check tuning. Run polyglotmesh show to
print the active (merged) version with defaults filled in.
A fully-commented reference is at
examples/config.sample.toml. It is
also installed alongside the live config as config.sample.toml so you
can diff and copy fields.
For the complete field list, see
docs/FEATURES.md.
Calling the router
# OpenAI-compatible (chat completions)
# OpenAI-compatible (streaming)
# Anthropic-compatible
# OpenAI-compatible (model listing)
# Health (no auth)
Endpoints at a glance
| Path | Auth | Notes |
|---|---|---|
POST /v1/chat/completions |
api | OpenAI; supports stream: true |
GET /v1/models |
api | Union of upstreams + aliases |
GET /v1/models/{model} |
api | Single model metadata |
POST /v1/messages |
api | Anthropic /v1/messages |
GET /healthz |
none | Liveness + queue + per-key stats |
* /v1/admin/* |
admin | Upstreams, keys, aliases, model_list |
See docs/FEATURES.md for the
complete table.
Operational behavior
- Queue — per-provider kind (
openai,anthropic). Greedy in priority order; if all eligible upstreams are saturated, the request waits on aNotify(exponential backoff up to 200ms) up toqueue.queue_wait_timeout_ms. SeeARCHITECTURE.md. - Per-key limits —
rpm_limit,tpm_limit,max_parallel_requests,max_budget,soft_budget,expires,modelsallow-list,allowed_providers. Returning 429 / 402 / 401 as appropriate. SeeFEATURES.md. - Per-upstream limits —
max_concurrency,rate_limit_rpm,rate_limit_tpm,max_budget,timeout_ms. - Health checks — background task probes every
healthcheck_interval_ms; consecutive failures flip the upstream Unhealthy; the next success flips it back. - Model aliases — three layers (
model_list,model_aliases, per-upstreammodels), all evaluated at request time.
Scripts
scripts/install.sh— build + install + copy the sample config.examples/bootstrap.sh— end-to-end installer: registers N upstreams from arrays, prints the final URLs and the active config path.scripts/smoke-test.sh— self-contained end-to-end test using mock upstreams.scripts/mock_slow.py— mock upstream for the smoke test.
License
MIT
Persistence & hot reload
Every router run opens a SQLite database at $POLYGLOTMESH_HOME/state.db (WAL mode,
bundled via rusqlite). It persists per-key counters, rolling RPM/TPM windows,
in-flight slot counts, and an audit trail of every successful request.
| Concern | Where it lives |
|---|---|
| API keys & admin tokens | api_keys table |
| Upstream definitions | upstreams table (mirror of config.toml) |
| Per-key totals (requests, tokens, spend) | key_usage table |
| Rolling 1-min RPM / TPM windows | key_usage.rpm_window_* / tpm_window_* |
| Per-request audit log | usage_events table |
Edit config.toml to add an upstream or change a limit, then have the router pick
it up without a restart:
# -> {"status":"reloaded","upstreams":3,"keys":2}
On reload the router rebuilds the in-memory upstream registry, atomically swaps the auth store, and re-hydrates per-key counters from SQLite — so request volumes, token totals, and rolling windows survive the reload.
Pricing & cost
The router ships with a built-in price table (USD per token) for popular models:
OpenAI gpt-4o*, gpt-3.5-turbo, o1*, o3-mini; Anthropic claude-3-5-*,
claude-3-*. On every successful response, real cost_usd is computed and
persisted to usage_events and key_usage.
Override per-upstream with model_info:
[[]]
= "openai-prod"
= "openai"
= "https://api.openai.com/v1"
= "sk-…"
= ["gpt-4o-mini"]
[]
= { = 0.0000001, = 0.0000004 }
Auto-reload (no restart)
The router watches $POLYGLOTMESH_HOME/config.toml for changes every 2s and
auto-applies them — rebuilds the upstream registry, atomically swaps the auth
store, re-hydrates per-key counters from SQLite. No POST /v1/admin/reload
needed; the manual endpoint still works for scripted deploys.
Usage analytics
# -> { "group_by":"model", "totals":{...}, "buckets":[{ key:"gpt-4o-mini", requests:8, cost_usd:4.2e-05 }] }
group_by may be alias, upstream, model, or all. Add since/until
as Unix seconds to bound the window.
Retention policy
usage_events rows can grow without bound. Set a retention window in
config.toml and the router prunes rows older than N days once per day:
[]
= 30 # 0 = keep forever
Or change it live:
Budget reset
Keys with a budget_duration (e.g. "1h", "7d", "30d") have their
total_spend_usd automatically reset to 0 when the window expires — even
if the key has zero traffic. A background task fires every 60s and the
reset is persisted to SQLite so it survives restarts.
Config auto-reload (inotify/FSEvents)
The router uses kernel-level file events (inotify on Linux, FSEvents on
macOS) to detect config changes within ~150 ms — no polling. It falls
back to a 2 s stat() poll automatically on platforms where inotify is
unavailable (e.g. inside some sandboxes).
Per-upstream pricing overrides
merge: true keeps existing overrides; false replaces the whole map.
Persisted to config.toml AND SQLite so it survives reload + restart.
Observability
Lightweight in-process metrics — zero external dependencies (no Prometheus client crate). Counters, fixed-bucket latency histograms, and gauges, all backed by atomics. Persisted to SQLite every 10s and rehydrated on startup.
# JSON snapshot (counts + P50/P95/P99 + active gauges)
# Prometheus text format (compatible with any scraper)
# Built-in HTML dashboard with live refresh
The dashboard is a single-file HTML page served from static/dashboard.html
(Chart.js via CDN, no build step). It includes P50/P95/P99 latency
cards, per-upstream RPS sparkline, sliding-window rates, live event
SSE tail, and a dark/light theme toggle. The SQLite store backs the
persisted metrics that survive restarts.
Metric set (Bifrost-parity, no Prom crate)
| Type | Names |
|---|---|
| Counter | requests_total, upstream_requests_total, success_total, error_total, input_tokens_total, output_tokens_total, cost_micros_total, cache_read_input_tokens_total, cache_write_input_tokens_total |
| Histogram | request_duration_seconds (full request), upstream_duration_seconds (send → first byte), time_to_first_token_seconds (TTFT for streams), stream_inter_token_seconds (inter-chunk gap) |
| Labeled histogram | request_duration_by_upstream{upstream_id, model}, upstream_duration_by_upstream{upstream_id, model} |
| Gauge | active_requests, active_streams, upstream_up{upstream_id=…} (1 if last attempt succeeded) |
Counters are labeled by method, model, upstream_id, reason (errors).
Histograms have 14 log-scale buckets from 1 ms to 30 s — the P50/P95/P99
quantile is computed on demand from the cumulative bucket counts.
Sliding-window rates, traces, live events, reset, audit
# 1m/5m/1h RPS, TPS, cost-per-second
# OTLP-shaped recent spans
# Server-Sent Events of every completed request (dashboard live-tail)
# Operational reset — zeros in-memory state; long-term tables untouched
# Audit log (key ops, config reload, metrics reset, …)
Docs
- docs/ARCHITECTURE.md — internals, storage, observability layer
- docs/FEATURES.md — full feature catalog
- docs/FUTURE_IMPROVEMENTS.md — working list of planned enhancements