Technical Guide

Systemes RAG Enterprise : Une Plongee Technique Approfondie

Un guide technique complet pour construire des systemes de Generation Augmentee par Recuperation prets pour la production a grande echelle. Decouvre les pipelines d'ingestion de documents, les strategies de chunking, les modeles d'embedding, l'optimisation du retrieval, le reranking et la recherche hybride par des ingenieurs qui deploient RAG en production.

21 février 202619 min de lectureEquipe d'Ingenierie Oronts

Pourquoi RAG ? Le probleme qu'on resout vraiment

Soyons directs : les LLMs sont puissants mais ils ont un probleme fondamental. Ils ne savent que ce sur quoi ils ont ete entraines, et cette connaissance a une date de peremption. Demande a GPT-4 les resultats du Q3 de ton entreprise ou la documentation de ton API interne, et tu obtiendras un poli "Je n'ai pas d'information la-dessus" ou pire, une hallucination confiante.

RAG resout ca en donnant au modele acces a tes donnees au moment de l'inference. Au lieu d'esperer que le modele a memorise les bonnes informations, tu recuperes les documents pertinents et tu les injectes directement dans le prompt. Concept simple, mais le diable est dans les details d'implementation.

On a construit des systemes RAG qui gerent des millions de documents a travers des dizaines de deploiements enterprise. Voici ce qu'on a appris pour les faire fonctionner a grande echelle.

RAG ce n'est pas juste ajouter des documents a un prompt. C'est construire un systeme de recuperation qui trouve systematiquement les bonnes informations, meme quand les utilisateurs posent des questions de facon inattendue.

Le Pipeline RAG : Architecture End-to-End

Avant de plonger dans les composants, comprenons comment tout s'assemble. Un systeme RAG en production a deux phases principales :

Phase d'Ingestion (Offline)

Documents → Preprocessing → Chunking → Embedding → Stockage Vectoriel

Phase de Query (Online)

Requete Utilisateur → Traitement Query → Retrieval → Reranking → Generation LLM
PhaseQuand elle s'executeExigences de latenceObjectif principal
IngestionBatch/ProgrammeMinutes a heures acceptableMaximiser le potentiel de recall
QueryTemps reelMoins d'une secondePrecision + Vitesse

La phase d'ingestion c'est ou tu prepares ta base de connaissances. La phase de query c'est ou tu reponds vraiment aux questions. Les deux doivent etre optimisees, mais elles ont des contraintes tres differentes.

Ingestion de Documents : Preparer tes donnees pour RAG

Connecteurs Sources : Ou vivent tes donnees

Les donnees enterprise sont eparpillees partout. On a construit des connecteurs pour :

Type de SourceExemplesDefis
Stockage DocumentsSharePoint, Google Drive, S3Controle d'acces, sync incrementale
Bases de DonneesPostgreSQL, MongoDB, SnowflakeMapping de schema, complexite des requetes
Plateformes SaaSSalesforce, Zendesk, ConfluenceLimites de rate API, pagination
CommunicationSlack, Teams, EmailConfidentialite, contexte des threads
Depots de CodeGitHub, GitLabRelations entre fichiers, historique des versions

L'insight cle : ne balance pas tout dans ton stockage vectoriel. Construis des connecteurs intelligents qui :

  1. Respectent les controles d'acces - Si un utilisateur ne peut pas acceder a un document dans SharePoint, il ne devrait pas le recuperer via RAG
  2. Gerent les mises a jour incrementales - Re-traiter des millions de documents parce qu'un seul a change c'est du gaspillage
  3. Preservent les metadonnees - Date de creation, auteur et source sont cruciaux pour le filtrage et l'attribution
// Exemple : Sync intelligente de documents avec detection de changements
const syncDocuments = async (source) => {
  const lastSync = await db.getLastSyncTime(source.id);
  const changes = await source.getChangesSince(lastSync);

  for (const doc of changes.modified) {
    const chunks = await processDocument(doc);
    await vectorStore.upsert(chunks, {
      sourceId: source.id,
      documentId: doc.id,
      permissions: doc.accessControl
    });
  }

  for (const docId of changes.deleted) {
    await vectorStore.deleteByDocumentId(docId);
  }
};

