Skip to content

LLM Gateway

Every LLM and embedding call Huddle makes goes through a self-hosted LiteLLM proxy at litellm.huddlesurety.co. Services never hold provider credentials: they authenticate to the gateway with a virtual key, and the gateway holds the Google AI Studio key and does the routing, accounting, and rate limiting.

graph LR
    RAG[RAG worker] -->|virtual key| GW[LiteLLM gateway]
    DEV[Developer laptop] -->|personal key| GW
    GW -->|provider key| G[Google AI Studio]
    GW --> PG[(Postgres — keys, teams, spend)]

One instance serves all environments. That matters for attribution: the gateway’s own identity is fixed, so which environment a call came from has to be carried by the caller’s credentials rather than inferred from the proxy.

Two variables, both per-environment:

Variable Value
LITELLM_PROXY_API_BASE https://litellm.huddlesurety.co
LITELLM_PROXY_API_KEY that service’s virtual key for that environment

RAG sets litellm.use_litellm_proxy = True, so ordinary litellm.completion and litellm.embedding calls route through the gateway with no per-call-site change. Any OpenAI-compatible client works the same way.

Models are configured as a gemini/* wildcard, so any Gemini model is reachable without being listed. check_provider_endpoint: true makes /v1/models return the resolved catalogue instead of the literal gemini/*.

The convention encodes two axes without duplicating either:

Primitive Carries Example
Team environment development, staging, production
Service key which service, in which environment rag-development, rag-production
Developer key which service, for which person rag-ethan
  • Team is the environment. The LiteLLM UI renders the team alias as the Env: badge on every request, and team-level budgets are how one environment is stopped from spending another’s allowance.
  • A key is a credential, never a reporting dimension. Keys rotate; when they do, the old key’s spend history stays behind under a hash that no longer resolves to an alias. Anything you need to chart over time should come from the team, the customer, or a tag instead.
  • Key aliases are globally unique across the whole gateway, so rag-development and rag-ethan can’t collide with anything else.

Adding a service is one key per environment plus nothing else — no new teams, no scheme change.

Every developer gets their own key per service, named <service>-<your name>rag-ethan, api-jane. Personal keys make spend attributable per person and revocable individually, and they keep a laptop experiment from being indistinguishable from deployed traffic.

  1. Open litellm.huddlesurety.co and sign in with your Huddle Google Workspace account (SSO — the UI redirects automatically).
  2. Create a virtual key on the development team, with the alias <service>-<your name>.
  3. Put it in that service’s .env.local as LITELLM_PROXY_API_KEY.

Developers are members of the development team only, so there is no path to minting a staging or production key by accident. Personal keys are a good place for a small max_budget and rpm_limit — a runaway loop then costs you a 429 rather than the environment’s budget.

Four things identify every call, and none of them are derived from the others:

Dimension Source Read it via
Environment team UI Env: badge, per-team spend
Service or developer key alias key spend views
Tenant (org) x-litellm-customer-id header /customer/info, /customer/list
Workflow x-litellm-tags: workflow:<Type> header /spend/tags, /tag/daily/activity

LiteLLM also auto-tags by User-Agent, so /spend/tags mixes workflow:… entries with User-Agent: OpenAI/Python 2.50.0. Filter on the prefix you want.

Customer display names are set with alias on /customer/new — never format a name into the customer id itself, because spend is keyed on that exact string and a rename would silently start a second customer.

Gateway telemetry deliberately does not flow into Alloy, Tempo, or Prometheus. It lives in LiteLLM’s own dashboard and API — spend by team, key, tag, customer, and model — rather than being duplicated into a second system.

The trade-off is that distributed traces end at RAG instead of continuing into the provider call, so gateway latency can’t be correlated with a bond request in Tempo. LiteLLM’s Prometheus callback, which would be the other way to export metrics, is an Enterprise-only feature.

Prompt and completion bodies are stored in the gateway’s own Postgres (store_prompts_in_spend_logs) and nowhere else — nothing exports them to the trace store.

Self-hosted on Railway from the litellm repo, in the Infrastructure project alongside Grafana and Temporal, deploying from main. The image pins an exact LiteLLM version and bakes config.yaml in, so configuration changes ship like code. Postgres holds keys, teams, customers, and spend logs.