Funk-lab/admin/ia/stt.md
ALI YESILKAYA 9fef555cc3
feat(stt): embeddings dédiés nomic-embed-text sur gpu-01 (:1238) + migration auto (#12)
* feat(stt): mémoire long-terme sémantique via Qdrant (5b)

Serveur : longterm.py — collection Qdrant stt-memory (embeddings Qwen3 gpu-01, dim auto,
Cosine), recall top-k injecté au prompt, remember des tours user. Tout dégrade proprement
si Qdrant/embeddings injoignables (la mémoire court-terme tient). Env STT_MEMORY_LONGTERM,
STT_QDRANT_URL, STT_EMBED_URL, STT_MEMORY_TOPK.

Testé en process : dégradation OK (Qdrant down → mem=0, pas de crash, court-terme tient).
Qdrant réparé le 17/06 (5c). Recherche sémantique réelle à valider sur cluster.

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

* feat(stt): endpoint /v1/memory/health + upsert Qdrant synchrone

- /v1/memory/health sonde activement embeddings + Qdrant + collection et
  expose les erreurs (recall/remember dégradent en silence → indébogables).
  Permet de diagnostiquer la mémoire long-terme sans kubectl exec.
- remember() : upsert avec ?wait=true → le souvenir est immédiatement
  cherchable (sans wait, Qdrant met l'écriture en file → un recall
  cross-session immédiat pouvait le rater).

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

* docs(stt): 5b mémoire long-terme validée en prod + backlog nomic-embed-text

- Rappel cross-session confirmé (« Felix » retrouvé dans une nouvelle session),
  points_count vérifié via /v1/memory/health.
- Note du fix upsert ?wait=true et de l'endpoint de diagnostic.
- Roadmap : 5d (nomic-embed-text dim 768) en backlog qualité ; états haut/bas
  du doc mis à jour (déployé + validé sur cible).

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

* feat(stt): embeddings dédiés nomic-embed-text sur gpu-01 (:1238) + migration auto

Remplace Qwen3 (chat réutilisé, dim 4096, peu discriminant) par un modèle
d'embedding spécialisé nomic-embed-text (dim 768) pour la mémoire long-terme.

Ansible (rôle llama_server) :
- nouvelle instance optionnelle `llama-embed` (llama_embed_enabled) servant un
  modèle d'embedding dédié sur :1238, GPU ; télécharge le GGUF si absent.
- activée sur gpu-01 (host_vars) : nomic-embed-text-v1.5 f16.

STT-server :
- STT_EMBED_URL → :1238, STT_EMBED_MODEL → nomic-embed-text (deployment + config).
- _ensure_collection détecte le changement de dimension (4096→768) et recrée
  automatiquement la collection stt-memory (anciens vecteurs incomparables) —
  pas de drop manuel.

Docs : llama_server README, rag.md, stt.md (5d ), CLAUDE.md.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 21:54:41 +02:00

181 lines
11 KiB
Markdown

# 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** (rappel cross-session OK via `/v1/memory/health`). Reste : test audio
> bout-en-bout sur le poste (phase 1), HUD avancé (4), 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 | ⚠️ **HS depuis 05/06** |
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`.
### 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` | `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`) |
| 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é** | visualiseur arc-reactor + thèmes + écran réglages | ⏳ |
| **5a — Mémoire court-terme** | historique de session côté serveur | ✅ |
| **5b — Mémoire long-terme** | Qdrant `stt-memory` + embeddings Qwen3 (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) |
| **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`). Reste côté poste : test audio
bout-en-bout (micro → STT → serveur → TTS) et HUD avancé.
---
## 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 :
- [AlexandreSajus/JARVIS](https://github.com/AlexandreSajus/JARVIS) — Voice→LLM→Speech, interface web (proche du front visé).
- [InterGenJLU/jarvis](https://github.com/InterGenJLU/jarvis) — AMD ROCm (comme la RX 6700XT), HUD santé, streaming TTS.
- [novik133/jarvis](https://github.com/novik133/jarvis) — whisper.cpp + Piper (même TTS) + monitoring système.
- [rhasspy/wyoming-addons](https://github.com/rhasspy/wyoming-addons) — faster-whisper / Piper conteneurisés (option phase 7).