devsite/docs-site-interne/04-api-llm-et-chatbot.md
2026-04-22 20:11:16 +02:00

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 par search.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

  1. FAB GrasBotFab (monté dans app/layout.tsx) affiche ChatBot.js.
  2. ChatBot.js appelle askAI(question) (app/utils/askAI.js).
  3. askAI envoie un GET vers /api/proxy?q=... (route Next.js App Router).
  4. app/api/proxy/route.js appelle https://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é par search.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 (default http://localhost:11434)
  • LLM_MODEL (default qwen3:8b)
  • VAULT_DIR (default <repo>/vault-grasbot)
  • SEARCH_TOP_K (default 5)
  • SEARCH_MIN_SCORE (default 1.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:8000 en dev et vers la prod en déploiement (au lieu de l'URL figée dans app/api/proxy/route.js).
  • Affichage des sources côté front : vignettes cliquables sous la réponse, utilisant le champ url renvoyé 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 visibility déjà en place dans load_vault() (les notes private sont exclues). Le vault perso pourra être fusionné sans exposer ses notes privées au chatbot public.