# Voice Assistant Gateway

Modulares FastAPI-Gateway für einen **seniorengerechten Sprachassistenten** —
cloud-first, aber hybrid/lokal betreibbar, mit austauschbaren STT-/LLM-/TTS-Providern.
Jede Achse — Hardware, Betrieb, Software — ist frei konfigurierbar, ohne Code zu ändern.

---

## Features

- **Sprach-Pipeline:** STT → Input-Cleaner → LLM → Spoken-Adapter → TTS-Normalizer → TTS
- **Provider austauschbar** über Registry: OpenRouter (Cloud), faster-whisper (STT lokal), piper (TTS lokal, schnell), chatterbox (TTS lokal, hohe Qualität + Voice-Cloning)
- **Geschichtete Konfiguration** mit Profilen (`cloud` / `hybrid` / `local-dev`)
- **Routing auf jeder Ebene:** Global → Profil → Nutzer → Session → Request
- **Authentifizierung** (Bearer-Token) + persistente Nutzer/Sessions (SQLite)
- **Resilienz:** Fallback-Ketten je Modul + In-Memory-Metriken (JSON + Prometheus)
- **Gesprächsgedächtnis** pro Session (Verlauf) + Langzeit-Erinnerungen pro Nutzer (manuell + automatisch)
- **WebSocket-Streaming:** Token-Streaming (LLM), Audio-Streaming (satzweises TTS), Sprach-Eingang, VAD, Barge-in
- **Notfall-Eskalation:** zweistufig (Stichwörter + LLM-Klassifikation im Hintergrund)
- **Web-Interface** unter `/` (Tailwind, kein Build, mobiltauglich) mit Geräte-TTS (Browser-Sprachausgabe) und Ton-Presets (Schnell/Hohe Qualität/Cloud)
- **Admin-Panel** (5 Bereiche + Übersicht-Dashboard): Nutzer/Sessions, LLM-/GPU-Status, Backend-Wechsel + Gateway-Neustart, Live-Config, Aussprache-Lexika, Live-Log, Audit-Logging
- **Keine Secrets im Code** — API-Keys nur über die Umgebung

---

## Schnellstart (30 Sekunden)

```bash
python3 -m venv .venv && source .venv/bin/activate
pip install -U pip && pip install -e .[test]
cp config/voice-assistant.example.toml config/voice-assistant.toml
export OPENROUTER_API_KEY=sk-or-v1-...   # für Cloud/Hybrid; bei local-dev nicht nötig
make run
```

Fehlt `.env`, wird sie aus `.env.example` erzeugt. Gateway läuft auf `http://localhost:8080`
(oder dem in `.env` gesetzten `PORT`).

```bash
curl http://localhost:8080/health        # {"status":"ok"}
curl http://localhost:8080/api/config    # aktives Profil + aufgelöste Provider
```

Sprechen → Antwort hören (CLI-Loop):
```bash
python scripts/voice_loop.py --session mein-gespraech
```

Web-Interface: Browser → `http://localhost:8080/`

---

## Starten — alle Szenarien

### Profil `cloud` (nur Gateway, alles via OpenRouter)

```bash
make run                        # Gateway auf PORT aus .env (Standard: 8080)
VA_PROFILE=cloud make run       # Profil explizit setzen (überschreibt .env)
PORT=8003 make run              # anderen Port für diesen Start
LOG_LEVEL=debug make run        # ausführlichere Logs
```

### Profil `hybrid` / `local-dev` mit llama.cpp (Docker + GPU)

```bash
# 1) LLM-Server starten
make llm-up                     # Default: GPU 1, Port 8001, Modell qwen3-35B-Uncensored
make llm-status                 # warten bis „Modell bereit" + HTTP 200 erscheint
make llm-down                   # stoppen

# Mit anderen Parametern (via ENV):
GPU_DEVICE=0 make llm-up
HOST_PORT=8101 GPU_DEVICE=2 make llm-up
GPU_DEVICE=0 MODEL_REL_PATH="models/qwen3/anderes-modell.gguf" make llm-up

# Direkt (ohne make):
bash scripts/llm-server/start-llm-server.sh
GPU_DEVICE=0 bash scripts/llm-server/start-llm-server.sh

# 2) Gateway starten
VA_PROFILE=hybrid make run      # STT/TTS cloud, LLM lokal
VA_PROFILE=local-dev make run   # alles lokal (STT/TTS in-process)
```

ENV-Optionen für `make llm-up` / `start-llm-server.sh`:

| Variable | Default | Bedeutung |
|----------|---------|-----------|
| `GPU_DEVICE` | `1` | GPU-Index (0-basiert) |
| `HOST_PORT` | `8001` | Host-Port des LLM-Servers |
| `MODEL_REL_PATH` | `models/qwen3/Qwen3.6-35B-...Q4_K_M.gguf` | Modellpfad relativ zu `HF_HOME` |
| `HF_HOME` | `~/nvme2n1p7_home/huggingface` | Modell-Basisverzeichnis |
| `MODEL_ALIAS` | `va_llm` | OpenAI-API-Modellname (→ `LOCAL_LLM_MODEL` in `.env`) |
| `CONTAINER_NAME` | `va_llm` | Docker-Containername |

