root 955075e3fd feat(mac-worker): LaunchAgent für Dauerbetrieb auf dem Mac
- com.voiceagent.worker.plist.template — User-LaunchAgent mit RunAtLoad,
  KeepAlive(Crashed=true), ProcessType=Interactive für MLX/Metal-Zugriff
- install-launchagent.sh — generiert plist mit Pfaden, lädt via launchctl,
  prüft Health-Endpoint; unterstützt install/uninstall/status
- READMEs: Abschnitt "Worker als Dienst" mit Verwaltungs-Befehlen
2026-05-13 22:30:50 +00:00

205 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
## Setup (Apple Silicon)
```bash
cd mac-worker
python3.11 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
cp .env.example .env
# Bei Bedarf .env anpassen
```
## 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
```
## Worker starten — Vordergrund (Test)
```bash
uvicorn app.main:app --host 0.0.0.0 --port 8080
```
Health-Check vom LXC:
```bash
curl http://<MAC-IP>:8080/health
```
## Worker als Dienst (LaunchAgent — empfohlen für Dauerbetrieb)
Der Worker soll dauerhaft laufen, nach Login automatisch starten und bei Crash automatisch neu starten. Dafür ein **LaunchAgent** (kein LaunchDaemon — MLX/Metal-GPU-Zugriff braucht User-Context).
**Installation** (einmalig, nachdem `.venv` und `.env` eingerichtet sind):
```bash
cd ~/voice-agent/mac-worker
./deploy/install-launchagent.sh
```
Das Skript:
1. prüft, ob `.venv/bin/uvicorn` existiert
2. legt `.env` aus Vorlage an, falls fehlt
3. generiert `~/Library/LaunchAgents/com.voiceagent.worker.plist` mit den passenden Pfaden
4. lädt den Agent (`launchctl load -w …`)
5. testet `http://127.0.0.1:8080/health`
**Verwaltung:**
| Aktion | Befehl |
|---|---|
| Status + Logs anzeigen | `./deploy/install-launchagent.sh status` |
| Entfernen | `./deploy/install-launchagent.sh uninstall` |
| Update (neue Version pullen) | `git pull && pip install -r requirements.txt && ./deploy/install-launchagent.sh` |
| Stoppen | `launchctl unload -w ~/Library/LaunchAgents/com.voiceagent.worker.plist` |
| Starten | `launchctl load -w ~/Library/LaunchAgents/com.voiceagent.worker.plist` |
| Live-Logs | `tail -f ~/Library/Logs/voice-agent-worker.{out,err}.log` |
**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.
## API-Endpunkte
| Methode | Pfad | Zweck |
|---|---|---|
| GET | `/health` | Status + Ollama-Reachability |
| POST | `/api/transcribe` | multipart: `audio`, `language` → Transkript-JSON |
| POST | `/api/summarize` | JSON: `transcript`, `title` → Summary-JSON |
| POST | `/api/protocol` | JSON: `transcript`, `summary`, `title` → Protokoll-JSON |
| POST | `/api/export/docx` | JSON: Protokoll → DOCX-Binary |
## 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 <neues-modell>
# 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 (~2060 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 2432 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 (~3060 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.