Pipeline RAG et système IA
Vue d’ensemble
Le système IA du support UnikSim est basé sur une architecture RAG (Retrieval-Augmented Generation). Le backend NestJS orchestre la conversation de support, délègue la recherche et la génération au serveur IA interne, puis décide si une réponse IA peut être retournée ou si la conversation doit être escaladée vers un humain.
Les providers de génération et d'embedding sont volontairement indépendants. Cela permet de changer le modèle de génération sans modifier l'espace vectoriel existant dans Chroma.
1. Architecture des composants
Ce diagramme montre les responsabilités principales et les frontières entre le support, le serveur IA et les providers.
flowchart LR
U[Utilisateur] --> FE[Frontend Angular / App]
FE --> SS[SupportService - NestJS]
SS --> AIS[AIService - NestJS]
AIS -->|POST /ai/query| AI[Serveur IA interne]
AI --> CG[CostGuard]
AI --> UM[UsageMeter]
AI --> EP[Embedding Provider]
EP --> CH[(Chroma)]
AI --> GP[Generation Provider]
GP --> LLM[Gemini / autre LLM]
SS --> DB[(MySQL)]
SS -->|answered| FE
SS -->|insufficient_context| ESC[Clarification puis suggestion d’escalade humaine]
Responsabilités
- SupportService : persistance de la conversation, historique, contexte de récupération, pièces jointes et décision de réponse/escalade.
- AIService : client NestJS du serveur IA interne, validation et gestion des erreurs/timeouts.
- Serveur IA : recherche vectorielle, construction du contexte, génération, comptabilisation des coûts et garde-fous.
- Embedding Provider : transforme une requête ou un chunk documentaire en vecteur.
- Chroma : stocke et retourne les chunks sémantiquement proches.
- Generation Provider : encapsule le SDK du fournisseur LLM et retourne une réponse normalisée.
2. Pipeline d’ingestion documentaire
Les documents de support sont préparés à l'avance par le script d'ingestion. Le même provider/modèle d'embedding doit être utilisé à l'ingestion et à la recherche.
flowchart TD
DOC[Documents PDF / Markdown] --> META[Lecture des métadonnées héritées]
META --> VALID[Validation game / scope / audience / language]
VALID --> PARSE[Parsing du document]
PARSE --> CHUNK[Découpage en chunks avec overlap]
CHUNK --> EMB[Embedding de chaque chunk]
EMB --> IDS[IDs stables SHA-256]
IDS --> UPSERT[Upsert par batch dans Chroma]
UPSERT --> CLEAN[Suppression des anciens chunks devenus obsolètes]
CLEAN --> ORPHAN[Nettoyage des documents supprimés ou déplacés]
UPSERT -->|échec| ROLLBACK[Rollback des nouveaux IDs]
Le chunking privilégie les paragraphes et titres, puis découpe les blocs trop longs en phrases et en mots. Un chevauchement est conservé entre les chunks afin de limiter la perte de contexte.
3. Séquence complète d’une requête RAG
sequenceDiagram
actor U as Utilisateur
participant S as SupportService
participant A as AIService
participant R as Serveur IA
participant E as Embedding Provider
participant C as Chroma
participant G as Generation Provider
U->>S: Envoie un message
S->>S: Sauvegarde message utilisateur
S->>S: Charge historique + langue + retrievalContexts
S->>A: queryRAG(...)
A->>R: POST /ai/query + token interne
R->>R: Réserve budget (CostGuard)
R->>R: Construit la requête de recherche
R->>E: embed(searchQuery)
E-->>R: vecteur
R->>C: query(vector, filter, nResults)
C-->>R: chunks + distances
R->>R: Filtre selon MAX_DISTANCE et construit le contexte
R->>G: generate(prefix, history, question, image?)
G-->>R: texte normalisé
R->>R: UsageMeter + règlement du budget
R-->>A: answered / insufficient_context
A-->>S: résultat RAG
alt réponse exploitable
S->>S: Sauvegarde message IA
S-->>U: Réponse IA
else contexte insuffisant
S->>S: Sauvegarde clarification ou suggestion d’escalade
S-->>U: Propose le support humain
end
Construction du contexte de récupération
L'historique envoyé à l'IA contient uniquement les tours USER et AI, dans une fenêtre bornée. L'image, lorsqu'elle existe, est attachée uniquement au tour courant.
4. Décision de réponse ou d’escalade
flowchart TD
Q[Requête utilisateur] --> SEARCH[Recherche Chroma]
SEARCH --> REL{Assez de chunks pertinents ?}
REL -->|Non et aucune image| IC[insufficient_context]
REL -->|Oui| CTX[Construire le contexte]
REL -->|Image présente| CTX
CTX --> GEN[Génération LLM]
GEN --> OUT{Réponse vide ou marqueur unsupported ?}
OUT -->|Oui| IC
OUT -->|Non| ANS[Retourner answer + confidence]
IC --> SUG[SupportService clarifie puis propose l’escalade]
ANS --> SAVE[Sauvegarder le message IA]
SUG --> HUMAN[Utilisateur peut escalader vers un humain]
Contrôle des coûts et garde-fous
Chaque opération protégée du serveur IA passe par une réservation de budget avant l'appel au modèle. Un UsageMeter accumule ensuite l'usage réellement observé et permet de régler la réservation dans un bloc finally, même si l'opération échoue.
- Plafonds d'entrée, de sortie et de taille de recherche.
- Timeouts côté NestJS et côté providers.
- Tarification associée au provider/modèle réellement utilisé.
- Refus des modèles non tarifés ou inconnus.
- Endpoint de santé séparant l'état du provider de génération, de l'embedding et de Chroma.
Fichiers principaux
uniksim-back-end/src/service/support.service.tsuniksim-back-end/src/service/ai.service.tsuniksim-back-end/src/node.js/server/ai/server.jsuniksim-back-end/src/embedding/ingest.jsserver/ai/provider/*pour les providers de génération, embedding, tokenizer et pricing.
Points d’attention
- Changer uniquement le provider de génération ne nécessite pas de réindexer Chroma.
- Changer le modèle d'embedding modifie l'espace vectoriel et implique normalement une réindexation.
- Les objets SDK spécifiques à un fournisseur ne doivent pas sortir de son provider.
- La réponse IA ne doit être considérée valide que si le contexte est suffisamment pertinent et que le provider retourne une réponse supportée.
Frontend et expérience utilisateur
Le frontend ne connaît pas directement Chroma, les embeddings ni les règles internes de retrieval. Il envoie le message utilisateur au backend et affiche le résultat normalisé retourné par le système de support.
flowchart LR
UI[UI Support / Chat]
HTTP[Service frontend HTTP]
SS[SupportService]
AI[Pipeline RAG]
SOCK[SupportSocketService]
STATE[Etat local / conversation]
UI -->|message + attachment| HTTP
HTTP --> SS
SS --> AI
AI --> SS
SS -->|persistance| DB[(MySQL)]
SS -->|MESSAGE_CREATED| SOCK
SOCK --> STATE
STATE --> UI
SS -->|insufficient_context| ESC[Clarification puis suggestion d’escalade]
ESC --> UI
Responsabilités du frontend
- collecter le texte saisi et la pièce jointe éventuelle;
- envoyer l'action utilisateur au backend;
- afficher les messages USER, AI, SYSTEM et ADMIN selon leur type;
- présenter la suggestion d'escalade lorsque le backend retourne un contexte insuffisant;
- mettre à jour l'interface à partir des événements temps réel plutôt que d'exécuter lui-même la logique RAG.
Ce que le frontend ne décide pas
Historique et pièces jointes
L'historique RAG est reconstruit côté backend à partir des messages persistés. Le frontend n'a donc pas à fabriquer le prompt conversationnel complet. Lorsqu'une image est jointe, elle est validée et chargée côté backend avant d'être transmise au serveur IA, et n'est attachée qu'au tour utilisateur courant.
Réponse IA vs expérience de support
Pour le frontend, le RAG est une source possible de message dans la conversation de support. Si une réponse est exploitable, elle apparaît comme un message IA. Si le contexte est insuffisant ou que le modèle ne peut pas répondre de façon supportée, l'interface reçoit plutôt un message système invitant l'utilisateur à escalader vers un humain.
État actuel du pipeline — 30 septembre 2026
GeminiProvider et GeminiEmbeddingProvider utilisent Vertex AI via vertex_client.js. Génération : gemini-3.5-flash-lite avec THINKING_LEVEL=LOW; embeddings : gemini-embedding-001. Chroma est un CloudClient avec collection game.
Le listener IA démarre sur 127.0.0.1:8106 avant l’initialisation des dépendances. NestJS attend ce listener, pas la disponibilité du modèle et de Chroma. /ai/health retourne 503 si une dépendance manque; les requêtes retentent son initialisation.
Clarifications avant support humain
Le statut positif du contrat HTTP est answered; sans contexte, il est insufficient_context. SupportService compte les clarifications IA consécutives : les deux premières réponses sauvegardent uniksim.support_ai_clarify, puis la suivante sauvegarde uniksim.support_suggest_escalation. Ces messages ont le type AI. L’utilisateur demande ensuite l’escalade; le statut insuffisant ne crée pas automatiquement un ticket.
Un salut simple sans image reçoit une réponse locale sans embedding ni génération. Une image validée peut permettre la génération sans passage assez proche; le prompt interdit d’inventer des faits sur la plateforme. Pour le texte, le seuil est au moins un chunk de distance ≤ 0,5. La confidence est la moyenne de clamp(1 - distance, 0, 1); elle indique la proximité documentaire, pas une probabilité de justesse.
Usage, cache et réservation
Instructions et contexte précèdent l’historique et l’image courante pour permettre le cache implicite Gemini. Le cache de contexte explicite a été retiré. Le tokenizer normalise input, cachedInput, output et reasoning; le pricing facture le raisonnement comme sortie. La réservation utilise le plafond du modèle de 65 536 tokens facturables de sortie, et non les seules limites visibles de 4 000 tokens par réponse ou 256 par résumé.
UsageMeter conserve les coûts en nanodollars entiers puis les convertit en cents. L’usage absent, incohérent ou d’un tier non pris en charge reste estimé au plafond. Le finally règle la réservation; si la réconciliation échoue, la réservation durable est conservée. Version : vertex-global-standard-2026-09-29. Voir Ajouter un nouveau provider IA.