Fondamentaux du Retrieval : Comment fonctionne la recherche RAG
Maîtrisez les bases du retrieval dans les systèmes RAG : embeddings, recherche vectorielle, chunking et indexation pour des résultats pertinents.
- Auteur
- Équipe Ailog
- Date de publication
- Temps de lecture
- 18 min de lecture
- Niveau
- intermediate
- Étape du pipeline RAG
- Retrieval
Fondamentaux du Retrieval : Comment fonctionne la recherche RAG
Le retrieval est le cœur battant de tout système RAG (Retrieval-Augmented Generation). Sans une recherche efficace, même le meilleur LLM du monde produira des réponses hors sujet ou incomplètes. Ce guide vous accompagne dans la compréhension approfondie des mécanismes de retrieval, de la théorie à l'implémentation pratique.
Pourquoi le retrieval est critique dans un système RAG
Un système RAG fonctionne en deux temps : d'abord récupérer les documents pertinents (retrieval), puis générer une réponse basée sur ces documents (generation). La qualité de la réponse finale dépend directement de la qualité des documents récupérés.
Imaginez un assistant qui doit répondre à "Quelle est votre politique de retour ?" Si le retrieval ramène des pages sur les conditions de livraison au lieu de la politique de retour, le LLM générera une réponse incorrecte ou inventera une politique fictive.
Les trois piliers du retrieval Représentation : Comment transformer le texte en vecteurs mathématiques Indexation : Comment organiser ces vecteurs pour une recherche rapide Recherche : Comment trouver les documents les plus pertinents
Comprendre les embeddings
Les embeddings sont des représentations vectorielles du texte. Chaque mot, phrase ou document est transformé en un vecteur de nombres (typiquement 384 à 1536 dimensions) qui capture son sens sémantique.
Comment fonctionnent les embeddings
``python from sentence_transformers import SentenceTransformer
Charger un modèle d'embedding model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
Créer des embeddings textes = [ "Comment retourner un produit ?", "Quelle est la politique de remboursement ?", "Horaires d'ouverture du magasin" ]
embeddings = model.encode(textes)
Calculer la similarité from sklearn.metrics.pairwise import cosine_similarity similarites = cosine_similarity(embeddings)
print("Similarité 'retour' vs 'remboursement':", similarites[0][1]) ~0.85 print("Similarité 'retour' vs 'horaires':", similarites[0][2]) ~0.25 `
Les deux premières phrases, bien que formulées différemment, ont une forte similarité car elles traitent du même sujet. La troisième est sémantiquement éloignée.
Choisir son modèle d'embedding
| Modèle | Dimensions | Performance | Vitesse | Usage recommandé | |--------|------------|-------------|---------|------------------| | all-MiniLM-L6-v2 | 384 | Bonne | Rapide | Prototypage, volumes importants | | all-mpnet-base-v2 | 768 | Très bonne | Moyenne | Production généraliste | | text-embedding-3-small | 1536 | Excellente | Rapide (API) | Production avec budget API | | text-embedding-3-large | 3072 | État de l'art | Moyenne (API) | Cas critiques haute précision | | multilingual-e5-large | 1024 | Excellente multilingue | Moyenne | Contenus FR/EN/multilingue |
Pour un projet en français, privilégiez les modèles multilingues ou entraînés sur des corpus francophones :
`python Excellent choix pour le français model = SentenceTransformer('intfloat/multilingual-e5-large')
Préfixe requis pour E5 query = "query: Comment fonctionne la garantie ?" documents = ["passage: La garantie couvre les défauts de fabrication pendant 2 ans..."] `
Le chunking : découper intelligemment les documents
Le chunking est l'art de découper les documents en morceaux de taille appropriée. Trop grand, le chunk contient du bruit. Trop petit, il perd le contexte.
Stratégies de chunking Chunking par taille fixe
La méthode la plus simple : découper tous les X caractères avec chevauchement.
`python from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter( chunk_size=500, Taille cible chunk_overlap=50, Chevauchement pour conserver le contexte separators=["\n\n", "\n", ". ", " ", ""] )
document = """ Politique de retour
Vous avez 30 jours pour retourner un produit non utilisé dans son emballage d'origine.
Procédure de retour : Connectez-vous à votre espace client Sélectionnez la commande concernée Cliquez sur "Demander un retour" Imprimez l'étiquette de retour
Les frais de retour sont à votre charge sauf en cas de produit défectueux.
Remboursement
Une fois le retour réceptionné et validé, le remboursement est effectué sous 5 jours ouvrés sur le moyen de paiement utilisé lors de l'achat. """
chunks = splitter.split_text(document) for i, chunk in enumerate(chunks): print(f"Chunk {i+1}: {chunk[:100]}...") ` Chunking sémantique
Plus sophistiqué : découper aux frontières naturelles du texte (paragraphes, sections).
`python from langchain.text_splitter import MarkdownTextSplitter
md_splitter = MarkdownTextSplitter( chunk_size=500, chunk_overlap=0 )
Respecte la structure Markdown chunks = md_splitter.split_text(markdown_document) ` Chunking par phrase avec fenêtre glissante
Idéal pour les FAQ et contenus courts :
`python import nltk nltk.download('punkt')
def chunk_by_sentences(text, sentences_per_chunk=3, overlap=1): sentences = nltk.sent_tokenize(text, language='french') chunks = []
for i in range(0, len(sentences), sentences_per_chunk - overlap): chunk = " ".join(sentences[i:i + sentences_per_chunk]) chunks.append(chunk)
return chunks `
Tableau comparatif des stratégies
| Stratégie | Avantages | Inconvénients | Cas d'usage | |-----------|-----------|---------------|-------------| | Taille fixe | Simple, prévisible | Coupe au milieu des idées | Documents homogènes | | Sémantique | Respecte le sens | Plus complexe | Documentation structurée | | Par phrase | Précision fine | Chunks parfois trop courts | FAQ, support | | Hiérarchique | Contexte parent préservé | Complexité accrue | Documentation technique |
Indexation avec les bases vectorielles
Une fois les embeddings créés, il faut les stocker et les indexer pour permettre une recherche rapide. Les bases de données vectorielles sont optimisées pour cette tâche.
Qdrant : exemple d'implémentation
`python from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct
Connexion client = QdrantClient(host="localhost", port=6333)
Créer une collection client.create_collection( collection_name="knowledge_base", vectors_config=VectorParams( size=384, Dimension de vos embeddings distance=Distance.COSINE ) )
Indexer des documents points = [ PointStruct( id=i, vector=embedding.tolist(), payload={ "text": chunk, "source": "politique_retour.md", "category": "support" } ) for i, (embedding, chunk) in enumerate(zip(embeddings, chunks)) ]
client.upsert( collection_name="knowledge_base", points=points ) `
Recherche vectorielle
`python def search(query: str, top_k: int = 5): Encoder la requête query_embedding = model.encode(query)
Rechercher results = client.search( collection_name="knowledge_base", query_vector=query_embedding.tolist(), limit=top_k )
return [ { "text": hit.payload["text"], "score": hit.score, "source": hit.payload["source"] } for hit in results ]
Exemple resultats = search("Comment me faire rembourser ?") for r in resultats: print(f"Score: {r['score']:.3f} - {r['text'][:100]}...") `
Métriques de similarité
Le choix de la métrique impacte les résultats de recherche.
Cosine similarity
La plus utilisée. Mesure l'angle entre deux vecteurs, indépendamment de leur magnitude.
`python import numpy as np
def cosine_similarity(a, b): return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) `
Avantages : Insensible à la longueur du texte original Inconvénients : Peut manquer des nuances de magnitude
Dot product (produit scalaire)
Plus rapide, mais sensible à la magnitude des vecteurs.
`python def dot_product(a, b): return np.dot(a, b) `
Avantages : Plus rapide à calculer Inconvénients : Nécessite des vecteurs normalisés pour être comparable au cosine
Distance euclidienne
Mesure la distance "à vol d'oiseau" entre deux points.
`python def euclidean_distance(a, b): return np.linalg.norm(a - b) `
Avantages : Intuitive géométriquement Inconvénients : Sensible aux outliers et à la dimensionnalité
Optimiser le retrieval Query expansion
Enrichir la requête utilisateur pour améliorer le recall :
`python def expand_query(query: str, llm) -> list[str]: prompt = f""" Génère 3 reformulations de cette question pour améliorer la recherche : Question originale : {query}
Reformulations : """
expansions = llm.generate(prompt) return [query] + expansions
Rechercher avec toutes les variantes def search_expanded(query: str, top_k: int = 5): queries = expand_query(query, llm) all_results = []
for q in queries: results = search(q, top_k=top_k) all_results.extend(results)
Dédupliquer et re-scorer return deduplicate_and_rerank(all_results) ` Reranking
Utiliser un modèle de reranking pour affiner les résultats :
`python from sentence_transformers import CrossEncoder
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
def rerank(query: str, documents: list[str], top_k: int = 3): pairs = [[query, doc] for doc in documents] scores = reranker.predict(pairs)
Trier par score décroissant ranked = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True) return ranked[:top_k]
Pipeline complet def search_with_rerank(query: str): Recherche initiale (recall élevé) initial_results = search(query, top_k=20) Reranking (précision élevée) documents = [r["text"] for r in initial_results] reranked = rerank(query, documents, top_k=5)
return reranked ` Filtrage par métadonnées
Combiner recherche vectorielle et filtres classiques :
`python from qdrant_client.models import Filter, FieldCondition, MatchValue
def search_filtered(query: str, category: str = None, top_k: int = 5): query_embedding = model.encode(query)
Construire le filtre filter_conditions = None if category: filter_conditions = Filter( must=[ FieldCondition( key="category", match=MatchValue(value=category) ) ] )
results = client.search( collection_name="knowledge_base", query_vector=query_embedding.tolist(), query_filter=filter_conditions, limit=top_k )
return results
Rechercher uniquement dans la catégorie "support" resultats = search_filtered("politique retour", category="support") `
Évaluer la qualité du retrieval
Pour mesurer l'efficacité de votre système de retrieval, utilisez ces métriques :
Recall@k
Proportion de documents pertinents retrouvés parmi les k premiers résultats.
`python def recall_at_k(retrieved: list, relevant: list, k: int) -> float: retrieved_k = set(retrieved[:k]) relevant_set = set(relevant)
return len(retrieved_k & relevant_set) / len(relevant_set) `
MRR (Mean Reciprocal Rank)
Position moyenne du premier document pertinent.
`python def mrr(queries_results: list[tuple[list, list]]) -> float: reciprocal_ranks = []
for retrieved, relevant in queries_results: for i, doc in enumerate(retrieved): if doc in relevant: reciprocal_ranks.append(1 / (i + 1)) break else: reciprocal_ranks.append(0)
return sum(reciprocal_ranks) / len(reciprocal_ranks) `
NDCG (Normalized Discounted Cumulative Gain)
Prend en compte l'ordre des résultats et les scores de pertinence.
`python import numpy as np
def ndcg_at_k(relevances: list[float], k: int) -> float: relevances = np.array(relevances[:k])
DCG discounts = np.log2(np.arange(2, len(relevances) + 2)) dcg = np.sum(relevances / discounts)
IDCG (DCG idéal) ideal_relevances = np.sort(relevances)[::-1] idcg = np.sum(ideal_relevances / discounts)
return dcg / idcg if idcg > 0 else 0 `
Pièges courants et solutions Chunks trop grands
Symptôme : Le retrieval ramène des documents vaguement pertinents mais pas précis.
Solution : Réduire la taille des chunks ou utiliser le chunking hiérarchique. Vocabulaire de domaine
Symptôme : Les termes métier ne sont pas bien compris par les embeddings.
Solution : Fine-tuner le modèle d'embedding ou utiliser un vocabulaire de synonymes.
`python synonymes = { "ticket": ["demande", "requête", "incident"], "KB": ["base de connaissances", "knowledge base"], }
def expand_with_synonyms(query: str) -> str: for term, syns in synonymes.items(): if term.lower() in query.lower(): query += " " + " ".join(syns) return query ` Requêtes ambiguës
Symptôme : "Problème avec ma commande" ramène trop de résultats différents.
Solution : Utiliser le contexte conversationnel ou demander des précisions. Cold start
Symptôme : Peu de données au démarrage, retrieval peu pertinent.
Solution : Enrichir avec des données synthétiques ou FAQ générées.
Architecture de production
Pour un système de retrieval en production, voici une architecture recommandée :
` ┌─────────────────────────────────────────────────────────────┐ │ API Gateway │ └─────────────────────┬───────────────────────────────────────┘ │ ┌─────────────────────▼───────────────────────────────────────┐ │ Query Processor │ │ - Normalisation │ │ - Détection de langue │ │ - Query expansion │ └─────────────────────┬───────────────────────────────────────┘ │ ┌────────────┴────────────┐ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ Dense Search │ │ Sparse Search │ │ (Qdrant) │ │ (BM25) │ └────────┬────────┘ └────────┬────────┘ │ │ └──────────┬─────────────┘ ▼ ┌─────────────────┐ │ Fusion/Rerank │ └────────┬────────┘ ▼ ┌─────────────────┐ │ LLM Context │ └─────────────────┘ ``
Prochaines étapes
Maintenant que vous maîtrisez les fondamentaux du retrieval, approfondissez avec nos guides spécialisés : • Dense Retrieval : Recherche sémantique avec embeddings - Plongez dans les embeddings avancés • Sparse Retrieval et BM25 - Découvrez quand la recherche lexicale surpasse • Fusion hybride - Combinez le meilleur des deux mondes
Pour une vue d'ensemble du RAG, consultez notre Introduction complète au RAG.
---
Passez à la pratique avec Ailog
Implémenter un système de retrieval performant demande du temps et de l'expertise. Avec Ailog, bénéficiez d'une infrastructure RAG clé en main : • Chunking intelligent optimisé pour votre type de contenu • Modèles d'embedding multilingues (français/anglais natif) • Reranking automatique pour des résultats ultra-précis • Hébergement souverain en France, conforme RGPD
Testez gratuitement Ailog et déployez votre premier assistant RAG en 3 minutes.