354 lines
21 KiB
Markdown
354 lines
21 KiB
Markdown
# llmrouter — Betrieb (`llm.lan`, seit 2026-09-05)
|
||
|
||
Der schlanke Nachfolger des LiteLLM-Gateways: ein OpenAI-kompatibler
|
||
Reverse-Proxy in Bun/TypeScript. `llm.lan` zeigt auf ihn. Er kennt vier
|
||
Eingriffe je Anfrage: Client-Key prüfen, Modellnamen auflösen, Kosten aus der
|
||
Antwort mitlesen, eine Log-Zeile schreiben. Keine Datenbank, kein Admin-UI,
|
||
keine Format-Übersetzung — alle Upstreams sprechen OpenAI-Format.
|
||
|
||
> **Wofür wichtig:** Alle Agenten (Spark, Buzz, Klara, Jessie, Inka …),
|
||
> opencode auf dem Mac und Claude Code im Terminal sprechen nur diesen
|
||
> Endpunkt. Fällt er aus, steht die Agenten-Arbeit. Provider-Keys (OpenRouter,
|
||
> Mistral, deizo) liegen nur hier.
|
||
>
|
||
> Diese Seite ist der Betriebsstand für Störung, Wartung und Nachfolge; die
|
||
> technische Referenz steht in [referenz.md](referenz.md).
|
||
|
||
## Wo läuft was
|
||
|
||
| Was | Wo |
|
||
|---|---|
|
||
| Router (`llmrouter`) | LXC **185020** (`llmlite`, `10.18.5.20`) auf `s18p1`, **OCI-Container** `llmrouter_llmrouter_1` (podman-compose, systemd-Unit `llmrouter`), Bild `git.42i.org/spine/llmrouter:latest`, Port **4010** |
|
||
| Alter Port **4000** | seit 2026-09-07 von Caddy auf 4010 geleitet (vorher LiteLLM). Hart verdrahtete Clients — Agenten-Images, spark-desktop, ältere Skripte — laufen darüber weiter |
|
||
| LiteLLM | **abgeschaltet am 2026-09-07** (`systemctl stop/disable llmlite`). Dateien und Container liegen unangetastet in `/srv/llmlite`, letzte Kosten-Datenbank als `spend-final-20260907.sql.gz` |
|
||
| Caddy (`llm.lan`, TLS über `acme.lan`) | derselbe LXC, **eigener oci-Dienst `caddy`** (`/srv/caddy`, systemd-Unit `caddy.service`), `reverse_proxy localhost:4010` |
|
||
| Dateien | `/srv/llmrouter/`: `compose.yml`, `.env` (0600); Laufzeit unter `data/`: `config.json`, `key-acls.json`, `keys.json` (0600), `model-blocklist.json`, `log/` (requests.jsonl, changes.log, state.json, aa-cache.json). Sidecar-Binaries daneben, werden nicht mehr gebaut (Ersatz: `podman run` aus dem Image, siehe Deploy) |
|
||
| Docker-Image (Prod) | `git.42i.org/spine/llmrouter:latest`, gebaut von CI — seit 2026-09-09 läuft der Prod-Einsatz darauf |
|
||
| Lokales Qwen | LLM-Knoten LXC 189080, `10.18.5.30:11507` |
|
||
| Sprachdienste lokal | `10.18.5.30:8803` (Qwen3-TTS + whisper.cpp), Ollama `:11434` (Embeddings) |
|
||
| Kira im Prod-RZ (deizo) | `10.6.42.111:8000/8001/8002` über den WireGuard-Peer des LXC |
|
||
|
||
Normalzustand: `curl http://llm.lan:4010/health` liefert `{"ok":true,"zuordnung":"<Zeitstempel>"}`;
|
||
der Zeitstempel ist jünger als 12 Stunden.
|
||
|
||
## Namensräume — was ein Client rufen kann
|
||
|
||
Die vollständige Tabelle der Namensräume (Stufen, Profile, Rollen, `alt/`,
|
||
Claude-Code-Pfad) steht in [referenz.md](referenz.md#namensräume). Kurzfassung:
|
||
|
||
| Name | Bedeutung |
|
||
|---|---|
|
||
| `role/<name>` | Rolle (`xo`, `architect`, `teamleader`, `worker`, `qa` …) — **Agenten kennen nur Rollen** |
|
||
| `auto/<profil>[:stufe]` | Profile auf drei Achsen: `mass`, `mass:fast`, `allround`, `coding`, `leader` je `:junior`/`:senior` |
|
||
| `42i/marvin-2609:low|medium|high|max` | konfigurierte Stufen (Index ≥ 25/45/49/53) |
|
||
| `alt/<id>[:effort]` | günstigste Alternative derselben Klasse; aufgelöst bei der ersten Anfrage, dann alle 12 h |
|
||
| `openrouter/<id>[:effort]` | 1:1 an OpenRouter |
|
||
| `deizo/kira-*`, `42i/marvin-*` | feste Ziele (Prod-RZ, lokal, Cloud-Sprachdienste) |
|
||
| alles andere | **404** — kein stiller Fallback mehr seit der LiteLLM-Abschaltung |
|
||
|
||
Ein Modellwechsel steht in `log/changes.log` (im LXC:
|
||
`/srv/llmrouter/data/log/changes.log`).
|
||
|
||
## Rollen und Profile — drei Ebenen
|
||
|
||
Ein Agent nennt **nie** ein Modell. Er fragt seine Rolle, die Rolle zeigt auf
|
||
ein Profil, und das Profil sucht sich das billigste Modell über den
|
||
geforderten Untergrenzen. Umgestellt wird auf jeder Ebene in `config.json` —
|
||
ohne Deploy, ohne Container anzufassen (42i/intern#1252).
|
||
|
||
| Profil | Regel | trifft am 2026-09-07 | $/1M gew. |
|
||
|---|---|---|---|
|
||
| `auto/mass` | fest: lokales Qwen, Ausweich auf `mass:fast` | qwen36:35b | 0 |
|
||
| `auto/mass:fast` | Index ≥ 25, Output ≥ 32768 (je Profil senkbar, sonst 65536) | ling-3.0-flash | 0,27 |
|
||
| `auto/allround:junior` | Index ≥ 40 | glm-5.3-flash | 0,95 |
|
||
| `auto/allround:senior` | Index ≥ 49 | grok-4.6:high | 26,00 |
|
||
| `auto/coding:junior` | Coding ≥ 68, Agentic ≥ 45 | glm-5.3-flash | 0,95 |
|
||
| `auto/coding:senior` | Coding ≥ 76, Agentic ≥ 50 | grok-4.6:high | 26,00 |
|
||
| `auto/leader:junior` | Index ≥ 45, Agentic ≥ 50 | glm-5.3-flash | 0,95 |
|
||
| `auto/leader:senior` | Coding ≥ 74, Agentic ≥ 53 | glm-5.3:max | 15,66 |
|
||
|
||
Warum drei Achsen und nicht eine Zahl: `gemini-3.8-flash` hat Coding 76,3 und
|
||
Agentic **41,2** — es schreibt guten Text und trägt den Werkzeug-Rundlauf nicht.
|
||
Ein einzelner Index sieht diesen Unterschied strukturell nicht.
|
||
|
||
**Rollenbelegung:** `xo`, `teamleader`, `lead` → `auto/leader:junior`;
|
||
`worker` → `auto/mass:fast`; `coder`, `engineer`, `qa`, `perf` →
|
||
`auto/coding:junior`; alle übrigen →
|
||
`auto/allround:junior`; `inka` fest auf `openrouter/openai/gpt-5.6-luna`.
|
||
**Keine Rolle steht auf einer Senior-Stufe** (Andreas, 2026-09-07) — hochgestuft
|
||
wird einzeln und mit Anlass, nicht vorsorglich. `role/coder` existiert seit
|
||
2026-09-09 neben `engineer`, damit Code-Arbeit nicht über `role/worker` (Masse)
|
||
läuft.
|
||
|
||
**Der Ausweich** gilt für Ziele ohne eigenen Rückfall — festes Ziel wie
|
||
`auto/mass` (lokales Qwen) oder Schwelle auf einem Ausfall-Modell wie
|
||
`auto/mass:fast`. Steht der Knoten, hält eine fremde Arbeit die Karte oder
|
||
liefert der Upstream 429/5xx, weicht der Router **einmal** auf den
|
||
konfigurierten Ausweich aus; die Log-Zeile trägt dann
|
||
`auto/mass->auto/mass:fast`, der Wechsel ist also sichtbar. Die Kette:
|
||
`auto/mass` → `auto/mass:fast` (ling) → `auto/coding:junior`
|
||
(glm-5.3-flash) — ling erfüllt die coding:junior-Schwellen strukturell nie,
|
||
der Rückfall landet also garantiert auf einem tauglichen Modell. Ohne das
|
||
hinge jede Titelzeile jedes Agenten, weil `auto/mass` überall als
|
||
`small_model` hängt. Wichtig: Die Ausweich-Auflösung läuft durch die
|
||
ACL-Prüfung des aufrufenden Keys — die Container-Personas brauchen die
|
||
Kettenziele deshalb in `key-acls.json`, sonst schlägt der Fallback still
|
||
fehl (403) und der ursprüngliche Fehler geht durch.
|
||
|
||
## Wer läuft worauf
|
||
|
||
| Wer | Rolle | Ziel heute |
|
||
|---|---|---|
|
||
| agent-spark | `role/engineer` | glm-5.3-flash |
|
||
| agent-buzz | `role/ops` | glm-5.3-flash |
|
||
| agent-jessie | `role/assistant` | glm-5.3-flash |
|
||
| agent-klara | `role/service` | glm-5.3-flash |
|
||
| Leiter auf spark-desktop (xo, xo-chat, teamleiter, worker, kanon, thema, diagnose, logbuch) | `role/xo`, `role/teamleader`, `role/worker`, `role/architect`, `role/archivist`, `role/ops` | glm-5.3-flash |
|
||
| Inka — Hirn | `role/inka` | **fest** `openai/gpt-5.6-luna` |
|
||
| Inka — STT / TTS | `42i/marvin-stt-turbo` / `-tts-turbo` | Voxtral Small / Grok Voice |
|
||
| latenztolerante Masse, `small_model` | `auto/mass` | lokales Qwen, 0 $ |
|
||
|
||
`role/inka` ist bewusst ein fester Modellname statt eines Profils: Die drei
|
||
Achsen messen **keine Antwortlatenz**, und am Telefon entscheidet genau die.
|
||
Gemessen am 2026-09-07 mit Inkas Zuschnitt (17–19k Token Systemprompt, vier
|
||
Züge): Luna 0,9–1,9 s bis zum ersten Wort, `glm-5.3-flash` 1,9–5,6 s. Der Cache
|
||
greift bei beiden; die Zeit geht bei GLM für 70–133 Denk-Token drauf, und
|
||
Reasoning lässt sich dort nicht abschalten (`"Reasoning is mandatory for this
|
||
endpoint"`). Nachmessen: `tools/telefon-latenz.py` in diesem Repo.
|
||
|
||
## Keys und Rechte
|
||
|
||
- Client-Token: `keys.json` (Token → Name; im LXC
|
||
`/srv/llmrouter/data/keys.json`, in der Docker-Demo `./data/keys.json`). Die
|
||
Werte der Personas liegen in OpenBao `agents/<name>/litellm` Feld `key`; es
|
||
sind **dieselben Token wie bei LiteLLM**. Noch ohne Token im Router:
|
||
`inka-mx`, `nightjob`, `lab-xo`, `team-copilot` (LiteLLM kennt nur Hashes).
|
||
Anlegen:
|
||
`KEYS_FILE=keys.json bun run src/keys.ts add <name> [--token <bestehend>]`.
|
||
- Rechte: `key-acls.json` (Name → erlaubte Namen, `*` als Präfix-Muster),
|
||
versioniert in diesem Repo. Elton und Andreas dürfen `openrouter/*` und
|
||
`alt/*`, die Agenten nur ihre Rollen und `:medium`.
|
||
- Upstream-Keys in `.env` (LXC: `/srv/llmrouter/.env`): `OPENROUTER_API_KEY`,
|
||
`MISTRAL_API_KEY`, `KIRA_REASONING_KEY`, `KIRA_STT_KEY`, `KIRA_TTS_KEY`,
|
||
`ARTIFICIALANALYSIS_API_KEY`, `ADMIN_KEY`. Quellen in OpenBao:
|
||
`agents/jessie/litellm` (OpenRouter, AA, Kira) und `agents/jessie/llmrouter`
|
||
(`admin_key`).
|
||
- OpenRouter-Konto: **ZDR-Guardrail bleibt bewusst** (Andreas 05.09.2026).
|
||
Alibaba- und Meta-gehostete Modelle sind dadurch nicht wählbar; der Router
|
||
baut nur auf der Kontoliste auf und kann nichts wählen, was dagegen verstößt.
|
||
|
||
## Log und Kosten
|
||
|
||
Je Anfrage eine Zeile: Key, angefragter Name, aufgelöster Alias, tatsächliches
|
||
Modell, Effort, Tokens, **Kosten wie von OpenRouter gemeldet**. Lokale Ziele
|
||
haben keine Kosten.
|
||
|
||
```bash
|
||
# Tagesstatistik je Key
|
||
curl -s "http://llm.lan:4010/stats?days=7" \
|
||
-H "Authorization: Bearer $(~/.agents/bin/secret get jessie llmrouter admin_key)" | python3 -m json.tool
|
||
# aktuelle Zuordnung aller Stufen und alt/-Referenzen
|
||
curl -s http://llm.lan:4010/zuordnung -H "Authorization: Bearer <ADMIN_KEY>" | python3 -m json.tool
|
||
# einzelne Anfragen / Modellwechsel / Journal
|
||
ssh s18p1.lan 'pct exec 185020 -- tail -20 /srv/llmrouter/data/log/requests.jsonl'
|
||
ssh s18p1.lan 'pct exec 185020 -- cat /srv/llmrouter/data/log/changes.log'
|
||
ssh s18p1.lan 'pct exec 185020 -- podman logs -l -n 50'
|
||
```
|
||
|
||
Die Antwort trägt `x-llmrouter-model`, damit ein Client sieht, was wirklich
|
||
lief. Weitere Log-Wege (SQLite, Stats-Binary): [referenz.md](referenz.md).
|
||
|
||
## Wartung und Deploy
|
||
|
||
Quelle dieses Projekts ist dieses Repo (`spine/llmrouter`). Der Prod-Einsatz
|
||
läuft seit dem **2026-09-09 als OCI-Container** im LXC (podman-compose hinter
|
||
der Unit `llmrouter`, Bild `git.42i.org/spine/llmrouter:latest`, Laufzeitdaten
|
||
unter `/srv/llmrouter/data/`). **Der alte Binary-Weg ist eingestellt** —
|
||
Server-Updates nur noch über das CI-Image; die Sidecar-Binaries
|
||
(`resolve`, `stats`, `keys`) werden nicht mehr gebaut, dafür läuft das
|
||
Werkzeug aus dem Image (siehe Schritt 3).
|
||
|
||
1. Änderung in diesem Repo (Config, Code), committen, pushen. CI baut und
|
||
published danach `git.42i.org/spine/llmrouter:latest`.
|
||
2. Im LXC Bild ziehen und Unit neu starten:
|
||
```sh
|
||
ssh s18p1.lan 'pct exec 185020 -- podman pull git.42i.org/spine/llmrouter:latest'
|
||
ssh s18p1.lan 'pct exec 185020 -- systemctl restart llmrouter'
|
||
```
|
||
Nach Config-Änderungen an Stufen oder `alt/`: `rm /srv/llmrouter/data/log/state.json`
|
||
vor dem Neustart, sonst bleibt die alte Zuordnung. Der Erstlauf fährt für jede
|
||
Stufe die Last-Probe (≈210k Token je Stufe).
|
||
3. Sidecar-Werkzeuge ohne Binary — Beispiel Tagesstatistik:
|
||
```sh
|
||
ssh s18p1.lan "pct exec 185020 -- podman run --rm --env-file /srv/llmrouter/.env \
|
||
-e LLMROUTER_CONFIG=/data/config.json -e KEYS_FILE=/data/keys.json \
|
||
-v /srv/llmrouter/data:/data git.42i.org/spine/llmrouter:latest bun src/stats.ts 7"
|
||
```
|
||
(analog `src/keys.ts`, `src/resolver.ts`)
|
||
4. Rollback auf die Binary (Notfall): `podman-compose down` im
|
||
`/srv/llmrouter`, dann die gesicherte Unit zurück
|
||
(`cp /etc/systemd/system/llmrouter.service.binary-bak /etc/systemd/system/llmrouter.service`,
|
||
`systemctl daemon-reload`, `systemctl enable --now llmrouter`) — die flachen
|
||
Dateien und `log/` liegen unverändert daneben.
|
||
5. **Caddyfile-Falle:** die Datei ist als Bind-Mount im Container; ein `mv`
|
||
tauscht den Inode und `caddy reload` sieht die alte Datei. Nach Änderung
|
||
`oci restart caddy`. Quelle: [caddy/Caddyfile](caddy/Caddyfile).
|
||
|
||
### Umstieg aufs Image (geprüft am 2026-09-08, vollzogen am 2026-09-09)
|
||
|
||
Voraussetzungen alle erfüllt: podman 5.4.2 im LXC (kein Docker nötig —
|
||
Muster wie `caddy.service`: podman-compose + systemd-oneshot), das Image ist
|
||
öffentlich pullbar (anonym, wie `42i/caddy`), Multi-Arch enthält amd64.
|
||
Layout wandert vorab ins Hausmuster: alles Laufzeitige unter `./data`
|
||
(siehe [data/README.md](../data/README.md)), der Compose-Dienst mountet
|
||
`./data:/data`. Einmalig migrieren — **Kopie, nicht Verschieben**, damit die
|
||
Binary-Unit bis zum Umschalten lauffähig bleibt:
|
||
|
||
```sh
|
||
cd /srv/llmrouter
|
||
mkdir data
|
||
cp config.json key-acls.json keys.json model-blocklist.json data/
|
||
cp -a log data/log # state.json + aa-cache.json wandern mit
|
||
```
|
||
|
||
`cp -a log` ist der entscheidende Schritt: `state.json` (Zuordnung) und
|
||
`aa-cache.json` (AA-Index-Cache) liegen im logDir. Mit ihnen startet der
|
||
Container sofort mit frischer Zuordnung; ohne sie gibt es eine Startlücke
|
||
(~40 s bei `LLMROUTER_LASTTEST=0`, sonst ≈210k Token), in der Stufen mit 503
|
||
antworten („noch nicht aufgelöst").
|
||
|
||
```yaml
|
||
services:
|
||
llmrouter:
|
||
image: git.42i.org/spine/llmrouter:latest
|
||
env_file: [.env]
|
||
environment:
|
||
# .env trägt die Host-Pfade — im Container auf /data übersteuern:
|
||
LLMROUTER_CONFIG: /data/config.json
|
||
LLMROUTER_ACLS: /data/key-acls.json
|
||
KEYS_FILE: /data/keys.json
|
||
MODEL_BLOCKLIST: /data/model-blocklist.json
|
||
ports: ["127.0.0.1:4010:4010"] # Caddy zeigt weiter auf localhost:4010
|
||
volumes: ["./data:/data"]
|
||
restart: unless-stopped
|
||
```
|
||
|
||
- `logDir` steht in `config.json` auf `./log` — relativ zur Config
|
||
aufgelöst, im Container also `/data/log`, ohne Config-Änderung.
|
||
- Sidecar-Binaries (`llmrouter-resolve`, `-stats`, `-keys`) bleiben auf dem
|
||
LXC; nach dem Umstieg mit den data/-Pfaden aufrufen
|
||
(`KEYS_FILE=/srv/llmrouter/data/keys.json` …). Die alten flachen Dateien
|
||
(`config.json`, `keys.json`, die `.bak`-Kopien) können erst aufgeräumt
|
||
werden, wenn der Umstieg hält.
|
||
- Ablauf: compose-Datei + Unit (Muster caddy.service) anlegen →
|
||
`podman-compose up -d` → `/health` prüfen (Zeitstempel aktuell) →
|
||
`systemctl stop/disable llmrouter`. Rollback: Binary-Unit wieder
|
||
aktivieren, Container stoppen.
|
||
- Updates danach: `podman pull` + Container neu starten; CI published
|
||
`:latest` (und `:sha-<sha>`) bei jedem Push auf main (paths-Filter —
|
||
Doku-Commits bauen nicht).
|
||
|
||
Prüfstand 2026-09-08 (Probelauf auf dem LXC, Produktion unberührt, Port 4011,
|
||
Log-Dir nach /tmp ausgelagert): Image startet mit der Produktions-Config (25
|
||
Aliasse, 21 Rollen, 12 Token), `/models` **byte-identisch** zur Binary, Chat
|
||
via `/chat/completions` 200 über beide Wege (`42i/marvin-2609:medium` →
|
||
`z-ai/glm-5.3-flash`), komplette Zuordnungs-Auffrischung im Container
|
||
funktioniert. Der Mount ist hostseitig — `./data:/data` ergibt dem Container
|
||
gegenüber denselben `/data`-Inhalt wie der getestete Mount. Container und
|
||
Image danach rückstandsfrei entfernt.
|
||
|
||
## Störung
|
||
|
||
| Symptom | Prüfen | Tun |
|
||
|---|---|---|
|
||
| Agenten bekommen 502/Timeout | `curl http://llm.lan:4010/health`; `podman ps` im LXC | `systemctl restart llmrouter`; `podman logs -l` |
|
||
| `… ist noch nicht aufgelöst (503)` | `/zuordnung` leer oder alt | `POST /auffrischen` mit Admin-Key, oder `rm /srv/llmrouter/data/log/state.json` + Neustart; braucht OpenRouter- und AA-Zugang |
|
||
| 401 für einen Agenten | `podman logs -l` zeigt `401 … (Key n Zeichen)` | Token fehlt in `data/keys.json` → mit `keys.ts add --token` nachtragen |
|
||
| 403 `darf … nicht rufen` | `key-acls.json` | Recht ergänzen (versioniert in diesem Repo), Neustart |
|
||
| Zahlen im TTS falsch gesprochen | Sprechtext-Regel greift nur für PLZ/Nummern | Fish über OpenRouter zerlegt deutsche Beträge/Daten — deshalb Grok Voice bis Fish bei Uwe läuft (live/live#2247) |
|
||
| `llm.lan` antwortet mit LiteLLM-Fehlern statt Router | Caddy zeigt auf 4000 | Caddyfile prüfen, `oci restart caddy` |
|
||
|
||
**Rückfall ohne Router:** Caddyfile auf `localhost:4000` stellen und Caddy neu
|
||
starten — LiteLLM bedient die `2608`-Namen weiter, die `2609`-, `alt/`- und
|
||
`role/`-Namen fehlen dann.
|
||
|
||
## Zugehörige Werkzeuge
|
||
|
||
- `~/src/tools/openrouter-models.ts` (git.home `andreas/tools`): zeigt die
|
||
Stufentabelle oder die Alternative zu einer Modell-ID (`bun run ./openrouter-models.ts anthropic/claude-opus-5:medium`).
|
||
- `~/.agents/bin/opencode-normalize`: erzeugt den `llmlan`-Provider in der
|
||
opencode-Config aus `/v1/models` des Routers, Effort-Stufen als `variants`.
|
||
- `claude-lan` (Funktion in `~/.zshrc`): Claude Code im Terminal über den
|
||
Router; die Desktop-App kann das nicht (sie erzwingt `api.anthropic.com`).
|
||
- Auswahl-Regel: [src/openrouter-auswahl.ts](../src/openrouter-auswahl.ts) —
|
||
seit 2026-09-08 eine eigenständige Kopie (vendored aus `42i/agents`
|
||
`llmlite/`, wo die llmlite-Fassung für den Tier-Updater weiterlebt).
|
||
|
||
## Verlauf
|
||
|
||
- 2026-09-09: **Output-Gate je Profil senkbar, neue Rolle `coder`.** Das
|
||
64K-Output-Gate (MIN_OUTPUT) trieb `auto/mass:fast` auf `solar-pro4`
|
||
(0,42 $/1M), weil ling mit 32768 Output-Token durchfiel — gemessen 09.09.:
|
||
1.415 Masse-Requests, 1.146 davon unter 100 Completion-Token; das Gate
|
||
verfehlte dort seinen Zweck und kostete ~35 % Aufschlag. Jetzt ist
|
||
`minOutput` pro Profil in `config.json` setzbar (Default bleibt 65536),
|
||
`auto/mass:fast` läuft mit 32768. Dazu `role/coder` →
|
||
`auto/coding:junior` neben `engineer`, ACL für `persona-spark`, der
|
||
engineer-Agent in der Mac-opencode.json von `role/worker` auf
|
||
`role/coder` umgezogen. Deploy über CI-Image (Run #8) + `podman pull` +
|
||
Neustart, Configs nach `data/` gesynct, `state.json` gelöscht und frisch
|
||
aufgelöst: `auto/mass:fast` → ling-3.0-flash wiederhergestellt (0,27
|
||
$/1M, Referenz trägt `Output>=32768`), `role/coder` in `/v1/models`, Chat
|
||
je Rolle 200. Seit dem Image-Betrieb antwortet Port 4010 nur auf
|
||
`127.0.0.1` des LXC — von außen nur noch über Caddy (443/4000).
|
||
|
||
- 2026-09-09: **Umstieg aufs Image vollzogen.** Prod läuft als OCI-Container
|
||
(`llmrouter_llmrouter_1`, podman-compose hinter der Unit `llmrouter`, Bild
|
||
`:latest` von Run #7/e144034). Laufzeitdaten wanderten nach
|
||
`/srv/llmrouter/data/` (Hausmuster `./data:/data`), `state.json` und
|
||
`aa-cache.json` mitkopiert — Zuordnung unverändert, keine Auffrischung.
|
||
Verifiziert: `/health` lokal und über `https://llm.lan` 200, Chat 200.
|
||
**Der Binary-Deploy-Weg (bun build, scp, pct push) ist eingestellt**;
|
||
Updates nur noch über CI-Image + `podman pull` + Neustart. Unit-Rollback
|
||
liegt als `llmrouter.service.binary-bak`.
|
||
- 2026-09-08: **Umstieg aufs Image geprüft.** Probelauf des CI-Images auf
|
||
dem LXC neben dem Binary (Port 4011): Konfig-/ACL-/Keys-Parität
|
||
(`/models` byte-identisch), Chat 200 über beide Wege, Zuordnungs-Refresh
|
||
läuft im Container. Rezept und Fallstricke stehen jetzt in
|
||
[Wartung und Deploy](#wartung-und-deploy); Prod läuft weiter auf der
|
||
Binary. Dazu Registry-Login-Fix in der CI (vertauschte Secret-Werte,
|
||
401er am Vormittag) und Login-Kommentar korrigiert.
|
||
- 2026-09-08: **Umzug nach `spine/llmrouter`** (git.42i.org). Das komplette
|
||
Projekt verließ das Repo `42i/agents` (Historie per subtree split bewahrt);
|
||
dazu Docker-Image, Compose-Demo mit `./data`-Mount und Gitea-Actions-CI
|
||
nach dem live-Muster. Die Referenz-Doku liegt im Repo (`README.md`,
|
||
`docs/`), die Betriebsstand-Seite in `info` bleibt als Zeiger.
|
||
- 2026-09-08: **Ausweich-Kette für die Massenarbeit** (42i/agents 4e92f17,
|
||
42i/intern#1252). Anlass: ling-3.0-flash fiel am Vorabend aus (23:20–23:52,
|
||
5× 502 nach ~270 s hängendem Upstream, 117× 429). `auto/mass:fast` hat jetzt
|
||
selbst einen Ausweich auf `auto/coding:junior`, und der Ausweich greift auch
|
||
bei 429/5xx-Antworten — vorher nur bei fetch-Fehlern und nur für fest-Ziele.
|
||
ACLs der Container-Personas um die Kettenziele ergänzt.
|
||
- 2026-09-08: **Live-Probe reasoning-tolerant** (42i/agents 312e43c). ling
|
||
denkt vor jedem Werkzeugaufruf mit variabler Länge; bei `max_tokens: 400`
|
||
scheiterte die Probe willkürlich und kippte `marvin-2609:low` und
|
||
`alt/claude-haiku-4.5` auf teurere Modelle (solar-pro4 bzw. glm-5.3-flash),
|
||
ohne dass ling defekt war. Budget auf 2000 Token, Timeout auf 90 s.
|
||
- 2026-09-07: **LiteLLM abgeschaltet, alles läuft über Rollen.** Alle Agenten
|
||
(Container spark/buzz/jessie/klara, die Leiter auf spark-desktop, der XO) rufen
|
||
`role/*` statt eines Modellnamens; der XO und das Logbuch kamen dabei vom
|
||
Claude-Max-Abo auf opencode. Der `fallback` an LiteLLM ist aus der Config
|
||
entfernt — unbekannte Namen sind jetzt 404 statt einer stillen Weiterleitung.
|
||
Keine Rolle steht mehr auf der teuren Stufe (42i/intern#1252).
|
||
- 2026-09-07: **Caddy aus dem LiteLLM-Stack gelöst** und eigener oci-Dienst
|
||
(`/srv/caddy`, Quelle `llmrouter/caddy/`, ausgerollt über
|
||
`agents-pull`). Vorher hing er in `llmlite/compose.yml` — ein `podman-compose
|
||
down` für LiteLLM hätte die TLS-Terminierung von `llm.lan` mitgenommen und den
|
||
Router von außen unerreichbar gemacht, obwohl er läuft. Vorarbeit zur
|
||
Abschaltung von LiteLLM (42i/intern#1252).
|
||
- 2026-09-05: Router gebaut und in Betrieb, `llm.lan` umgestellt; Stufen
|
||
25/45/49/53; Namensräume `openrouter/`, `alt/`, `role/`, `deizo/`; Claude-Code-Pfad.
|
||
- 2026-09-06: Sprechtext-Normalisierung (Postleitzahl); TTS-Turbo auf Grok
|
||
Voice `ara`, STT-Turbo auf Voxtral Small 24B nach Hör- und Telefontest;
|
||
Auftrag an Uwe für Fish S2 Pro und Voxtral Small im Prod-RZ (live/live#2247).
|