
Behind each public model sits a pool of upstream providers. A lightweight classifier decides how much work a request needs, and the router picks a backend under fail-open policy — with per-key circuit isolation so one revoked credential cannot poison the entire pool.
Nexith exposes one model name — nexith-core, nexith-pro — but each is served by multiple upstream providers running different underlying weights. Some are hosted APIs, some are self-deployed inference servers. When a request arrives the gateway must decide which provider to call, and it must do so in a way that survives transient failures, respects rate limits, and isolates bad credentials from good ones.
Not every request needs the most expensive model. Simple questions can be routed to a cheaper, faster backend. The L1 classifier is a small local model — currently Qwen 2.5 3B, chosen because it never hallucinates about task difficulty — that reads the prompt and returns a routing decision: direct (simple, factual) or reasoning (complex, multi-step).
The classifier itself costs almost nothing to run. Inference takes about 0.5 seconds, and because it is local there is no per-request billing. Misclassifications are bounded by policy: if the classifier says "direct" but the request would benefit from reasoning, the answer might be less thorough than ideal — but it will still be coherent, and the caller paid a fraction of what a reasoning call would cost.
The L1 classifier runs on every chat request. Embeddings, image generation, and audio transcription skip it — those workloads have only one backend per SKU, so there is nothing to route.
A provider can fail in several ways: the API key is revoked, the endpoint is down, rate limits are hit. The naive approach is a global circuit breaker that opens after N failures and stops routing requests to that provider. The problem with that approach is that it treats all keys the same. If one customer's key is revoked, every other key in the pool is effectively penalised — because successful calls from good keys reset the shared failure counter, and the breaker never trips.
We use per-key circuit breakers instead. Each (provider, api_key) pair tracks its own failure count, independently. A revoked key will hit the failure threshold and enter quarantine — at which point the router stops selecting it — but good keys continue to route normally. The pool remains healthy even when one credential is bad.
The gateway reloads its routing catalog every 5 seconds, so operators can add a provider, update a model mapping, or change a rate limit without restarting the service. The reload builds a new provider pool from scratch — which would normally reset all circuit breakers and throttle state, allowing a bad provider to immediately pollute the pool again.
The solution is state inheritance. When the new pool is built, the gateway walks the old pool and copies circuit state, throttle state, and per-key health from any (public_model, provider_id, provider_model) tuple that exists in both. The only exception is half-open probes — requests that are in-flight testing whether a previously broken circuit has recovered — which are never inherited, because copying "true" would freeze the breaker in the half-open state forever.
Rate limits are inherited too. A provider that was returning 429s before the reload will still be throttled immediately after, with the same exponential backoff window. The reload does not give it a free retry.
The result is a router that survives configuration changes, credential failures, and transient errors without dropping requests or letting one bad key poison the pool.