Lokale KI-Plattform

Dieses private Repository dokumentiert und installiert die reproduzierbare KI-Umgebung rund um einen lokalen llama.cpp-Host. Der AI Profile Router bleibt der zentrale Bestandteil, ist aber nicht mehr die einzige Komponente.

Plattform auf einen Blick

Clients (Open WebUI, Hermes, Apps)
                 |
                 v
AI Profile Router :8081
  |-- qwen-fast   (72K, maximale Geschwindigkeit)
  |-- qwen-medium (92K, reines IQ4_XS)
  |-- qwen-long   (128K, CPU-Offload)
  |-- Vision-Hotswap
  |-- lokale Bildgenerierung
  |-- Whisper STT
  `-- XTTS TTS
                 |
                 v
llama.cpp :8080 + profilabhängige MCP-Server

Enthalten

  • produktiver Router samt Tests und Deployment
  • fest definierte Fast-/Medium-/Long-Profile
  • reproduzierbarer llama.cpp-Build über einen festgelegten Commit
  • systemd-Vorlagen und Profilumschaltung
  • Modellmanifest ohne Modelldateien
  • sichere MCP-Beispielkonfiguration ohne Zugangsdaten
  • Installations-, Betriebs-, Sicherheits- und Migrationsanleitung
  • Prüfskript für einen frisch installierten Host

Bewusst nicht enthalten

  • GGUF-, Whisper-, FLUX- oder XTTS-Modelldateien
  • API-Schlüssel, Tokens, SSH-Schlüssel oder Zertifikate
  • Chats, Prompts, Logs, Bilder oder Audiodateien
  • alte Benchmarks, experimentelle Builds und ausgemusterte RX-Dienste
  • hostgebundene Backups und Cache-Verzeichnisse

Dokumentation

AI Profile Router

Kleiner OpenAI-kompatibler Proxy (Python, nur Standardbibliothek) vor einem lokalen llama.cpp-Server. Er leitet normale OpenAI-Requests transparent weiter (Streaming, Tool Calls, JSON), schaltet zwischen drei festen llama.cpp-Profilen um, orchestriert lokale Bildgenerierung mit FLUX.2 [klein] 4B Base (GPU-Hotswap: Qwen stoppen → FLUX laden → Bild → FLUX entladen → Qwen wiederherstellen) und stellt lokale deutsche Sprachausgabe bereit (XTTS-v2, CPU-only, OpenAI-kompatibel).

Zielsystem

Host frei wählbarer Linux-KI-Host
SSH administrativer Zugang nur für Installation und Wartung
llama.cpp http://127.0.0.1:8080 (Service mike-ai-llama-ui.service)
Profil-Skript /usr/local/bin/llama-profile {fast|medium|long}
Router-Port 8081
Router-Service mike-ai-profile-router.service
TTS-Worker http://127.0.0.1:8085 (Service mike-ai-xtts.service)
STT-Worker http://127.0.0.1:8084 (Service mike-ai-whisper.service)

Profile / virtuelle Modelle

Profil Modell Kontext
fast qwen-fast 73728
medium qwen-medium 94208
long qwen-long 131072

Endpunkte

Endpunkt Beschreibung
GET /v1/models Die drei virtuellen Modelle inkl. context_length/context_window
GET /health öffentliche Liveness-Prüfung des Routerprozesses
GET /ready öffentliche Readiness-Prüfung von Router + Textmodell
GET /status Aktives Profil, Upstream-Zustand, Modell, Kontext, Uptime
POST /fast /medium /long expliziter Profilwechsel
POST /v1/chat/completions Weiterleitung an llama.cpp (Streaming + Tool Calls)
POST /v1/images/generations Bildgenerierung (FLUX.2 [klein] 4B Base, OpenAI-kompatibel)
GET /images Liste der aufbewahrten Bilder
GET /images/<datei> PNG-Download (nur images/-Verzeichnis, validiert)
POST /v1/audio/speech Sprachausgabe (XTTS-v2, OpenAI-kompatibel)
POST /v1/audio/transcriptions Deutsche Spracherkennung (whisper.cpp, OpenAI-kompatibel)
GET /v1/audio/models Verfügbare Audio-Modelle (STT + TTS)
GET /v1/audio/voices Verfügbare TTS-Stimmen
POST /vision/test Direkter Vision-Test (Bild + Frage → Q3-Analyse)
alles andere Transparente Weiterleitung an llama.cpp

Bis auf GET /health und GET /ready benötigen alle Endpunkte einen Router-API-Key als Authorization: Bearer … oder X-API-Key. Der Installer erzeugt ihn einmalig in /etc/mike-ai/router-api-key und gibt ihn niemals im Installationslog aus. Ein fehlender oder zu kurzer Schlüssel verhindert den Produktivstart (fail closed).

Verhalten

  • Virtuelles Modell (qwen-fast/qwen-medium/qwen-long in chat.completions): Der Router stellt sicher, dass das passende Profil aktiv ist (notfalls Wechsel + Warten), ersetzt das Modell durch das echte llama.cpp-Modell und leitet weiter.
  • Profilwechsel (POST /fast …): Führt /usr/local/bin/llama-profile <profil> aus (ohne Shell, feste Argumente → keine Injection), wartet dann, bis llama.cpp wieder erreichbar ist, und liefert erst dann 200.
  • Impliziter Wechsel bei downem llama.cpp: Ein Chat-Request mit virtuellem Modell liefert sofort 502, wenn das Profil bereits aktiv ist, aber llama.cpp down ist (kein stiller Neustart). Der Neustart wird explizit über POST /<profil> angestoßen.
  • Ungültige Profile/Modelle: POST /<anderes> → 400; qwen-<anderes> als Modell → 400. Nur die drei festen Profile sind schaltbar.
  • Fehlerformat: OpenAI-kompatibel ({"error": {"message", "type", "code"}}).

Bildgenerierung (FLUX.2 [klein] 4B Base)

Der Router orchestriert lokale Bildgenerierung mit black-forest-labs/FLUX.2-klein-base-4B (Apache 2.0, ~13 GB, bf16 + CPU-Offload). Da Qwen (llama.cpp) und FLUX denselben GPU/VRAM teilen, macht der Router einen GPU-Hotswap:

  1. Zentrales GPU/Modell-Lock übernehmen (Profilwechsel und Bild teilen sich dasselbe Lock → kein Race).
  2. Aktives Qwen-Profil merken.
  3. mike-ai-llama-ui.service stoppen, warten bis Port + VRAM frei sind.
  4. FLUX-Worker starten (eigener Prozess, Flux2KleinPipeline, bf16 + enable_model_cpu_offload()), Bild generieren, PNG speichern.
  5. Worker beenden (nicht nur entladen), VRAM-Freiheit verifizieren.
  6. Vorheriges Qwen-Profil exakt wiederherstellen, Readiness-Check (Modell geladen + Kontext passt).
  7. Erst dann antworten und das GPU-Lock freigeben.

Endpunkt POST /v1/images/generations

OpenAI-kompatibel. Unterstützt prompt, size, n, seed, quality, response_format.

Parameter Werte Default
prompt Text (Pflicht) –
size 1024x1024, 1536x1024, 1024x1536, 1920x1088, 1088x1920 1024x1024
n 1–4 1
seed int (reproduzierbar) zufällig
quality standard (30 Steps), high (50 Steps) standard
response_format url (Default), b64_json url

Beispiel:

curl -s http://AI_HOST:8081/v1/images/generations \
  -H "Authorization: Bearer $ROUTER_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"ein roter Würfel auf weißem Grund","size":"1024x1024","quality":"standard"}'

Die Antwort enthält data[].url (absolute URL, über den Router abrufbar) und data[].b64_json (optional). Jedes Bild wird unter /opt/mike-ai/ai-profile-router/images/ gespeichert (kollisionsfreie Namen, img-<zeitstempel>-<seed>-<i>.png) und ist über GET /images/<datei> abrufbar.

Verhalten während eines Bild-Jobs

  • Chat-Requests warten (kein 502): Der Router merkt sich, dass Qwen vorübergehend nicht verfügbar ist (qwen.available=false), und Chat-Requests warten, bis Qwen wieder bereit ist (Timeout CHAT_WAIT_TIMEOUT, Default 300 s). So gibt es keine 502 llama.cpp nicht erreichbar während des Hotswaps.
  • Profilwechsel warten: Ein Profilwechsel während eines Bild-Jobs blockiert auf dem GPU-Lock, bis der Bild-Job fertig ist (kein Race).
  • /status zeigt den aktuellen Zustand: image.phase (idle, stopping-qwen, loading-image, generating, unloading-image, restoring-qwen), image.worker, image.model_loaded, image.last_image, image.last_seconds, image.last_error, qwen.available, qwen.active_chats.

Recovery (robust)

  • try/finally: Qwen wird immer wiederhergestellt, egal ob die Bildgenerierung erfolgreich war, fehlgeschlagen ist (OOM, Python-Fehler, ungültiger Prompt, Speichern-Fehler, Client-Disconnect, Timeout) oder der Worker abstürzt.
  • Worker-Beendigung: Nach jedem Job wird der Worker beendet (SIGTERM → SIGKILL), nicht nur entladen. So wird der VRAM (inkl. CUDA-Kontext) frei.
  • VRAM-Check: Nach dem Worker-Beenden wartet der Router, bis der VRAM unter 1000 MiB fällt (nvidia-smi), bevor Qwen neu startet.
  • Qwen-Readiness: Nach dem Neustart wartet der Router, bis llama.cpp erreichbar ist, das Modell geladen ist und der Kontext zum Profil passt.
  • qwen.available: Bleibt false, wenn die Wiederherstellung fehlschlägt (Chat-Requests warten weiter, statt 502 zu liefern). Der Fehler wird in image.last_error und im Log protokolliert.

Benchmarks (RTX 5080, 16 GB, CPU-Offload, gemessen)

Auflösung Steps Zeit Peak-VRAM (torch)
512×512 10 ~9.3 s ~8.4 GB
1024×1024 30 ~31.3 s ~8.4 GB
1024×1024 50 ~45.3 s ~8.4 GB
1920×1088 50 ~91 s ~8.9 GB

Entscheidung: standard = 30 Steps (Default, ~31 s bei 1024×1024), high = 50 Steps (maximale Qualität, ~45 s bei 1024×1024). Ab 20–30 Steps ist der Qualitätsgewinn bei einfachen Motiven gering; 50 Steps lohnt sich für komplexe Szenen.

Hinweis: FLUX.2 [klein] 4B Base passt nicht vollständig GPU-resident in 16 GB (OOM bei ~15.5 GB). Deshalb wird enable_model_cpu_offload() verwendet (Modelle werden pro Layer zwischen CPU und GPU gewechselt).

Hotswap-Gesamtzeit: Ein vollständiger Bild-Job (Qwen stoppen → FLUX laden → Bild → FLUX entladen → Qwen wiederherstellen) dauert ~41–42 s bei 1024×1024 / 30 Steps (davon ~31 s Generierung, ~10 s Qwen-Stop/Start + VRAM-Check).

Erforderliche Python-Pakete (im Venv)

  • torch (2.11.0+cu128, CUDA 12.8)
  • diffusers (0.40.0.dev0, für Flux2KleinPipeline)
  • transformers (5.15.0)
  • accelerate (1.14.0, für enable_model_cpu_offload())

Das Venv liegt unter /opt/mike-ai/ai-profile-router/venv/ und wird von install.sh automatisch angelegt/aktualisiert.

Vision (Q3 "Augen")

Bilder in POST /v1/chat/completions werden automatisch analysiert, ohne dass das Hauptmodell (Fast/Medium/Long) ein Vision-Modell braucht:

  1. Neues Bild (in der letzten User-Message, noch nicht analysiert): Der Router entlädt das Hauptprofil, startet kurzzeitig einen Q3-Vision-Server (llama.cpp + mmproj, Port VISION_PORT), analysiert das Bild und stellt das Hauptprofil wieder her. Die Analyse wird im internen Cache (keyed by Bild-Hash) gespeichert.
  2. Finale Antwort: Das (wiederhergestellte) Hauptmodell erzeugt die sichtbare Antwort. Die Vision-Analyse wird als interne User-Message injiziert – sie erscheint nie als Assistant-Turn in Open WebUI.
  3. Folgefragen: Open WebUI schickt den multimodalen Verlauf erneut. Der Router ersetzt alle Bild-Parts durch die gecachte Analyse (klar gekennzeichnet) und entfernt Base64/URLs komplett – das Nicht-Vision-Hauptmodell bekommt also keine Bilddaten mehr (kein image input is not supported). Bereits analysierte Bilder triggern keinen neuen Hotswap (Cache-Treffer).
  • Zentrale GPU-Lock: Vision und FLUX schließen sich gegenseitig aus (kein gleichzeitiges Modell-Laden).
  • Fehlerbehandlung: Bei jedem Fehler wird das Hauptprofil wiederhergestellt; finally dient nur als Cleanup-Sicherung.
  • Test: POST /vision/test mit {"image_url": "...", "question": "..."} liefert die Analyse direkt (ohne finale Hauptmodell-Antwort).

Verhalten während eines Vision-Jobs

  • GET /status → vision.phase (idle, stopping-main, loading-vision, analyzing, unloading-vision, restoring-main) und vision.analysis_cache_size.
  • /v1/streams/lookup antwortet fail-fast ([]), statt zu blockieren.
  • Timing-Log: Vision-Timing: main_unload=… vision_load=… vision_infer=… vision_unload=… main_restore=… | Gesamt … s.

Sprachausgabe (XTTS-v2, CPU-only)

Der Router stellt lokale Sprachausgabe bereit. Die Synthese läuft in einem separaten, langlebigen Worker (mike-ai-xtts.service), der das XTTS-v2-Modell einmalig beim Start lädt und dauerhaft im RAM hält (niedrige Warm-Start-Latenz). Der Worker ist CPU-only und blockiert weder Qwen/llama.cpp noch FLUX/GPU – er teilt sich nur den Prozessor.

  • Modell: Coqui XTTS-v2 (tts_models/multilingual/multi-dataset/xtts_v2, ~1.9 GB, CPML-Lizenz).
  • Stimme: claribel (Claribel Dervla) – natürliche weibliche Stimme, unterstützt Deutsch und Englisch.
  • Venv: eigenes Venv unter /opt/mike-ai/xtts/venv/ mit Python 3.11 (Coqui TTS unterstützt kein Python 3.13) und CPU-only torch.
  • Modelle: HuggingFace-Cache unter /opt/mike-ai/xtts/.cache/ (persistent).

Endpunkt POST /v1/audio/speech

OpenAI-kompatibel. Unterstützt input (oder text), voice, speed, response_format, model.

Parameter Werte Default
input Text (Pflicht, max. 8000 Zeichen) –
voice claribel claribel
speed 0.5–2.0 1.0
response_format mp3 (Default), wav mp3
model xtts-v2 (optional) –

Die Antwort ist binäres Audio (nicht JSON) mit passendem Content-Type (audio/mpeg, audio/wav).

Beispiele:

# MP3 (Default), Stimme claribel
curl -s http://AI_HOST:8081/v1/audio/speech \
  -H "Authorization: Bearer $ROUTER_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"input":"Hallo, dies ist ein Test.","voice":"claribel"}' -o out.mp3

# WAV, 1.5x Tempo
curl -s http://AI_HOST:8081/v1/audio/speech \
  -H "Authorization: Bearer $ROUTER_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"input":"Guten Tag.","voice":"claribel","speed":1.5,"response_format":"wav"}' -o out.wav

Lange Texte: XTTS-v2 verarbeitet den Text intern in Sätze. Längere Texte werden automatisch in Segmente geteilt; jedes Segment wird einzeln synthetisiert und die Audios zusammengeführt.

Verhalten

  • Kein GPU-Lock: TTS läuft CPU-only und greift nicht in den GPU-Hotswap (Bild) oder Profilwechsel (Qwen) ein. TTS-Requests können parallel zu Chats laufen.
  • Serialisierte Synthese: Der Worker synthetisiert nacheinander (CPU-bound), parallele Requests werden intern gewartet.
  • /status zeigt tts.reachable, tts.ready, tts.voices, tts.last_seconds, tts.last_voice, tts.last_error.
  • Fehler: Worker down → 503 (tts_failed); ungültige Parameter → 400. OpenAI-kompatibles Fehlerformat.

Benchmark (CPU-only, gemessen)

Textlänge Audio Synthese RTF
~6 s 6.2 s 8.2 s 1.32×
~15 s 15.0 s 19.8 s 1.32×

RTF ~1.32 bedeutet: Synthese ist ~1.3× langsamer als Echtzeit. RAM-Belegung des Workers: ~4.5 GB (inkl. torch, XTTS-v2-Modell).

Erforderliche Python-Pakete (im XTTS-Venv)

  • torch (CPU-only, --index-url https://download.pytorch.org/whl/cpu)
  • torchaudio (CPU-only)
  • TTS (0.22.0, Coqui XTTS)
  • transformers (4.40.2, für BeamSearchScorer)
  • tokenizers (0.19.1)
  • huggingface-hub (0.36.2)
  • librosa (Audio-Verarbeitung)
  • soundfile (WAV-Export)

Das Venv liegt unter /opt/mike-ai/xtts/venv/ und wird von install.sh automatisch angelegt (nur wenn noch nicht vorhanden).

Hinweis (Python 3.11): Coqui TTS 0.22.0 unterstützt offiziell nur Python >=3.9.0, <3.12. Daher wird Python 3.11.16 via uv verwendet. Der Zielsystem-Python (3.13.5) wird nicht verwendet.

Hinweis (PyTorch 2.6+): Coqui TTS nutzt torch.load() ohne weights_only=False, was in PyTorch 2.6+ standardmäßig fehlschlägt. Dies wird durch einen Patch in TTS/utils/io.py umgangen.

Spracherkennung (whisper.cpp, deutsch, CPU-only)

Der Router stellt lokale deutsche Spracherkennung bereit. Die Transkription läuft in einem separaten, langlebigen Worker (mike-ai-whisper.service), der whisper-cli als Subprozess aufruft. Der Worker ist CPU-only und blockiert weder Qwen/llama.cpp noch FLUX/GPU – er teilt sich nur den Prozessor.

  • Modell: Whisper large-v3-turbo (ggml, ~1.6 GB, Vollpräzision)
  • Build: whisper.cpp CPU-only (AVX2+FMA, 8 Threads)
  • Audio-Vorbereitung: ffmpeg konvertiert WebM/Opus/M4A/AAC → 16 kHz mono WAV
  • Nativ unterstützt: WAV, MP3, OGG, FLAC
  • Kein GPU-Lock: STT läuft vollständig auf CPU, parallel zu Qwen (GPU) und TTS (CPU)

Endpunkt POST /v1/audio/transcriptions

OpenAI-kompatibel (Multipart-Form-Data). Unterstützt file, model, language, prompt, temperature, response_format.

Parameter Werte Default
file Audio-Datei (Pflicht: webm, wav, mp3, m4a, ogg, flac) –
model whisper-1 (oder whisper) whisper-1
language de, en, … (optional) Auto-Detektion
prompt Kontext-Hinweis (optional) –
temperature 0.0–1.0 (optional) 0.0
response_format json (Default), verbose_json json

Die Antwort ist JSON mit text (und optional language, duration bei verbose_json).

Beispiele:

# WebM/Opus (z.B. aus Open WebUI-Mikrofon)
curl -s http://AI_HOST:8081/v1/audio/transcriptions \
  -H "Authorization: Bearer $ROUTER_KEY" \
  -F "file=@aufnahme.webm" \
  -F "model=whisper-1"

# WAV mit expliziter Sprache
curl -s http://AI_HOST:8081/v1/audio/transcriptions \
  -H "Authorization: Bearer $ROUTER_KEY" \
  -F "file=@aufnahme.wav" \
  -F "model=whisper-1" \
  -F "language=de"

# Verbose-Format
curl -s http://AI_HOST:8081/v1/audio/transcriptions \
  -H "Authorization: Bearer $ROUTER_KEY" \
  -F "file=@aufnahme.wav" \
  -F "model=whisper-1" \
  -F "response_format=verbose_json"

Discovery-Endpunkte

  • GET /v1/audio/models – listet verfügbare Audio-Modelle (whisper-1 für STT, xtts-v2 für TTS)
  • GET /v1/audio/voices – listet verfügbare TTS-Stimmen (claribel)

Verhalten

  • Kein GPU-Lock: STT läuft CPU-only und greift nicht in den GPU-Hotswap (Bild) oder Profilwechsel (Qwen) ein. STT-Requests können parallel zu Chats, TTS und Bildgenerierung laufen.
  • Serialisierte Transkription: Der Worker transkribiert nacheinander (CPU-bound), parallele Requests werden intern gewartet.
  • /status zeigt stt.reachable, stt.ready, stt.model, stt.threads, stt.language, stt.ffmpeg_exists.
  • Fehler: Worker down → 503 (stt_failed); ungültige Parameter → 400. OpenAI-kompatibles Fehlerformat.

Benchmark (CPU-only, gemessen)

Audio-Dauer Transkription RTF
7.3 s 8.9 s 1.22×
30 s 16.8 s 0.56×
50 s 18.5 s 0.37×

RTF < 1.0 bedeutet: Transkription ist schneller als Echtzeit. RAM-Belegung des Workers: ~1.7 GB (inkl. Modell).

Erforderliche Komponenten

  • whisper.cpp (CPU-only Build, /opt/mike-ai/whisper.cpp/build-cpu/)
  • ggml-large-v3-turbo.bin (/opt/mike-ai/models/whisper/)
  • ffmpeg (für WebM/Opus/M4A/AAC-Konvertierung)
  • Python 3.13 (nur Standardbibliothek, kein Venv nötig)

Repository-Struktur

router/ai_profile_router.py   # der Router (einzige Laufzeit-Datei)
router/image_worker.py        # FLUX-Worker (eigener Prozess, JSON-Protokoll)
router/xtts_worker.py         # XTTS-v2-TTS-Worker (eigener Prozess, HTTP-API)
router/stt_worker.py          # Whisper-STT-Worker (eigener Prozess, HTTP-API)
deploy/mike-ai-profile-router.service   # systemd-Unit (Router)
deploy/mike-ai-xtts.service             # systemd-Unit (TTS-Worker)
deploy/mike-ai-whisper.service          # systemd-Unit (STT-Worker)
deploy/install.sh             # läuft auf dem Zielsystem (per SSH)
deploy/deploy.sh              # läuft lokal: SCP + SSH
dev/                          # lokale Tests (Mock-llama.cpp, Mock-Worker, Benchmarks)

Entwicklungsdateien (dev/) und Deployment-Dateien (router/, deploy/) sind getrennt. Auf dem Zielsystem landet nur router/ + deploy/.

Deployment

Voraussetzung: administrativer SSH-Key für den Zielhost. Ziel und Key werden explizit über TARGET und SSH_KEY übergeben.

./deploy/deploy.sh

Das Skript:

  1. Überträgt ai_profile_router.py, image_worker.py, tts_worker.py, install.sh und beide systemd-Units per SCP nach /tmp/ai-profile-router/ auf dem Zielsystem.
  2. Führt install.sh per SSH aus, das:
    • den alten Router (mike-ai-local-llm-router.service + /opt/mike-ai/local-llm-router) mit Backup entfernt,
    • den neuen Router + Worker nach /opt/mike-ai/ai-profile-router/ installiert,
    • ein Python-Venv mit torch, diffusers, transformers, accelerate anlegt (nur wenn noch nicht vorhanden),
    • das FLUX-Modell nach /opt/mike-ai/models/FLUX.2-klein-base-4B lädt (nur wenn noch nicht vorhanden, ~15 GB),
    • espeak-ng installiert (nur wenn noch nicht vorhanden),
    • das XTTS-Venv unter /opt/mike-ai/xtts/venv/ anlegt (nur wenn noch nicht vorhanden, Python 3.11 via uv + CPU-only torch + Coqui TTS),
    • das XTTS-v2-Modell herunterlädt (nur wenn noch nicht vorhanden, ~1.9 GB),
    • mike-ai-profile-router.service und mike-ai-xtts.service aktivieren (Start beim Boot) und starten,
    • GET /status verifiziert.

Die Installation ist idempotent (Update = erneut ausführen).

Konfiguration (Umgebungsvariablen in der systemd-Unit)

Variable Default Bedeutung
ROUTER_HOST 0.0.0.0 Bind-Adresse
ROUTER_PORT 8081 Port
ROUTER_AUTH_MODE required Authentifizierung; off nur für lokale Tests
ROUTER_API_KEY_FILE /etc/mike-ai/router-api-key Schlüsseldatei (0600)
ROUTER_STATE_FILE /var/lib/mike-ai-profile-router/state.json atomarer Crash-/Recovery-Zustand
ROUTER_PROFILES_FILE /etc/mike-ai/router-profiles.json Profile, Kontext und erwarteter Alias
ROUTER_MAX_CONCURRENT_REQUESTS 16 harte Grenze paralleler Requests
UPSTREAM_URL http://127.0.0.1:8080 llama.cpp
PROFILE_SCRIPT /usr/local/bin/llama-profile Profil-Skript
PROFILE_DIR /etc/systemd/system/mike-ai-llama-ui.service.d Ort der override.conf
SWITCH_TIMEOUT 600 Warten auf llama.cpp nach Wechsel (s)
REQUEST_TIMEOUT 600 Read-Timeout für Upstream-Requests (s)
CONNECT_TIMEOUT 10 Connect-Timeout Upstream (s)
POLL_INTERVAL 2 Polling-Intervall (s)
LOG_LEVEL INFO Logging-Level
LLAMA_SERVICE mike-ai-llama-ui.service llama.cpp-Service (für Bild-Hotswap)
IMAGE_WORKER <router-dir>/image_worker.py FLUX-Worker-Skript
IMAGE_PYTHON sys.executable Python für den Worker (venv mit torch)
IMAGE_DIR /opt/mike-ai/ai-profile-router/images Bild-Speicherort
IMAGE_WORKER_LOG /opt/mike-ai/ai-profile-router/worker.log Worker-Log
IMAGE_GEN_TIMEOUT 600 Timeout pro Bild (s)
IMAGE_VRAM_FREE_TIMEOUT 120 Warten auf VRAM-Freiheit (s)
CHAT_WAIT_TIMEOUT 300 Chat wartet auf Qwen (s)
TTS_WORKER_URL http://127.0.0.1:8085 TTS-Worker (Router-Seite)
TTS_TIMEOUT 300 Timeout pro Synthese (s)
TTS_CONNECT_TIMEOUT 5 Connect-Timeout TTS-Worker (s)
VISION_MODEL /opt/mike-ai/models/qwen3.8-27b/Qwen3.8-27B-Q3_K_M.gguf Q3-Vision-Modell
VISION_MMPROJ /opt/mike-ai/models/qwen3.8-27b-nvfp4/mmproj-BF16.gguf Vision-Projektor
VISION_CTX 32768 Kontext des Vision-Servers
VISION_PORT 8086 Port des Vision-Servers (nur lokal)
VISION_LOAD_TIMEOUT 300 Warten auf Vision-Ready (s)
VISION_INFER_TIMEOUT 300 Timeout pro Vision-Inferenz (s)
VISION_UNLOAD_TIMEOUT 120 Warten auf VRAM-Freiheit (s)
VISION_MAX_TOKENS 4096 Max. Tokens der Vision-Analyse
VISION_CACHE_MAX 64 Größe des Analyse-Caches (LRU)
VISION_MAX_IMAGE_BYTES 20971520 maximales dekodiertes Bild (20 MiB)
VISION_ALLOW_REMOTE_URLS false externe Bild-URLs; standardmäßig SSRF-sicher aus
VISION_LOG /opt/mike-ai/ai-profile-router/vision_server.log Vision-Server-Log

TTS-Worker (mike-ai-xtts.service):

Variable Default Bedeutung
XTTS_HOST 127.0.0.1 Bind-Adresse (nur lokal, Router proxyt)
XTTS_PORT 8085 Port
XTTS_MODEL_ID tts_models/multilingual/multi-dataset/xtts_v2 Modell-ID
XTTS_DEFAULT_VOICE claribel Default-Stimme
LOG_LEVEL INFO Logging-Level

Lokale Tests

./dev/test_local.sh

Startet einen Mock-llama.cpp, einen Mock-Bild-Worker, einen Mock-TTS-Worker und den Router mit einem Fake-Profil-Skript und prüft: /v1/models, /status, Forwarding, Streaming, Tool Calls, Profilwechsel (fast→medium→fast), virtuelles Modell triggert Wechsel, ungültige Profile, Upstream down → 502, Recovery, Bildgenerierung (standard→30 Steps, high→50 Steps, Validierung, Image-Fehler→Qwen wiederhergestellt, Fast/Medium/Long→Image→gleiches Profil, /status während Bild-Job, paralleler Chat während Bild-Job wartet statt 502) und Sprachausgabe (/status mit tts-Section, POST /v1/audio/speech wav/mp3, Validierung, Worker-Fehler→503, Worker down→503, Worker-Neustart→Recovery).

Aktuell: 63 Integrationsassertions plus Chunked-, Multipart- und Security/Recovery-Unit-Tests.

Betrieb

systemctl status mike-ai-profile-router
systemctl status mike-ai-xtts
journalctl -u mike-ai-profile-router -f
journalctl -u mike-ai-xtts -f
ROUTER_KEY='aus lokalem Secret-Store'
curl -s http://AI_HOST:8081/status -H "Authorization: Bearer $ROUTER_KEY" | python3 -m json.tool
curl -s -X POST http://AI_HOST:8081/medium -H "Authorization: Bearer $ROUTER_KEY"
# TTS-Test
curl -s http://AI_HOST:8081/v1/audio/speech \
  -H "Authorization: Bearer $ROUTER_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"input":"Hallo","voice":"claribel"}' -o test.mp3

Sicherheit

  • Keine Shell-Aufrufe: Profil-Skript wird mit subprocess.run([script, profil]) aufgerufen, profil ist Whitelist-geprüft (fast|medium|long).
  • Keine Secrets/Tokens im Code oder in der Unit.
  • API-Key-Pflicht, konstante Schlüsselprüfung und keine Weitergabe des Router-Schlüssels an llama.cpp.
  • Remote-Bilder standardmäßig gesperrt; lokale Data-URLs sind typ- und größenvalidiert.
  • systemd-Hardening: NoNewPrivileges, PrivateTmp, ProtectHome, Kernel-/Control-Group-Schutz und restriktive Dateirechte.
  • Die bestehende llama.cpp-/Profil-Konfiguration wird nicht verändert; der Router nutzt nur das vorhandene llama-profile-Skript und liest die override.conf.
S
Description
No description provided
Readme
8.6 MiB
Languages
Python 77.5%
Shell 17%
TypeScript 1.9%
Dockerfile 1.4%
Jinja 1.4%
Other 0.7%