eltonandClaude Fable 5.1 d8cc9b5dbb llmrouter: Deploy-Stand — Port 4010, Kira-Keys, MODEL_BLOCKLIST, max auf fable-5.1:high
Läuft seit 2026-09-05 als systemd-Unit llmrouter im LXC 185020 unter
/srv/llmrouter, parallel zu LiteLLM (:4000). 4001 war von nginx belegt.
Admin-Key in OpenBao jessie/llmrouter. Token für inka-mx, nightjob, lab-xo,
team-copilot fehlen (nur Hash in LiteLLM).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 07:18:31 +02:00

llmrouter

Leichtgewichtiger Ersatz für den LiteLLM-Gateway llm.lan (siehe ../llmlite). Ein OpenAI-kompatibler Reverse-Proxy in Bun/TypeScript mit genau vier Eingriffen je Anfrage: Client-Key prüfen, Alias auf ein Modell auflösen, Usage und Kosten aus der Antwort mitlesen, eine Log-Zeile schreiben.

Keine Format-Übersetzung, keine Datenbank, kein Admin-UI. Alle Upstreams sprechen OpenAI-Format (OpenRouter, llama-server, Ollama, Mistral), deshalb reicht Durchreichen. Entscheidung Andreas, 2026-09-05.

Aliasse (config.json)

Zwei Arten:

  • alternative — der Alias nennt ein Referenzmodell mit optionaler Effort-Stufe (openai/gpt-5.6-luna:low, anthropic/claude-opus-5:medium). Gerufen wird das günstigste OpenRouter-Modell derselben Klasse, nach der Regel in ../llmlite/openrouter-auswahl.ts (Intelligence-Index je Stufe von Artificial Analysis, Kontext, Tool-Calls, Sperrliste, Live-Probe, Last-Probe bei Wechsel). Findet sich nichts Billigeres, wird die Referenz selbst gerufen. Die Effort-Stufe des Ziels wird als reasoning.effort gesetzt; ein vom Client mitgeschicktes reasoning_effort wird verworfen, die Klasse gehört zum Alias.
  • fest — Upstream und Modell stehen fest (lokales Qwen, TTS/STT, Embeddings, kira auf deizo). Optional ein body-Patch, etwa chat_template_kwargs.enable_thinking für die Qwen-Stufen.

altnamen übersetzt die LiteLLM-Namen (42i/marvin-2608:low …), damit kein Container angefasst werden muss; geloggt wird der neue Name. Abbauen, sobald die Clients umgestellt sind.

Auflösung und Takt

src/resolver.ts löst alle alternative-Aliasse auf und schreibt log/state.json. OpenRouter-Preise ändern sich oft: alle refreshHours (12) neu. AA-Indexe ändern sich nie, nur neue Modelle kommen dazu: log/aa-cache.json, alle aaRefreshDays (7) neu. Der Server frischt selbst auf (beim Start, wenn veraltet, und im 15-Minuten-Takt geprüft); Wechsel stehen in log/changes.log.

bun run src/resolver.ts --force            # jetzt auflösen, Tabelle zeigen
LLMROUTER_LASTTEST=0 bun run src/resolver.ts --force   # ohne Last-Probe (Tests; spart ~210k Token je Wechsel)

Der erste Lauf mit leerem State prüft jeden Alias unter Last, das ist gewollt.

Keys und Rechte

  • key-acls.json (versioniert): Name → erlaubte Aliasse, [] = alles.
  • KEYS_FILE (nur auf dem Host, 0600): Token → Name. Pflege mit KEYS_FILE=… bun run src/keys.ts add <name> [--token <bestehend>]; mit --token lässt sich der LiteLLM-Key eines Agenten übernehmen, sodass der Container nichts merkt.
  • Upstream-Schlüssel aus der Umgebung: OPENROUTER_API_KEY, MISTRAL_API_KEY.
  • ADMIN_KEY schützt GET /stats?days=N, GET /zuordnung, POST /auffrischen.

Log und Statistik

Je Anfrage eine Zeile in log/requests.jsonl und in log/requests.sqlite: Key, Alias, angefragter Name, tatsächliches Modell, Effort, Tokens (prompt/completion/cached), Kosten wie von OpenRouter gemeldet (usage.cost, auch im Stream über den letzten Chunk). Lokale Upstreams haben keine Kosten, das Feld bleibt leer, wir erfinden keine Preise.

bun run src/stats.ts 7        # Tag × Key: Anfragen, Tokens, Kosten, Modelle
curl -H "Authorization: Bearer $ADMIN_KEY" http://llm.lan:4010/stats?days=7

Die Antwort trägt x-llmrouter-model, damit ein Client sieht, was wirklich lief.

Betrieb

PORT=4010 KEYS_FILE=/srv/llmrouter/keys.json ADMIN_KEY=OPENROUTER_API_KEY=… bun run src/server.ts
bun run build      # dist/llmrouter, ein statisches Linux-Binary

Parallelbetrieb neben LiteLLM (Port 4000) im LXC 185020 auf Port 4010; die Umschaltung ist ein Wechsel der base_url in den Client-Configs.

Bedient: /v1/chat/completions, /v1/completions, /v1/responses, /v1/embeddings, /v1/audio/speech, /v1/audio/transcriptions, /v1/audio/translations, /v1/models (zeigt je Key die erlaubten Aliasse), /health. Der Präfix /v1 ist optional.

S
Description
No description provided
Readme
374 KiB
Languages
TypeScript 95.1%
Python 4.1%
Dockerfile 0.8%