Skip to content

LLM Gateway

The LLM Gateway is Nova’s model routing layer. It exposes a unified API that translates requests to any configured provider — Anthropic, OpenAI, Ollama, Groq, Gemini, Cerebras, OpenRouter, GitHub Models, and the ChatGPT Plus subscription provider.

PropertyValue
Port8001
FrameworkFastAPI + LiteLLM
State storeRedis (db 1)
Sourcellm-gateway/
  • Model routing — resolve model IDs to provider instances and forward requests
  • OpenAI compatibility — expose /v1/chat/completions and /v1/models so any OpenAI-compatible tool works out of the box
  • Subscription auth — use ChatGPT Plus/Pro subscriptions as zero-cost providers
  • Rate limiting — per-provider daily quotas enforced via Redis sliding window
  • Response caching — cache deterministic (temperature=0) completions to avoid duplicate API calls
  • Local inference routing — auto-discovers models from the active managed backend (Ollama, vLLM) and routes via LocalInferenceProvider

The routing strategy is configurable at runtime via the platform config:

StrategyBehavior
local-onlyOnly use the active local inference backend. Fail if offline.
local-firstTry local backend first, fall back to cloud. (default)
cloud-onlySkip local inference, use cloud providers only.
cloud-firstTry cloud first, use local backend as backup.

Requests can carry tier: best | mid | cheap instead of a concrete model (pods and agents can pin tier:<name> the same way). The resolver walks the tier’s preference list and picks the first candidate that passes validated discovery — the provider’s credential answered a real API call and the model is on its current live list — plus quota and context-window checks. Dead keys, retired models, and un-pulled local models are skipped, so a tier hint can never resolve to a model that doesn’t exist. An empty tier falls through to the next cheaper tier. Preference lists are seeded from TIER_PREFERENCES_BEST/MID/CHEAP and overridable at runtime via the Redis config key llm.tier_preferences (JSON object of tier → model list). GET /v1/models/tiers shows every candidate’s verdict and what each tier resolves to right now.

A provider whose API key is rejected (expired, revoked, or never configured) never takes a request down with it:

  • The failing provider is sidelined for a 10-minute cooldown and skipped by the fallback chain; the request is retried once on the local default model (unless the strategy is cloud-only, which returns a structured 502 provider_credentials_invalid instead of a raw error).
  • Paid providers are isolated per credential — a dead Anthropic key never sidelines OpenAI, and vice versa.
  • GET /health/providers reports credential_invalid: true for any provider currently in cooldown, so a bad key is visible at a glance. Rotate keys in Settings → AI & Models → Provider Status — the new key applies live (no restart): the gateway hot-reloads its credentials the moment a key is saved or removed, rebuilds its failover chains, and clears the cooldown so a fixed key is retried immediately.
