# Voice-Agent Mac-Worker Stateless FastAPI-Service auf dem Mac. Macht die schwere Arbeit: Whisper-Transkription und Ollama-Zusammenfassung. Wird vom LXC-Frontend per HTTP angesprochen. ## Voraussetzungen - Apple Silicon (M1/M2/M3/M4), 16 GB RAM minimum - macOS mit Xcode-CLI-Tools (`xcode-select --install`) - Homebrew - Python 3.11+ (`brew install python@3.11` — falls nicht vorhanden) ## Schritt 1 — Ollama vorbereiten ```bash # Ollama installieren, falls noch nicht geschehen brew install ollama # Modell ziehen (Default — siehe unten für Alternativen) ollama pull llama3.1:8b # Ollama netzwerkweit erreichbar machen (LaunchAgent) launchctl setenv OLLAMA_HOST "0.0.0.0:11434" brew services restart ollama ``` ## Schritt 2 — Repo + LaunchAgent installieren ```bash # Repo holen git clone https://git.cynfo.net/christian/voice-agent.git ~/voice-agent cd ~/voice-agent/mac-worker # Vollständige Installation in einem Befehl: ./deploy/install-launchagent.sh ``` Das Skript macht **alles in einem Rutsch**, idempotent: 1. legt `.venv` an (wenn fehlt) und installiert `requirements.txt` 2. legt `.env` aus Vorlage an (wenn fehlt) 3. generiert `~/Library/LaunchAgents/com.voiceagent.worker.plist` mit den richtigen Pfaden 4. registriert den Agent via modernem `launchctl bootstrap` 5. wartet auf `http://127.0.0.1:8080/health` und gibt die Status-Antwort aus **Verwaltung:** | Aktion | Befehl | |---|---| | Status + letzte Logs | `./deploy/install-launchagent.sh status` | | Worker neu starten (kein Reinstall) | `./deploy/install-launchagent.sh restart` | | **Update** (git-pull + pip + restart + Health) | `./deploy/update.sh` | | Vollständige Neuinstallation | `./deploy/install-launchagent.sh` | | Entfernen | `./deploy/install-launchagent.sh uninstall` | | Live-Logs | `tail -f ~/Library/Logs/voice-agent-worker.{out,err}.log` | > **Hinweis (modernes macOS):** `launchctl unload -w …` wirft auf neueren Versionen > oft `Input/output error 5`. Die Skripte nutzen daher `launchctl bootout` / > `bootstrap` / `kickstart -k` — das funktioniert zuverlässig. **Eigenschaften des Agents:** - `RunAtLoad` — startet beim Login - `KeepAlive` mit `Crashed=true`, `SuccessfulExit=false` — Restart nach Crash, kein Loop bei sauberem Exit - `ThrottleInterval=10` — frühestens 10 s nach Restart erneut starten - Logs in `~/Library/Logs/voice-agent-worker.{out,err}.log` **Wichtig:** der Agent läuft nur, wenn der Mac-User eingeloggt ist. Für headless-Server-Betrieb (Mac Mini ohne Login) entweder: - Auto-Login aktivieren (System-Settings → Users), oder - in einen LaunchDaemon umbauen — dann fällt allerdings die Apple-Neural-Engine-Beschleunigung weg, MLX läuft nur auf der CPU. ## Worker im Vordergrund starten (für Entwicklung) ```bash cd ~/voice-agent/mac-worker source .venv/bin/activate uvicorn app.main:app --host 0.0.0.0 --port 8080 ``` ## API-Endpunkte | Methode | Pfad | Zweck | |---|---|---| | GET | `/health` | Status + Ollama-Reachability | | GET | `/api/profiles` | Verfügbare YAML-Profile auflisten | | POST | `/api/transcribe` | multipart: `audio`, `language` → Transkript-JSON | | POST | `/api/summarize` | JSON: `transcript`, `title`, `profile` → Summary-JSON | | POST | `/api/protocol` | JSON: `transcript`, `summary`, `title`, `profile` → Protokoll-JSON | | POST | `/api/export/docx` | JSON: `data`, `profile` → DOCX-Binary | | POST | `/api/export/pdf` | JSON: `data`, `profile` → PDF-Binary | | POST | `/api/preload` | JSON: `profile` → forciert Ollama-Modell-Load (`keep_alive 30 m`) | | GET | `/api/jobs/{id}/log` | Letzte 200 Logzeilen für genau diesen Job | Alle relevanten Calls dürfen einen Header `X-Job-Id: ` mitschicken — der Mac-Worker schreibt dann jede Logzeile dieses Requests in einen per-Job Ringbuffer, den `/api/jobs/{id}/log` für die LXC-UI ausliefert. ## Firewall am Mac ```bash # Eingehende Verbindungen auf Port 8080 erlauben (System-Settings > Network > Firewall) # oder via pf — siehe Apple-Doku. ``` Der Worker hat **keine Authentifizierung** — Betrieb nur im internen Netz. --- ## Whisper-Engine wählen In `.env`: ``` WHISPER_ENGINE=mlx # mlx | faster | mock WHISPER_MODEL=large-v3 # Modellname (siehe Tabellen unten) WHISPER_BATCH_SIZE=12 # MLX: höher = schneller + mehr RAM WHISPER_LANGUAGE=de ``` | `WHISPER_ENGINE` | Wann nutzen | |---|---| | `mlx` (Default) | Apple Silicon — nutzt Apple Neural Engine via [`lightning-whisper-mlx`](https://github.com/mustafaaljadery/lightning-whisper-mlx) | | `faster` | Intel-Mac, Linux oder CUDA — [`faster-whisper`](https://github.com/SYSTRAN/faster-whisper) (CTranslate2) | | `mock` | Testbetrieb ohne Modell-Download | ### MLX-Modelle (`WHISPER_ENGINE=mlx`) | `WHISPER_MODEL` | Größe | Geschwindigkeit (M2) | Qualität (Deutsch) | |---|---|---|---| | `tiny` | 75 MB | ~20× Realzeit | Niedrig — nur für Voice-Commands | | `base` | 145 MB | ~15× Realzeit | Niedrig | | `small` | 466 MB | ~6× Realzeit | Mittel | | `medium` | 1.5 GB | ~2× Realzeit | Gut, schwächelt bei Fachbegriffen | | `large-v3` (Default) | 3 GB | ~0.3× Realzeit | **Sehr gut** — Standard für Sitzungsprotokolle | | `distil-large-v3` | 1.5 GB | ~1.5× Realzeit | Fast wie `large-v3`, ~5× schneller | | `large-v3-turbo` | 1.6 GB | ~3× Realzeit | Wie `large-v3` für lange Inhalte, schwächer bei kurzen | Empfehlung: `large-v3` für maximale Qualität, `distil-large-v3` wenn Geschwindigkeit wichtiger ist. Modelle werden beim ersten Aufruf automatisch von HuggingFace gezogen (`mlx-community/whisper-...`). ### Alternative Transkriptions-Engines Diese sind im Standard-Setup **nicht** aktiviert, lassen sich aber statt `lightning-whisper-mlx` einbauen: - **`whisper.cpp` mit CoreML** — pure C++, sehr ressourceneffizient, kann das Whisper-Modell auf der Apple Neural Engine ausführen. Eigener Subprozess statt Python-Bindings. - **WhisperX** ([github.com/m-bain/whisperX](https://github.com/m-bain/whisperX)) — basiert auf `faster-whisper`, fügt **Wort-genaue Timestamps** und **Sprecher-Diarization** (via pyannote) hinzu. Erfordert HuggingFace-Token. Sinnvoll, sobald „Wer hat was gesagt?" wichtiger wird. - **Voxtral** (Mistral, 2025) — multimodales Sprach-LLM, kann Audio direkt zusammenfassen ohne separaten Whisper-Schritt. Reduziert die Pipeline auf einen einzigen Modell-Call, braucht aber andere Library und höheren VRAM. - **NVIDIA Parakeet TDT** — extrem schnell, aber primär englisch. Für deutsche Sitzungen nicht empfehlenswert. Für reinen Geschwindigkeits-Boost ohne Architektur-Änderung: einfach `WHISPER_MODEL=distil-large-v3` setzen. --- ## Ollama-Modell wählen In `.env`: ``` OLLAMA_MODEL=llama3.1:8b OLLAMA_TIMEOUT=600 ``` Anschließend nur noch: ```bash ollama pull # Worker neu starten ``` ### Empfehlung nach Mac-RAM | Mac-RAM | Empfohlenes Modell | Begründung | |---|---|---| | 16 GB | `llama3.1:8b` oder `qwen2.5:7b` | Genug Spielraum neben Whisper-large-v3 | | 24 GB | `qwen2.5:14b` oder `gemma3:12b` | Großer Qualitätssprung bei deutscher JSON-Ausgabe | | 32 GB | `mistral-small:24b` (Q4) oder `gpt-oss:20b` | Beste Kompromiss-Wahl | | 64 GB+ | `llama3.3:70b` oder `qwen2.5:72b` | Top-Qualität, aber langsamer (~20–60 s pro Summary) | ### Detailbewertung der Kandidaten | Modell | Größe | Deutsch | JSON-Stabilität | Geschwindigkeit | Notizen | |---|---|---|---|---|---| | `llama3.1:8b` (Default) | 8 B | Gut | Mittel — gelegentlich Codefences | Schnell (~5 s) | Ausreichend für die meisten Sitzungen | | `qwen2.5:7b` | 7 B | Sehr gut | Hoch | Schnell | Beste 7B-Wahl für Deutsch | | `qwen2.5:14b` | 14 B | Sehr gut | Sehr hoch | Mittel (~10 s) | **Empfehlung für 24–32 GB Macs** | | `gemma3:12b` | 12 B | Gut | Hoch | Mittel | Google, gute Strukturierung | | `mistral-small:24b` | 24 B | Sehr gut | Sehr hoch | Mittel (~15 s) | Europäisch trainiert, sehr stark bei Fachsprache | | `gpt-oss:20b` | 20 B | Gut | Sehr hoch | Mittel | OpenAI Open-Weight (2025), explizit auf Tool-Use trainiert | | `phi4:14b` | 14 B | Mittel | Hoch | Mittel | Microsoft, stark im Reasoning, schwächer im Deutschen | | `llama3.3:70b` | 70 B | Exzellent | Sehr hoch | Langsam (~30–60 s) | Top-Qualität, nur mit 48 GB+ RAM sinnvoll | ### Modell wechseln ```bash # 1. Modell ziehen ollama pull qwen2.5:14b # 2. .env anpassen sed -i '' 's/^OLLAMA_MODEL=.*/OLLAMA_MODEL=qwen2.5:14b/' .env # 3. Worker neu starten # (z.B. Ctrl-C im Terminal und uvicorn erneut) ``` ### Tipp: JSON-Modus erzwingen Der aktuelle Worker parst die Antwort manuell und greift bei Fehlern auf einen Text-Fallback zurück. Mit Ollama lässt sich JSON-Output erzwingen, indem im Generate-Call `format: "json"` gesetzt wird — das senkt Parse-Fehler bei kleineren Modellen drastisch. Bei Bedarf in `app/core/ollama_client.py` ergänzen.