No description
  • Python 86.1%
  • HTML 13.9%
Find a file
2026-09-10 12:18:33 +01:00
Action init 2026-08-31 18:52:18 +02:00
admin Admin-Web-Oberfläche mit SSO/OIDC-Login (eigener Port, Default :5051) 2026-09-09 21:25:25 +00:00
display Fix: recipe_open ohne LLM (direkt) + deutlicherer Thinking-Indikator 2026-09-08 21:56:31 +00:00
llm Admin-Web-Oberfläche mit SSO/OIDC-Login (eigener Port, Default :5051) 2026-09-09 21:25:25 +00:00
Screen init 2026-08-31 18:52:18 +02:00
server fix(server): automatischer CPU-Fallback bei CUDA-/cuBLAS-Fehler 2026-09-08 10:02:03 +00:00
Voice Fix: Rezept-Matching + Wake-Word-Kaskaden 2026-09-08 11:11:50 +00:00
.gitignore fix 2026-09-01 21:21:41 +02:00
chat_mode.py OVA: ntfy-Auth, Voice-Env-Vars, CLI-Defaults, Wake-Word-Cooldown-Fix 2026-09-02 21:38:56 +00:00
main.py Admin-Server läuft jetzt automatisch mit python main.py (ein Start) 2026-09-09 21:47:20 +00:00
mcp_config.json fix 2026-09-01 21:21:41 +02:00
README.md README.md aktualisiert 2026-09-10 12:18:33 +01:00
requirements.txt Admin-Web-Oberfläche mit SSO/OIDC-Login (eigener Port, Default :5051) 2026-09-09 21:25:25 +00:00
test_cancel.py fix: Harness-Loop bei Unterbrechung abbrechen (Cancel-Event) 2026-09-02 10:35:54 +00:00
test_context_budget.py Kontext-Management: Token-Budget statt harem 10-Messages-Limit 2026-09-02 22:38:30 +00:00
test_context_budget_live.py Kontext-Management: Token-Budget statt harem 10-Messages-Limit 2026-09-02 22:38:30 +00:00
test_notify.py feat(notify): Display-Benachrichtigungen automatisch per ntfy pushen 2026-09-06 13:45:30 +02:00
test_skills_history.py feat: Transcript-Log + Skill-Index im Prompt + Human-in-the-loop Skill-Erfassung 2026-09-02 11:56:18 +00:00
test_triggers.py docs+prompt: klarerer System-Prompt (Werkzeug-Map, Regeln, kein harter Name) + README mit Funktionsübersicht 2026-09-02 12:21:23 +00:00
test_view_library.py feat(display): View-Bibliothek + Themes (persistent, wiederverwendbar) 2026-09-02 13:01:04 +00:00

OpenVoiceAssistant

Ein lokal laufender, Open-Source-Sprachassistent für Raspberry Pi / PC. Komplett offline-fähig: Wake-Word, Spracherkennung, LLM, TTS und Display laufen lokal – alle sensiblen Daten bleiben auf dem Gerät.

AI Nutzung

Dieser Code ist nahezu vollständig von Qwen3.8 27b generiert, da es sich unter anderem um einen Test der bisherigen Fähigkeiten lokal lauffähiger Modelle handelt.

Was kann der Assistent?

Sprache: Per Wake-Word (OpenWakeWord) ansprechen – versteht Befehle, fragt nach, antwortet per Sprache (TTS) und ist unterbrechbar (Barge-in).

