# 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ée ``` ## Espaces 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 1. 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). 2. Candidats : FTS5 `bm25()` (top 30, unicode61 + remove_diacritics) ∥ cosinus vectoriel (top 30). 3. **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. 4. Dédoublonnage par document/diapositive, **expansion aux diapositives voisines** (±1) quand le fragment gagnant est une suite (1/2 → 2/2). 5. Budget de contexte : top 8-12 fragments équilibrés entre documents, plafonné en jetons. 6. 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 dans `prompts/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_rate` journalisé). 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).