LLM observability — LiteLLM + Phoenix¶
Split stack for homelab LLM operations:
| Tool | Host | URL | Purpose |
|---|---|---|---|
| LiteLLM | infra-services | https://litellm.infra.realemail.app |
Usage metrics, spend, virtual keys, OpenAI-compatible gateway |
| Phoenix | prox LXC 124 | https://phoenix.infra.realemail.app |
Trace interrogation, RAG debugging, evals |
LiteLLM — usage and gateway¶
Route traffic through the proxy¶
Point any OpenAI-compatible client at:
Authenticate with a virtual key (Authorization: Bearer sk-…), not your Authentik session.
Create keys in the Admin UI: https://litellm.infra.realemail.app/ui (Authentik-gated).
Add models¶
Edit services/litellm/config.yaml on infra-services and redeploy:
model_list:
- model_name: gpt-4o
litellm_params:
model: openai/gpt-4o
api_key: os.environ/OPENAI_API_KEY
Provider API keys live in .env (from SOPS). After Phase 9 Ollama returns, add a local
model entry pointing at the inference guest.
Grafana¶
Open Grafana → Homelab → LiteLLM for request rate, token throughput, and cumulative spend
(Prometheus job litellm scrapes litellm:4000/metrics).
Smoke test¶
curl -s https://litellm.infra.realemail.app/v1/models \
-H "Authorization: Bearer $LITELLM_VIRTUAL_KEY"
Phoenix — traces and research¶
OTLP endpoint¶
From any host on 192.168.6.0/24:
(gRPC OTLP — not exposed through Traefik.)
Python example (OpenInference)¶
from phoenix.otel import register
from openinference.instrumentation.openai import OpenAIInstrumentor
import openai
register(endpoint="http://192.168.6.124:4317/v1/traces", project_name="homelab")
OpenAIInstrumentor().instrument()
client = openai.OpenAI() # or point base_url at LiteLLM
client.chat.completions.create(model="gpt-4o-mini", messages=[{"role": "user", "content": "ping"}])
Open https://phoenix.infra.realemail.app and inspect the trace tree.
Edge auth (Traefik + Authentik)¶
| Path | Authentik | Auth mechanism |
|---|---|---|
litellm…/ui |
Yes | Authentik forward-auth |
litellm…/v1 |
No | LiteLLM virtual key |
phoenix.infra… |
Yes | Authentik forward-auth |
OTLP :4317 |
N/A | LAN reachability only |
Configure Authentik proxy providers for litellm.infra.realemail.app and
phoenix.infra.realemail.app per authentik-cross-host-sso.md
(owner UI — required before browser access works through Traefik).
Cloudflare DNS: both hostnames are covered by *.infra.realemail.app → 192.168.6.17
(infra-dns-rewrites.yaml).
After merge to main, on infra-services encrypt secrets with SOPS (.env.sops.yaml.example
→ encrypt → decrypt to gitignored .env). Phoenix secrets stay on LXC 124 only.
Wazuh agent on phoenix: runs on next ansible-pull / patch wave once the branch is merged
and someone SSH is fully bootstrapped (patch-controller key on root@192.168.6.124 is in place).
Operations¶
LiteLLM (infra-services)¶
Phoenix (prox LXC 124)¶
Backups¶
Both stacks are tier 2 — Postgres volumes documented in each backup.yml.
Related¶
- LiteLLM service README
- Phoenix service README
- phoenix host
- ollama host — retired; Phase 9 inference uses a new guest