Traitement de Documents : Gerer les formats du monde reel

Les PDFs sont la bete noire de tout ingenieur RAG. Ils ont l'air simples mais contiennent des cauchemars : mises en page multi-colonnes, tableaux integres, images scannees, en-tetes et pieds de page qui se repetent sur chaque page.

Voici notre hierarchie de traitement :

Type de DocumentApproche de TraitementNotes de Qualite
Markdown/Texte BrutExtraction directeExcellente qualite
HTML/Pages WebParsing DOM + nettoyageBon, attention au boilerplate
Documents Wordpython-docx ou similaireBon, preserver la structure
PDFs (numeriques)PyMuPDF + analyse de layoutVarie enormement
PDFs (scannes)OCR + analyse de layoutQualite inferieure, verifier la precision
TableursExtraction consciente des cellulesNecessite comprehension semantique
Images/DiagrammesModeles de vision + OCRCapacite emergente

Pour les PDFs specifiquement, on a trouve que l'extraction consciente du layout fait une enorme difference :

# Mauvais : L'extraction simple de texte perd la structure
text = pdf_page.get_text()  # "CA Q1 Q2 Q3 1000 1200 1500"

# Mieux : L'extraction consciente du layout preserve les tableaux
blocks = pdf_page.get_text("dict")["blocks"]
tables = identify_tables(blocks)
# Donne des donnees structurees que tu peux vraiment utiliser

Strategies de Chunking : Le coeur d'un bon retrieval

C'est la ou la plupart des implementations RAG echouent. Un mauvais chunking mene a un mauvais retrieval, et aucun reranking sophistique ne peut corriger des chunks fondamentalement casses.

Pourquoi la taille des chunks compte

Des chunks trop petits manquent de contexte. Des chunks trop grands diluent la pertinence et gaspillent le precieux espace de fenetre de contexte.

Taille de ChunkAvantagesInconvenientsIdeal pour
Petit (100-200 tokens)Haute precisionPerd le contexteFAQ, definitions
Moyen (300-500 tokens)EquilibreTouche-a-toutBases de connaissances generales
Grand (500-1000 tokens)Contexte richePrecision plus faible, couteuxDocumentation technique

Approches de chunking qu'on utilise vraiment

1. Decoupage Recursif par Caracteres (Baseline)

L'approche la plus simple : decoupe sur les paragraphes, puis les phrases, puis les caracteres si necessaire. Fonctionne etonnamment bien pour les documents homogenes.

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", ". ", " ", ""]
)

2. Chunking Semantique (Mieux pour le contenu divers)

Au lieu de tailles fixes, detecte les changements de sujet en utilisant les embeddings. Quand la similarite semantique entre des phrases consecutives chute significativement, commence un nouveau chunk.

def semantic_chunking(sentences, embedding_model, threshold=0.5):
    chunks = []
    current_chunk = [sentences[0]]

    for i in range(1, len(sentences)):
        similarity = cosine_similarity(
            embedding_model.encode(sentences[i-1]),
            embedding_model.encode(sentences[i])
        )

        if similarity < threshold:
            chunks.append(" ".join(current_chunk))
            current_chunk = [sentences[i]]
        else:
            current_chunk.append(sentences[i])

    return chunks

3. Chunking Conscient de la Structure du Document (Meilleur pour la doc technique)

Utilise la structure du document : titres, sections, blocs de code. Une definition de fonction devrait rester ensemble. Une section avec ses sous-sections forme une unite naturelle.

Element du DocumentStrategie de Chunking
Titres (H1, H2)Utiliser comme limites de chunk
Blocs de codeGarder intacts, inclure le contexte environnant
TableauxExtraire comme donnees structurees + description textuelle
ListesGarder avec le contexte precedent
ParagraphesRespecter comme unites minimales

La Strategie d'Overlap

L'overlap entre chunks aide a preserver le contexte aux frontieres. On utilise typiquement 10-20% d'overlap :

