billigsterUeberSchwelle in openrouter-auswahl, gemeinsamer klasseFiltern mit dem Referenzmodus. Stufen jetzt als Schwelle: low 25/256k, medium 40/256k, high 47/500k, max 50/500k (Andreas 2026-09-05). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
91 lines
4.2 KiB
Markdown
91 lines
4.2 KiB
Markdown
# 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.
|
||
|
||
`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`.
|
||
|
||
```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 <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.
|
||
|
||
```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; 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.
|