Das 64K-Output-Gate war global und trieb auto/mass:fast auf solar-pro4 (0,42 statt 0,27 $/1M), weil ling-3.0-flash 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. Jetzt kann je Schwelle-Profil minOutput gesetzt werden (Default 65536); auto/mass:fast laeuft mit 32768, max_output unbekannt bleibt abgelehnt. Dazu role/coder -> auto/coding:junior (ACL fuer persona-spark), damit Code-Arbeit nicht ueber role/worker in die Masse faellt; der engineer-Agent der Mac-opencode.json wechselt auf role/coder. Refs #1283
20 KiB
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.
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. 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 |
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 OpenBaoagents/<name>/litellmFeldkey; 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ürfenopenrouter/*undalt/*, 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) undagents/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.
# 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.
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).
- Änderung in diesem Repo (Config, Code), committen, pushen. CI baut und
published danach
git.42i.org/spine/llmrouter:latest. - Im LXC Bild ziehen und Unit neu starten:
Nach Config-Änderungen an Stufen oder
ssh s18p1.lan 'pct exec 185020 -- podman pull git.42i.org/spine/llmrouter:latest' ssh s18p1.lan 'pct exec 185020 -- systemctl restart llmrouter'alt/:rm /srv/llmrouter/data/log/state.jsonvor dem Neustart, sonst bleibt die alte Zuordnung. Der Erstlauf fährt für jede Stufe die Last-Probe (≈210k Token je Stufe). - Sidecar-Werkzeuge ohne Binary — Beispiel Tagesstatistik:
(analog
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"src/keys.ts,src/resolver.ts) - Rollback auf die Binary (Notfall):
podman-compose downim/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 undlog/liegen unverändert daneben. - Caddyfile-Falle: die Datei ist als Bind-Mount im Container; ein
mvtauscht den Inode undcaddy reloadsieht die alte Datei. Nach Änderungoci restart caddy. Quelle: 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), der Compose-Dienst mountet
./data:/data. Einmalig migrieren — Kopie, nicht Verschieben, damit die
Binary-Unit bis zum Umschalten lauffähig bleibt:
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").
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
logDirsteht inconfig.jsonauf./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→/healthprü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.homeandreas/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 denllmlan-Provider in der opencode-Config aus/v1/modelsdes Routers, Effort-Stufen alsvariants.claude-lan(Funktion in~/.zshrc): Claude Code im Terminal über den Router; die Desktop-App kann das nicht (sie erzwingtapi.anthropic.com).- Auswahl-Regel: src/openrouter-auswahl.ts —
seit 2026-09-08 eine eigenständige Kopie (vendored aus
42i/agentsllmlite/, 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) triebauto/mass:fastaufsolar-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 istminOutputpro Profil inconfig.jsonsetzbar (Default bleibt 65536),auto/mass:fastläuft mit 32768. Dazurole/coder→auto/coding:juniornebenengineer, ACL fürpersona-spark, der engineer-Agent in der Mac-opencode.json vonrole/workeraufrole/coderumgezogen. -
2026-09-09: Umstieg aufs Image vollzogen. Prod läuft als OCI-Container (
llmrouter_llmrouter_1, podman-compose hinter der Unitllmrouter, Bild:latestvon Run #7/e144034). Laufzeitdaten wanderten nach/srv/llmrouter/data/(Hausmuster./data:/data),state.jsonundaa-cache.jsonmitkopiert — Zuordnung unverändert, keine Auffrischung. Verifiziert:/healthlokal und überhttps://llm.lan200, 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 alsllmrouter.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 (
/modelsbyte-identisch), Chat 200 über beide Wege, Zuordnungs-Refresh läuft im Container. Rezept und Fallstricke stehen jetzt in 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 Repo42i/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 ininfobleibt 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:fasthat jetzt selbst einen Ausweich aufauto/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: 400scheiterte die Probe willkürlich und kipptemarvin-2609:lowundalt/claude-haiku-4.5auf 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. Derfallbackan 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, Quellellmrouter/caddy/, ausgerollt überagents-pull). Vorher hing er inllmlite/compose.yml— einpodman-compose downfür LiteLLM hätte die TLS-Terminierung vonllm.lanmitgenommen 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.lanumgestellt; Stufen 25/45/49/53; Namensräumeopenrouter/,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).