Chunk 1: [-------- contenu --------][overlap]
Chunk 2:                     [overlap][-------- contenu --------]

Mais l'overlap n'est pas gratuit - ca augmente le stockage et peut causer des retrievals en double. Pour les grands corpus, on utilise une fenetre glissante avec deduplication au moment de la query.

Modeles d'Embedding : Convertir le texte en vecteurs

Ton modele d'embedding determine a quel point la similarite semantique correspond a la pertinence reelle. Choisis mal, et les queries ne trouveront pas les documents correspondants meme quand ils existent.

Comparaison des Modeles

ModeleDimensionsForcesFaiblessesCout
OpenAI text-embedding-3-large3072Excellente qualite, multilingueDependance API, cout a l'echelle~$0.13/1M tokens
OpenAI text-embedding-3-small1536Bonne qualite, plus rapideQualite legerement inferieure~$0.02/1M tokens
Cohere embed-v31024Fort multilingueDependance API~$0.10/1M tokens
BGE-large-en-v1.51024Auto-heberge, rapideFocus anglaisAuto-heberge
E5-mistral-7b-instruct4096Qualite etat de l'artLourd, lentAuto-heberge
GTE-Qwen2-7B-instruct3584Excellente qualiteGourmand en ressourcesAuto-heberge

Quand fine-tuner ton modele d'embedding

Les modeles prets a l'emploi fonctionnent bien pour le contenu general. Mais pour les vocabulaires specifiques a un domaine - juridique, medical, technique - le fine-tuning peut ameliorer le retrieval de 15-30%.

Signes que tu as besoin de fine-tuning :

  • La terminologie specifique a l'industrie ne matche pas bien
  • Les acronymes de ton domaine ont des significations differentes de l'usage courant
  • Tes documents ont des patterns structurels uniques
# Fine-tuning avec sentence-transformers
from sentence_transformers import SentenceTransformer, losses

model = SentenceTransformer('BAAI/bge-base-en-v1.5')

# Preparer les paires d'entrainement de ton domaine
train_examples = [
    InputExample(texts=["requete utilisateur", "document pertinent"]),
    # ... plus d'exemples
]

train_loss = losses.MultipleNegativesRankingLoss(model)
model.fit(train_objectives=[(train_dataloader, train_loss)], epochs=3)

Bonnes Pratiques d'Embedding

Traitement par Batch : N'embedde jamais un document a la fois en production. Batch pour le debit.

# Mauvais : O(n) appels API
for doc in documents:
    embedding = model.encode(doc)

# Bon : O(1) appel API
embeddings = model.encode(documents, batch_size=32)

Normaliser les Vecteurs : La plupart des recherches de similarite supposent des vecteurs normalises. Assure-toi que tes embeddings sont L2-normalises.

Cacher Agressivement : Embedder la meme query deux fois est du pur gaspillage. Utilise un cache de query avec TTL.

Bases de Donnees Vectorielles : Stocker et chercher a grande echelle

Ta base de donnees vectorielle gere le gros du travail de recherche par similarite. Le choix compte enormement a l'echelle.

Matrice de Comparaison

Base de DonneesTypeEchelle MaxFiltrageForces
PineconeGeree1B+ vecteursExcellentFacile a demarrer, auto-scaling
WeaviateAuto-heberge/Cloud100M+BonAPI GraphQL, recherche hybride
QdrantAuto-heberge/Cloud100M+ExcellentPerformance, base Rust
MilvusAuto-heberge1B+BonEchelle, support GPU
pgvectorExtension PostgreSQL10MBasiqueSimplicite, infra existante
ChromaEmbarque1MBasiqueDeveloppement, prototypage

Strategies d'Indexation

Le type d'index affecte dramatiquement la performance des queries et le recall :

Type d'IndexTemps de BuildTemps de QueryRecallMemoire
Flat (force brute)O(1)O(n)100%Faible
IVFMoyenRapide95-99%Moyen
HNSWLentTres rapide98-99%Eleve
PQ (Product Quantization)RapideRapide90-95%Tres faible

