Skip to main content

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 :

  1. Le frontend bascule sur polling GET /api/ask/:questionId (3s × 15 = 45s max)
  2. La réponse est récupérée depuis la BDD telle qu'elle a été persistée
  3. L'UI affiche "L'Oracle finalise hors-ligne…" pendant la récupération
  4. Si rien après 45s, erreur user-visible

Pattern réutilisable pour tout endpoint streamSSE : exposer GET /api/<resource>/:id retournant 200 (prêt) / 202 (pending) / 404.