eltonandClaude Fable 5.1 e13baaab28 llmrouter vor LiteLLM: Caddy llm.lan -> :4010, /models mischt LiteLLM-Liste dazu
Seit 2026-09-05 06:11 läuft llm.lan über den Router; 2608-Namen und role/*
gehen per Durchreiche an LiteLLM (:4000). Caddyfile-Falle: Datei-Bind-Mount,
mv tauscht den Inode -> podman restart llmlite-caddy statt caddy reload.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 08:12: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.
  • schwelle — Index-Schwelle (0100) plus Mindestkontext, ohne Referenzmodell: das günstigste Modell, das beides erfüllt, mit der niedrigsten ausreichenden Effort-Stufe. Stand 2026-09-05: low 25/256k, medium 40/256k, high 47/500k, max 50/500k. Stabiler als eine Referenz, sagt aber weniger über die Bedeutung.
  • 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.

Die Namen sind versioniert wie bei LiteLLM: 42i/marvin-2609:low:max, 42i/marvin-2609-core:none|medium. Unbekannte Namen (alles aus der LiteLLM-Zeit, 42i/marvin-2608:*) reicht der Router unverändert mit dem Client-Key an LiteLLM auf :4000 durch (fallback). Caddy zeigt llm.lan auf den Router; ein Agent wechselt also per Modellname 2608 → 2609, nicht per base_url, und rollt per Umbenennen zurück. Durchgereichte Anfragen stehen im Log mit Upstream litellm, ohne Kosten (die kennt nur LiteLLM).

Wird LiteLLM abgeschaltet, entfällt fallback, und altnamen (Übersetzung 2608 → 2609) übernimmt, damit auch Nachzügler nicht brechen.

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; Caddy zeigt llm.lan auf 4010, die Umschaltung je Agent ist der Modellname.

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%