# 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 (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 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`. ```bash 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 [--token ]`; 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. ```bash 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 ```bash 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.