Stratégies de Récupération Avancées pour le RAG
Au-delà de la recherche de similarité basique : recherche hybride, expansion de requêtes, MMR et récupération multi-étapes pour de meilleures performances RAG.
TL;DR
- Recherche hybride (sémantique + mots-clés) bat la recherche purement sémantique de 20-35%
- Expansion de requêtes aide quand les requêtes sont vagues ou utilisent une terminologie différente
- MMR réduit la redondance dans les résultats récupérés
- Commencez simple : Pur sémantique → Ajoutez hybride → Optimisez avec reranking
- Testez les stratégies de récupération côte à côte sur Ailog
Au-Delà de la Recherche de Similarité Simple
Le RAG de base utilise la similarité sémantique pour récupérer des documents. Bien qu'efficace, cette approche a des limitations :
- Cécité aux mots-clés : Rate les correspondances de termes exacts (IDs de produit, noms propres)
- Décalage requête-document : Questions formulées différemment des réponses
- Redondance : Les chunks récupérés contiennent souvent des informations similaires
- Insuffisance de contexte : Les k premiers chunks peuvent ne pas fournir un contexte complet
Les stratégies de récupération avancées abordent ces limitations.
Recherche Hybride
Combine la recherche sémantique (vectorielle) et lexicale (mots-clés).
BM25 + Recherche Vectorielle
BM25 (Best Matching 25) : Classement statistique par mots-clés
DEVELOPERpythonfrom rank_bm25 import BM25Okapi # Index documents tokenized_docs = [doc.split() for doc in documents] bm25 = BM25Okapi(tokenized_docs) # Keyword search keyword_scores = bm25.get_scores(query.split()) # Vector search vector_scores = cosine_similarity(query_embedding, doc_embeddings) # Combine scores (weighted average) alpha = 0.7 # Weight for vector search final_scores = alpha * vector_scores + (1 - alpha) * keyword_scores # Retrieve top-k top_k_indices = np.argsort(final_scores)[-k:][::-1]
Reciprocal Rank Fusion (RRF)
Combiner les classements de plusieurs récupérateurs.
DEVELOPERpythondef reciprocal_rank_fusion(rankings_list, k=60): """ rankings_list: List of ranked document IDs from different retrievers k: Constant (typically 60) """ scores = {} for ranking in rankings_list: for rank, doc_id in enumerate(ranking, start=1): if doc_id not in scores: scores[doc_id] = 0 scores[doc_id] += 1 / (k + rank) return sorted(scores.items(), key=lambda x: x[1], reverse=True) # Example usage vector_results = ["doc1", "doc3", "doc5", "doc2"] bm25_results = ["doc2", "doc1", "doc4", "doc3"] final_ranking = reciprocal_rank_fusion([vector_results, bm25_results]) # Result: [("doc1", score), ("doc2", score), ...]
Quand Utiliser la Recherche Hybride
Utilisez l'hybride quand :
- Les requêtes contiennent des termes spécifiques (IDs, noms, termes techniques)
- Mélange de correspondance sémantique et exacte nécessaire
- Le domaine a un vocabulaire spécialisé
Utilisez le vecteur seul quand :
- Requêtes en langage naturel
- Gestion des synonymes critique
- Recherche multilingue
Les benchmarks montrent :
- L'hybride surpasse souvent les deux seuls de 10-20%
- Particulièrement efficace pour les domaines techniques
- Critique pour la recherche de produits, recherche de code
Expansion de Requêtes
Reformuler ou étendre les requêtes pour une meilleure récupération.
Génération Multi-Requêtes
Générer plusieurs variantes de requêtes.
DEVELOPERpythondef generate_query_variations(query, llm): prompt = f"""Étant donné la requête utilisateur, génère 3 variantes qui capturent différents aspects : Original: {query} Génère 3 variantes: 1. 2. 3. """ variations = llm.generate(prompt) all_queries = [query] + variations # Retrieve for each query all_results = [] for q in all_queries: results = retrieve(q, k=5) all_results.extend(results) # Deduplicate and rerank unique_results = deduplicate(all_results) return rerank(unique_results, query)
Avantages :
- Capture plusieurs interprétations
- Augmente le rappel
- Gère les requêtes ambiguës
Coût :
- Récupérations multiples (plus lent, plus cher)
- Appel LLM pour la génération
HyDE (Hypothetical Document Embeddings)
Générer une réponse hypothétique, puis la rechercher.
DEVELOPERpythondef hyde_retrieval(query, llm, k=5): # Generate hypothetical answer prompt = f"""Écris un passage qui répondrait à cette question : Question: {query} Passage:""" hypothetical_answer = llm.generate(prompt) # Embed and search using the hypothetical answer answer_embedding = embed(hypothetical_answer) results = vector_search(answer_embedding, k=k) return results
Pourquoi ça marche :
- Les réponses sont sémantiquement similaires aux réponses (pas aux questions)
- Comble le fossé requête-document
- Efficace quand les questions et réponses sont formulées différemment
Quand utiliser :
- Systèmes de questions-réponses
- Quand les requêtes sont des questions mais les documents sont des déclarations
- Recherche académique/de recherche
Décomposition de Requêtes
Décomposer les requêtes complexes en sous-requêtes.
DEVELOPERpythondef decompose_query(complex_query, llm): prompt = f"""Décompose cette question complexe en sous-questions plus simples : Question: {complex_query} Sous-questions: 1. 2. 3. """ sub_questions = llm.generate(prompt) # Retrieve for each sub-question all_contexts = [] for sub_q in sub_questions: contexts = retrieve(sub_q, k=3) all_contexts.extend(contexts) # Generate final answer using all contexts final_answer = llm.generate( context=all_contexts, query=complex_query ) return final_answer
Cas d'usage :
- Questions multi-sauts
- Requêtes analytiques complexes
- Quand une seule récupération est insuffisante
Maximal Marginal Relevance (MMR)
Réduire la redondance dans les résultats récupérés.
DEVELOPERpythondef mmr(query_embedding, doc_embeddings, documents, k=5, lambda_param=0.7): """ Maximize relevance while minimizing similarity to already-selected docs. lambda_param: Tradeoff between relevance (1.0) and diversity (0.0) """ selected = [] remaining = list(range(len(documents))) while len(selected) < k and remaining: mmr_scores = [] for i in remaining: # Relevance to query relevance = cosine_similarity( query_embedding, doc_embeddings[i] ) # Max similarity to already selected docs if selected: similarities = [ cosine_similarity(doc_embeddings[i], doc_embeddings[j]) for j in selected ] max_sim = max(similarities) else: max_sim = 0 # MMR score mmr_score = lambda_param * relevance - (1 - lambda_param) * max_sim mmr_scores.append((i, mmr_score)) # Select best MMR score best = max(mmr_scores, key=lambda x: x[1]) selected.append(best[0]) remaining.remove(best[0]) return [documents[i] for i in selected]
Paramètres :
lambda_param = 1.0: Pertinence pure (pas de diversité)lambda_param = 0.5: Équilibre pertinence et diversitélambda_param = 0.0: Diversité maximale
Utiliser quand :
- Les chunks récupérés sont très similaires
- Besoin de perspectives diverses
- Tâches de résumé
Récupération Parent-Enfant
Récupérer de petits chunks, retourner un contexte plus large.
DEVELOPERpythonclass ParentChildRetriever: def __init__(self, documents): self.parents = [] # Original documents self.children = [] # Small chunks self.child_to_parent = {} # Mapping for doc_id, doc in enumerate(documents): # Split into small chunks for precise retrieval chunks = split_document(doc, chunk_size=256) for chunk_id, chunk in enumerate(chunks): self.children.append(chunk) self.child_to_parent[len(self.children) - 1] = doc_id self.parents.append(doc) # Embed children for retrieval self.child_embeddings = embed_batch(self.children) def retrieve(self, query, k=3): # Search small chunks for precision query_emb = embed(query) child_indices = vector_search(query_emb, self.child_embeddings, k=k) # Return parent documents for context parent_indices = [self.child_to_parent[i] for i in child_indices] unique_parents = list(set(parent_indices)) return [self.parents[i] for i in unique_parents]
Avantages :
- Récupération précise (petits chunks)
- Contexte riche (grands documents)
- Le meilleur des deux mondes
Utiliser quand :
- Besoin de contexte complet pour la génération
- Les documents ont une hiérarchie naturelle (sections, paragraphes)
- La fenêtre de contexte permet des chunks plus grands
Récupération d'Ensemble
Combiner plusieurs méthodes de récupération.
DEVELOPERpythonclass EnsembleRetriever: def __init__(self, retrievers, weights=None): self.retrievers = retrievers self.weights = weights or [1.0] * len(retrievers) def retrieve(self, query, k=5): all_results = [] # Get results from each retriever for retriever, weight in zip(self.retrievers, self.weights): results = retriever.retrieve(query, k=k*2) # Overfetch # Weight scores for doc, score in results: all_results.append((doc, score * weight)) # Deduplicate and aggregate scores doc_scores = {} for doc, score in all_results: doc_id = doc['id'] if doc_id not in doc_scores: doc_scores[doc_id] = {'doc': doc, 'score': 0} doc_scores[doc_id]['score'] += score # Sort and return top-k ranked = sorted( doc_scores.values(), key=lambda x: x['score'], reverse=True ) return [item['doc'] for item in ranked[:k]]
Exemple d'ensemble :
DEVELOPERpythonensemble = EnsembleRetriever( retrievers=[ VectorRetriever(embedding_model="openai"), BM25Retriever(), VectorRetriever(embedding_model="sentence-transformers") ], weights=[0.5, 0.3, 0.2] )
Récupération Auto-Requête
Extraire des filtres des requêtes en langage naturel.
DEVELOPERpythondef self_query_retrieval(query, llm, vector_db): # Extract structured query prompt = f"""Extrait les filtres de recherche de cette requête : Requête: {query} Extrait: - search_text: Texte de recherche sémantique - filters: Filtres de métadonnées (dict) Sortie (JSON):""" structured = llm.generate(prompt, format="json") # Example output: # { # "search_text": "meilleures pratiques support client", # "filters": {"department": "support", "date_range": "2024"} # } # Execute filtered search results = vector_db.query( text=structured['search_text'], filter=structured['filters'], k=5 ) return results
Avantages :
- Exploite efficacement les métadonnées
- Interface en langage naturel pour les filtres
- Meilleure précision
Utiliser quand :
- Métadonnées riches disponibles
- Les requêtes contiennent des attributs filtrables
- Filtrage basé sur le temps, la catégorie ou l'attribut nécessaire
Récupération Multi-Étapes
Pipeline de récupération du grossier au fin.
DEVELOPERpythonclass MultiStageRetriever: def __init__(self, fast_retriever, accurate_reranker): self.retriever = fast_retriever self.reranker = accurate_reranker def retrieve(self, query, k=5): # Stage 1: Fast retrieval (overfetch) candidates = self.retriever.retrieve(query, k=k*10) # Stage 2: Accurate reranking reranked = self.reranker.rerank(query, candidates) # Return top-k return reranked[:k]
Étapes :
- Récupération (rapide, rappel élevé) : 100 candidats
- Reranking (précis, coûteux) : Top 10
- Optionnel : Affinement basé LLM : Top 3
Avantages :
- Équilibre vitesse et précision
- Rentable (modèles coûteux sur petit ensemble de candidats)
- Résultats de meilleure qualité
Compression Contextuelle
Supprimer les parties non pertinentes des chunks récupérés.
DEVELOPERpythondef compress_context(query, chunks, llm): compressed = [] for chunk in chunks: prompt = f"""Extrait uniquement les parties pertinentes pour la requête : Requête: {query} Document: {chunk} Extrait pertinent:""" relevant_part = llm.generate(prompt, max_tokens=200) compressed.append(relevant_part) return compressed
Avantages :
- Réduire l'utilisation de tokens
- Adapter plus de chunks dans la fenêtre de contexte
- Se concentrer sur les informations pertinentes
Coûts :
- Appels LLM (coûteux)
- Latence supplémentaire
Utiliser quand :
- Le budget de tokens est serré
- Les chunks récupérés sont longs et partiellement pertinents
- Besoin d'adapter de nombreuses sources
Choisir une Stratégie de Récupération
Cadre de Décision
Commencez avec :
- Recherche sémantique de base (similarité vectorielle)
- k=3 à 5 chunks
Ajoutez la recherche hybride si :
- Les requêtes contiennent des termes spécifiques
- Le domaine a un vocabulaire spécialisé
- Les performances s'améliorent lors de l'évaluation
Ajoutez l'expansion de requêtes si :
- Les requêtes sont ambiguës
- Le rappel est plus important que la précision
- Prêt à accepter une latence/coût plus élevé
Ajoutez MMR si :
- Les chunks récupérés sont redondants
- Besoin de perspectives diverses
- Tâches de résumé ou d'analyse
Ajoutez le reranking si :
- Les résultats top-k ne sont pas systématiquement pertinents
- Prêt à échanger latence contre qualité
- Le budget le permet (le prochain guide couvre cela)
Impact sur les Performances
| Stratégie | Impact Latence | Impact Coût | Gain Qualité |
|---|---|---|---|
| Recherche hybride | +20-50ms | Faible | +10-20% |
| Multi-requêtes | +3x | Élevé | +15-25% |
| HyDE | +appel LLM | Élevé | +10-30% |
| MMR | +10-50ms | Faible | +5-15% |
| Parent-enfant | +0-20ms | Moyen | +10-20% |
| Reranking | +50-200ms | Moyen | +20-40% |
Implémentation Pratique
Exemple LangChain
DEVELOPERpythonfrom langchain.retrievers import ( EnsembleRetriever, ContextualCompressionRetriever ) from langchain.retrievers.document_compressors import LLMChainExtractor # Ensemble: Vector + BM25 vector_retriever = vector_db.as_retriever(search_kwargs={"k": 5}) bm25_retriever = BM25Retriever.from_documents(documents) ensemble = EnsembleRetriever( retrievers=[vector_retriever, bm25_retriever], weights=[0.7, 0.3] ) # Add compression compressor = LLMChainExtractor.from_llm(llm) compressed_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=ensemble )
Exemple LlamaIndex
DEVELOPERpythonfrom llama_index import VectorStoreIndex, SimpleKeywordTableIndex from llama_index.retrievers import RouterRetriever # Create retrievers vector_retriever = VectorStoreIndex.from_documents(documents).as_retriever() keyword_retriever = SimpleKeywordTableIndex.from_documents(documents).as_retriever() # Router retriever (chooses based on query) router = RouterRetriever( selector=llm_selector, retriever_dict={ "vector": vector_retriever, "keyword": keyword_retriever } ) # Query-dependent routing results = router.retrieve("What are the system requirements?")
Surveillance de la Qualité de Récupération
Suivez ces métriques :
Métriques de Récupération :
- Precision@k : Docs pertinents dans top-k
- Recall@k : Docs pertinents récupérés / tous docs pertinents
- MRR : Mean Reciprocal Rank du premier résultat pertinent
Métriques Utilisateur :
- Notes de qualité de réponse
- Taux de questions de suivi
- Achèvement de tâche
Métriques Techniques :
- Latence de récupération
- Taux de succès du cache
- Débit de requêtes
Conseil d'Expert d'Ailog : La recherche hybride est l'amélioration de récupération avec le meilleur ROI. Ajouter la recherche par mots-clés BM25 à côté de la recherche sémantique offre systématiquement 20-35% de meilleurs résultats dans tous les domaines. C'est plus facile à implémenter que l'expansion de requêtes et plus fiable que l'ajustement MMR. Si vous ne faites qu'une seule optimisation de récupération, faites la recherche hybride. Nous l'avons implémentée en production et avons vu des gains de qualité immédiats avec une complexité minimale.
Tester les Stratégies de Récupération sur Ailog
Comparez les méthodes de récupération avec vos documents :
Ailog vous permet de benchmarker :
- Recherche sémantique pure vs recherche hybride
- Différentes techniques d'expansion de requêtes
- MMR vs récupération standard
- Filtrage de métadonnées personnalisé
Voir les métriques réelles : Comparaisons Precision@k, MRR, latence
Commencez les tests → Accès gratuit à toutes les stratégies de récupération.
Prochaines Étapes
Les documents récupérés ont souvent besoin de reranking pour optimiser la requête spécifique. Le prochain guide couvre les stratégies de reranking et les modèles cross-encoder pour améliorer davantage la qualité du RAG.
Tags
Articles connexes
MMR : Diversifier les Résultats de Recherche avec la Pertinence Marginale Maximale
Réduisez la redondance dans la récupération RAG : utilisez MMR pour équilibrer pertinence et diversité pour une meilleure qualité de contexte.
Recherche Hybride : Combiner Recherche Sémantique et par Mots-clés
Augmentez la Précision de Récupération de 20-30% : Combinez la Recherche Vectorielle avec le Matching par Mots-clés BM25 pour des Performances RAG Supérieures.
Expansion de Requêtes : Récupérer des Résultats Plus Pertinents
Améliorer le recall de 40% : expandez les requêtes utilisateur avec des synonymes, des sous-requêtes et des variations générées par LLM.