- Python 74.7%
- JavaScript 18.6%
- HTML 4%
- Shell 2.4%
- Makefile 0.3%
Ist die Web-Suche bei einer offensichtlich tagesaktuellen Frage
(should_force_search) nicht verfuegbar, sendet ws.py einen {type:"notice"}-
Event mit einer kleinen, lokalisierten Senioren-Systemnachricht ("Web-Suche
aus" bzw. "Tageskontingent aufgebraucht"); Frontend zeigt sie als System-Bubble.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|---|---|---|
| .forgejo/workflows | ||
| app | ||
| config | ||
| deploy | ||
| Docs | ||
| emergency_bridge | ||
| eval/tool_calling | ||
| scripts | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| BEDIENUNGSANLEITUNG.md | ||
| CHANGELOG.md | ||
| chat_client.py | ||
| CLAUDE.md | ||
| DEPLOYMENT.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE.md | ||
| Makefile | ||
| MULTITOOL_AUSBAU_PLAN.md | ||
| pyproject.toml | ||
| README.md | ||
Alexis — modulares Sprachassistenten-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
- Web-Suche (Tool-Calling, „Weg 2"): das Modell holt bei tagesaktuellen Fragen frische Fakten über
perplexity/sonar(Standard an, pro Nutzer abschaltbar), mit Quellen-Links, deterministischem Backstop für heikle Kategorien und gesprochenen Beruhigungssätzen während der Recherche — sieheDocs/weg2-tool-calling.md - 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)
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).
curl http://localhost:8080/health # {"status":"ok"}
curl http://localhost:8080/api/config # aktives Profil + aufgelöste Provider
Sprechen → Antwort hören (CLI-Loop):
python scripts/voice_loop.py --session mein-gespraech
Web-Interface: Browser → http://localhost:8080/
Starten — alle Szenarien
Profil cloud (nur Gateway, alles via OpenRouter)
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)
# 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_PORToderMODEL_ALIASgeändert, müssenLOCAL_LLM_BASE_URLundLOCAL_LLM_MODELin.enventsprechend angepasst werden.
Profil hybrid / local-dev mit Ollama
# 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
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)
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).
# → 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
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 | alle | Installation, Betriebsprofile, Bedienung, Konfiguration, Admin, Deployment, Fehlerbehebung, Referenz |
| Docs/voice-assistant-architecture.md | Entwickler | Architekturprinzipien, Interfaces, Pipeline, Roadmap, Verzeichnisstruktur |
| 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)
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.