Pour la plupart des systemes en production, HNSW offre le meilleur equilibre. Mais a des milliards de vecteurs, tu auras probablement besoin d'IVF-PQ avec un tuning soigneux.

# Exemple de configuration HNSW avec Qdrant
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance

client = QdrantClient("localhost", port=6333)

client.create_collection(
    collection_name="documents",
    vectors_config=VectorParams(
        size=1536,
        distance=Distance.COSINE
    ),
    hnsw_config={
        "m": 16,           # Connexions par noeud
        "ef_construct": 100  # Precision de construction
    }
)

Optimisation du Retrieval : Trouver les bons documents

Transformation de Query

Les utilisateurs ne posent pas les questions comme les documents sont ecrits. La transformation de query comble ce fosse.

TechniqueComment ca marcheQuand utiliser
Expansion de queryAjouter synonymes et termes liesDomaines techniques avec terminologie variee
HyDE (Hypothetical Document Embeddings)Generer une reponse hypothetique, l'embedderQuand les queries sont tres differentes des documents
Decomposition de queryDiviser les queries complexes en sous-queriesQuestions en plusieurs parties
Reecriture de queryLe LLM reecrit la query pour un meilleur retrievalQueries conversationnelles/ambigues
# Implementation HyDE
def hyde_retrieval(query, llm, retriever):
    # Generer une reponse hypothetique
    hypothetical = llm.generate(
        f"Ecris un court passage qui repondrait a : {query}"
    )

    # Chercher en utilisant le document hypothetique
    results = retriever.search(hypothetical)
    return results

Recherche Hybride : Combiner Vecteur + Mots-cles

La recherche vectorielle pure rate les correspondances exactes. La recherche par mots-cles pure rate la similarite semantique. L'hybride combine les deux.

ApprochePoids VecteurPoids Mots-clesIdeal pour
Vecteur d'abord0.80.2Connaissances generales
Equilibre0.50.5Contenu mixte
Mots-cles d'abord0.20.8Technique avec termes exacts
Reciprocal Rank FusionDynamiqueDynamiqueDistribution de queries inconnue
def hybrid_search(query, vector_store, keyword_index, alpha=0.7):
    # Recherche vectorielle
    vector_results = vector_store.search(query, k=20)

    # Recherche BM25 par mots-cles
    keyword_results = keyword_index.search(query, k=20)

    # Reciprocal Rank Fusion
    scores = {}
    k = 60  # Constante RRF

    for rank, doc in enumerate(vector_results):
        scores[doc.id] = scores.get(doc.id, 0) + alpha / (k + rank)

    for rank, doc in enumerate(keyword_results):
        scores[doc.id] = scores.get(doc.id, 0) + (1-alpha) / (k + rank)

    return sorted(scores.items(), key=lambda x: x[1], reverse=True)

Reranking : La precision quand ca compte

Le retrieval initial lance un filet large. Le reranking utilise un modele plus couteux pour ordonner precisement les meilleurs candidats.

Modeles de Reranking

ModeleApprocheLatenceQualite
Cohere RerankAPI cross-encoder~100msExcellente
BGE-reranker-largeCross-encoder auto-heberge~50msTres bonne
ColBERTLate interaction~30msBonne
Reranking base LLMScoring base prompt~500msExcellente mais lente

Quand Reranker

Le reranking ajoute de la latence. Utilise-le strategiquement :

def smart_retrieval(query, top_k=5):
    # Retrieval initial rapide
    candidates = vector_search(query, k=100)

    # Reranke seulement si necessaire
    if needs_precision(query):
        candidates = reranker.rerank(query, candidates)

    return candidates[:top_k]

def needs_precision(query):
    # Reranke pour les queries specifiques cherchant des faits
    # Saute pour les queries larges, exploratoires
    return query_classifier.predict(query) == "factual"

Considerations de Production

Monitoring et Observabilite

Tu ne peux pas ameliorer ce que tu ne mesures pas. Suis ces metriques :