> Wird `HOST_PORT` oder `MODEL_ALIAS` geändert, müssen `LOCAL_LLM_BASE_URL` und
> `LOCAL_LLM_MODEL` in `.env` entsprechend angepasst werden.

### Profil `hybrid` / `local-dev` mit Ollama

```bash
# 1) Ollama-Dienst starten
sudo systemctl start ollama     # empfohlen (bei systemd-Installation)
# oder im Vordergrund:
ollama serve

# 2) Modell herunterladen (einmalig, ~20 GB):
ollama pull qwen3:30b-a3b       # Thinking deaktiviert (empfohlen)
# kleinere Alternative (CPU-tauglich, ~5 GB):
ollama pull qwen3:8b

# Status prüfen:
ollama list                     # installierte Modelle
ollama ps                       # gerade aktive Modelle (mit VRAM-Verbrauch)

# 3) .env anpassen (einmalig):
# LOCAL_LLM_BASE_URL=http://127.0.0.1:11434/v1
# LOCAL_LLM_API_KEY=ollama
# LOCAL_LLM_MODEL=qwen3:30b-a3b   ← exakter Name aus 'ollama list'

# 4) Gateway starten:
VA_PROFILE=hybrid make run
VA_PROFILE=local-dev make run
```

### Stoppen

```bash
make stop           # Gateway (alle Varianten) + llama.cpp-Container
# Einzeln:
Strg + C            # Gateway im Vordergrund
pkill -f "uvicorn app.main:app"          # Gateway im Hintergrund
systemctl --user stop voice-assistant    # Gateway als systemd-Dienst
make llm-down                            # nur llama.cpp
sudo systemctl stop ollama               # nur Ollama
```

### Neu starten (alles)

```bash
make restart        # make stop + make start  (llama.cpp + Gateway)
# Nur Gateway neu (llama.cpp läuft weiter):
systemctl --user restart voice-assistant
```

### Backend wechseln (llama.cpp ↔ Ollama)

Beide Server können nicht gleichzeitig laufen (geteilter GPU-Speicher).

```bash
# → zu llama.cpp wechseln (Ollama vorher killen):
sudo systemctl stop ollama && pkill -f "ollama serve" 2>/dev/null || true
make llm-up && make llm-status

# → zu Ollama wechseln (llama.cpp vorher killen):
make llm-down   # oder: docker rm -f va_llm
sudo systemctl start ollama
```

### Alle `make`-Targets im Überblick

```bash
make install    # venv anlegen + Abhängigkeiten installieren
make run        # Gateway starten (uvicorn --reload, Port aus .env)
make start      # llama.cpp + Gateway starten (hybrid/local-dev)
make stop       # alles stoppen (Gateway + llama.cpp)
make restart    # make stop + make start
make test       # Pytest-Suite (offline, kostenlos)
make smoke      # Live-End-to-End-Test gegen OpenRouter (geringe Kosten)
make llm-up     # llama.cpp-Docker-Container starten
make llm-down   # llama.cpp-Container stoppen
make llm-status # Container- und HTTP-Status prüfen
```

---

## Dokumentation

| Dokument | Zielgruppe | Inhalt |
|----------|-----------|--------|
| **[BEDIENUNGSANLEITUNG.md](BEDIENUNGSANLEITUNG.md)** | alle | Installation, Betriebsprofile, Bedienung, Konfiguration, Admin, Deployment, Fehlerbehebung, Referenz |
| **[Docs/voice-assistant-architecture.md](Docs/voice-assistant-architecture.md)** | Entwickler | Architekturprinzipien, Interfaces, Pipeline, Roadmap, Verzeichnisstruktur |
| **[deploy/README.md](deploy/README.md)** | Admin | Remote-Betrieb: nginx, YunoHost-SSO, systemd, Firewall, Chatterbox |

**Einstiegspunkte je Zielgruppe:**

- 👤 **Endnutzer** → BEDIENUNGSANLEITUNG § 5 (Bedienung)
- 🔧 **Admin/Betreiber** → BEDIENUNGSANLEITUNG § 2–4 (Installation + Profile), § 7–11 (Betrieb)
- 💻 **Entwickler** → BEDIENUNGSANLEITUNG § 2 + Architektur-Dokument

---

## Projektstruktur (Kurzform)

```text
app/         Gateway: config, api/, core/, audio/, pipeline/, providers/, web/ (Frontend + Admin)
config/      voice-assistant.example.toml  (lokale .toml ist gitignored)
deploy/      systemd-Unit, nginx-Vorlage, env-Beispiel
scripts/     voice_loop.py, chat_client.py, smoke_e2e.py, add_pronunciation.py
scripts/llm-server/   start/stop/status-llm-server.sh (llama.cpp-Docker)
tests/       Pytest-Suite (offline + smoke)
Docs/        Architektur-Dokument
```

---

## Lizenz

**Proprietär — alle Rechte vorbehalten.** Siehe [LICENSE.md](LICENSE.md).