ClassDescription
LocalInferenceProviderWrapper that reads active backend config from Redis (5s cache) and delegates to the appropriate provider (Ollama, vLLM, SGLang, or custom). Recreates delegate on backend/URL change.
OpenAICompatibleProviderBase class for OpenAI-compatible inference servers (vLLM, SGLang)
VLLMProviderThin subclass for vLLM — chat, streaming, embeddings, function calling, structured output
SGLangProviderThin subclass for SGLang — same capabilities as vLLM, benefits from RadixAttention prefix caching
RemoteInferenceProviderFor user-managed OpenAI-compatible servers — custom URL + optional auth header via extra_headers
OllamaProviderExisting Ollama provider (unchanged)
ProviderSetupModel prefix
ChatGPT Plus/ProRun codex login or auto-read from ~/.codex/auth.jsonchatgpt/
ProviderDaily limitEnv var
OllamaUnlimited (local)
Groq14,400 req/dayGROQ_API_KEY
Gemini250 req/dayGEMINI_API_KEY
Cerebras1M tokens/dayCEREBRAS_API_KEY
OpenRouter50+ req/dayOPENROUTER_API_KEY
GitHub Models50-150 req/dayGITHUB_TOKEN
ProviderEnv var
AnthropicANTHROPIC_API_KEY
OpenAIOPENAI_API_KEY
MethodPathDescription
POST/completeNon-streaming LLM completion
POST/streamSSE streaming completion
POST/embedGenerate text embeddings
MethodPathDescription
POST/v1/chat/completionsChat completions (streaming and non-streaming)
GET/v1/modelsList all registered model IDs
MethodPathDescription
GET/v1/inference/statsPerformance metrics — tokens/sec, latency, request counts for the active local backend
MethodPathDescription
GET/v1/models/discoverValidated discovery: each provider’s live model list plus a key_status verdict from a real API call — ok, not_configured, invalid_key (credential rejected), or error (unreachable). available means the provider actually answered, not merely that a key is present. ?refresh=true bypasses the 5-minute cache.
GET/v1/models/tiersTier-system health: each tier’s preference list with per-candidate verdicts (ok, provider_unavailable, unknown_model, unregistered, no_quota) and the model the tier currently resolves to (null when nothing on the list is usable).
GET/v1/models/ollama/*Ollama model management
DELETE/v1/models/ollama/{name}Delete a pulled model — refused with 409 while any pod, agent, or config knob still points at it (the response lists them). Fail-closed: if the orchestrator can’t confirm zero references, the delete is rejected.

The orchestrator cross-checks every configured model reference (pod agent pins, pod default models, task-agent models, llm.default_chat_model, llm.cloud_fallback_model) against this validated catalog at GET /api/v1/models/assignments; tier:* pins are verified against /v1/models/tiers — a tier that can’t resolve on any level is flagged as a problem instead of rubber-stamped. The dashboard’s Models page shows a warning banner for assignments that point at retired models or dead providers. Writes are guarded too: pinning a pod or agent to a model that no provider serves is rejected with 422, and deleting a still-referenced local model opens a repoint dialog in the dashboard.

MethodPathDescription
GET/health/liveLiveness probe
GET/health/readyReadiness probe
GET/health/inflightCount of active local-backend requests (used by drain protocol)
VariableDescriptionDefault
ANTHROPIC_API_KEYAnthropic API key
OPENAI_API_KEYOpenAI API key
OLLAMA_BASE_URLOllama API URLhttp://ollama:11434
GROQ_API_KEYGroq API key
GEMINI_API_KEYGemini API key
CEREBRAS_API_KEYCerebras API key
OPENROUTER_API_KEYOpenRouter API key
GITHUB_TOKENGitHub PAT for GitHub Models
REDIS_URLRedis connection stringredis://redis:6379/1
LOG_LEVELLogging levelINFO
CORS_ALLOWED_ORIGINSComma-separated allowed origins*
INFERENCE_BACKENDActive local backend (read from Redis)ollama
INFERENCE_STATEBackend state: ready, draining, starting, errorready
INFERENCE_URLOverride URL for active backend(auto-detected)
Terminal window
# List available models
curl http://localhost:8001/v1/models | jq '.data[].id'
# OpenAI-compatible completion
curl http://localhost:8001/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2.5:7b",
"messages": [{"role": "user", "content": "Hello from Nova"}]
}'
# Nova internal completion
curl http://localhost:8001/complete \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2.5:7b",
"messages": [{"role": "user", "content": "Hello"}]
}'
  • LiteLLM abstraction — all provider calls go through LiteLLM for unified request/response translation
  • Provider auto-detection — providers are registered at startup based on available credentials (env vars, credential files, keychain)
  • Rate limiting — per-provider daily quotas tracked in Redis; returns HTTP 429 when exhausted
  • Response cache — temperature=0 requests are cached to avoid redundant API calls; cache is keyed on the full request body (excluding metadata)
  • Translation layeropenai_compat.py converts between OpenAI wire format and Nova’s internal CompleteRequest/CompleteResponse types
  • Local inference abstractionLocalInferenceProvider wraps the active backend, reading nova:config:inference.* from Redis. Supports ollama, vllm, sglang, and custom backend types. The is_local property on ModelProvider enables inflight request counting without string matching.
  • Model discovery — gateway discovers models from the active backend’s /v1/models endpoint (vLLM/SGLang) or Ollama’s model list. LocalInferenceProvider maintains a dynamic set of known local models for routing decisions.
  • Inference metrics — the /v1/inference/stats endpoint tracks tokens per second, average latency, and request counts for the active local backend, displayed in the dashboard’s Models page.
  • Extra headersOpenAICompatibleProvider supports extra_headers for custom authentication, used by RemoteInferenceProvider to pass user-configured auth to custom endpoints.