refactor(stt): pivot client-serveur — STT-server in-cluster + client pipx

Sépare STT en deux :
- stt/client/ : commande `stt` (pipx), voix locale (Whisper/Piper) + HUD ; envoie
  le texte au serveur via api.py (ServerClient → POST /v1/ask). URL serveur paramétrable,
  pas de cerveau local (suppression du routeur 3 modes).
- stt/server/ : STT-server FastAPI (conteneur), /healthz + /v1/ask → LiteLLM (Qwen3/Claude).

Déploiement cluster :
- k8s/apps/stt/ : Deployment, Service, IngressRoute (stt.lab.local), litellm-ext
  (Service + Endpoints → 192.168.10.1:4000 pour joindre LiteLLM hors cluster)
- k8s/apps-of-apps/apps/stt.yaml : Application ArgoCD (depuis main)
- .github/workflows/build-stt-server.yml : build/push image → ghcr.io/alkatrazz24/funk-stt-server

Inférence/chat seulement (outils Hermes 'agir sur Funk' = phase ultérieure, API :8080 à spécifier).
Vérifié : py_compile client+serveur, YAML manifests, ServerClient.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
This commit is contained in:
Claude 2026-06-17 09:37:34 +00:00
parent 1ff3fd9bed
commit 6947b394f1
No known key found for this signature in database
38 changed files with 566 additions and 356 deletions

View file

