Flux d'une question (RAG complet) Flux d'une question (RAG complet) Dernière mise à jour : 2026-05-10 Source de vérité : ARCHITECTURE.md § "Flux principal (question)". Cette page reformule pour vue rapide. 9 étapes POST /api/ask/stream { gameId, question, extensions, history, cardMentions, stickyCardMentions } │ 1. Validation Zod (askStreamSchema) — UUID, question ≤500 chars, history ≤5 │ 2. Résolution extensions (IDs → noms via gamesRepo.getById) │ 3. Résolution cartes @-citées (resolveMentions) — fresh + sticky │ 4. Classification question (classify.ts via Haiku, en parallèle du retrieval) ├─ Output: 'rules' | 'synergy' | 'meta' | 'deckbuilding' └─ Si timeout → fallback 'rules' │ 5. RETRIEVAL (orchestrator.ts retrieveChunks) │ ├─ Étape 0 — Embeddings question │ ├─ Question brute → TEI bge-m3 (1024 dense) │ └─ HyDE → Haiku passage hypothétique → TEI bge-m3 │ (timeout 25s, fallback : query brute seule) │ ├─ Étape 1 — Recherches hybrides parallèles │ ├─ Pour chaque collection (base + extensions cochées) : │ │ ├─ Dense bge-m3 (cosine) │ │ └─ Sparse BM25 natif Qdrant (keyword match) │ │ │ ├─ Fusion RRF v2 (RAG_FUSION_V2_ENABLED) │ │ ├─ Score = 2/(rank_dense+2) + 1/(rank_bm25+2) ← question×2 vs HyDE×1 │ │ ├─ Top-rank bonus : +0.05 si rank=0, +0.02 si rank ∈ {1,2} │ │ └─ Dedup par pointId │ │ │ ├─ [Si intent=meta] meta-retrieval │ │ └─ Recherche chunks 17Lands / MTGGoldfish / etc. (filter meta_format) │ │ │ └─ [Si MTG/FAB/Riftbound] synergy-expansion │ ├─ decompose-query (Haiku) → JSON {couleurs, format, types, themes} │ ├─ Filters Qdrant natifs (payload matching) │ ├─ Multi-query TCG (synonymes/variantes) │ └─ set-matcher : "Strixhaven" → "STX" │ └─ Étape 2 — Reranking cross-encoder (bge-reranker-v2-m3) ├─ Re-classe top-50 candidats RRF └─ Blending position-aware ├─ Top 1-3 : 75% RRF + 25% rerank (préserve exact-match) ├─ Top 4-10 : 60/40 └─ Top 11+ : 40/60 │ 6. Enrichissement contexte (answer.ts) ├─ BGG metadata (mechanics, categories) si dispo ├─ Image PNG : 1 page max (resolvePageImageFile) │ └─ Granularité (livret, page) — pas seulement page ├─ Cartes @-mentionnées (bloc CARTES CITÉES PAR LE JOUEUR (ce tour)) ├─ Sticky cards (bloc CARTES ÉVOQUÉES DANS LA CONVERSATION, format abrégé ~100 tokens) └─ Historique (≤5 turns) │ 7. Génération réponse Claude (claude-ssh.ts promptStream) ├─ Construction userPrompt (chunks + cartes + image + historique) ├─ SSH oracle@VM : claude -p --append-system-prompt ... --output-format stream-json │ └─ ⚠️ --append (pas --system-prompt) : préserve le contrat outils (Read, etc.) ├─ withTimeout HYDE_TIMEOUT_MS, fallback Haiku/Opus └─ Stream JSON verbose → yield tokens (event.type='assistant') │ 8. Événements SSE streamés au client ├─ meta — questionId (1er event, pour fallback polling) ├─ phase — "L'Oracle pèse votre question", "plonge dans le grimoire", etc. ├─ context — chunks retrouvés + scores + mentionedCards + stickyMentionedCards ├─ token — streaming réponse ├─ heartbeat — toutes les 8s (anti idle-timeout NPM) ├─ diagnostics — {chunks, hyde, timings, bestScore} └─ done — {fullAnswer, questionId, bestScore} │ 9. Persistance (questionsRepo.updateAnswer) └─ {answer, bestScore, diagnostics} Optimisations notables Parallélisation Qdrant (commit 95229af) : dense + sparse en parallèle, pas séquentiel withTimeout partout (8 sites) : évite blocages de boot et de RAG Fusion RRF v2 : pondération question brute ×2, blending position-aware (cf. ADR-005) Sticky mentions : maîtrise tokens sur multi-tours (cap FIFO 20 / 80 si deck) Vision 1 image max : ~30s par image lue par Claude — passer à 2-3 doublait la latence pour gain marginal Fallback connexion SSE Si la connexion SSE casse mais qu'on a déjà reçu meta.questionId : Le frontend bascule sur polling GET /api/ask/:questionId (3s × 15 = 45s max) La réponse est récupérée depuis la BDD telle qu'elle a été persistée L'UI affiche "L'Oracle finalise hors-ligne…" pendant la récupération Si rien après 45s, erreur user-visible Pattern réutilisable pour tout endpoint streamSSE : exposer GET /api//:id retournant 200 (prêt) / 202 (pending) / 404.