Architecture RAG — Immbot AI
Vue d'ensemble
Corpus : les fichiers sources LaTeX des deux cours (diapositives beamer, plans de cours, ateliers, aide-mémoires, glossaire) — soit ~1 700 diapositives et ~40 documents. L'ingestion travaille sur la source (pas le PDF) : structure exacte, numéros de diapositives fiables, équations intactes.
Fichiers cours ──> Scanner ──> Parseur LaTeX ──> Chunker structurel ──> Fragments+métadonnées
│
SQLite : chunks + FTS5 + embeddings (blob 384d)
│
Question ──> embedding local ──> [FTS5 BM25 ∥ cosinus] ──> fusion RRF ──> filtres/boost ──>
expansion voisins ──> contexte numéroté [S1..Sn] ──> LLM ──> validation citations ──> réponse citéeEspaces de connaissances (isolation)
official-imm1003 · official-imm1033 · student-temporary-upload (fichiers joints à une
conversation, portée = cette conversation) · student-persistent-files (bibliothèque personnelle) ·
instructor-private (examens, blueprints, analyses — jamais servis aux étudiants) ·
general-knowledge (aucun fragment : étiquette pour les réponses hors RAG).
Croisement inter-cours désactivé par défaut, activable par l'admin, toujours signalé dans la réponse.
Fragmentation structurelle
| Source | Unité de fragment | Métadonnées clés |
|---|---|---|
| Diapositives beamer | 1 frame = 1 fragment (titre + contenu + boîtes sémantiques aplaties) ; fusion des frames de suite (1/2, 2/2) au même titre | cours, séance, n° de diapositive, titre, section courante, type de boîte (définition/important/exemple/formule) |
| Plans de cours / articles LaTeX | section/sous-section, redécoupée par paragraphes si > ~1 800 caractères, avec chevauchement d'une phrase | cours, document, section, titre |
| Ateliers (énoncés/solutions) | par exercice/question (\section, enumerate de premier niveau) |
cours, atelier, type (énoncé/solution) |
| Glossaire | par entrée (terme FR/EN + définition) | terme, cours |
| Markdown/TXT | par titre #/## |
document, titre |
| Téléversements étudiants (PDF/DOCX/XLSX/CSV/images) | extraction texte par page/feuille ; images passées telles quelles aux modèles vision | conversation, page/feuille |
Le texte LaTeX est détexifié pour l'indexation (macros sémantiques UQO converties en préfixes
« Définition : », « Important : » ; équations conservées en notation $...$ pour l'affichage)
tout en conservant la version affichable. Contexte minimal garanti : chaque fragment inclut
cours + document + section pour rester compréhensible isolément.
Embeddings
Xenova/multilingual-e5-small (384 d) exécuté localement via @huggingface/transformers
(préfixes query: / passage: conformes à E5). Choix motivé : OpenRouter n'expose pas
d'embeddings ; le modèle est multilingue (corpus français), léger (~120 Mo), et le corpus tient
en mémoire (2-3k × 384 floats ≈ 4 Mo) → cosinus exact en < 10 ms, pas d'index ANN nécessaire.
Recherche hybride
- Analyse de la requête : cours actif (jamais deviné : choisi dans l'interface), détection de séance/diapositive citée explicitement, type de question (définition/calcul/comparaison).
- Candidats : FTS5
bm25()(top 30, unicode61 + remove_diacritics) ∥ cosinus vectoriel (top 30). - Fusion RRF (k=60) + boosts : correspondance exacte de terme du glossaire, fragments de type « définition » pour les questions de définition, tableaux pour les questions de données.
- Dédoublonnage par document/diapositive, expansion aux diapositives voisines (±1) quand le fragment gagnant est une suite (1/2 → 2/2).
- Budget de contexte : top 8-12 fragments équilibrés entre documents, plafonné en jetons.
- Contexte transmis :
[S1] (IMM1003 — Séance 4 — Diapositive 18 — « Titre ») texte….
Citations : contrat strict
- Le modèle ne peut citer que les balises
[Sx]du contexte fourni (politique dansprompts/citation-policy.md). - Post-traitement serveur : chaque
[Sx]est résolu vers son fragment ; toute balise inconnue est retirée et comptée (invalid_citation_ratejournalisé). Le client reçoit la liste résolue (document, séance, diapositive, extrait exact, contexte voisin) → panneau source cliquable. - Mode « Cours uniquement » sans contexte pertinent (scores sous seuil) → réponse de refus honnête standardisée, sans appel « créatif ».
Ingestion incrémentale
Somme de contrôle SHA-256 par fichier ; réingestion seulement si modifiée ; suppression des
fragments orphelins ; exécutions journalisées dans ingestion_runs (fichiers, fragments, erreurs,
durée) ; rapport lisible dans docs/ingestion-report.md et l'admin.
Évaluation
evaluation/imm1003-test-set.json et imm1033-test-set.json : questions dorées avec documents
attendus. Script scripts/verify.sh → evaluation/rag-evaluation.md : rappel@k des sources,
taux de citations valides, taux de refus corrects (questions hors corpus), isolation inter-cours
(les questions IMM1033 ne doivent pas remonter de fragments IMM1003 quand le croisement est off).