@ -1,15 +1,16 @@
# 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, affiche un HUD animé, et **agit sur le homelab** via Hermes Agent.
Il s'installe sur le poste perso comme **une commande unique** (`stt`) et démarre au boot.
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.
> ⚠️ **Conception validée (2026-06-17)** — squelette en place, implémentation par phases
> (voir Roadmap). Aucun composant n'est encore déployé sur le poste.
> ⚠️ **2026-06-17** — client (phase 1) + serveur (phase 2) + manifests cluster (phase 3) écrits.
> Reste : build image (CI), déploiement ArgoCD (après merge sur `main`), test audio sur poste.
---
@ -34,74 +35,53 @@ Maillon réellement manquant : **l'interface graphique HUD** + le packaging + la
| Décision | Choix retenu |
|---|---|
| Interface | **HUD web sur-mesure** (canvas/WebGL) + backend Python local |
| STT / TTS | **Local sur le poste** (faster-whisper CPU + Piper) |
| Packaging | **Commande `stt` via pipx** (`pipx install git+…#subdirectory=stt`) |
| Cerveau | **3 modes + auto-détection LAN** (voir ci-dessous) |
| Mémoire | **3 tiers** : local (SQLite) + s01 (Qdrant) + GitHub (distillée, versionnée) |
| 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
```
┌─ POSTE PERSO — commande `stt` (systemd --user, démarrage auto) ─────┐
│ │
│ ┌──────────────┐ websocket ┌───────────────────────────┐ │
│ │ HUD web │ ◀────────────▶ │ backend `stt` (Python) │ │
│ │ (kiosk) │ états + │ • micro + VAD + wake word │ │
│ │ • visualiseur│ transcript │ • faster-whisper (STT) │ │
│ │ • avatar │ + réponses │ • Piper (TTS) │ │
│ │ • thème │ │ • routeur cerveau (3 modes)│ │
│ │ • réglages │ │ • gestionnaire mémoire │ │
│ └──────────────┘ └───┬─────────────┬──────────┘ │
│ │ │ │
│ mémoire │ │ cerveau │
│ ┌────────────────────┘ │ │
│ ▼ ▼ │
│ SQLite local + cache (selon mode + LAN) │
│ git pull/push GitHub │
└───────────────────────────────────────────────────┼─────────────────┘
│ LAN (192.168.1.200)
┌───────────────────────────────────┼──────────────────┐
▼ ▼ ▼ ▼
Hermes :8080 LiteLLM :4000 Qdrant :6333 API Anthropic
(mode hermes) (mode local-direct) (mémoire s01) (mode claude-direct,
agit sur Funk chat g01/Claude sémantique hors-LAN, chat seul)
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 │ Service Endpoints : litellm-ext │
└──────────────┬─────────────────────────┘
LiteLLM :4000 (storage-01, hors cluster)
→ Qwen3 (g01) / Claude (hermes-default)
```
### Le cerveau — 3 modes + auto-détection
### Le cerveau — côté serveur
Au démarrage (et périodiquement), `stt` ping s01 (`192.168.1.200`) :
Le STT-server appelle **LiteLLM `:4000`** (OpenAI-compatible), joint depuis le cluster via
un Service sans sélecteur + Endpoints manuel (`litellm-ext``192.168.10.1:4000`). LiteLLM
route lui-même vers Qwen3 (g01) ou Claude selon l'alias `hermes-default` / `hermes-switch`.
| Mode | Déclenché quand | Chemin | Agit sur Funk ? | Coût |
|---|---|---|---|---|
| **`hermes`** | LAN OK (défaut maison) | PC → Hermes `:8080` (route Qwen3 g01 ou Claude via `hermes-switch`) | ✅ oui | gratuit/local sauf si switch claude |
| **`local-direct`** | LAN OK, chat pur voulu | PC → LiteLLM `:4000` (Qwen3 g01) | ❌ chat seul | gratuit |
| **`claude-direct`** | hors-LAN (nomade) | PC → API Anthropic (clé locale) | ❌ (Funk injoignable) | facturé |
> **« 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.
> **Clé de lecture** : « agir sur Funk » vit dans Hermes (sur s01). À la maison, même pour
> l'intelligence de Claude, on passe par Hermes (`hermes-switch claude`) → pouvoirs Funk
> conservés. Le mode `claude-direct` est le fallback nomade (Funk inatteignable de toute façon).
> Auto-détection par défaut, **override manuel** dans les réglages.
### La mémoire — côté serveur (futur)
### La mémoire — 3 tiers
- **Client** : cache local SQLite (`~/.local/share/stt/`, gitignoré) — historique brut.
- **Serveur** (à venir) : Qdrant s01 (sémantique, partagé RAG) + mémoire **distillée**
versionnée dans `stt/server/memory/distilled/`. Sessions multi-clients.
| Tier | Support | Contenu | Disponibilité |
|---|---|---|---|
| **Local** | SQLite + cache (`stt/memory/` local, gitignoré) | Historique brut récent, working memory | Toujours (offline) |
| **s01** | Qdrant `:6333`, collection dédiée STT | Mémoire sémantique long-terme, partagée avec le RAG Funk | Sur LAN |
| **GitHub** | `stt/memory/distilled/` (versionné) | Mémoire **distillée** : faits, préférences, résumés — source de vérité portable | Via git pull/push |
**Flux de synchro :**
1. Démarrage → `git pull` la mémoire distillée GitHub → charge en local.
2. Pendant l'usage → écriture immédiate en SQLite local.
3. Périodiquement / à l'arrêt → distillation (résumé via le cerveau) → `git commit` GitHub
**+** (si LAN) embedding dans Qdrant s01.
4. Sur nouveau poste / réinstall → GitHub re-seed local + Qdrant.
> ⚠️ **Vie privée** : seule la mémoire **distillée** est committée (faits/préférences).
> Les transcripts bruts restent locaux et **gitignorés**. Repo privé impératif.
> ⚠️ **Vie privée** : seule la mémoire distillée serait committée. Repo privé impératif.
---
@ -109,56 +89,53 @@ Au démarrage (et périodiquement), `stt` ping s01 (`192.168.1.200`) :
| Composant | Emplacement | Description |
|---|---|---|
| Commande `stt` | `stt/pyproject.toml` → entrypoint | `stt` (lance), `stt --setup` (install Whisper/Piper/voix), `stt --stop`, `stt --mode <hermes\|local\|claude>` |
| Backend | `stt/stt/` (package Python) | Extension de `tools/hermes-voice.py` : wake word, STT, TTS, routeur cerveau, mémoire, serveur websocket |
| HUD web | `stt/stt/hud/` | Front statique (HTML/JS/canvas) embarqué dans le package (livré par pipx) : visualiseur animé, conversation, écran de réglages. Servi localement, ouvert en kiosk |
| Config | `stt/config/` | `stt.example.toml` + thèmes, avatars, voix Piper |
| Mémoire | `stt/memory/` | `distilled/` versionné + raw local gitignoré |
| Auto-start | `stt/install-service.sh` | `systemd --user` : backend + ouverture HUD au login |
| (opt.) cluster | `k8s/apps/stt/` | Phase 6 : STT/TTS déportés en Wyoming sur gpu-01 |
| Client `stt` | `stt/client/` (pipx) | `stt`, `stt --setup`, `stt --server <url>`, `stt --no-tts` |
| — voix | `stt/client/stt/voice/` | wake word, STT faster-whisper, TTS Piper |
| — api | `stt/client/stt/api.py` | `ServerClient``POST /v1/ask` |
| — UI/HUD | `stt/client/stt/ui/` + `hud/` | HTTP statique + websocket d'états ; 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`), `litellm-ext` |
| Application ArgoCD | `k8s/apps-of-apps/apps/stt.yaml` | déploie depuis `main` |
---
## Personnalisation (« l'image, le design, la voix »)
Pilotable depuis `stt/config/` + l'écran de réglages du HUD, sans recompiler :
Côté client, pilotable depuis `stt/client/config/` + l'écran de réglages du HUD :
- **Avatar / image** : asset swappable (`config/avatars/`).
- **Thème / design** : couleurs, fond, style du visualiseur (`config/themes/`).
- **Voix** : modèle Piper `.onnx` interchangeable (`config/voices/`), fr_FR par défaut.
- **Wake word** : configurable (défaut « Ok Hermès »).
- **Cerveau** : mode par défaut + override, clé API Anthropic locale (pour `claude-direct`).
- **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 (kiosk). `stt --setup` gère Whisper/Piper/voix.
- **LAN** : accès `192.168.1.200` (s01) pour modes `hermes`/`local-direct` + Qdrant.
- **Qdrant réparé** requis pour la mémoire tier s01 (crash-loop depuis 05/06, voir
`admin/incidents.md`). **Non bloquant** : MVP fonctionne avec mémoire locale + GitHub seules.
- **Clé Anthropic** locale (poste) pour le mode `claude-direct` nomade.
- **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 | Livrable | État |
|---|---|---|---|
| **0 — Cadrage** | Conception validée | doc + squelette `stt/` | ✅ fait |
| **1 — Commande + backend** | `stt`/`stt --setup` lancent le moteur vocal + websocket | `pyproject.toml`, `stt/` (cli, config, voice, server) | ✅ fait |
| **2 — Routeur cerveau** | 3 modes + auto-détection LAN | module `brain` (hermes/local/claude) | ✅ fait |
| **3 — HUD** | Visualiseur + conversation | `stt/stt/hud/index.html` (MVP) → visualiseur avancé | 🟡 MVP fait |
| **4 — Mémoire 3 tiers** | local ✅ + GitHub + Qdrant | `memory` : SQLite ✅ ; distillation/sync git + Qdrant | 🟡 local fait |
| **5 — Personnalisation** | Thèmes / avatar / voix / wake word | écran réglages + `stt/config/` | ⏳ |
| **6 — Auto-start** | Démarre au boot du poste | `install-service.sh` + kiosk | ⏳ |
| **7 — Cluster (opt.)** | STT/TTS sur GPU | Wyoming sur gpu-01 + `k8s/apps/stt/` | ⏳ |
| 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-ext` | ✅ (à merger sur `main`) |
| **4 — HUD avancé** | visualiseur arc-reactor + thèmes + écran réglages | ⏳ |
| **5 — Mémoire serveur** | Qdrant s01 + distillée GitHub + sessions | ⏳ |
| **6 — Auto-start client** | `install-service.sh` (systemd --user) + kiosk | ⏳ |
| **7 — Outils Hermes** | « agir sur Funk » via gateway `:8080` (API à spécifier) | ⏳ |
### État phase 1-2 (testé hors-LAN)
### État (testé hors-LAN)
`py_compile` OK sur tout le package ; `stt --help`, chargement config, routeur cerveau
(auto-détection → `claude` hors-LAN) et mémoire SQLite locale vérifiés. Le test bout-en-bout
audio (Whisper/Piper/micro) se fait sur le poste après `pipx install` + `stt --setup`.
`py_compile` OK (client + serveur) ; client : `stt --help`, config, `ServerClient` vérifiés ;
serveur : import/compile OK. Reste à valider sur cible : build image (CI), déploiement ArgoCD
après merge, et test audio bout-en-bout sur le poste.
---