Erweiterte Retrieval-Strategien für RAG
Über die grundlegende Ähnlichkeitssuche hinaus: hybride Suche, Query Expansion, MMR und mehrstufiges Retrieval für bessere RAG-Ergebnisse.
TL;DR
- Hybride Suche (semantisch + Stichworte) schlägt rein semantische Suche um 20–35 %
- Query Expansion hilft, wenn Anfragen vage sind oder andere Terminologie verwenden
- MMR reduziert Redundanz in den abgerufenen Ergebnissen
- Einfach anfangen : Reine semantische Suche → Hybride hinzufügen → Mit reranking optimieren
- Vergleichen Sie Retrieval-Strategien nebeneinander auf Ailog
Außerhalb der einfachen Similarity-Suche
Basis-RAG verwendet semantische Similarität, um Dokumente zu retrievalen. Obwohl effektiv, hat dieser Ansatz Einschränkungen:
- Blindheit gegenüber Schlüsselwörtern : Verpasst exakte Term-Übereinstimmungen (Produkt-IDs, Eigennamen)
- Query-Document-Mismatch : Fragen werden anders formuliert als Antworten
- Redundanz : Die abgerufenen Chunks enthalten oft ähnliche Informationen
- Unzureichender Kontext : Die k ersten Chunks können keinen vollständigen Kontext liefern
Fortgeschrittene Retrieval-Strategien adressieren diese Einschränkungen.
Hybride Suche
Kombiniert semantische (vector) und lexikalische (Stichwort) Suche.
BM25 + Vector Search
BM25 (Best Matching 25) : Statistisches Stichwort-Ranking
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)
Kombiniert die Rankings mehrerer Retrieverb.
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), ...]
Wann Hybride Suche verwenden
Verwenden Sie hybride Suche, wenn :
- Anfragen spezifische Begriffe enthalten (IDs, Namen, technische Begriffe)
- Mischung aus semantischer und exakter Übereinstimmung benötigt wird
- Die Domäne spezialisiertes Vokabular hat
Verwenden Sie nur vector, wenn :
- Anfragen in natürlicher Sprache sind
- Synonym-Handling kritisch ist
- Multilinguale Suche erforderlich ist
Benchmarks zeigen :
- Hybride übertrifft oft beide Einzelsysteme um 10–20 %
- Besonders effektiv in technischen Domänen
- Kritisch für Produktsuche, Code-Suche
Query Expansion
Umformulieren oder Erweitern von Anfragen zur besseren Retrieval.
Multi-Query-Generierung
Generiert mehrere Varianten einer Anfrage.
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)
Vorteile :
- Erfasst mehrere Interpretationen
- Erhöht Recall
- Handhabt mehrdeutige Anfragen
Kosten :
- Mehrfache retrievals (langsamer, teurer)
- LLM-Aufruf zur Generierung
HyDE (Hypothetical Document Embeddings)
Generiert eine hypothetische Antwort und sucht danach.
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
Warum das funktioniert :
- Antworten sind semantisch ähnlicher zu den tatsächlichen Antworten (nicht zu Fragen)
- Schließt die Query-Document-Lücke
- Effektiv wenn Fragen und Antworten unterschiedlich formuliert sind
Wann verwenden :
- Frage-Antwort-Systeme
- Wenn Anfragen Fragen sind, die Dokumente aber Aussagen enthalten
- Akademische / Forschungssuche
Zerlegung von Anfragen
Zerlegt komplexe Anfragen in Unteranfragen.
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
Anwendungsfälle :
- Multi-Hop-Fragen
- Komplexe analytische Anfragen
- Wenn ein einzelnes retrieval nicht ausreicht
Maximal Marginal Relevance (MMR)
Reduziert Redundanz in den abgerufenen Ergebnissen.
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]
Parameter :
lambda_param = 1.0: Pure Relevanz (keine Diversität)lambda_param = 0.5: Ausgewogenheit zwischen Relevanz und Diversitätlambda_param = 0.0: Maximale Diversität
Verwenden wenn :
- Abgerufene Chunks sind sehr ähnlich
- Bedarf an vielfältigen Perspektiven
- Zusammenfassungs-Aufgaben
Parent-Child Retrieval
Kleine Chunks retrievalen, größeren Kontext zurückgeben.
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]
Vorteile :
- Präzise retrieval (kleine Chunks)
- Reichhaltiger Kontext (große Dokumente)
- Das Beste aus beiden Welten
Verwenden wenn :
- Vollständiger Kontext für die Generierung benötigt wird
- Dokumente eine natürliche Hierarchie haben (Abschnitte, Paragraphen)
- Die Kontextfenstergröße größere Chunks erlaubt
Ensemble Retrieval
Kombiniert mehrere Retrieval-Methoden.
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]]
Beispiel für ein Ensemble :
DEVELOPERpythonensemble = EnsembleRetriever( retrievers=[ VectorRetriever(embedding_model="openai"), BM25Retriever(), VectorRetriever(embedding_model="sentence-transformers") ], weights=[0.5, 0.3, 0.2] )
Self-Query Retrieval
Extrahiert Filter aus natürlicher Sprache.
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
Vorteile :
- Nutzt Metadaten effizient
- Natürliche Sprachschnittstelle für Filter
- Höhere Präzision
Verwenden wenn :
- Reichhaltige Metadaten verfügbar sind
- Anfragen filterbare Attribute enthalten
- Filterung nach Zeit, Kategorie oder Attribut nötig ist
Multi-Stage Retrieval
Pipeline von grob nach fein.
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]
Schritte :
- Retrieval (schnell, hoher Recall) : 100 Kandidaten
- Reranking (präzise, teuer) : Top 10
- Optional: LLM-basierte Verfeinerung : Top 3
Vorteile :
- Balance zwischen Geschwindigkeit und Präzision
- Kosteneffizient (teure Modelle nur auf kleinem Kandidaten-Set)
- Bessere Ergebnisqualität
Contextual Compression
Nicht relevante Teile der abgerufenen Chunks entfernen.
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
Vorteile :
- Reduziert token-Nutzung
- Ermöglicht mehr Chunks im Kontextfenster
- Fokus auf relevante Information
Kosten :
- LLM-Aufrufe (teuer)
- Zusätzliche Latenz
Verwenden wenn :
- Token-Budget knapp ist
- Abgerufene Chunks lang und nur teilweise relevant sind
- Viele Quellen integriert werden müssen
Auswahl einer Retrieval-Strategie
Entscheidungsrahmen
Starten Sie mit :
- Grundlegende semantische Suche (vector similarity)
- k = 3 bis 5 Chunks
Fügen Sie hybride Suche hinzu, wenn :
- Anfragen spezifische Begriffe enthalten
- Die Domäne spezialisiertes Vokabular hat
- Die Leistung sich in Evaluationen verbessert
Fügen Sie Query Expansion hinzu, wenn :
- Anfragen mehrdeutig sind
- Recall wichtiger ist als Precision
- Sie Latenz/Kosten erhöhen können
Fügen Sie MMR hinzu, wenn :
- Abgerufene Chunks redundant sind
- Bedarf an vielfältigen Perspektiven besteht
- Zusammenfassungs- oder Analyseaufgaben
Fügen Sie Reranking hinzu, wenn :
- Top-k-Ergebnisse nicht konsequent relevant sind
- Sie Latenz gegen Qualität tauschen können
- Budget vorhanden ist (der nächste Guide behandelt das)
Einfluss auf Performance
| Strategie | 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% |
Praktische Implementierung
LangChain-Beispiel
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 )
LlamaIndex-Beispiel
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?")
Überwachung der Retrieval-Qualität
Verfolgen Sie diese Metriken :
Retrieval-Metriken :
- Precision@k : Relevante Docs in den Top-k
- Recall@k : Relevante abgerufene Docs / alle relevanten Docs
- MRR : Mean Reciprocal Rank des ersten relevanten Ergebnisses
Nutzer-Metriken :
- Qualitätsbewertungen der Antworten
- Follow-up Frage-Rate
- Task Completion
Technische Metriken :
- Retrieval-Latenz
- Cache-Hit-Rate
- Request-Durchsatz
Ailog Expertentipp : Hybride Suche ist die Retrieval-Verbesserung mit dem besten ROI. Das Hinzufügen von BM25-Schlüsselwortsuche neben der semantischen Suche liefert konsistent 20–35 % bessere Ergebnisse über Domänen hinweg. Es ist einfacher zu implementieren als Query Expansion und verlässlicher als das Feinjustieren von MMR. Wenn Sie nur eine Retrieval-Optimierung durchführen, wählen Sie die hybride Suche. Wir haben sie in Produktion eingesetzt und sofortige Qualitätsgewinne bei minimaler Komplexität gesehen.
Retrieval-Strategien auf Ailog testen
Vergleichen Sie Retrieval-Methoden mit Ihren Dokumenten :
Ailog ermöglicht Ihnen das Benchmarken von :
- Reine semantische Suche vs hybride Suche
- Verschiedene Query-Expansion-Techniken
- MMR vs Standard-Retrieval
- Maßgeschneiderte Metadaten-Filterung
Sehen Sie reale Metriken : Vergleiche für Precision@k, MRR, Latenz
Jetzt Tests starten → Kostenloser Zugang zu allen Retrieval-Strategien.
Nächste Schritte
Abgerufene Dokumente benötigen oft ein Reranking, um die spezifische Anfrage zu optimieren. Der nächste Guide behandelt Reranking-Strategien und Cross-Encoder-Modelle zur weiteren Verbesserung der RAG-Qualität.
Tags
Verwandte Artikel
Grundlagen des Retrievals: Wie die RAG-Suche funktioniert
Beherrschen Sie die Grundlagen des Retrievals in RAG-Systemen: Embeddings, vector search, chunking und indexing für relevante Ergebnisse.
MMR: Suchergebnisse diversifizieren mit maximaler marginaler Relevanz
Reduzieren Sie Redundanz in der RAG-Recherche: Verwenden Sie MMR, um Relevanz und Vielfalt auszubalancieren und so die Qualität des Kontexts zu verbessern.
Hybride RAG-Suche: BM25 + Vektorsuche (2025)
+20–30% RAG-Genauigkeit mit hybrider Suche. Schritt-für-Schritt-Anleitung zum Kombinieren von BM25 und Vektorsuche mit Weaviate, Qdrant oder Pinecone.