devsite/obsidian-site-docs/docs-site-interne/08-vault-obsidian-retrieval.md
2026-04-23 19:19:31 +02:00

9.2 KiB
Raw Blame History

Vault Obsidian + retrieval GrasBot (v3 — graph + BM25)

Créé : 2026-04-22 (v1 RAG vectoriel) Refondu : 2026-04-22 (v3 — graph + BM25, sans embeddings) Statut : opérationnel (17 projets + 4 compétences + CV + 3 notes techniques + 15 MOCs)

Raison d'être

Avant ce pipeline, GrasBot interrogeait mistral:7b sans aucun contexte — il répondait de manière générique sur n'importe quoi. Depuis :

  • Modèle chat : qwen3:8b (meilleur en FR, reasoning solide).
  • Base de connaissance structurée comme vault Obsidian.
  • Pipeline de retrieval branché : chaque question récupère les notes pertinentes avant génération.

Pourquoi graph + BM25 plutôt que RAG vectoriel ?

La première version (avril 2026, v2) utilisait ChromaDB + embeddings nomic-embed-text. Ça marchait, mais :

  • Vault de taille modeste (~40 notes, ~100 Ko) : la sémantique vectorielle sur-dimensionne le problème.
  • Retrieval imprévisible sur vocabulaire précis (une question « compétences en IA » pouvait ne pas remonter la note ia.md si son embedding était dominé par d'autres concepts).
  • Chaîne d'installation lourde : chromadb dépend de chroma-hnswlib, qui nécessite un compilateur C++ sous Windows → blocage fréquent.
  • Coût en VRAM : nomic-embed-text mobilisait ~500 Mo et ~1 s par requête, inutile à cette échelle.
  • Désynchronisation vault / index : étape index_vault.py oubliable.

En v3, on exploite directement la structure du vault : frontmatter YAML (aliases, answers, domains, tags, priority), wikilinks, MOCs. Le retrieval est déterministe, traçable (on sait pourquoi une note est remontée), instantané (~50 ms), et ne demande qu'une dépendance : pyyaml.

Résultat : GrasBot cite toujours ses sources, et le top-5 est beaucoup plus prévisible pour une question précise.

Vault — vault-grasbot/

Arborescence :

vault-grasbot/
├── 00-MOC/              # hubs thématiques (MOC-Projets, MOC-Ia, MOC-Technique, ...)
├── 10-Projets/          # 17 projets Strapi (push-swap, minishell, ft-transcendence, ...)
├── 20-Competences/      # 4 compétences Strapi (IA, domotique, web, 3D)
├── 30-Parcours/         # CV curaté manuellement (source: manual)
├── 40-Glossaire/        # (vide, prévu pour le content-type glossaire Strapi)
├── 50-Technique/        # auto-doc : architecture-site, grasbot-retrieval, vault-structure
├── README.md            # résumé utilisateur (généré)
└── TAXONOMIE.md         # vocabulaire contrôlé (domaines, tags, aliases, answers, priority)

Frontmatter YAML

Chaque note porte une en-tête enrichie :

---
title: "push_swap"
slug: push-swap
type: projet                    # projet | competence | parcours | moc | technique
source: strapi/projects         # strapi/... | pdf/... | manual | vault/generated
domains: [algorithmique, c, ecole-42]
tags: [42-commun, tri, makefile]
aliases:
  - push swap
  - push_swap
  - algo de tri 42
answers:
  - "Parle-moi de push-swap"
  - "Comment fonctionne push-swap ?"
priority: 5                     # 1..10, boost léger au scoring
linked: ["[[MOC-Projets]]"]
related: ["[[minishell]]"]
updated: 2026-04-22
visibility: public
---

Détail des champs et leur usage exact par le retrieval : voir vault-grasbot/TAXONOMIE.md et la note interne vault-structure du vault.

Règle de régénération

strapi_extraction/build-vault.py écrase les notes dont source: strapi/* ou source: pdf/*. Il ne touche jamais aux notes source: manual.

Le drapeau --clean supprime tout le vault avant régénération : à utiliser uniquement si on veut repartir de zéro (attention aux notes manual).

Génération — strapi_extraction/build-vault.py

Pipeline :

  1. Lit les project-*.md et competence-*.md de strapi_extraction/docs/ (eux-mêmes produits par generate-docs.js à partir de l'API Strapi).
  2. Parse titre, slug, description, détails.
  3. Infère domains / tags via DOMAIN_KEYWORDS / TAG_KEYWORDS (ajustables dans le script).
  4. Génère automatiquement :
    • aliases à partir du slug + titre + DOMAIN_ALIASES (synonymes courants par domaine).
    • answers selon le type (projet → « Parle-moi de X », compétence → « Quelles sont ses compétences en X ? », etc.).
    • priority heuristique (CV=10, MOCs=7, compétences=7, projets=5).
  5. Calcule les related par intersection de domaines (top 3).
  6. Écrit chaque note avec frontmatter + corps + section « Liens » en pied.
  7. Génère les MOCs (un par type + un par domaine significatif).
  8. Optionnel : convertit le CV PDF via pypdf si installé (mais la version manuelle cv-grascalvet-fernand.md avec source: manual est toujours préservée).

Commandes :

python strapi_extraction/build-vault.py            # régénère tout
python strapi_extraction/build-vault.py --dry-run  # liste sans écrire
python strapi_extraction/build-vault.py --clean    # supprime puis regénère

Retrieval — llm-api/search.py

Module lu par api.py. Fournit :

  • load_vault() — lecture mémoïsée du vault (frontmatter YAML + body + wikilinks). Filtre visibility: private.
  • tokenize_fr(text) — tokenisation FR + normalisations (c++cpp, split sur -/_, stop-words).
  • score_note(note, query, tokens, stats) — score déterministe multi-signaux. Retourne un ScoredNote(score, reasons[]).
  • expand_by_graph(seeds, vault) — ajoute les voisins (linked, related, wikilinks du body) avec un score dérivé de 60 %.
  • search(query, top_k) — orchestration : score + expansion + dedupe + top-K.
  • build_prompt(query, notes) — couple (system, user) pour /api/chat.
  • generate(system, user) — appel Ollama /api/chat, retourne le texte.
  • answer(query) — pipeline complet, retourne un dict {response, sources, grounded, model, vault_size}.

Barème de scoring (documentation opérationnelle)

Signal Points Détails
Alias match +10 1+ aliases de la note apparaissent dans la question
Title exact +8 Titre complet dans la query (len ≥ 4)
Title tokens +4 Au moins 2 tokens du titre dans la query
Slug +8 Tous les tokens du slug sont dans la query
Answers full +12 ≥ 3 tokens communs avec une question-type
Answers partial +5 2 tokens communs
Domains +5 × n Par domaine strictement matché
Tags +3 × n Par tag strictement matché
BM25 body 0..5 Normalisé
Priority (p-5) × 0.3 Boost léger si déjà scoré
MOC-hub +1.0 Si note de type moc ET déjà scorée
Graph neighbor 60 % du parent Via expand_by_graph

Seuil SEARCH_MIN_SCORE (défaut 1.0) : en-dessous, le mode « sans contexte pertinent » se déclenche et Qwen3 est invité à ne pas inventer de faits sur Fernand.

Compatibilité rétro

L'API garde la signature GET /ask?q=.... Le JSON renvoyé a :

  • response (conservé, consommé par askAI.js)
  • sources[] (enrichi : slug, title, type, score, reasons, url)
  • grounded (bool — nouveau)
  • model (conservé)
  • vault_size (nouveau)

Le champ rag de la v2 est remplacé par grounded (plus explicite).

Commandes utiles

# Régénérer le vault depuis strapi_extraction/docs/
python strapi_extraction\build-vault.py

# Démarrer l'API locale (pas d'indexation préalable à faire)
cd llm-api ; uvicorn api:app --host 0.0.0.0 --port 8000

# Vérifier la config active et la taille du vault
curl http://localhost:8000/health

# Forcer la relecture du vault sans redémarrer uvicorn
curl -X POST http://localhost:8000/reload-vault

# Tester une question en direct
curl "http://localhost:8000/ask?q=parle-moi+de+push-swap"

Fusion avec un vault Obsidian perso

Deux voies :

  • Vault séparé (recommandé au début) : on ouvre vault-grasbot/ comme vault Obsidian indépendant.
  • Fusion : on copie vault-grasbot/ comme sous-dossier d'un vault existant. Les wikilinks restent valides tant que les noms sont uniques. Les notes persos doivent porter source: manual (évite l'écrasement par build-vault.py) et visibility: private (exclues automatiquement du retrieval par load_vault()).

Limites actuelles

  • Pas de mémoire conversationnelle : chaque question est indépendante.
  • Pas de streaming : la réponse arrive en un bloc après 2-10 s.
  • Aliases / answers auto-générés : c'est une base. Les notes stratégiques (CV, IA, MOCs) méritent un enrichissement manuel en passant source: manual.
  • clean-api-data.js n'extrait pas les homepages ni les glossaires : bug préexistant, à corriger pour enrichir 40-Glossaire/ et la home.
  • Re-chargement manuel via POST /reload-vault (pas encore automatisé via file watcher).

Évolutions priorisables

  1. Corriger clean-api-data.js (homepages + glossaires).
  2. Afficher les sources citées sous la réponse dans ChatBot.js.
  3. Ajouter un badge grounded pour informer le visiteur de la confiance.
  4. Historique conversationnel court (3-4 tours).
  5. Streaming Ollama stream: true (Server-Sent Events côté API).
  6. File watcher sur vault-grasbot/ qui appelle POST /reload-vault automatiquement.