LLM gateway (LiteLLM)
AiSOC runs several distinct LLM workloads — triage, recon, investigation, the contextual copilot, summaries, reports, and natural-language generation. The LiteLLM gateway is the single entry point for every live LLM call these workloads make. AiSOC asks for a logical task alias; the gateway decides which real provider and model that alias resolves to.
This page is about the gateway — which provider and model requests are routed to. Where that model physically executes (CPU, an NVIDIA GPU, a host Ollama using Apple's Metal, or someone else's datacentre) is a separate decision with its own page: Where the model runs.
GET /api/v1/llm/status answers the first question and
GET /api/v1/llm/runtime answers the second. They disagree more often than
you would expect, because a GPU reservation is a request rather than a
guarantee.
AiSOC task ──▶ alias (e.g. "aisoc-triage") ──▶ LiteLLM ──▶ real model
This gives operators two things without any AiSOC code change:
- Per-task model assignment. Point
aisoc-triageat a cheap local model andaisoc-investigationat a strong hosted one — or swap either at any time — by editing one config file. - Centralized observability. LiteLLM exports per-task latency, tokens,
cost, errors, retries, and fallbacks on
/metrics, scraped by the bundled Prometheus (jobaisoc-litellm). This complements the Investigation Ledger, which records what the agent decided; the gateway records what each model call cost and how it behaved.
The gateway sits in front of the LLM tier of the multi-model router. When no live model is reachable, AiSOC still degrades to its deterministic offline path — the gateway is never on the critical path for a baseline triage.
Task aliases
The shipped aliases mirror AiSOC's workloads. They live in
infra/litellm/config.yaml:
| Alias | Workload | Shipped default |
|---|---|---|
aisoc-triage | Auto-triage of fused alerts (high volume) | gpt-4o-mini |
aisoc-recon | Recon / enrichment reasoning | gpt-4o-mini |
aisoc-investigation | Deep multi-step investigation | gpt-4o |
aisoc-copilot | Contextual analyst copilot | gpt-4o-mini |
aisoc-summary | Alert / incident summaries | gpt-4o-mini |
aisoc-report | Analyst-facing report write-ups | gpt-4o |
aisoc-nl | NL→query / NL→detection translation | gpt-4o-mini |
aisoc-hunt | Hypothesis → structured hunt plan | gpt-4o-mini |
aisoc-detection | Sigma rewrite from an analyst note | gpt-4o-mini |
The "shipped default" is only the example mapping in the config — the whole point is that you change it. The alias names stay constant.
Enable the gateway
The litellm service is in the CORE profile, so make up and a plain
docker compose up -d already start it and routing to it is already wired.
Supplying a key is the only step:
OPENAI_API_KEY=<your-real-provider-key> # the gateway uses this to reach the upstream model
LITELLM_MASTER_KEY=<a-strong-key> # AiSOC authenticates to the gateway with this
docker compose up -d # or: make up
It was a full-profile service until 2026-09, which meant a CORE deployment
could not do AI triage even with a key — every aisoc-<role> alias resolves
here and nowhere else. See
ADR-0006
for the trade-off, including the measured cost of the container.
With no provider key, the gateway still boots, serves its seven aliases
and answers /health/liveliness; AiSOC makes no LLM call and every alert is
triaged by the deterministic path. Nothing about the install is broken and
nothing claims AI is running.
That is the whole configuration. docker-compose.yml sets LLM_GATEWAY_URL on
the api and agents services, and both services' resolvers read it for any
aisoc-<role> alias — an alias resolves nowhere else, so the gateway is the
only correct destination for one. The bearer token is resolved with the route:
when AiSOC picks the gateway itself it sends LITELLM_MASTER_KEY, never a
provider key, which the gateway would reject as an invalid proxy token.
To send AiSOC somewhere else — a gateway on another host, or your own
OpenAI-compatible endpoint — set OPENAI_BASE_URL (or LLM_BASE_URL) and it
outranks LLM_GATEWAY_URL for every role.
Calling a provider directly, with no gateway
Pin each role to a concrete provider model. A concrete pin is not rerouted
to the gateway, so this keeps working with LLM_GATEWAY_URL set:
AISOC_MODEL_PIN_TRIAGE=gpt-4o-mini
AISOC_MODEL_PIN_INVESTIGATION=gpt-4o
# … one per role: triage, recon, investigation, copilot, summary, report, nl
OPENAI_MODEL=gpt-4o-mini # the BYOK / "explain this alert" path, direct too
With neither the gateway nor pin overrides configured, AiSOC uses its deterministic offline path, which reports zero model calls and zero tokens rather than presenting a heuristic as triage.
A model the gateway does not define is an error, not a fallback
Two failures used to be indistinguishable from "no LLM available", because both arrived as an exception a caller turned into a deterministic result:
- An
aisoc-<role>alias sent to a provider default, which does not know the name. AiSOC now raisesUnroutableModelErrornaming the alias and the remedy instead of building a client destined to fail. - A concrete model sent to the gateway, which answers
Invalid model name.preflight_llm()reports this at boot, in both directions, andscripts/check_llm_model_routing.pyfails the build when a model named in.env.exampleordocker-compose.ymlis not oneinfra/litellm/config.yamldefines — or when the gateway defines an alias no role requests.
OPENAI_MODEL applies to the BYOK / "explain this alert" path only. It is
never a task role's model: the auto-triage worker layers a tenant's BYOK
configuration over the role pin, and only the fields the tenant set are
treated as overrides, so an environment default cannot displace an alias.
Embeddings
The MITRE RAG path calls an embedding endpoint, and infra/litellm/config.yaml
declares chat aliases only — so embeddings deliberately do not follow chat
traffic to the gateway. They have their own pair of variables,
AISOC_EMBEDDING_BASE_URL and AISOC_EMBEDDING_MODEL (default
text-embedding-3-large), and default to the provider. The routing gate checks
that exclusion in both directions.
Re-point a task to a local model
Duplicate the alias in infra/litellm/config.yaml with a local backend. The
alias name must stay the same so AiSOC is unaware of the swap:
- model_name: aisoc-triage
litellm_params:
model: ollama/llama3.1
api_base: http://ollama:11434
Commented Ollama, vLLM, and Anthropic examples ship in the config. For a fully offline deployment, see air-gapped operation, which fronts a local Ollama.
Observe
- Metrics:
curl http://localhost:4000/metrics(or the Grafana/Prometheus stack under themonitoringprofile) showslitellm_*counters broken down by task alias and model. - Health:
curl http://localhost:4000/health/liveliness.
Notes
- Host port
4000is bound to127.0.0.1only, like the rest of the stack. - No provider key is ever written to
infra/litellm/config.yaml— aliases resolve credentials from the process environment (os.environ/...).