docs(cluster): aligne la doc sur l'état réel + re-ingest RAG + fix ubatch embed (#44)

* fix(rag): ubatch=2048 pour llama-embed + retry rag-ingest + nettoie defaults rag

L'instance llama-embed (:1238) tournait avec l'ubatch llama.cpp par défaut (512).
En mode --embeddings + pooling, toute l'entrée doit tenir dans un seul ubatch →
tout chunk > 512 tokens échouait en HTTP 500 « input too large to process.
increase the physical batch size ». ~46 chunks du RAG (CHUNK_MAX=2000 chars ≈
700 tokens) n'étaient pas indexés.

- llama_server : ajoute llama_embed_ubatch_size (2048 = ctx) + flags
  --batch-size/--ubatch-size dans le template llama-embed.service.
- rag-ingest : retry (5×, backoff) sur erreurs transitoires 5xx/réseau de l'embed.
- rag/defaults : aligne embed_url/embed_model sur nomic :1238 (étaient des vars
  mortes pointant encore qwen3-8b :1234 — trompeuses).

Résultat : ré-ingestion 436/436 chunks, 0 erreur.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(cluster): aligne la doc sur l'état réel (relevé 2026-06-21) + snapshot

Passe de vérification terrain (SSH s01/g01 + kubectl) pour corriger les écarts
doc↔réalité, et ajout d'un document de référence vérifié.

- admin/ops/etat-cluster.md (nouveau) : snapshot daté — machines, services+ports
  par hôte, workloads k8s, namespaces, IngressRoute, bases PG, modèles LiteLLM,
  RAG/Qdrant, + procédure pour régénérer le relevé.
- OS AlmaLinux 9.7 → 9.8 (s01/g01) ; précise k8s v1.33.1 (CLAUDE/README/install/talos).
- RAG funk-docs : retire le warning périmé « supprimée 2026-06-17 / à re-ingérer » →
  reconstruite (436 chunks, nomic-embed-text :1238 dim 768, CPU). Maj rag.md,
  incidents.md, stt.md.
- Namespace sacrifice (hors-repo, hors ArgoCD) + bases PG grafana/sacrifice +
  services ai via IngressRoute + registry in-cluster (CLAUDE.md, k9s.md).
- Services de résilience (llm-heartbeat, llama-watchdog/embed) et instances CPU
  manuelles :1236/:1237 (souvent éteintes) documentés.
- Journal progress/2026-06-21.md : section relevé + RAG.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ALI YESILKAYA 2026-06-21 23:29:05 +02:00 committed by GitHub
parent f0e33c1121
commit af98b47018
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 292 additions and 82 deletions

View file

