5. RetrievalExperte

Erweiterte Retrieval-Strategien für RAG

5. Februar 2025
13 Min. Lesezeit
Équipe de Recherche Ailog

Ü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

DEVELOPERpython
from 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.

DEVELOPERpython
def 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.

DEVELOPERpython
def 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.

DEVELOPERpython
def 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.

DEVELOPERpython
def 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.

DEVELOPERpython
def 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ät
  • lambda_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.

DEVELOPERpython
class 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.

DEVELOPERpython
class 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 :

DEVELOPERpython
ensemble = 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.

DEVELOPERpython
def 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.

DEVELOPERpython
class 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 :

  1. Retrieval (schnell, hoher Recall) : 100 Kandidaten
  2. Reranking (präzise, teuer) : Top 10
  3. 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.

DEVELOPERpython
def 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

StrategieImpact LatenceImpact CoûtGain Qualité
Recherche hybride+20-50msFaible+10-20%
Multi-requêtes+3xÉlevé+15-25%
HyDE+appel LLMÉlevé+10-30%
MMR+10-50msFaible+5-15%
Parent-enfant+0-20msMoyen+10-20%
Reranking+50-200msMoyen+20-40%

Praktische Implementierung

LangChain-Beispiel

DEVELOPERpython
from 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

DEVELOPERpython
from 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

récupérationrecherche-hybridequery expansionmmr

Verwandte Artikel

Ailog Assistant

Ici pour vous aider

Salut ! Pose-moi des questions sur Ailog et comment intégrer votre RAG dans vos projets !