Qwen3 kann natives OpenAI-Tool-Calling; llama.cpp (--jinja) unterstuetzt es. Lokales Tier bekommt einen kleinen, risikoarmen Start-Satz (Wetter, memory_search, trigger_timer, Spotify, Licht); volles Arsenal bleibt bei Claude, Escalation-Netz bleibt. Klein halten = Speed. Plan-Entscheidungen + Phasen entsprechend gezogen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
164 lines
8.4 KiB
Markdown
164 lines
8.4 KiB
Markdown
# Plan B — Lokaler LLM-Router (Gamebox) neben Claude
|
||
|
||
**Ziel:** „Gemini-Feeling" für den Alltag, ohne die Claude-Max-Subscription
|
||
aufzugeben. Ein schnelles lokales LLM beantwortet die einfachen ~80 % der Turns
|
||
in <1 s; nur die schweren 20 % (Tiefe, Code, Tools, Pentest, langer Kontext)
|
||
gehen an Claude. Claude bleibt das Tiefen-Hirn.
|
||
|
||
## Warum das der einzige realistische Weg zu „live" ist
|
||
|
||
Gemessen (10.07.2026): CLI-Round-trip über den Claude-Max-Proxy hat einen
|
||
**harten Boden von ~3,5 s** (Subprozess-Start pro Turn). Streaming-API würde das
|
||
brechen, kostet aber API-Geld → verliert die Max-Subscription. Ein lokales
|
||
LLM für die einfachen Turns umgeht den 3,5-s-Boden komplett und ist **gratis**
|
||
(läuft auf vorhandener Gamebox-GPU). Echtes Speech-to-Speech-Duplex (Gemini
|
||
Live nativ) ist mit einem Text-Modell als Hirn prinzipiell nicht drin.
|
||
|
||
## Modell & Serving (entschieden)
|
||
|
||
- **Modell:** Qwen3 8B, GGUF **Q4_K_M** (~6 GB). Bestes Tool-Calling der 7/8B-
|
||
Klasse, solides Deutsch, Apache-2.0. Alt.: Mistral Small 3 7B (schneller,
|
||
weniger Tool-Calling).
|
||
- **Serving:** **llama.cpp `llama-server`** im Docker-Container auf der Gamebox
|
||
(kein Ollama nötig — nativer OpenAI-kompatibler `/v1/chat/completions`).
|
||
- **VRAM-Budget:** 12-GB-Karte, Whisper-small (~1–2 GB) + F5-TTS (~1–2 GB) →
|
||
~8–9 GB frei → passt. (FLUX ist auf 12 GB eh raus.)
|
||
|
||
## Anbindung: über den RVS, wie TTS/STT (kein IP-Pflegen)
|
||
|
||
Die Gamebox ist ein anderer Host als das Brain. Statt direktem HTTP (IP/Port/
|
||
Firewall) läuft das LLM **über den RVS-Token-Room**, exakt wie Whisper/F5-TTS:
|
||
|
||
- llama.cpp hört nur auf localhost der Gamebox.
|
||
- Ein **dünner RVS-Adapter** daneben (Vorbild: whisper-/xtts-Bridge) verbindet
|
||
sich mit dem RVS-Token, lauscht auf `llm_request`, ruft lokal llama-server,
|
||
schickt `llm_response` (korreliert per requestId) zurück.
|
||
- `rvs/server.js` `ALLOWED_TYPES` um `llm_request`, `llm_response` und (Phase 2)
|
||
`llm_partial` erweitern.
|
||
- Das Brain bekommt einen zweiten „Proxy" — nur über RVS statt direktem HTTP.
|
||
|
||
## Router-Logik im Brain
|
||
|
||
Reihenfolge pro Turn (früh raus = schnell):
|
||
|
||
- **Tier 0 — Fast-Path (existiert):** reine Steuerbefehle (Spotify, Licht) →
|
||
Skill direkt, **kein LLM**. <1 s.
|
||
- **Tier 1 — Lokal (Qwen3):** einfache Konversation, kurze Fakten, Smalltalk,
|
||
Bestätigungen. Ziel <1 s.
|
||
- **Tier 2 — Claude:** tief/technisch, Code, Tool-Use nötig, Pentest-Projekt,
|
||
langer/komplexer Kontext.
|
||
|
||
**Routing-Signal (heuristisch zuerst, deterministisch & schnell):**
|
||
Nachrichtenlänge, Schlüsselwörter, ob ein Tool nötig scheint, Projekt-Kontext
|
||
(Pentest-Projekt → immer Claude), Konversationstiefe.
|
||
|
||
**Escalation statt perfekter Vorab-Klassifikation:** Das lokale Modell bekommt
|
||
die Anweisung, bei Unsicherheit oder Tool-Bedarf **NICHT zu raten**, sondern zu
|
||
eskalieren (z.B. Antwort `<<ESCALATE>>`). Das Brain routet den Turn dann an
|
||
Claude. So sind Fehlklassifikationen billig — lieber einmal lokal→Claude als
|
||
eine falsche lokale Antwort.
|
||
|
||
**Modus „Nur lokales LLM" (Diagnostic-Checkbox, Eval-Schalter):** Ein Flag
|
||
`localLlmOnly` (in Diagnostic setzbar, vom Brain beim Routen gelesen). Ist es an:
|
||
JEDER Turn geht ans lokale LLM, `<<ESCALATE>>` / „zu schwer" werden ignoriert
|
||
(kein Claude-Fallback) — damit Stefan die echte Staerke/Schwaeche des lokalen
|
||
Modells sieht, ohne dass Claude die schweren Turns rettet. Haken aus = normale
|
||
Heuristik + Escalation. Ehrlicher Hinweis: im Nur-lokal-Modus funktionieren
|
||
werkzeug-abhaengige Turns (Wetter, Timer, Memory, Bild) nicht — das lokale Tier
|
||
hat keine Tools; das ist ein Gespraechs-Eval-Modus, kein Voll-ARIA. Fast-Path
|
||
(Spotify etc.) laeuft davon unberuehrt weiter.
|
||
|
||
## Persona auf BEIDEN Modellen
|
||
|
||
Das lokale Modell braucht ARIAs Identität, sonst bricht es aus der Rolle
|
||
(gelernt aus dem `--system-prompt`-Debakel). Aber **schlanker**:
|
||
- IDENTITY_SEED + Kern-Persona: ja.
|
||
- Volles Memory / ALLE Skill-Schemas: **nein** — nur eine **kuratierte, kleine
|
||
Tool-Auswahl** (siehe unten). Haelt den lokalen Prompt klein → schnell.
|
||
- Persona kommt lokal auch als echter System-Prompt (llama.cpp `system`-Rolle).
|
||
|
||
## Tool-Calling lokal (kuratierte Auswahl)
|
||
|
||
Das lokale LLM DARF Werkzeuge nutzen (Qwen3 = natives OpenAI-Tool-Calling, von
|
||
llama.cpp `--jinja` unterstuetzt). Ablauf wie bei Claude: Brain schickt
|
||
messages + tools → Qwen antwortet mit `tool_calls` → Brain fuehrt via
|
||
`_dispatch_tool` aus → Ergebnis zurueck → finale Antwort. Tool-Loop im Brain,
|
||
Ziel = lokales LLM statt Claude-Proxy.
|
||
|
||
**Kritische Regel: klein halten.** Der Wert des lokalen Tiers ist Speed; das
|
||
volle Arsenal (~49 K Zeichen Schemas) wuerde den Prompt sprengen und das kleine
|
||
Modell ueberfordern. Daher nur ein **Start-Satz haeufiger, risikoarmer Tools**:
|
||
- Lokal: Wetter, Uhrzeit, `memory_search` (lesen), `trigger_timer`,
|
||
Spotify-Steuerung (Nicht-Fast-Path-Saetze), Licht/Smart-Home.
|
||
- Bleibt bei Claude: `skill_create/update/delete`, `oauth_*`, `project_*`,
|
||
`flux_generate`, `memory_save` (schreiben) — erst spaeter evtl. lokal.
|
||
|
||
Escalation-Netz bleibt: braucht ein Turn ein Tool, das lokal nicht dabei ist →
|
||
`<<ESCALATE>>` → Claude mit vollem Arsenal.
|
||
|
||
Implementierung (B1): Adapter reicht `tools` an llama.cpp + gibt `tool_calls`
|
||
zurueck; Bridge schleust beides durch (llm_request/llm_response); Brain-Tool-Loop
|
||
mit Ziel lokal.
|
||
|
||
## Phasen
|
||
|
||
- **B0 — Infra:** llama.cpp-Container + RVS-Adapter auf der Gamebox,
|
||
`ALLOWED_TYPES`, `local_llm_chat()` im Brain. Isoliert testen („sag hallo").
|
||
- **B1 — Router + lokale Tools:** Heuristik Tier-1/2 + Escalation, schlanke
|
||
Persona lokal, **kuratierte Tool-Auswahl lokal** (Adapter/Bridge/Brain-Tool-
|
||
Loop, siehe oben) + „Nur lokales LLM"-Checkbox. Einfache Turns → lokal.
|
||
Messen: Trefferquote, Tool-Zuverlaessigkeit & Latenz.
|
||
- **B2 — Streaming/Voice:** `llm_partial` → TTS beginnt beim ersten Satz →
|
||
der „live"-Sprung. **Hier den Gong-/Ohr-Re-Arm-Bug mit-fixen** (Barge-In,
|
||
sauberes Re-Listen).
|
||
- **B3 (optional):** lokalen Tool-Satz erweitern, sobald Qwen sich als
|
||
zuverlaessig erweist (z.B. `memory_save`).
|
||
|
||
## Offene Entscheidungen (für Stefan)
|
||
|
||
1. **Modell:** Qwen3 8B (Tool-Calling) — oder doch Mistral Small 3 7B (Speed)?
|
||
2. **Routing v1:** rein heuristisch + Escalation (entschieden).
|
||
3. **Tools lokal:** kuratierte kleine Auswahl (entschieden — Start-Satz oben;
|
||
Stefan bestaetigt/justiert die konkrete Liste vor dem B1-Bau).
|
||
|
||
## Folge-Baustein: Modell-Auswahl in ARIA Diagnostic (B0.5)
|
||
|
||
Ziel: In Diagnostic ein Modell auswählen; ist es nicht da, lädt der Container
|
||
es on-demand und aktiviert es. Spiegelt zwei bestehende Muster: den
|
||
`whisperModel`-Hotswap (RVS-Config-Broadcast → Bridge hot-swapped) und die
|
||
kuratierte Claude-Tier-Liste aus `models.json`.
|
||
|
||
**Kernproblem:** `llama.cpp`-Server serviert **ein** Modell pro Prozess —
|
||
„anderes aktivieren" = neu laden/swappen.
|
||
|
||
**Lösung: `llama-swap`** (Proxy vor llama.cpp): kennt eine Liste von Modellen,
|
||
lädt bei Anfrage das gewünschte on-demand (Download via `-hf` beim ersten Mal),
|
||
swappt bei VRAM-Knappheit das alte raus. OpenAI-kompatibel — der llm-adapter
|
||
zeigt statt auf `llama:8081` auf `llama-swap`.
|
||
|
||
**Bausteine:**
|
||
- `llama-swap`-Service in `xtts/docker-compose.yml` (ersetzt/ergänzt `llama`),
|
||
Config mit den verfügbaren Modellen (Name → `-hf`-Command).
|
||
- Kuratierte Liste `local_models.json` (analog `models.json`) — Diagnostic-UI
|
||
liest sie, zeigt Dropdown „Lokales Modell".
|
||
- Diagnostic → RVS-Config-Broadcast `localLlmModel` → llm-adapter setzt das
|
||
`model`-Feld seiner llama-swap-Requests → swap/Download passiert automatisch.
|
||
- Status zurück an Diagnostic (lädt / bereit / VRAM-OOM), analog whisper-Status.
|
||
|
||
**Konkret gewünschte UI (Stefan):**
|
||
- Modell-Status sichtbar: **lädt (mit Fortschrittsbalken) → heruntergeladen →
|
||
aktiviert**. Ist ein Modell schon im Cache: **nicht neu laden, nur
|
||
aktivieren** (llama.cpp/llama-swap macht das nativ ueber den Cache).
|
||
- **Testchat-Zeile** in Diagnostic: kurze Nachricht direkt ans lokale LLM
|
||
schicken, Antwort + Latenz anzeigen. Nutzt denselben RVS-Pfad
|
||
(`llm_request`/`llm_response`) wie der Self-Test — kein neuer Kanal noetig.
|
||
|
||
Bis dahin: **ein** Modell via `-hf` Auto-Download (B0, erledigt). Erst end-to-end
|
||
grün, dann dieser Komfort-Layer.
|
||
|
||
## Nicht-Ziele
|
||
|
||
- Kein echter Gemini-Live-Duplex-Klon (Text-Modell als Hirn).
|
||
- FLUX bleibt optional/später (dickere GPU). Bild-Generierung separat als
|
||
pluggbarer Provider (ChatGPT/DALL·E-Alternative) — eigenes Feature, nicht Teil B.
|