5.2 KiB
API LLM et chatbot (GrasBot)
Dernière mise à jour : 2026-04-22 (v3 — bascule graph + BM25)
Vue d'ensemble
GrasBot répond aux visiteurs en s'appuyant sur un pipeline de retrieval local, sans embeddings ni base vectorielle :
- Vault Obsidian
vault-grasbot/lu directement en mémoire parsearch.py. - Scoring déterministe multi-signaux (aliases, titre/slug, answers, domains, tags, BM25 sur le body).
- Expansion par graphe via les wikilinks (
linked,related,[[...]]dans le corps). - Prompt construit avec top-5 notes entières, envoyé à Qwen3 8B via Ollama.
Détails architecturaux dans
08-vault-obsidian-retrieval.md.
Chaîne côté navigateur
- FAB
GrasBotFab(monté dansapp/layout.tsx) afficheChatBot.js. ChatBot.jsappelleaskAI(question)(app/utils/askAI.js).askAIenvoie un GET vers/api/proxy?q=...(route Next.js App Router).app/api/proxy/route.jsappellehttps://llmapi.fernandgrascalvet.com/ask?q=...(URL figée en dur pour l'instant) et renvoie le corps JSON tel quel.
Le champ consommé par le front reste data.response. Les champs ajoutés
par la refonte (sources, grounded, model, vault_size) passent dans
la réponse JSON et pourront être affichés dans une itération suivante
(voir pistes d'évolution).
FastAPI — llm-api/
| Fichier | Rôle |
|---|---|
api.py |
Endpoints GET /ask?q=..., GET /health, POST /reload-vault. |
search.py |
load_vault, tokenize_fr, score_note, expand_by_graph, search, build_prompt, generate, answer. |
requirements.txt |
fastapi, uvicorn, requests, pyyaml. Plus besoin de chromadb / chroma-hnswlib (supprimés v3). |
Modules supprimés en v3 :
rag.py→ remplacé parsearch.py.index_vault.py→ plus d'étape d'indexation (lecture directe du vault).
Modèle Ollama
| Rôle | Modèle | VRAM | Commande |
|---|---|---|---|
| Chat | qwen3:8b |
~5 Go (Q4_K_M) | ollama pull qwen3:8b |
Plus d'embeddings. Le modèle nomic-embed-text n'est plus nécessaire.
Tu peux libérer de la place avec ollama rm nomic-embed-text si jamais
il reste installé.
Variables d'environnement (facultatives)
Toutes définies dans search.py, surchargeables via env sans toucher au code :
OLLAMA_URL(defaulthttp://localhost:11434)LLM_MODEL(defaultqwen3:8b)VAULT_DIR(default<repo>/vault-grasbot)SEARCH_TOP_K(default5)SEARCH_MIN_SCORE(default1.0) — seuil en-dessous duquel le chatbot bascule en mode « pas de contexte pertinent » (évite les réponses inventées sur des questions hors sujet).
Mise en service
# 1. Installer les dépendances Python (pure Python, pas de compilation C++)
cd llm-api
pip install -r requirements.txt
# 2. Pull le modèle Ollama (Ollama doit tourner)
ollama pull qwen3:8b
# 3. Lancer l'API
uvicorn api:app --host 0.0.0.0 --port 8000
Plus besoin d'étape d'indexation : l'API lit le vault au démarrage.
Health-check : curl http://localhost:8000/health retourne la config active,
la taille du vault et le nombre de notes par type.
Après édition du vault (ajout/modification d'une note) :
# Force la relecture sans redémarrer uvicorn
curl -X POST http://localhost:8000/reload-vault
Réponse du backend
{
"response": "Push Swap est un projet 42 qui explore les algorithmes de tri sur piles…",
"sources": [
{
"slug": "push-swap",
"title": "push_swap",
"type": "projet",
"score": 32.27,
"reasons": ["alias:push-swap", "slug", "answers-partial", "bm25:2.12"],
"url": "/portfolio/push-swap"
},
{
"slug": "cpp-partie1",
"title": "cpp_module_00 à 04",
"type": "projet",
"score": 20.62,
"reasons": ["graph-from:push-swap", "graph-reinforce"],
"url": "/portfolio/cpp-partie1"
}
],
"grounded": true,
"model": "qwen3:8b",
"vault_size": 41
}
askAI.js ne lit que data.response → rétrocompatibilité assurée.
Le champ reasons sert à tracer pourquoi une note a été remontée : très
utile pour ajuster aliases / answers quand une question renvoie de mauvais
résultats.
Pistes d'évolution
- Variable d'environnement côté proxy Next pour pointer vers
http://localhost:8000en dev et vers la prod en déploiement (au lieu de l'URL figée dansapp/api/proxy/route.js). - Affichage des sources côté front : vignettes cliquables sous la
réponse, utilisant le champ
urlrenvoyé par l'API. - Badge
grounded: afficher « Réponse basée sur les notes » vs « Réponse générale » pour informer le visiteur de la confiance. - Historique court (3-4 derniers tours) pour la continuité conversationnelle.
- Streaming des réponses pour l'UX temps réel (Qwen3 supporte
stream: true). - Reload automatique via file watcher sur
vault-grasbot/quand on édite dans Obsidian. - Filtre
visibilitydéjà en place dansload_vault()(les notesprivatesont exclues). Le vault perso pourra être fusionné sans exposer ses notes privées au chatbot public.