Caddy zeigt llm.lan künftig auf den Router; alles Unbekannte (2608, role/*) geht unverändert mit dem Client-Key an LiteLLM :4000. Umstellung je Agent = Modellname, kein base_url-Wechsel (Andreas 2026-09-05). altnamen entfällt, bis LiteLLM abgeschaltet wird. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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 alsreasoning.effortgesetzt; ein vom Client mitgeschicktesreasoning_effortwird verworfen, die Klasse gehört zum Alias.schwelle— Index-Schwelle (0–100) 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 einbody-Patch, etwachat_template_kwargs.enable_thinkingfü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 mitKEYS_FILE=… bun run src/keys.ts add <name> [--token <bestehend>]; mit--tokenlä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_KEYschütztGET /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.