feat(pm): LLM routing endpoint, usage capture, and admin review view

Backend counterpart to the Python engine's semantic router.

- POST /api/decision/route: service-secret-guarded endpoint the Python
  backend calls on regex-miss. Calls gpt-5-mini (ROUTER_LLM_MODEL),
  validates the chosen id against the candidate set, and writes both a
  UsageEvent (central cost ledger) and a routing-review record — including
  timeouts, with timeoutMs + actual latencyMs — so the budget can be tuned
- LlmRoutingDecision model + GET /api/admin/llm-routing (paginated,
  status-filtered, per-status counts)
- admin "LLM Routing" tab: transcript vs expected phrase, candidate chips
  with the chosen one highlighted, latency/budget chip, model reason
- serviceAuth util (mirrors CRON_SECRET pattern)
- .env.example: ROUTER_LLM_MODEL, SERVICE_SECRET

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
leubeem
2026-06-29 10:27:57 +02:00
parent cecd27ea97
commit 9802c438a5
4 changed files with 296 additions and 0 deletions

View File

@@ -17,6 +17,15 @@ OPENAI_BASE_URL=
LLM_MODEL=gpt-5-nano
TTS_MODEL=tts-1
VOICE_ID=alloy
# Model used by the /api/decision/route LLM router (the Python backend calls
# this when regex routing misses a pilot transmission).
ROUTER_LLM_MODEL=gpt-5-mini
# Internal service-to-service auth. The Python decision backend calls
# /api/decision/route with this value in the `x-service-secret` header. Must
# match SERVICE_SECRET in the Python backend's env. If unset, the LLM router is
# disabled (the backend falls back to deterministic bad_next routing).
SERVICE_SECRET=CHANGE_ME
# ATC audio generation
ATC_OUT_DIR=./storage/atc