# 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).

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.

---

## Architecture

```
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
  │  2. Recherche vectorielle → POST http://127.0.0.1:6333/collections/funk-docs/points/search
  ▼
Qdrant  (storage-01:6333)
  │  Retourne les N chunks les plus proches sémantiquement
  ▼
rag-query affiche les extraits + fichier source + score
  │
  ▼
Hermes formule sa réponse en citant les sources
```

---

## Composants déployés

| Composant | Emplacement | Rôle |
|---|---|---|
| Qdrant | storage-01:6333/6334 | Base vectorielle — stocke les embeddings |
| `rag-ingest` | `/usr/local/bin/rag-ingest` | Indexe les docs dans Qdrant |
| `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) |

---

## Modèle d'embedding utilisé

**Qwen3-8B** via llama-server GPU (port 1234), avec les flags `--embeddings --pooling mean`.

C'est le modèle de chat principal, réutilisé pour les embeddings.

### Limitation connue

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.90–0.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`).

```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/
```

---

## Utilisation

### Via Hermes (funk-ai)

Le skill `rag-docs` est chargé dans le profil `funk-ai`. Hermes l'utilise
automatiquement pour les questions sur le cluster :

```
# Dans le TUI Hermes :
"Comment je relance dnsmasq ?"
→ Hermes appelle rag-query en interne et cite les sources
```

### En ligne de commande

```bash
# Depuis storage-01
rag-query "ma question"
rag-query "ma question" --top 3   # limiter les résultats (défaut: 5)

# Exemples
rag-query "comment relancer llama-server gpu"
rag-query "nftables firewall port cluster"
rag-query "hermes profils configuration"
rag-query "alertmanager webhook pipeline"
rag-query "monitoring grafana dashboard"
```

---

## Mise à jour de l'index

L'index doit être re-généré après chaque modification de la doc `admin/`.

### Via Ansible (recommandé)

```bash
cd ansible/
../.venv/bin/ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags rag
```

Ansible synchronise `admin/` vers `/srv/data/rag/docs/` puis relance automatiquement
`rag-ingest` (handler Ansible).

### Manuellement

```bash
# Synchroniser les docs (depuis le poste perso)
rsync -av --delete admin/ storage-01:/srv/data/rag/docs/

# Ré-indexer (depuis storage-01)
ssh storage-01 "rag-ingest /srv/data/rag/docs/"
```

L'ingestion prend ~5-10 minutes pour 339 chunks (embeddings via GPU).
Elle est idempotente — re-lancer ne crée pas de doublons (IDs stables basés sur MD5 du fichier+section).

---

## Administration Qdrant

```bash
# Collections existantes
curl -s http://storage-01:6333/collections | python3 -m json.tool

# Stats de la collection funk-docs
curl -s http://storage-01:6333/collections/funk-docs | python3 -m json.tool

# Nombre de points indexés
curl -s http://storage-01:6333/collections/funk-docs \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['points_count'])"

# Vider et re-créer la collection (si re-indexation propre nécessaire)
curl -s -X DELETE http://storage-01:6333/collections/funk-docs
rag-ingest /srv/data/rag/docs/

# Statut du service
ssh storage-01 "systemctl status qdrant --no-pager"
```

---

## Structure des fichiers

```
ansible/roles/rag/
├── defaults/main.yml          # URLs Qdrant/embed, collection, répertoires
├── tasks/main.yml             # Déploiement scripts + docs + skill Hermes
├── handlers/main.yml          # Handler : Run rag-ingest + Restart hermes-agent
└── files/
    ├── rag-ingest             # Script Python d'ingestion
    ├── rag-query              # Script Python de requête
    └── rag-docs/SKILL.md      # Skill Hermes

hermes-skills/funk/rag-docs/
└── SKILL.md                   # Source versionnée du skill (copié dans files/)

/srv/data/rag/           (NVMe storage-01 — bind-mount /home/data)
├── docs/                      # Copie de admin/ — source des chunks
└── (Qdrant stocke dans /srv/data/qdrant/)
```

---

## Points d'attention

| 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 |
| 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 |