Verstehen & Handeln (Werkzeuge):

  • Sandbox – Shell-Befehle, Dateien lesen/schreiben/suchen, APIs aufrufen (Smarthome, Wetter, Züge …)
  • Display – Views, interaktive Seiten/Formulare, Spiele, Dashboards, Timer
  • Kamera & Vision – USB-Webcam, RTSP, ONVIF/PTZ; multimodal (sieht Bilder)
  • Barcode & Produkt-Suche – Barcode/QR scannen, GTIN/EAN in Open Food Facts + retraceable.io suchen (Produktname, Nährwerte, Zutaten, Vegan/Vegetarian-Analyse)
  • Gedächtnis – merkt sich Fakten & Vorlieben (lokal, JSON)
  • Skills & Routinen – wiederverwendbare Abläufe, die die KI selbst anlegt
  • Timer & Planung – Countdown-Timer + geplante Routinen („weck mich um 8 und zeig mir die Nachrichten", auch täglich)
  • Push – ntfy-Benachrichtigungen aufs Handy; alles, was als Benachrichtigung auf dem Display erscheint, wird automatisch mitgepusht (mit Priorität: Timer/Alarme = höchste Prio)
  • Tandoor – Rezepte, Meal-Plan, Einkaufsliste, Vorrat, Koch-Log
  • Vikunja – Task-Management: To-dos, Projekte, Labels, Kommentare
  • Wetter – aktuelles Wetter + Vorhersage (OpenWeatherMap)
  • Immich – Foto-/Video-Bibliothek: Slideshow, Personensuche, Memories, Stats
  • Traccar – GPS-Tracking: Standort, Akku, Heatmaps, Tagesrouten
  • MCP – externe MCP-Server anbinden und deren Tools nutzen

Externe Trigger: Webhook (POST /api/webhook) für Person-Erkennung, neue RSS-Einträge, n8n, Home Assistant … – triggert den Assistenten ohne Wake-Word.

Architektur (Kurz)

Mikrofon → Wake-Word (OpenWakeWord) → ASR (faster-whisper) → LLM (OpenAI-kompatibel)
                                                                            │  Tool-Loop (Harness)
TTS (OmniVoice / Piper) ◄───────────────────────────────────────────────────┘
Display (Flask, :5050) ◄── Views / Kamera / Bilder ──►  Sandbox (OpenTerminal)

Komponenten:

  • main.py – Einstieg, Voice-Loop, Display-/Webhook-Trigger
  • Voice/ – Wake-Word, Aufnahme (Pre-Roll), ASR, TTS
  • llm/ – Harness (Tool-Loop), Tools, Skills, Timer, Gedächtnis, Sandbox-Client
  • display/ – Flask-Server, Views, Action-Bridge, Webhook
  • server/ – optionaler Voice-Server: ASR (Whisper) + TTS (OmniVoice) als HTTP-API

ASR (Whisper) und TTS (OmniVoice) können optional per ASR_SERVER_URL / TTS_SERVER_URL auf einen Voice-Server (python -m server) ausgelagert werden. Ohne diese Vars läuft alles lokal – der Client bleibt vollständig autark.

Setup

python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Konfiguration per Env-Vars in .env (Tabelle unten).

Starten

python main.py

Sage dann das Wake-Word (OpenWakeWord, z.B. „Hey Mycroft").

Standard-Verhalten: --save (WAV-Aufnahmen) und --display (Display-Server) sind standardmäßig an. Deaktivieren geht mit --no-save bzw. --display_off; --chat (Terminal-Chat statt Voice) schaltet --save automatisch ab.

Voice-Server (optional: ASR / TTS auslagern)

Whisper (ASR) und OmniVoice (TTS) sind die rechenintensiven Bausteine. Du kannst sie optional auf einen (GPU-)Server auslagern – der Client macht dann Wake-Word, Aufnahme, LLM, Display und Audio-Playback und schickt nur das Audio (ASR) bzw. den Text (TTS) zum Server. Beide Bausteine sind unabhängig schaltbar und optional: Ohne Server-Vars läuft alles lokal, genau wie vorher.

Server starten (auf der GPU-Maschine):

python -m server          # Endpoints: GET /health · POST /asr · POST /tts
# oder: python server/main.py

Client konfigurieren (in der .env des Clients):

Env Default Bedeutung
ASR_SERVER_URL (leer = lokal) Base-URL des Voice-Servers; gesetzt → Whisper läuft dort
TTS_SERVER_URL (leer = lokal) Base-URL des Voice-Servers; gesetzt → OmniVoice läuft dort
VOICE_SERVER_API_KEY (auto) Bearer-Key; wird vom Server automatisch generiert & gespeichert – Client + Server müssen denselben Wert haben

Server-seitige Env-Vars: VOICE_SERVER_PORT (8099), VOICE_SERVER_HOST (0.0.0.0), WHISPER_MODEL, ASR_DEVICE (cuda/cpu), ASR_COMPUTE (float16/int8/float32), ASR_LANGUAGE, ASR_BEAM, TTS_DEVICE, TTS_DTYPE, TTS_LANGUAGE, TTS_INSTRUCT.

API-Key: Der Server generiert beim ersten Start automatisch einen Key und speichert ihn unter ~/.assistant/voice_server_api_key (stabil über Neustarts). Beim Start wird er zusammen mit der Client-Konfiguration ausgegeben – einfach kopieren. Optional: VOICE_SERVER_API_KEY setzen, um den Key selbst zu bestimmen.

Robustheit: Ist der Server nicht erreichbar, fällt der Client automatisch auf lokalen Whisper zurück (falls installiert) bzw. bei TTS auf Piper/espeak – der Client bleibt also autark und funktioniert auch ohne Server.

Admin-Web-Oberfläche (SSO)

Eine separate Admin-Oberfläche zum Beobachten und Steuern des Assistenten – mit SSO-Login (kein extra Passwort, du nutzt deinen bestehenden Identity Provider). Läuft auf eigenem Port (Default :5051), sauber vom Kiosk-Display (:5050) getrennt.

Starten: Läuft automatisch mit python main.py (einziger Start, als Background-Thread neben Display & Voice). Deaktivieren: python main.py --admin_off. Fürs Einzeltesten geht auch standalone:

python main.py             # startet Voice + Display (:5050) + Admin (:5051)
python -m admin            # nur der Admin-Server, standalone

Anmeldung – zwei Wege (OIDC hat Vorrang):

  1. SSO / OIDC (empfohlen) – Authentik, Keycloak, Zitadel oder jeder OpenID-Provider. Authorization Code + PKCE, ID-Token wird gegen die JWKS des Providers verifiziert. Kein Client-Secret im Browser.
  2. Lokaler Pin – nur wenn kein OIDC konfiguriert ist. Einmaliger ADMIN_PIN genügt. Damit ist die Oberfläche sofort nutzbar.

Was kann sie?

  • Monitoring: Live-Status (Zustand/aktuelle View), Health, Transcript, Gedächtnis, Skills & Routinen, Timer, Views, Config (Secrets maskiert).
  • Steuerung: Befehl an den Assistenten senden, Routine/Skill ausführen, Timer setzen, Push-Benachrichtigung, Views speichern/löschen.

Steuer-Aktionen laufen über den Display-Server (/api/webhook …) – die einzige Stelle, die den Assistenten angreift. Read-only-Daten (Transcript, Gedächtnis, Skills, Config) werden lokal gelesen.

Authentik einrichten (Beispiel):

  1. In Authentik ein Application (OIDC) anlegen, z. B. ova-admin.
  2. Redirect URI setzen: http://<host>:5051/auth/callback (muss exakt stimmen).
  3. Scopes: openid profile email. Client-Typ public (PKCE) → AUTH_CLIENT_SECRET bleibt leer.
  4. In der .env des Assistenten:
ADMIN_PORT=5051
AUTH_ISSUER=https://<authentik>/application/o/          # Issuer (endet auf /o/)
AUTH_CLIENT_ID=ova-admin
AUTH_CLIENT_SECRET=                                     # public client → leer
AUTH_REDIRECT_URI=http://<host>:5051/auth/callback
AUTH_ALLOW_EMAILS=du@example.com                         # nur du (empfohlen)
# AUTH_ALLOW_SUBS=…                                      # alternativ per sub
# ADMIN_PIN=                                             # nur ohne OIDC

Sicherheits-Hinweis: AUTH_ALLOW_EMAILS / AUTH_ALLOW_SUBS begrenzen, wer sich einloggen darf – ohne Allowlist darf jeder IdP-User. Für einen Admin-Zugang die Allowlist setzen.

Env Default Bedeutung
ADMIN_HOST / ADMIN_PORT 0.0.0.0 / 5051 Bind-Adresse des Admin-Servers
AUTH_ISSUER (leer) OIDC-Issuer; gesetzt + AUTH_CLIENT_ID → SSO aktiv
AUTH_CLIENT_ID (leer) OIDC-Client-ID
AUTH_CLIENT_SECRET (leer) nur „confidential"-Clients; public+PKCE → leer
AUTH_REDIRECT_URI (abgeleitet) muss exakt mit der im IdP registrierten stimmen
AUTH_ALLOW_EMAILS (leer = alle) Komma-Liste erlaubter E-Mails
AUTH_ALLOW_SUBS (leer) Komma-Liste erlaubter sub-Werte
AUTH_SCOPE openid profile email OIDC-Scopes
ADMIN_PIN (leer) lokaler Fallback-Pin (nur ohne OIDC)
ADMIN_SECRET (auto) Cookie-Signing-Secret; leer → wird generiert & gespeichert
ADMIN_SESSION_HOURS 12 Session-Gültigkeit in Stunden

Log & Debugging

Der Log ist farbig und übersichtlich: Level, Komponente und das Action-Label (z.B. Tool-Call, Tool-Result, Antwort) werden farbig getrennt, damit man den Flow im Terminal schnell lesen kann – und jeder Befehl beginnt mit einem farbigen Trenner (📌 Befehl).

python main.py -v                 # ausführlicher (DEBUG: Runden, Timing, Tokens)
python main.py --log-file ova.log # zusätzlich in Datei loggen (immer DEBUG, ohne Farben)
python main.py --no-color         # Farben aus (z.B. wenn die Ausgabe gepiped wird)

Farben werden automatisch deaktiviert, wenn die Ausgabe kein TTY ist oder NO_COLOR gesetzt ist; FORCE_COLOR=1 erzwingt sie.

Konfiguration (Env-Vars)

Sensible Werte (API-Keys, Kamera-Zugangsdaten) nur in .env, nie im Repo.

Env Default Bedeutung
LLM_BASE_URL http://localhost:11434/v1 LLM-Endpoint (Ollama / OpenAI-kompatibel)
LLM_MODEL – Modellname
LLM_API_KEY ollama API-Key
SANDBOX_URL http://localhost:3000 OpenTerminal-Sandbox
SANDBOX_API_KEY – Sandbox-Auth
DISPLAY_URL http://localhost:5050 Display-Server
DISPLAY_DIR ~/.assistant/display Bilder für das Display
CAMERA_DEVICE /dev/video0 USB-Webcam
CAMERA_MAX_WIDTH / CAMERA_QUALITY 768 / 80 Vision-Downscale
WHISPER_MODEL base Whisper-Modell (tiny/base/small/medium/large-v3)
ASR_SERVER_URL (leer = lokal) Whisper auf Voice-Server auslagern (Base-URL)
TTS_SERVER_URL (leer = lokal) OmniVoice auf Voice-Server auslagern (Base-URL)
VOICE_SERVER_API_KEY (leer = offen) Bearer-Key für den Voice-Server
PRE_ROLL_SECONDS 90 Dauer des Kontextes/Pre-Roll in s
VAD_ENERGY_THRESHOLD 0.01 VAD-Lautstärkeschwelle (RMS); zu niedrig → Aufnahme stoppt nicht (läuft auf 30 s), in lauten Umgebungen höher (0.03–0.05)
TTS_COOLDOWN_S 2.0 Wake-Word-Cooldown nach TTS in s
ASSISTANT_NAME Tess Name des Assistenten
USER_NAME (leer) Name der Person (Personalisierung)
NTFY_URL / NTFY_TOPIC – Push-Benachrichtigungen (Server + Topic)
NTFY_USER / NTFY_PASS (leer) Basic-Auth für den ntfy-Server
MCP_CONFIG – Pfad zur MCP-Config (JSON)
RETRACEABLE_API_KEY (leer) API-Key für retraceable.io (Zutaten-Analyse, optional)
RETRACEABLE_API_URL https://retraceable.io Base-URL für retraceable.io
TANDOOR_URL (leer) Tandoor Recipes Base-URL
TANDOOR_API_TOKEN (leer) Tandoor Bearer-Token
VIKUNJA_URL (leer) Vikunja Base-URL
VIKUNJA_API_TOKEN (leer) Vikunja Bearer-Token
IMMICH_URL (leer) Immich Base-URL
IMMICH_API_KEY (leer) Immich API-Key
TRACCAR_URL https://traccar.jennes.dev Traccar Base-URL
TRACCAR_TOKEN (leer) Traccar API-Token
TRACCAR_DEVICE (leer) Zu trackendes Gerät (Name oder ID)
OPENWEATHER_API_KEY (leer) OpenWeatherMap API-Key
OPENWEATHER_CITY (leer) Default-Stadt für Wetteranfragen

Skills & Routinen:

Env Default Bedeutung
SKILL_INDEX_IN_PROMPT 1 Skill-Index im System-Prompt an/aus
SKILL_INDEX_MAX 15 Max. Skills im Index

Transcript-Log (Backup/Audit, kein Kontext):

Env Default Bedeutung
HISTORY_LOG_ENABLED 1 Log an/aus
HISTORY_LOG_PATH ~/.assistant/history.jsonl Log-Datei
HISTORY_LOG_RETENTION_DAYS 30 Einträge älter als N Tage werden entfernt

Webhook (externe Trigger):

Env Default Bedeutung
WEBHOOK_TOKEN (leer) Freigabe-Token (Header X-Webhook-Token oder ?token=)
WEBHOOK_AUTH 1 Fail-closed: ohne gültiges Token → 401/403

Webhook nutzen

Externe Ereignisse triggern den Assistenten direkt, ohne Wake-Word:

# Freier Befehl (z.B. aus einer Person-Erkennung):
curl -X POST http://<host>:5050/api/webhook \
  -H "X-Webhook-Token: $WEBHOOK_TOKEN" -H "Content-Type: application/json" \
  -d '{"command": "Person erkannt: Paketbote", "source": "kamera"}'

# Oder eine Routine (Skill) mit Parametern ausführen:
curl -X POST http://<host>:5050/api/webhook \
  -H "X-Webhook-Token: $WEBHOOK_TOKEN" -H "Content-Type: application/json" \
  -d '{"routine": "guten_morgen", "params": {"k": "v"}}'

Geplante Routinen

Per Sprache: „Weck mich morgen um 8 und zeig mir die Nachrichten" → schedule_routine(at=<ISO-Zeit>, routine=<Name>, recurring=true für täglich). Die Routine ist ein Skill und wird zum Zeitpunkt per skill_run ausgeführt.

Repository-Struktur

OpenVoiceAssistant/
├── main.py              # Einstieg, Voice-Loop, Trigger-Behandlung
├── Voice/               # Wake-Word, Aufnahme, ASR, TTS
├── llm/                 # Harness, Tools, Skills, Timer, Gedächtnis, Sandbox
│   ├── pipeline.py      #   Einstiegspunkt (Kontext, Log, TTS)
│   ├── harness.py       #   Tool-Calling-Loop
│   ├── tools.py         #   alle Werkzeuge (128 Tools)
│   ├── config.py        #   Konfiguration (Env-Vars)
│   ├── system_prompt.py #   System-Prompt (dynamisch)
│   ├── vikunja.py       #   Vikunja API-Client (Tasks, Projekte, Labels)
│   ├── tandoor.py       #   Tandoor API-Client (Rezepte, Meal-Plan, Einkaufsliste)
│   ├── weather.py       #   Wetter (OpenWeatherMap)
│   ├── immich.py        #   Immich API-Client (Fotos, Videos, Memories)
│   ├── traccar.py       #   Traccar API-Client (GPS-Tracking)
│   ├── product_lookup.py #  Produkt-Suche (OFF + retraceable.io)
│   ├── camera.py        #   Kamera-Steuerung (USB, RTSP, ONVIF)
│   ├── skills.py        #   Skill-System (Routinen)
│   ├── timers.py        #   Timer & geplante Routinen
│   ├── memory.py        #   Gedächtnis (JSON)
│   ├── history_log.py   #   Transcript-Log (JSONL)
│   ├── view_library.py  #   View-Bibliothek (speichern, laden, kopieren)
│   ├── mcp.py           #   MCP-Server-Client
│   ├── sandbox.py       #   OpenTerminal-Client
│   └── tts.py           #   TTS (OmniVoice / Piper / espeak)
├── display/             # Flask-Server, Views, Action-Bridge, Webhook
├── admin/               # Admin-Web-Oberfläche (SSO/OIDC, `python -m admin`)
├── server/              # optionaler Voice-Server (ASR + TTS als HTTP-API)
├── test_skills_history.py
├── test_triggers.py
├── requirements.txt
└── README.md