@ -8,10 +8,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
**État actuel (2026-06-21)** :
- ✅ Hermes Agent opérationnel — multi-profils (funk-ai/system/monitor/brain)
- ✅ Qdrant réparé (2026-06-17) — fin du crash-loop (segment `funk-docs` corrompu, voir `admin/incidents.md`). ⚠️ La collection RAG `funk-docs` a été supprimée lors de la réparation → **re-`rag-ingest` requis** pour réactiver `rag-query`
- ✅ Qdrant réparé (2026-06-17) — fin du crash-loop (segment `funk-docs` corrompu, voir `admin/incidents.md`). La collection RAG `funk-docs` a depuis été **reconstruite** (436 chunks, 🟢 green, embeddings nomic-embed-text dim 768) — `rag-query` opérationnel
- ✅ Monitoring complet — Prometheus + Grafana + AlertManager → webhook Hermes + n8n
- ✅ storage-01 installé — `192.168.10.1` (LAN cluster), `192.168.1.200` (WAN)
- ✅ gpu-01 installé — llama-server GPU (:1234) + CPU (:1236/:1237) + embeddings dédiés nomic-embed-text (:1238, GPU) opérationnels, kernel 5.14.0-687.5.3
- ✅ gpu-01 installé — llama-server GPU (:1234, qwen3-8b) + embeddings dédiés nomic-embed-text (:1238, **CPU** — découplé du slot chat GPU) + watchdog/heartbeat de résilience, kernel 5.14.0-687.5.3. ⚠️ Les instances CPU (:1236/:1237 — profils Hermes `system`/`monitor`) sont **manuelles** et souvent éteintes (gap IaC)
- ✅ LiteLLM sur storage-01 — proxy multi-modèles + ask-agent (écoute `0.0.0.0:4000`)
- ✅ Cluster k8s Talos opérationnel (MetalLB + Traefik + wildcard DNS lab.local)
- ✅ postfix_relay — SMTP sortant via Gmail SMTP + App Password (aliyesilkaya93@gmail.com)
@ -30,11 +30,13 @@ Journal de progression : `PROGRESS.md` → `progress/YYYY-MM-DD.md`
| Machine | IP | OS | Rôle |
|---|---|---|---|
| storage-01 | 192.168.10.1 / 192.168.1.200 | AlmaLinux 9.7 | Passerelle + bastion admin + données + Hermes Agent — **hors cluster** |
| compute-01 | 192.168.10.11 | Talos v1.13 | k8s control-plane |
| compute-02 | 192.168.10.12 | Talos v1.13 | k8s worker |
| compute-03 | 192.168.10.13 | Talos v1.13 | k8s worker |
| gpu-01 | 192.168.10.20 | AlmaLinux 9.7 | llama-server dédié (RX 6700XT, ROCm 7.x) — **hors cluster** |
| storage-01 | 192.168.10.1 / 192.168.1.200 | AlmaLinux 9.8 | Passerelle + bastion admin + données + Hermes Agent — **hors cluster** |
| compute-01 | 192.168.10.11 | Talos v1.13 (k8s v1.33.1) | k8s control-plane |
| compute-02 | 192.168.10.12 | Talos v1.13 (k8s v1.33.1) | k8s worker |
| compute-03 | 192.168.10.13 | Talos v1.13 (k8s v1.33.1) | k8s worker |
| gpu-01 | 192.168.10.20 | AlmaLinux 9.8 | llama-server dédié (RX 6700XT, ROCm 7.x) — **hors cluster** |
> 📸 État live vérifié (services, ports, workloads, bases, RAG) : **`admin/ops/etat-cluster.md`** (snapshot 2026-06-21).
**Points clés d'architecture :**
- `storage-01` et `gpu-01` sont **hors du cluster k8s** — consommés via `ExternalName` services ou `Endpoints` manuels
@ -179,7 +181,9 @@ k8s/
2. Créer `k8s/apps-of-apps/apps/<nom>.yaml` (ArgoCD Application CRD) — copier `n8n.yaml` comme modèle
3. `git commit` + `git push` → ArgoCD prend en charge automatiquement
Namespaces actifs : `argocd`, `infra` (Traefik, MetalLB, NFS-provisioner), `monitoring` (Prometheus/Grafana), `ai` (n8n, open-webui, stt, ghostfolio).
Namespaces actifs : `argocd`, `infra` (Traefik, MetalLB, NFS-provisioner, registry in-cluster), `monitoring` (Prometheus/Grafana), `ai` (n8n, open-webui, stt, ghostfolio), `sacrifice` (jeu FPS — déployé hors de ce repo et hors ArgoCD, cf. `admin/ops/etat-cluster.md`).
> Les services du namespace `ai` sont exposés via des **IngressRoute Traefik** (CRD), pas des `Ingress` standards.
---
@ -220,9 +224,9 @@ ask-agent monitor "logs litellm : <résultat étape 1> — erreurs ?"
### RAG — Documentation Funk
> ⚠️ **Qdrant réparé le 2026-06-17** (fin du crash-loop, segment `funk-docs` corrompu — voir `admin/incidents.md`). La réparation a **supprimé** la collection `funk-docs` sans la reconstruire → relancer `rag-ingest` pour réactiver `rag-query`. Le service Qdrant et la mémoire long-terme STT (`stt-memory`) sont opérationnels.
> **`funk-docs` reconstruite et opérationnelle** (relevé 2026-06-21 : **436 chunks**, 🟢 green). Le warning « collection supprimée le 2026-06-17, re-ingest requis » est **périmé** : elle a été ré-indexée depuis. Qdrant et la mémoire long-terme STT (`stt-memory`) sont opérationnels.
Qdrant collection `funk-docs` indexée depuis `admin/` (≈339 chunks avant suppression, score min 0.60 ; à re-ingérer).
Qdrant collection `funk-docs` indexée depuis `admin/` (436 chunks, embeddings **nomic-embed-text** `:1238` dim 768 — plus qwen3-8b ; score min ajusté pour nomic dans `rag-query`).
```bash
rag-query "comment relancer dnsmasq ?"
@ -333,6 +337,6 @@ talosctl etcd snapshot /path/to/backup.db --nodes compute-01
- **Git** : branches `main` (prod) / `feature/*` — conventional commits (`feat:`, `fix:`, `chore:`, `docs:`)
- **Secrets** : Ansible Vault (Ansible), SOPS+age (Talos), Sealed Secrets (k8s)
- **Namespaces k8s** : `argocd`, `infra`, `monitoring`, `ai`, `external`
- **Namespaces k8s** : `argocd`, `infra`, `monitoring`, `ai`, `external` (+ `sacrifice`, hors-repo)
- **Hermes data** : profils/données dans `/srv/data/hermes` (NVMe, bind-mount `/home/data`), code dans `/opt/hermes/` (SSD OS)
- **RAM compute-02/03** : toujours définir `resources.requests` + `resources.limits` (8 GB RAM, OOM-killer actif)

View file

@ -83,7 +83,7 @@ L'ensemble est géré en Infrastructure-as-Code : **Ansible** pour les hôtes Al
| Composant | Rôle | Hôte |
|---|---|---|
| **llama-server** (×4) | Inférence GPU (:1234) + 2× CPU isolés (:1236/:1237) + embeddings dédiés nomic-embed-text (:1238) — ROCm 7.x | gpu-01 |
| **llama-server** | Inférence GPU (:1234, qwen3-8b — ROCm 7.x) + embeddings dédiés nomic-embed-text (:1238, CPU) + watchdog/heartbeat. Instances CPU :1236/:1237 (profils `system`/`monitor`) **manuelles, souvent éteintes** | gpu-01 |
| **LiteLLM** | Proxy API — routing local/cloud transparent | storage-01 |
| **Hermes Agent** | Agent IA multi-profils avec mémoire vectorielle | storage-01 |
| **ask-agent** | Délégation inter-agents (system / monitor / brain) | storage-01 |
@ -108,11 +108,11 @@ L'ensemble est géré en Infrastructure-as-Code : **Ansible** pour les hôtes Al
| Machine | IP | OS | RAM | Rôle |
|---|---|---|---|---|
| storage-01 | 192.168.10.1 | AlmaLinux 9.7 | 32 GB | Gateway · NFS · Services IA |
| gpu-01 | 192.168.10.20 | AlmaLinux 9.7 | 32 GB | Inférence LLM (RX 6700XT) |
| compute-01 | 192.168.10.11 | Talos v1.13 | 16 GB | k8s control-plane |
| compute-02 | 192.168.10.12 | Talos v1.13 | 8 GB | k8s worker |
| compute-03 | 192.168.10.13 | Talos v1.13 | 8 GB | k8s worker |
| storage-01 | 192.168.10.1 | AlmaLinux 9.8 | 32 GB | Gateway · NFS · Services IA |
| gpu-01 | 192.168.10.20 | AlmaLinux 9.8 | 32 GB | Inférence LLM (RX 6700XT) |
| compute-01 | 192.168.10.11 | Talos v1.13 (k8s v1.33.1) | 16 GB | k8s control-plane |
| compute-02 | 192.168.10.12 | Talos v1.13 (k8s v1.33.1) | 8 GB | k8s worker |
| compute-03 | 192.168.10.13 | Talos v1.13 (k8s v1.33.1) | 8 GB | k8s worker |
### Stockage
@ -248,7 +248,7 @@ Elle est gérée automatiquement par le rôle Ansible `llama_server`.
- [x] Ansible — gpu-01 (ROCm, llama-server : GPU :1234 + embeddings :1238 via le rôle ; 2× CPU manuels)
- [x] Stack IA opérationnelle (Hermes multi-profils + Qwen3 + Claude API)
- [x] Délégation inter-agents (`ask-agent` + skill `agent-delegation`)
- [x] RAG opérationnel — Qdrant 339 chunks, `rag-query` / `rag-ingest`
- [x] RAG opérationnel — Qdrant ~436 chunks (embeddings nomic-embed-text `:1238`, dim 768), `rag-query` / `rag-ingest`
- [x] User `hermes` avec SSH partagé entre storage-01 et gpu-01
- [x] Cluster Kubernetes opérationnel (Talos v1.13 + talhelper)
- [x] MetalLB + Traefik + wildcard DNS `lab.local`
@ -259,8 +259,8 @@ Elle est gérée automatiquement par le rôle Ansible `llama_server`.
- [x] hermes-auto-improve — analyse quotidienne de la doc par Hermes (n8n 22h → PR `hermes/daily-work`)
- [x] Ghostfolio — suivi de portefeuille (`ghostfolio.lab.local`, PostgreSQL s01 + Redis)
- [x] STT — assistant vocal « Jarvis » (client `stt` + STT-server in-cluster, mémoire Qdrant `stt-memory`)
- [x] Modèle d'embedding dédié — nomic-embed-text sur gpu-01 `:1238` (utilisé par la mémoire STT)
- [x] Réparer Qdrant — crash-loop résolu 2026-06-17 (collection `funk-docs` supprimée → à re-ingérer)
- [ ] Re-ingérer le RAG (`rag-ingest`) et le basculer sur l'embedding dédié `:1238`
- [x] Modèle d'embedding dédié — nomic-embed-text sur gpu-01 `:1238` (utilisé par la mémoire STT **et le RAG**)
- [x] Réparer Qdrant — crash-loop résolu 2026-06-17 ; collection `funk-docs` **reconstruite** depuis (436 chunks)
- [x] Re-ingérer le RAG (`rag-ingest`) **basculé sur l'embedding dédié `:1238`** (dim 768)
- [ ] WOL compute nodes (configuration BIOS)
- [ ] Route statique Freebox

View file

@ -1,9 +1,8 @@
# RAG — Documentation Funk
> ⚠️ **À re-ingérer** — Qdrant a été réparé le **2026-06-17** (fin du crash-loop), mais la
> réparation a **supprimé la collection `funk-docs`** : `rag-query` ne retourne rien tant que
> `rag-ingest` n'a pas reconstruit l'index (reconstructible depuis `admin/`).
> Détails de l'incident : `admin/incidents.md` (entrée 2026-06-05).
> ✅ **Opérationnel** (relevé 2026-06-21) — `funk-docs` reconstruite après l'incident Qdrant du
> 2026-06-17 : **436 chunks**, statut 🟢 green, embeddings **nomic-embed-text** (`:1238`, dim 768).
> `rag-query` fonctionne. Détails de l'incident : `admin/incidents.md` (entrée 2026-06-05).
Le RAG (Retrieval-Augmented Generation) permet à Hermes de répondre en s'appuyant
sur la documentation du repo (`admin/`) plutôt que sur sa seule mémoire de modèle.
@ -17,7 +16,7 @@ Hermes (funk-ai)
│ terminal: rag-query "comment relancer dnsmasq ?"
/usr/local/bin/rag-query (storage-01)
│ 1. Embed la question → POST http://192.168.10.20:1234/v1/embeddings
│ 1. Embed la question → POST http://192.168.10.20:1238/v1/embeddings (nomic-embed-text)
│ 2. Recherche vectorielle → POST http://127.0.0.1:6333/collections/funk-docs/points/search
Qdrant (storage-01:6333)
@ -40,52 +39,37 @@ Hermes formule sa réponse en citant les sources
| `rag-query` | `/usr/local/bin/rag-query` | Interroge Qdrant depuis la ligne de commande |
| Skill `rag-docs` | Profil `funk-ai` Hermes | Enseigne à Hermes comment utiliser `rag-query` |
| Docs indexées | `/srv/data/rag/docs/` (NVMe) | Copie locale du dossier `admin/` |
| Collection Qdrant | `funk-docs` | supprimée le 2026-06-17 → **à re-ingérer** (≈339 chunks, dim 4096 Cosine) |
| Collection Qdrant | `funk-docs` | **436 chunks**, 🟢 green, **dim 768 Cosine** (nomic-embed-text) |
---
## Modèle d'embedding utilisé
**Qwen3-8B** via llama-server GPU (port 1234), avec les flags `--embeddings --pooling mean`.
**nomic-embed-text** via l'instance dédiée llama-server `llama-embed` sur gpu-01 (`:1238`, **CPU**,
dim 768). C'est un **vrai modèle d'embedding** — les scripts déployés `rag-ingest` et `rag-query`
le hardcodent (`EMBED_URL=http://192.168.10.20:1238/v1/embeddings`, `EMBED_MODEL=nomic-embed-text`).
C'est le modèle de chat principal, réutilisé pour les embeddings.
> Historique : le RAG utilisait au départ **Qwen3-8B** (modèle de chat, `:1234`, dim 4096) réutilisé
> pour les embeddings → scores cosinus saturés (0.900.95), peu discriminants. La migration vers
> nomic (dim 768) étale les scores → tri par pertinence réel. La collection a été recréée à dim 768.
### Limitation connue
> ⚠️ **Drift IaC** : `ansible/roles/rag/defaults/main.yml` a longtemps gardé `embed_url: :1234` /
> `embed_model: qwen3-8b` — variables **mortes** (le handler lance `rag-ingest` sans passer d'env →
> le défaut hardcodé nomic du script gagne). Aligné sur nomic dans le même lot que ce relevé.
Qwen3-8B est un modèle génératif, **pas un modèle d'embedding dédié**.
Ses représentations vectorielles ont peu de discrimination sémantique :
les scores de similarité cosinus sont uniformément hauts (0.900.95)
quelle que soit la pertinence du résultat.
Conséquence : les résultats retournés ne sont pas triés par pertinence réelle —
le fichier avec le plus de tokens (ex : `incidents.md`) remonte souvent en premier.
### Quand améliorer
Pour une recherche sémantique précise, utiliser un modèle d'embedding dédié :
| Modèle | Taille | Dimension | Notes |
|---|---|---|---|
| `nomic-embed-text-v1.5` | ~274 MB | 768 | Rapide, bon équilibre qualité/taille |
| `bge-m3` | ~1.2 GB | 1024 | Multilingue (français natif) — meilleur choix pour ce repo |
Déploiement — **déjà fait pour STT** (instance `nomic-embed-text` sur `:1238`, dim 768) :
le rôle `llama_server` gère désormais une instance dédiée embeddings sur gpu-01 (GPU),
activée par `llama_embed_enabled` (voir `ansible/roles/llama_server/README.md`). STT pointe
dessus (`STT_EMBED_URL=http://192.168.10.20:1238/v1/embeddings`).
L'instance `llama-embed` est gérée par le rôle `llama_server` (`llama_embed_enabled`, voir son README).
```bash
# (re)déployer l'instance embeddings sur gpu-01
ansible-playbook -i inventory.yml playbooks/gpu-01.yml --tags llama_server
```
Pour basculer **le RAG** sur cette même instance (quand il sera réparé) :
```bash
# Modifier ansible/roles/rag/defaults/main.yml :
# embed_url: "http://192.168.10.20:1238/v1/embeddings"
# embed_model: "nomic-embed-text"
# Puis re-indexer (la dimension passe à 768 → collection recréée) : rag-ingest /srv/data/rag/docs/
```
### Alternative envisagée
| Modèle | Taille | Dimension | Notes |
|---|---|---|---|
| `nomic-embed-text` | ~274 MB | 768 | **En service** — rapide, bon équilibre qualité/taille |
| `bge-m3` | ~1.2 GB | 1024 | Multilingue (français natif) — option si la qualité FR doit monter |
---
@ -143,7 +127,7 @@ rsync -av --delete admin/ storage-01:/srv/data/rag/docs/
ssh storage-01 "rag-ingest /srv/data/rag/docs/"
```
L'ingestion prend ~5-10 minutes pour 339 chunks (embeddings via GPU).
L'ingestion prend quelques minutes pour ~436 chunks (embeddings via l'instance nomic `:1238`, CPU).
Elle est idempotente — re-lancer ne crée pas de doublons (IDs stables basés sur MD5 du fichier+section).
---
@ -197,9 +181,8 @@ hermes-skills/funk/rag-docs/
| Sujet | Détail |
|---|---|
| Qualité embeddings | Qwen3-8B = modèle chat réutilisé — scores peu discriminants. Fonctionnel mais pas optimal. |
| Modèle dédié | `nomic-embed-text` ou `bge-m3` diviseraient les faux positifs par ~3 |
| Contention GPU | `rag-ingest` sollicite le GPU (port 1234) pendant ~5-10 min — éviter pendant une session d'inférence active |
| Qualité embeddings | `nomic-embed-text` (modèle dédié) — scores cosinus discriminants, tri par pertinence réel |
| Embeddings CPU | `llama-embed` (:1238) tourne en **CPU** (découplé du slot chat GPU) — `rag-ingest` ne contend pas le GPU. ubatch=2048 requis (sinon 500 sur chunks > 512 tokens) |
| Re-indexation | Obligatoire après modification de `admin/` — pas de sync automatique |
| Qdrant persistance | Données dans `/srv/data/qdrant/` sur NVMe — survivent aux redémarrages |
| Score minimum | `MIN_SCORE = 0.60` dans `rag-query` — tous les résultats sont au-dessus avec ce modèle |
| Score minimum | `MIN_SCORE` dans `rag-query` ajusté pour nomic (scores plus étalés que qwen3-8b) |

View file

@ -31,7 +31,7 @@ Objectif ultérieur : « agir sur le homelab » via les outils de Hermes (phase
| 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 | `stt --install-service` (systemd --user) — repris du pattern `tools/hermes-voice/` | ✅ |
| Mémoire sémantique | Qdrant `:6333` + RAG (`rag-query`/`rag-ingest`) sur s01 | ✅ Qdrant réparé 17/06 (RAG `funk-docs` à re-ingérer) |
| Mémoire sémantique | Qdrant `:6333` + RAG (`rag-query`/`rag-ingest`) sur s01 | ✅ Qdrant réparé 17/06, RAG `funk-docs` re-ingéré (436 chunks, nomic dim 768) |
Maillon réellement manquant : **l'interface graphique HUD** + le packaging + la mémoire perso.
@ -125,7 +125,7 @@ au prompt système (`STT_DISABLE_THINKING`, défaut `true` ; inoffensif hors Qwe
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
`funk-docs` du RAG), embeddings via nomic-embed-text `:1238` (dim 768, 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

View file

@ -23,7 +23,7 @@ error: unexpected character 'D' while parsing minor version number
Le fichier `version.info` du segment contient `0.D\0` au lieu d'un numéro de version — corruption de données (première occurrence le 2026-06-05 à 21h10).
### Résolution (crash-loop résolu le 2026-06-17 ; re-ingest encore à faire)
### Résolution (crash-loop résolu le 2026-06-17 ; RAG re-ingéré le 2026-06-21)
La collection `funk-docs` est entièrement reconstructible depuis `admin/` :
@ -37,9 +37,10 @@ curl -s http://127.0.0.1:6333/collections # doit répondre
> **État au 2026-06-17** : les 3 premières étapes (stop + drop `funk-docs` + start) ont été
> appliquées sur s01 pour sortir Qdrant du crash-loop (cf. `admin/ia/stt.md`, jalon 5c). Le
> service Qdrant et la mémoire STT (`stt-memory`) sont donc opérationnels, **mais** `rag-ingest`
> n'a pas encore été relancé : la collection `funk-docs` est vide → `rag-query` reste muet
> tant que l'index n'est pas reconstruit.
> service Qdrant et la mémoire STT (`stt-memory`) sont opérationnels.
>
> ✅ **Clôturé (2026-06-21)** : `funk-docs` a été **reconstruite** via `rag-ingest` (relevé : 436
> chunks, 🟢 green, embeddings nomic-embed-text `:1238` dim 768) → `rag-query` opérationnel.
### Prévention
@ -52,7 +53,7 @@ curl -s http://127.0.0.1:6333/collections # doit répondre
| Date | Composant | Résumé | Sévérité |
|---|---|---|---|
| 2026-06-19 | llama-server (gpu-01) | Inférence gelée (`/health` OK, génération bloquée) → 502 LLM — **résolu** par restart. Détail : [`incidents-llm-gpu.md`](incidents-llm-gpu.md) | Important |
| 2026-06-05 | Qdrant | **Résolu 2026-06-17** — crash-loop corrigé (drop `funk-docs`) ; RAG à re-ingérer | Important |
| 2026-06-05 | Qdrant | **Résolu 2026-06-17**, RAG **re-ingéré 2026-06-21** (436 chunks, nomic dim 768) | Important |
| 2026-05-29 | RAID5 / sdb + storage-01 | sdb ATA bus errors sous I/O — migration /srv/data du RAID vers NVMe | Important |
| 2026-05-27 | RAID5 / sdd | Disque sdd défaillant (44 secteurs morts) — corruptions multiples en cascade | Critique |
| 2026-05-14 | Grafana | OOMKilled — limite 256Mi trop serrée pour Grafana 13 | Warning |

View file

@ -5,9 +5,9 @@ Chaque fichier couvre une machine ou une couche du projet de A à Z.
| Guide | Machine | OS | Rôle |
|---|---|---|---|
| [storage-01.md](storage-01.md) | storage-01 | AlmaLinux 9.7 | Passerelle + RAID5 + services IA (Hermes, LiteLLM, Qdrant, PostgreSQL) |
| [gpu-01.md](gpu-01.md) | gpu-01 | AlmaLinux 9.7 | Inférence LLM (ROCm + llama-server, RX 6700XT) |
| [kubernetes.md](kubernetes.md) | compute-01/02/03 | Talos v1.13 | Cluster Kubernetes (MetalLB, Traefik, DNS wildcard) |
| [storage-01.md](storage-01.md) | storage-01 | AlmaLinux 9.8 (ISO 9.7) | Passerelle + RAID5 + services IA (Hermes, LiteLLM, Qdrant, PostgreSQL) |
| [gpu-01.md](gpu-01.md) | gpu-01 | AlmaLinux 9.8 (ISO 9.7) | Inférence LLM (ROCm + llama-server, RX 6700XT) |
| [kubernetes.md](kubernetes.md) | compute-01/02/03 | Talos v1.13 (k8s v1.33.1) | Cluster Kubernetes (MetalLB, Traefik, DNS wildcard) |
## Ordre d'installation recommandé

View file

@ -37,7 +37,7 @@ Il est consommé par storage-01 (LiteLLM le pointe directement via HTTP).
| CPU | AMD Ryzen 5 3600 (6c/12t) |
| RAM | 32 GB DDR4 |
| GPU | AMD RX 6700XT — 12 GB VRAM, architecture RDNA2 (gfx1031) |
| OS | AlmaLinux 9.7 |
| OS | AlmaLinux 9.8 (installé depuis ISO 9.7, point releases via `dnf update`) |
| Disque | SSD SATA 500 GB (OS + binaires) |
| Stockage modèles | NFS via storage-01 `/srv/data/models` → monté sur `/mnt/models` |

View file

@ -74,9 +74,12 @@ ssh -t storage-01 k9s # -t obligatoire pour allouer un TTY (TUI)
| Namespace | Contenu |
|---|---|
| `monitoring` | Prometheus, Grafana, AlertManager |
| `ai` | Open WebUI, n8n |
| `infra` | Traefik, MetalLB, NFS provisioner |
| `ai` | Open WebUI, n8n, **STT-server**, **Ghostfolio** (+ Redis) |
| `infra` | Traefik, MetalLB, NFS provisioner, **registry** in-cluster |
| `argocd` | ArgoCD (GitOps) |
| `metallb-system` | MetalLB (controller + speakers) |
| `nfs-provisioner` | nfs-subdir-external-provisioner |
| `sacrifice` | Jeu FPS Sacrifice (web + CronJob) — **hors repo Funk-lab, hors ArgoCD** |
| `kube-system` | Composants Kubernetes internes |
---

View file

@ -10,7 +10,7 @@ Le cluster Kubernetes **funk** compte **3 nœuds** (trois machines ThinkCentre),
sous **Talos Linux v1.13.0** (Kubernetes v1.33.1) : **compute-01** est le control-plane,
**compute-02** et **compute-03** sont des workers. Les deux autres machines du homelab —
**storage-01** (passerelle) et **gpu-01** (llama-server) — sont **hors du cluster** et
tournent sous **AlmaLinux 9.7**. Donc : 3 nœuds Kubernetes Talos + 2 hôtes AlmaLinux hors cluster.
tournent sous **AlmaLinux 9.8**. Donc : 3 nœuds Kubernetes Talos + 2 hôtes AlmaLinux hors cluster.
| Nœud | IP | Rôle | Disque | RAM |
|---|---|---|---|---|

167
admin/ops/etat-cluster.md Normal file
View file

@ -0,0 +1,167 @@
# État réel du cluster Funk — snapshot vérifié
> Relevé **2026-06-21** directement sur les machines (SSH s01/g01 + `kubectl`). Ce document
> reflète l'état **observé**, pas l'état *souhaité* de l'IaC — en cas d'écart, les écarts connus
> sont signalés (⚠️). Pour la procédure de relevé, voir la fin du fichier.
---
## Machines
| Machine | IP | OS / kernel | Uptime relevé | Rôle |
|---|---|---|---|---|
| storage-01 | 192.168.10.1 (LAN) / 192.168.1.200 (WAN) | AlmaLinux **9.8** (Olive Jaguar), `5.14.0-687.5.3.el9_8` | 20 j | Passerelle + bastion + données + IA hors-cluster |
| gpu-01 | 192.168.10.20 | AlmaLinux **9.8**, `5.14.0-687.5.3.el9_8` | — | llama-server (RX 6700XT, ROCm 7.x) hors-cluster |
| compute-01 | 192.168.10.11 | Talos v1.13 / **k8s v1.33.1** | 40 j | k8s control-plane |
| compute-02 | 192.168.10.12 | Talos v1.13 / **k8s v1.33.1** | 40 j | k8s worker |
| compute-03 | 192.168.10.13 | Talos v1.13 / **k8s v1.33.1** | 40 j | k8s worker |
> ⚠️ `CLAUDE.md` indiquait AlmaLinux **9.7** pour s01/g01 — la réalité est **9.8**.
---
## storage-01 — services actifs (systemd)
| Service | Port(s) écoute | Rôle |
|---|---|---|
| `litellm` | `0.0.0.0:4000` | Proxy multi-modèles (Hermes + ask-agent + STT-server) |
| `llm-heartbeat` | — | **Anti-502** : garde chaud le tuyau LiteLLM↔llama-server (rôle `litellm`) |
| `hermes-agent` | (pas de port WAN) | Agent Hermes — appli **TUI**, **pas d'API HTTP** (le « gateway :8080 » de la doc historique n'écoute pas) |
| `hermes-auto-improve` | `0.0.0.0:9095` | Trigger HTTP analyse doc quotidienne |
| `hermes-exec` | `0.0.0.0:9096` | Exécuteur d'actions vocales `hermes -z --yolo` (jeton Bearer) — assistant STT |
| `qdrant` | `0.0.0.0:6333` / `:6334` | Base vectorielle (RAG `funk-docs` + mémoire STT `stt-memory`) |
| `postgresql-16` | `192.168.10.1:5432` | PostgreSQL 16 (8 bases, cf. plus bas) |
| `dnsmasq` | `192.168.10.1:53` | DNS + wildcard `*.lab.local` |
| `nfs-*` (mountd/idmapd/statd) | `:2049 :111 :20048` | Export NFS modèles + PVCs k8s |
| `node_exporter` | `:9100` | Métriques Prometheus |
| `alertmanager-webhook` | `0.0.0.0:9093` | Pont AlertManager → Hermes monitor |
| `postfix` | `:25` | Relais SMTP sortant (Gmail App Password) |
> Note : `:9093` côté s01 = **alertmanager-webhook** (storage-01), à ne pas confondre avec
> l'Alertmanager k8s (`alertmanager.lab.local`, in-cluster).
---
## gpu-01 — services llama-server (systemd)
| Service | Port | État relevé | Rôle |
|---|---|---|---|
| `llama-server` | `:1234` | ✅ actif | Chat principal **qwen3-8b** (GPU, ROCm) |
| `llama-embed` | `:1238` | ✅ actif | Embeddings dédiés **nomic-embed-text** (**CPU**, dim 768, `--ubatch-size 2048`) |
| `llama-embed-heartbeat` | — | ✅ actif | Garde chaud le slot embeddings :1238 |
| `llama-watchdog` | — | ✅ actif | **Auto-réparation** du llama-server figé (bug ROCm gfx1031) |
| llama-server CPU `:1236` | `:1236` | ⚠️ **éteint** | Instance manuelle (profil Hermes `system`) — **non gérée par IaC** |
| llama-server CPU `:1237` | `:1237` | ⚠️ **éteint** | Instance manuelle (profil Hermes `monitor`) — **non gérée par IaC** |
> ⚠️ Au relevé, **seuls `:1234` et `:1238` écoutent**. Les instances CPU `:1236`/`:1237` sont
> **arrêtées** → les profils Hermes `system` et `monitor` (qui pointent dessus) sont actuellement
> **dégradés**. Ce sont des services manuels (gap IaC connu, cf. `ansible/roles/llama_server/README.md`).
>
> ⚠️ GPU : `rocminfo` rapporte `gfx1030` (et non `gfx1031`) — c'est l'effet attendu de
> `HSA_OVERRIDE_GFX_VERSION=10.3.0`, indispensable car la RX 6700XT (gfx1031) n'est pas
> officiellement supportée par ROCm.
---
## LiteLLM — modèles exposés (`:4000`)
| Alias | Backend |
|---|---|
| `hermes-default` | Alias consommé par Hermes (bascule via `hermes-switch`) |
| `qwen3-8b` | llama-server GPU gpu-01 `:1234` |
| `claude-sonnet-4-6` | API Anthropic (profil `brain`, facturé) |
| `claude-opus-4-7` | API Anthropic (facturé) |
---
## Cluster k8s — workloads
**Nœuds** : compute-01/02/03 tous `Ready`, k8s `v1.33.1` (40 j d'uptime).
**Namespaces & pods** (tous `Running` au relevé) :
| Namespace | Workloads |
|---|---|
| `ai` | ghostfolio, ghostfolio-redis, n8n, open-webui, **stt-server** |
| `argocd` | application-controller, applicationset, notifications, redis, repo-server, server |
| `infra` | **traefik** (LB `192.168.10.200`), **registry** (registry Docker in-cluster) |
| `monitoring` | kube-prometheus-stack (Prometheus, Alertmanager, Grafana **13.0.1**, kube-state-metrics, operator, node-exporters) |
| `metallb-system` | controller + speakers |
| `nfs-provisioner` | nfs-subdir-external-provisioner |
| `kube-system` | coredns, flannel, kube-proxy, control-plane statiques |
| **`sacrifice`** | **`sacrifice-web`** (`sacrifice.lab.local`) + CronJob `sacrifice-assign-renfort` |
> ⚠️ **Namespace `sacrifice` non documenté** dans `CLAUDE.md`. C'est le projet de jeu FPS
> [Sacrifice](https://github.com/Alkatrazz24/Sacrifice) (monorepo séparé). Déployé **hors du repo
> Funk-lab** et **hors ArgoCD** (absent de la liste des Applications) — sa propre chaîne le pousse.
> Il possède aussi sa base PostgreSQL `sacrifice` sur storage-01.
**ArgoCD Applications** (toutes `Synced` / `Healthy`) :
`root` · `ghostfolio` · `n8n` · `open-webui` · `stt` · `monitoring` · `kube-prometheus-stack` ·
`nfs-provisioner` · `nfs-subdir-external-provisioner`.
**Exposition HTTP** :
| Type | Hôtes |
|---|---|
| `Ingress` standard | `argocd.lab.local`, `grafana.lab.local`, `prometheus.lab.local`, `alertmanager.lab.local`, `sacrifice.lab.local` |
| `IngressRoute` (CRD Traefik) | `ai/open-webui`, `ai/n8n`, `ai/ghostfolio`, `ai/stt-server`, `infra/traefik-dashboard` |
> Les services du namespace `ai` (openwebui, n8n, ghostfolio, stt) passent par des **IngressRoute
> Traefik** (CRD), pas par des `Ingress` standards.
---
## PostgreSQL 16 (storage-01) — bases
`hermes` · `litellm` · `n8n` · `openwebui` · `ghostfolio` · `grafana` · `sacrifice` · `postgres`
> ⚠️ Bases non listées dans `CLAUDE.md` : **`grafana`** (Grafana k8s utilise un PG externe sur s01)
> et **`sacrifice`** (projet hors-repo).
---
## RAG / Qdrant — état
| Collection | Points | Statut | Dim / distance |
|---|---|---|---|
| `funk-docs` | **436** | 🟢 green | 768 / Cosine |
| `stt-memory` | — | 🟢 | 768 / Cosine |
> ✅ **`funk-docs` reconstruite et complète (436 chunks, 0 erreur)** — le warning « collection
> supprimée le 2026-06-17, re-ingest requis » de `CLAUDE.md`/`README`/`admin/ia/rag.md` est
> **périmé**. Embeddings via **nomic-embed-text** (`:1238`, dim 768), **plus** qwen3-8b.
>
> 🐛 **Fix de ce lot — embeddings tronqués à 512 tokens** : le relevé initial montrait 389 points
> avec **~46 chunks manquants** (HTTP 500 `input too large to process. increase the physical batch
> size (current: 512)`). Cause : `llama-embed` (:1238) tournait avec l'**ubatch llama.cpp par
> défaut (512)**, or en mode `--embeddings`+pooling toute l'entrée doit tenir dans un seul ubatch
> → tout chunk > 512 tokens échouait (CHUNK_MAX=2000 chars ≈ 700 tokens). **Corrigé** : `--batch-size`
> /`--ubatch-size = 2048` (= ctx) → ré-ingestion **436/436, 0 erreur**. Fix durable dans le rôle
> `llama_server` (`llama_embed_ubatch_size`) ; appliqué en live via drop-in systemd sur g01 (à
> réconcilier au prochain `make apply-gpu`). `rag-ingest` gagne aussi un retry sur 5xx transitoires.
>
> ⚠️ Drift IaC mineur (corrigé) : `ansible/roles/rag/defaults/main.yml` mentionnait encore
> `embed_url: :1234` / `embed_model: qwen3-8b` (variables **mortes** — les scripts déployés
> hardcodent nomic `:1238`).
---
## Comment régénérer ce snapshot
Depuis le poste perso (accès `ssh storage-01` = user `ansible` ; g01 via `root@192.168.10.20` ;
kubeconfig `/home/ansible/.kube/config` sur s01) :
```bash
# Services + ports s01
ssh storage-01 'systemctl list-units --type=service --state=running | grep -iE "litellm|hermes|qdrant|postgres|nfs|dnsmasq|node_exporter|alertmanager|postfix"; ss -tlnp'
# gpu-01
ssh storage-01 'ssh root@192.168.10.20 "systemctl list-units llama* ; ss -tlnp | grep :12"'
# k8s
ssh storage-01 'KUBECONFIG=/home/ansible/.kube/config kubectl get nodes,pods,ingress,ingressroute -A'
ssh storage-01 'KUBECONFIG=/home/ansible/.kube/config kubectl get applications -n argocd'
# Bases PG + Qdrant + LiteLLM
ssh storage-01 'sudo -u postgres psql -tAc "SELECT datname FROM pg_database WHERE datistemplate=false"'
ssh storage-01 'curl -s localhost:6333/collections | python3 -m json.tool'
ssh storage-01 'curl -s localhost:4000/v1/models -H "Authorization: Bearer lm-studio" | python3 -m json.tool'
```

View file

@ -23,6 +23,11 @@ llama_embed_model_path: "" # chemin GGUF sur /mnt/models (NFS)
llama_embed_model_url: "" # URL de téléchargement (HuggingFace) — get_url si absent
llama_embed_model_alias: "nomic-embed-text"
llama_embed_ctx_size: 2048
# Batch physique (ubatch) = contexte : en mode --embeddings avec pooling, toute l'entrée doit
# tenir dans UN seul ubatch. Le défaut llama.cpp (512) fait échouer en HTTP 500 tout chunk
# > 512 tokens (« input too large to process. increase the physical batch size ») — c'est ce
# qui faisait rater ~46 chunks du RAG (CHUNK_MAX=2000 chars ≈ 700 tokens). Aligné sur ctx-size.
llama_embed_ubatch_size: 2048
# CPU par défaut (0 couche GPU). nomic-embed-text est minuscule (137M) → ~100 ms en CPU,
# DÉTERMINISTE. Sur GPU (99) il partage la RX 6700XT avec le chat qwen3-8b (:1234) et tombe
# dans la file derrière une génération → latence BIMODALE (16 ms ou 9-12 s, voire timeout) →

View file

@ -13,6 +13,8 @@ ExecStart=/opt/llama.cpp/build/bin/llama-server \
--host {{ llama_server_host }} \
--port {{ llama_embed_port }} \
--ctx-size {{ llama_embed_ctx_size }} \
--batch-size {{ llama_embed_ubatch_size }} \
--ubatch-size {{ llama_embed_ubatch_size }} \
--n-gpu-layers {{ llama_embed_n_gpu_layers }} \
--alias {{ llama_embed_model_alias }} \
--embeddings \

View file

@ -3,6 +3,10 @@ rag_data_dir: /srv/data/rag
rag_docs_dir: /srv/data/rag/docs
qdrant_url: "http://127.0.0.1:6333"
embed_url: "http://192.168.10.20:1234/v1/embeddings"
embed_model: "qwen3-8b"
# Embeddings : instance dédiée nomic-embed-text (gpu-01 :1238, dim 768), pas le modèle de chat.
# NB : ces deux vars ne sont PAS injectées dans les scripts (le handler lance rag-ingest sans env) —
# les scripts files/rag-ingest et files/rag-query hardcodent ces mêmes valeurs par défaut.
# Conservées ici pour documenter la cible et éviter toute régression vers qwen3-8b/:1234.
embed_url: "http://192.168.10.20:1238/v1/embeddings"
embed_model: "nomic-embed-text"
rag_collection: "funk-docs"

View file

@ -7,6 +7,7 @@ Usage: rag-ingest [docs_dir]
import os
import sys
import json
import time
import hashlib
import re
import urllib.request
@ -57,8 +58,22 @@ def ensure_collection():
def embed(text):
result = _request("POST", EMBED_URL, {"model": EMBED_MODEL, "input": text})
return result["data"][0]["embedding"]
# L'instance llama-embed (:1238) renvoie occasionnellement des 500 transitoires sous charge
# soutenue (slot saturé) → retry avec backoff. Les chunks sont bornés (CHUNK_MAX=2000) donc
# un 500 n'est pas dû à un dépassement de contexte.
last = None
for attempt in range(5):
try:
result = _request("POST", EMBED_URL, {"model": EMBED_MODEL, "input": text})
return result["data"][0]["embedding"]
except (urllib.error.HTTPError, urllib.error.URLError) as e:
last = e
code = getattr(e, "code", None)
# Ne réessaie que sur erreurs transitoires (5xx) ou réseau
if code is not None and code < 500:
raise
time.sleep(0.5 * (attempt + 1))
raise last
def chunk_markdown(rel_path, content):

View file

@ -85,6 +85,29 @@ Bascule du client sur `asr_engine = "onnx"` (NVIDIA Parakeet `nemo-parakeet-tdt-
---
## Relevé d'état réel + RAG re-ingéré (soir)
Passe de **vérification terrain** (SSH s01/g01 + `kubectl`) pour aligner la doc sur la réalité, et
**re-ingestion du RAG**. Nouveau doc de référence : **`admin/ops/etat-cluster.md`** (snapshot daté).
Écarts doc↔réalité corrigés (CLAUDE.md, README, install, k8s, incidents, rag, stt) :
- OS **AlmaLinux 9.8** (et non 9.7) sur s01/g01 ; **k8s v1.33.1** sur les compute nodes.
- RAG `funk-docs` **n'était pas vide** : le warning « supprimée 2026-06-17, re-ingest requis » était
périmé. Embeddings basculés sur **nomic-embed-text** (`:1238`, dim 768, **CPU**) — plus qwen3-8b.
- Namespace **`sacrifice`** (jeu FPS, hors-repo & hors ArgoCD) + base PG `sacrifice` + `grafana`
non documentés ; services `ai` exposés via **IngressRoute** Traefik ; registry in-cluster.
- Services de résilience non mentionnés : `llm-heartbeat` (s01), `llama-watchdog`/`llama-embed(-heartbeat)` (g01).
- Instances llama CPU `:1236`/`:1237` (profils `system`/`monitor`) **manuelles et éteintes** au relevé.
🐛 **Bug RAG trouvé & corrigé** : ~46 chunks rataient l'embedding (HTTP 500 *input too large… increase
physical batch size: 512*). `llama-embed` tournait avec l'**ubatch llama.cpp par défaut (512)** ; en
mode `--embeddings`+pooling l'entrée doit tenir dans un seul ubatch → chunks > 512 tokens KO. **Fix** :
`--batch-size`/`--ubatch-size = 2048` (rôle `llama_server`, var `llama_embed_ubatch_size` ; appliqué
en live via drop-in systemd g01). `rag-ingest` gagne un retry 5xx. Drift IaC nettoyé (`rag/defaults`
pointait encore qwen3-8b `:1234`). **Résultat : ré-ingestion 436/436, 0 erreur, collection 🟢.**
---
## Versions
- Client `stt` : `0.7.0 → 0.13.0` (portail, contrôles, santé, contextes, intents, version, unification).
@ -94,7 +117,10 @@ Bascule du client sur `asr_engine = "onnx"` (NVIDIA Parakeet `nemo-parakeet-tdt-
## Prochaines étapes
- **Merger #43** → activer les actions ; faire le 1er test vocal avec confirmation (HUD → 🤖 Agent).
- ✅ **#43 mergé** (actions activées) et **RAG re-ingéré** (436 chunks) — faits.
- **Appliquer `make apply-gpu`** pour réconcilier le drop-in `llama-embed` (ubatch) dans l'IaC, puis
supprimer `/etc/systemd/system/llama-embed.service.d/batch.conf` sur g01.
- 1er test vocal d'action avec confirmation (HUD → 🤖 Agent).
- Optionnel : ajouter le jeton Ghostfolio en config client si l'intent « combien » est souhaité hors contexte.
- Streaming ASR (transcription partielle live dans le HUD).
- Corriger le wake word (« Asa » vs « Hermès ») si désiré.