MetriqueCe qu'elle te ditCible
Latence retrieval (p50, p99)Experience utilisateur<200ms p99
Recall@kLes docs pertinents sont-ils dans les resultats ?>95%
MRR (Mean Reciprocal Rank)Le bon doc est-il pres du haut ?>0.7
Taux d'attribution LLMLe LLM utilise-t-il le contexte recupere ?>80%
Feedback utilisateur (pouce haut/bas)Qualite end-to-end>90% positif

Strategies de Cache

RAG implique des operations couteuses. Cache agressivement :

ComposantStrategie de CacheTTL
Embeddings de queryLRU avec dedup semantique1 heure
Resultats de rechercheHash query → resultats15 min
Chunks de documentsPermanent jusqu'a changement doc-
Reponses LLMHash query + contexte5 min

Gerer les Mises a Jour

Ta base de connaissances n'est pas statique. Gere les mises a jour sans tout reconstruire :

  1. Indexation incrementale : Mettre a jour uniquement les documents changes
  2. Controle de version : Suivre les versions de documents, supporter le rollback
  3. Invalidation de cache : Vider les caches quand les documents sources changent
  4. Verifications de coherence : Verifier periodiquement que le stockage vectoriel correspond a la source de verite

Pieges Courants et Comment les Eviter

PiegeSymptomeSolution
Chunking trop petitChunks recuperes manquent de contexteAugmenter la taille, ajouter overlap
Chunking trop grandContenu irrelevant recupereDiminuer la taille, utiliser la structure
Ignorer les metadonneesImpossible de filtrer par date/sourceStocker et indexer les metadonnees
Strategie de retrieval uniqueMarche pour certaines queries, echoue pour d'autresImplementer recherche hybride
Pas de rerankingPremier resultat souvent fauxAjouter reranker cross-encoder
Mismatch modele d'embeddingTermes techniques ne matchent pasFine-tuner ou utiliser modele de domaine
Ignorer structure documentTableaux, blocs code defiguresTraitement conscient de la structure

Chiffres de Performance Reels

De nos deploiements en production :

MetriqueAvant OptimisationApres Optimisation
Latence query (p50)850ms180ms
Latence query (p99)2.5s450ms
Precision retrieval72%94%
Satisfaction utilisateur68%91%
Cout par query$0.08$0.03

Les plus gros gains sont venus de :

  1. Strategie de chunking appropriee (ni trop petit, ni trop grand)
  2. Recherche hybride avec poids ajustes
  3. Caching agressif a plusieurs couches
  4. Reranking pour les queries critiques en precision

Pour Commencer

Si tu construis ton premier systeme RAG :

  1. Commence simple : Utilise une base de donnees vectorielle geree, modele d'embedding standard, chunking basique
  2. Mesure tout : Configure le monitoring des le premier jour
  3. Construis un jeu de test : Cree des paires query-document pour mesurer la qualite du retrieval
  4. Itere sur la base des donnees : Ne sur-ingenierie pas ; optimise ce que les mesures montrent comme casse

Si tu fais monter en echelle un systeme RAG existant :

  1. Profile ton pipeline : Trouve les vrais goulets d'etranglement
  2. Considere la recherche hybride : Le vectoriel pur ne suffit souvent pas
  3. Ajoute du reranking : C'est souvent l'optimisation avec le meilleur ROI
  4. Investis dans le chunking : C'est la que la plupart des problemes de qualite naissent

RAG n'est pas un probleme resolu. C'est un ensemble de compromis entre latence, precision et cout. Les meilleurs systemes sont ceux qui font ces compromis consciemment et mesurent les resultats.

On a aide des dizaines d'organisations a construire des systemes RAG qui fonctionnent vraiment en production. Si tu galeres avec la qualite du retrieval ou les defis de mise a l'echelle, on sera ravis de partager ce qu'on a appris.

Topics covered

RAGgeneration augmentee par recuperationbases de donnees vectoriellesmodeles dembeddingchunking de documentsrecherche hybridererankingIA enterpriserecherche semantiquerecuperation de connaissances

Ready to implement agentic AI?

Our team specializes in building production-ready AI systems. Let's discuss how we can help you leverage agentic AI for your enterprise.

Start a conversation