Funk-lab/admin/ia/stt.md
ALI YESILKAYA 2658f92c2f
feat(stt): HUD avancé (phase 4) — design Claude Design câblé au backend (#17)
* feat(stt): HUD avancé — design Claude Design câblé au backend

Remplace le HUD minimal (cercle CSS) par le design Claude Design : avatar
portrait réactif (anneau + ping d'écoute + spinner de réflexion), transcript
à bulles (tag modèle), indicateur de connexion + reconnexion auto, et un
drawer Réglages.

- HUD (stt/hud/index.html) : HTML/CSS/JS sans dépendance externe, DEMO=false
  branché sur le websocket existant (états idle/listening/thinking/speaking,
  user/assistant/error). Portraits chargés depuis hud/avatars/ (repli « あ »
  si absent — voir avatars/README.md).
- Backend (ui/app.py) : le HUD renvoie {"type":"settings",...} ; le serveur
  applique à chaud reset / mode cerveau (modèle LiteLLM) / mot de réveil via
  _apply_settings (best-effort, non bloquant).
- engine/cli : tag du modèle courant ajouté aux réponses (events assistant).
- Docs : hud/README + avatars/README + admin/ia/stt.md (phase 4 ) + config.

Phase 4 de la roadmap STT. Reste : test sur poste + portraits réels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa

* fix(stt): HUD servi sans cache navigateur (anti ancienne version)

Le HUD est un dashboard live servi en statique : sans en-tête anti-cache, le
navigateur garde l'ancien index.html après mise à jour (« je vois encore la
version bleue »). Ajoute Cache-Control: no-store + Pragma: no-cache via un
handler dédié, et silence le log HTTP par requête.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa

* chore(stt): bump client 0.1.0 → 0.2.0 (HUD avancé)

La version inchangée faisait que pipx/uv réutilisait l'ancien build en cache
lors d'un réinstall (HUD bleu persistant malgré le nouveau code). Le bump force
une résolution propre vers la nouvelle version (HUD design Claude Design).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa

* fix(stt): cache-bust de l'URL HUD en kiosk + bump 0.2.1

Ouvre le HUD avec ?v=<timestamp> (URL unique par lancement) pour que le
navigateur ne puisse jamais servir une version cachée de l'ancien HUD.
Complète Cache-Control: no-store côté serveur.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-18 22:03:39 +02:00

12 KiB

STT — Assistant vocal "Jarvis" du homelab Funk

STT est l'interface vocale et graphique de Funk : un assistant type Jarvis / Iron Man qui écoute, parle et affiche un HUD animé. Architecture client-serveur : un client stt sur le poste (voix + HUD) qui interroge un STT-server in-cluster (orchestration AI). Objectif ultérieur : « agir sur le homelab » via les outils de Hermes (phase 7).

Nom de code : STT. À l'origine « Speech-To-Text », mais le projet couvre toute la chaîne voix → cerveau → voix + interface + mémoire. Renommable plus tard sans impact technique (FRIDAY, etc.) — c'est juste un identifiant de répertoire/commande.

2026-06-17 — déployé in-cluster (ArgoCD), mémoire court-terme (5a) + long-terme (5b) validées sur cible, embeddings dédiés nomic :1238 (5d) + fix thinking Qwen3 /no_think (5e). 2026-06-18 — HUD avancé (4) implémenté (design Claude Design) : avatar portrait réactif (anneau/ping/spinner), transcript à bulles, drawer Réglages câblé au backend (reset / mode cerveau / mot de réveil à chaud). Reste : test audio bout-en-bout sur le poste (phase 1), portraits réels + test HUD sur cible, auto-start (6), outils Hermes (7).


Principe directeur : réutiliser, ne pas réinventer

~70 % du backend existe déjà dans Funk. STT n'ajoute que le visage (HUD), la personnalisation, la mémoire multi-tiers et le packaging en commande.

Besoin Jarvis Brique Funk réutilisée État
Modèle local ou Claude LiteLLM :4000 + hermes-default + hermes-switch qwen|claude opérationnel
Agir sur le homelab Hermes Agent (:8080, profils funk-ai/system/monitor/brain) opérationnel
Voix (STT + TTS + wake word) tools/hermes-voice/ — faster-whisper + Piper + webrtcvad existe (CLI only)
Démarrage auto au boot pattern systemd --user (tools/hermes-voice/install-service.sh) éprouvé
Mémoire sémantique Qdrant :6333 + RAG (rag-query/rag-ingest) sur s01 Qdrant réparé 17/06 (RAG funk-docs à re-ingérer)

Maillon réellement manquant : l'interface graphique HUD + le packaging + la mémoire perso.


Décisions d'architecture (verrouillées 2026-06-17)

Décision Choix retenu
Architecture Client-serveur : client sur le poste, STT-server in-cluster (révise le tout-local)
Interface HUD web sur-mesure côté client (canvas/WebGL)
STT / TTS Local sur le poste (faster-whisper CPU + Piper) — le serveur ne touche pas à l'audio
Packaging Client : commande stt via pipx (#subdirectory=stt/client). Serveur : conteneur (ghcr) déployé par ArgoCD
Cerveau Côté serveur : route vers LiteLLM :4000 (Qwen3 / Claude). Outils Hermes = phase ultérieure
Client → serveur Client serveur-only (pas de repli Claude). URL serveur paramétrable
Mémoire Côté serveur (futur) : Qdrant s01 + distillée GitHub. Client : cache local SQLite

Pivot 2026-06-17 (post-test) : on est passé du tout-local à un modèle client-serveur. Le « cerveau » (ex-routeur 3 modes côté client) a migré côté serveur.


Architecture

                       LAN / *.lab.local
   ┌─ POSTE — client `stt` (pipx) ─┐        ┌─ CLUSTER k8s (namespace ai) ─────────┐
   │  • micro + VAD + wake word     │  HTTP  │  STT-server (Deployment + Service)    │
   │  • faster-whisper (STT)        │ ─────▶ │   GET  /healthz                        │
   │  • Piper (TTS)                 │ POST   │   POST /v1/ask {text} → {reply}        │
   │  • HUD web (ui/ + hud/)        │ /v1/ask│   brain → LiteLLM (httpx)              │
   │  • api.py → ServerClient       │ ◀───── │  IngressRoute : stt.lab.local         │
   └────────────────────────────────┘  reply │  → LiteLLM s01 192.168.10.1:4000      │
                                              └──────────────┬─────────────────────────┘
                                                             ▼
                                                LiteLLM :4000 (storage-01, hors cluster)
                                                → Qwen3 (g01) / Claude (hermes-default)

Le cerveau — côté serveur

Le STT-server appelle LiteLLM :4000 (OpenAI-compatible), joint depuis le cluster en IP directe 192.168.10.1:4000 (même pattern qu'open-webui). LiteLLM route lui-même vers Qwen3 (g01) ou Claude selon l'alias hermes-default / hermes-switch.

« Agir sur Funk » (outils de l'agent Hermes via le gateway :8080) n'est pas dans le MVP : l'API :8080 n'est pas spécifiée, et depuis un pod le SSH vers Hermes est impraticable. MVP = inférence/chat. L'intégration Hermes est une étape ultérieure.

Choix du modèle : le client envoie un model (alias LiteLLM) par requête à /v1/ask ; le serveur le valide contre STT_ALLOWED_MODELS et le passe à LiteLLM. Pas de switch global type hermes-switch (pas de restart, chaque client choisit le sien). GET /v1/models liste le défaut + les alias autorisés. Noms courts client : hermes (=hermes-default), qwen, claude (=claude-sonnet-4-6), opus.

Caveat Qwen3 — mode « thinking » (corrigé) : Qwen3 est un modèle de raisonnement. Sans précaution, il dépense tout le budget max_tokens (200) dans reasoning_contentcontent vide, ou réfléchit trop longtemps → timeout 502 (upstream LiteLLM : au message vide — signature d'un timeout httpx). Diagnostiqué via appel direct LiteLLM : réponse en 0.87s mais content="" et tout dans reasoning_content. Fix : brain.py ajoute le token /no_think au prompt système (STT_DISABLE_THINKING, défaut true ; inoffensif hors Qwen) et récupère reasoning_content en filet de sécurité si content est vide. Caveat partagé : admin/ia/llama_server.md.

La mémoire — côté serveur

  • Court-terme (5a, fait) : le serveur garde l'historique par session_id (en mémoire, borné max_turns + TTL) et l'injecte dans l'appel LLM. /v1/ask {session_id} + /v1/reset. Le client génère un session_id par run. Deployment en 1 worker (cohérence mémoire process).
  • Long-terme (5b, validé 17/06) : collection Qdrant dédiée stt-memory (indépendante du funk-docs du RAG), embeddings via Qwen3 :1234 (dim 4096, Cosine), souvenirs pertinents (top-k) injectés au prompt. Qdrant en IP directe 192.168.10.1:6333. Dégrade proprement si Qdrant/embeddings injoignables (la mémoire court-terme tient).
    • Validé bout-en-bout : « mon chat s'appelle Felix » (session A) rappelé dans une nouvelle session B (« comment s'appelle mon chat ? » → « Felix »). points_count confirmé via /v1/memory/health.
    • upsert ?wait=true : l'écriture Qdrant est synchrone, donc un souvenir est immédiatement cherchable (sans wait, l'écriture est mise en file → un rappel cross-session immédiat pouvait la rater).
    • GET /v1/memory/health : sonde active embed + Qdrant + collection, expose les erreurs (recall/remember dégradent en silence → indébogables sans cet endpoint). Sert au diagnostic.
  • Embeddings dédiés (5d, fait) : passage de Qwen3 (chat réutilisé, dim 4096, cosinus uniformément hauts → peu discriminant) à nomic-embed-text (modèle spécialisé, dim 768), servi par une instance llama-server dédiée sur gpu-01 :1238 (GPU), gérée par le rôle llama_server (llama_embed_enabled). STT : STT_EMBED_URL=…:1238, STT_EMBED_MODEL=nomic-embed-text.
    • Migration auto de la collection : _ensure_collection détecte le changement de dimension (4096 → 768) et recrée stt-memory (les anciens vecteurs sont incomparables dans le nouvel espace). Aucun drop manuel — la collection est reconstruite à la première requête après bascule.
  • Réparation Qdrant (5c, fait) : crash-loop depuis 05/06 (segment funk-docs corrompu). Réparé le 17/06 sur s01 : systemctl stop qdrant && rm -rf /srv/data/qdrant/storage/collections/funk-docs && systemctl start qdrant.

⚠️ Vie privée : seule la mémoire distillée serait committée. Repo privé impératif.


Composants

Composant Emplacement Description
Client stt stt/client/ (pipx) stt (voix+HUD), stt --text (chat texte), stt --setup, stt --server <url>, stt --model <hermes|claude|qwen|opus>
— voix stt/client/stt/voice/ wake word, STT faster-whisper, TTS Piper
— api stt/client/stt/api.py ServerClientPOST /v1/ask
— UI/HUD stt/client/stt/ui/ + hud/ HTTP statique + websocket bidirectionnel (états → HUD, réglages → backend) ; HUD embarqué dans le package
STT-server stt/server/ (conteneur) FastAPI : /healthz, /v1/ask ; brain.py → LiteLLM
Image ghcr.io/alkatrazz24/funk-stt-server construite par .github/workflows/build-stt-server.yml
Manifests k8s/apps/stt/ Deployment, Service, IngressRoute (stt.lab.local)
Application ArgoCD k8s/apps-of-apps/apps/stt.yaml déploie depuis main

Personnalisation (« l'image, le design, la voix »)

Côté client, pilotable depuis stt/client/config/ + l'écran de réglages du HUD :

  • Avatar / image (config/avatars/), thème (config/themes/), voix Piper .onnx (config/voices/), wake word, URL serveur.

Prérequis / dépendances

  • Poste : micro, Python 3.11+, pipx, navigateur. Piper + voix dans ~/.local/share/piper/, aplay.
  • Cluster : image poussée sur ghcr ; ArgoCD déploie depuis main (donc merge requis) ; ghcr privé ⇒ éventuel imagePullSecret.
  • LiteLLM joignable depuis le cluster (192.168.10.1:4000).

Roadmap

Phase Objectif État
0 — Cadrage Conception validée
1 — Client voix + HUD stt : voix locale + HUD + websocket
2 — STT-server FastAPI /v1/ask → LiteLLM
3 — Déploiement cluster image ghcr + manifests k8s + ArgoCD (LiteLLM en IP directe) déployé
4 — HUD avancé avatar portrait réactif (anneau/ping/spinner) + transcript + drawer réglages (modèle/reset/wake à chaud) + thème accent (à tester sur poste)
5a — Mémoire court-terme historique de session côté serveur
5b — Mémoire long-terme Qdrant stt-memory + embeddings (dégrade si down) validé 17/06 (rappel cross-session OK)
5c — Réparation Qdrant drop funk-docs corrompu + restart (s01) (17/06)
5d — Embeddings dédiés nomic-embed-text (dim 768) sur gpu-01 :1238 → recherche plus précise (rôle llama_server + migration auto collection)
5e — Fix thinking Qwen3 /no_think (content vide / timeout 502) + fallback reasoning_content (17/06)
6 — Auto-start client install-service.sh (systemd --user) + kiosk
7 — Outils Hermes « agir sur Funk » via gateway :8080 (API à spécifier)

État (validé sur cible)

Serveur déployé in-cluster via ArgoCD (image sha-<commit> gérée par CI). Mémoire 5a + 5b validées en prod : court-terme (historique de session) et long-terme (rappel cross-session « Felix », points_count confirmé via /v1/memory/health). HUD avancé (4) implémenté (design Claude Design, câblé au websocket backend). Reste côté poste : test audio bout-en-bout (micro → STT → serveur → TTS) et test du HUD avec portraits réels.


Projets de référence (inspiration, non forkés)

Aucun ne se branche proprement sur LiteLLM + Hermes (ils embarquent leur propre LLM/agent), mais bons pour le design et le code à piocher :