Commit graph

10 commits

Author SHA1 Message Date
e49b4fc3ba
refactor(stt): unifie le jeton Ghostfolio — source client unique, appel côté serveur (#41)
Avant : jeton Ghostfolio en double (config client pour l'intent vocal + secret k8s pour
le contexte serveur). Désormais une SEULE source — la config client — transmise au
serveur, qui fait tous les appels Ghostfolio (un seul code, lecture seule).

Serveur :
- sources.py : cœur _ghostfolio_fetch(token) partagé ; ghostfolio_block (contexte) et
  ghostfolio_phrase (intent) acceptent un jeton par requête (priorité client > env).
- app.py : /v1/ask accepte `secrets` (jetons client) passés aux sources ; nouvel
  endpoint POST /v1/portfolio {token} → valeur du portefeuille.
- bump 0.4.0 → 0.5.0.

Client :
- api.py : ServerClient.secrets transmis dans /v1/ask ; méthode portfolio() → /v1/portfolio.
- cli.py : _make_client renseigne secrets["ghostfolio_token"] depuis [ghostfolio].
- ui/app.py : l'intent « combien ghostfolio » appelle client.portfolio (serveur).
- supprime portal/ghostfolio.py (client) — l'appel API vit côté serveur.
- config : [ghostfolio] = juste access_token (transmis au serveur).
- bump 0.12.0 → 0.13.0.

Le secret k8s STT_GHOSTFOLIO_TOKEN devient un simple fallback. Validé bout-en-bout
contre une instance serveur locale + Ghostfolio réel : « …vaut environ 16 520 EUR. »

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 03:28:52 +02:00
1faf50a9be
feat(stt-server): contextes présélectionnables + sources live + contexte assemblé (visualiseur) (#38)
Asa n'est plus bloqué sur le seul contexte « doc cluster grounding-strict ». Le client
choisit un contexte par requête ; le serveur change le system prompt ET injecte les
données live du domaine, puis renvoie le contexte assemblé pour le visualiseur du HUD.

- contexts.py : profils funk / ghostfolio / grafana / alerting / cluster (system prompt
  + sources) + assemble() (prompt final + structure de visualisation).
- sources.py : fetchers live best-effort (Ghostfolio auth+details, Alertmanager alerts
  hors Watchdog, Prometheus cluster/metrics), env-config, dégradation propre.
- brain.py : ask() reçoit le system prompt déjà assemblé (assemblage remonté).
- app.py : /v1/ask accepte `context`, renvoie context_id + le contexte assemblé ;
  nouveau GET /v1/contexts ; RAG doc conditionné au profil.
- config.py : URLs sources + STT_GHOSTFOLIO_TOKEN + STT_DEFAULT_CONTEXT.
- deployment : env in-cluster (Prometheus/Alertmanager monitoring, Ghostfolio ai),
  jeton via secret optionnel stt-server-secrets/ghostfolio-token.
- bump 0.3.1 → 0.4.0.

Validé en local : assemblage (blocs+RAG+mémoire), parsing des sources (mock),
endpoints /v1/contexts et /v1/ask (LLM mocké) — context_id, visualiseur, fallback
contexte inconnu → funk.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:49:29 +02:00
1db84e9041
feat(stt-server): grounding RAG — le cerveau répond depuis la doc Funk (#27)
Sans contexte, le modèle (qwen3-8b) hallucinait sur le homelab (« stockage
décentralisé », « worker nodes »…) car il ignore tout du cluster réel. Le
cerveau interroge désormais la collection funk-docs (la doc admin/ indexée) et
injecte les passages pertinents au prompt → il répond à partir de la vraie doc.

Coût ~nul : funk-docs et stt-memory partagent le même embedder nomic (:1238,
768 dim) → le vecteur de requête calculé pour le recall mémoire est réutilisé
pour la recherche doc (une embed, deux recherches). Dégrade en silence si
Qdrant/embed down (réponse sans doc plutôt qu'échec).

Module knowledge.py (STT_DOCS_RAG, TOPK=6, MIN_SCORE=0.45). Log /v1/ask
expose docs=<n>. Serveur 0.2.1 → 0.3.0.

Validé : « c'est quoi le nom des nœuds ? » → avant « worker/master nodes »
(inventé) ; après « Kubernetes ; nœuds storage-01, compute-01, compute-02 »
(tiré de la doc).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:53:30 +02:00
681b94e268
fix(stt-server): retry brain + timeout par tentative + log timing effectif (#21)
Suite au déploiement 0.2.0, deux points observés en pilotant l'app en direct :

- Le log de timing (`ask … recall/gen/total`) n'émettait pas : uvicorn ne
  configure que ses propres loggers → on attache un handler INFO sur `stt_server`.
- La chaîne LiteLLM→llama-server (ROCm gfx1031) se fige par à-coups (~1-2 min,
  502 pour tous les appels, cf. incidents-llm-gpu.md). Résilience côté STT :
  brain tente 2× (fermeture du client httpx entre les essais → connexion neuve si
  le pool est vicié) et le timeout passe à 30s PAR tentative (≤ ~2× au pire).
  Un blip transitoire devient une requête récupérée au lieu d'un 502.

bump serveur 0.2.0 → 0.2.1

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:46:35 +02:00
914942de73
perf(stt-server): mémoire long-terme hors chemin de réponse + résilience embed (#20)
Cause de la latence 30-45s : l'endpoint d'embeddings (gpu-01:1238) peut se
geler ; recall ET remember l'attendaient ~20s chacun (timeout → dégradation
silencieuse), s'ajoutant à la réponse. Refactor :

- store (ex-remember) en BackgroundTasks → APRÈS la réponse, hors latence perçue ;
  suppression de `?wait=true` (pas d'attente du flush Qdrant)
- recall renvoie aussi le vecteur de la requête → store le réutilise (1 embed/tour
  au lieu de 2, le 2ᵉ portait sur le même texte)
- timeout recall serré (4s, STT_MEMORY_RECALL_TIMEOUT) : un embed lent/mort dégrade
  vite (souvenirs vides) au lieu de bloquer ; store tolère 20s en arrière-plan
- clients httpx persistants (pooling/keep-alive) côté brain + longterm, fermés via
  lifespan (plus de handshake TCP par appel)
- log de timing par requête (recall/gen/total/mem) pour diagnostiquer
- bump serveur 0.1.0 → 0.2.0

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:27:59 +02:00
1c3128a319
feat(stt): /v1/memory/health + upsert Qdrant synchrone (debug 5b) (#11)
* 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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 20:32:04 +02:00
1986ab56d8
feat(stt): mémoire long-terme sémantique via Qdrant (5b) (#10)
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.


Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 16:52:33 +02:00
815beaf37c
feat(stt): mémoire court-terme de session côté serveur (5a) (#8)
Serveur : SessionStore (historique borné + TTL, en mémoire), /v1/ask accepte session_id
et injecte l'historique dans l'appel LLM, /v1/reset l'efface. Dockerfile en 1 worker
(cohérence mémoire process).

Client : session_id généré par run (uuid), envoyé à chaque requête ; commande /reset
en mode texte.

Testé en process (TestClient) : historique croît 0→2→4, reset→0, sessions isolées,
sans session_id = sans état.

Mémoire long-terme Qdrant (5b) + réparation Qdrant (5c) à suivre.


Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 16:15:57 +02:00
bcdb4f8b2a
feat(stt): mode texte + sélection de modèle (client→serveur par requête) (#7)
Serveur : /v1/ask accepte un model optionnel (validé contre STT_ALLOWED_MODELS),
nouvel endpoint GET /v1/models (défaut + alias autorisés). Pas de switch global.

Client : 'stt --text' (chat texte simple sans micro/HUD), '--model hermes|claude|qwen|opus'
(noms courts → alias LiteLLM), commandes /model et /models en mode texte. Le modèle
choisi est envoyé au serveur à chaque requête (voix comme texte).


Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 13:36:06 +02:00
e390ddef12
feat(stt): assistant vocal Jarvis — client pipx + STT-server in-cluster (#4)
* feat(stt): cadrage + squelette assistant vocal Jarvis

Conception validée du projet STT — assistant vocal/HUD du homelab Funk :
- HUD web sur-mesure + STT/TTS local (faster-whisper + Piper)
- Packaging commande pipx (stt), démarrage auto systemd --user
- Cerveau 3 modes + auto-détection LAN : hermes / local-direct / claude-direct
- Mémoire 3 tiers : SQLite local + Qdrant s01 + GitHub (distillée, versionnée)

Réutilise tools/hermes-voice, LiteLLM, Hermes Agent. Squelette + doc admin/ia/stt.md,
implémentation par phases (roadmap dans le doc).

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

* feat(stt): phase 1-2 — commande, backend vocal, routeur cerveau, HUD MVP

- cli.py : commande `stt` (--setup, --mode, --no-tts)
- config.py : défauts embarqués + ~/.config/stt/stt.toml
- voice/engine.py : refactor de hermes-voice en classe avec callbacks d'état
- brain/router.py : 3 modes (hermes SSH / local LiteLLM / claude API) + auto-détection LAN
- server/app.py : HTTP statique (HUD) + websocket (états → HUD)
- memory/store.py : tier local SQLite (Qdrant + sync GitHub = phase 4)
- hud/index.html : HUD MVP (visualiseur d'état + conversation)

Vérifié hors-LAN : py_compile, --help, config, routeur (→ claude), mémoire SQLite.

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

* fix(stt): embarquer le HUD dans le package (404 après pipx install)

Le HUD était à la racine du projet (stt/hud/) donc absent du package installé
par pipx → HTTP 404 sur /. Déplacé dans le package (stt/stt/hud/) + package-data,
HUD_DIR ajusté. Vérifié : le wheel contient bien stt/hud/index.html.

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

* 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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 12:08:58 +02:00