Startseite / Artikel

Das RAG-Retrieval-Toolkit: BM25, Dense, HyDE, RRF, MMR und Reranking

Das RAG-Auswertungstoolkit: BM25, dichte Vektoren, HyDE, RRF, MMR sowie Neubewertung, wann welche Methode verwendet werden sollte, ungefähre Latenzzeiten pro Schritt und wie ein hybrider Ablauf sie miteinander verbindet.

Verlag: REACTAPP.TOP8 Min. Lesezeit1736 Wörter

Quellen: The Retrieval Toolkit That’s Quietly Deciding Whether Your RAG Pipeline Works.

Nach einigen RAG-Pipelines stößt man in der Regel auf dasselbe Problem. Die Embeddings sind gut und die Prompts klar formuliert, doch die Antworten fehlen weiterhin den Anforderungen. Oft liegt das Problem nicht am Modell, sondern daran, was ihm zur Verfügung gestellt wird. Die Informationsabrufung ist das stillschweigende Engpassproblem, und die meisten Misserfolge lassen sich auf eine begrenzte Anzahl von Techniken zurückführen, die Teams überspringen oder falsch anwenden.

Hier finden Sie dieses Werkzeugset: Wie jedes Element funktioniert, warum es existiert, wann es eingesetzt werden sollte und wie die Elemente in einem Produktionspipeline zusammenwirken, sowie wie viel Zeit für die Erstellung einer Antwort benötigt wird.

1. BM25: eine niemals veraltende Schlüsselwortbewertungsmethode

BM25 (Best Matching 25) ist eine spärliche, lexikalische Ranking-Funktion, die eine Verbesserung gegenüber TF-IDF darstellt. Sie ordnet Dokumente danach, wie viele Abfrageschlüsselwörter sie teilen, wobei dabei die Häufigkeit der Schlüsselwörter sowie die Länge der Dokumente berücksichtigt werden.

Für jeden Suchbegriff erhält ein Dokument Punkte, wenn der Begriff häufig vorkommt – allerdings mit abnehmender Wirkung: Zwanzig Vorkommen sind bei weitem nicht zwanzigmal so relevant wie ein einziges. Lange Dokumente werden bestraft, damit sie nicht einfach durch ihre Länge mehr Treffer sammeln können.

from rank_bm25 import BM25Okapi
corpus = [doc.split() for doc in documents]  # tokenized docs
bm25 = BM25Okapi(corpus)
query = "vector database indexing".split()
scores = bm25.get_scores(query)
top_k = sorted(range(len(scores)), key=lambda i: -scores[i])[:5]

BM25 achtet auf den wörtlichen Token und nicht auf die Bedeutung, was es für folgende Anwendungen geeignet macht:

  • Produktcodes, SKUs, Fehlercodes und IDs
  • Benannte Entitäten wie Personen, Unternehmen und Arzneimittelnamen
  • Seltene oder technische Begriffe, die bei Embeddings oft miteinander vermischt werden

Verwenden Sie es dort, wo Präzision bei spezifischen Begriffen wichtiger ist als Toleranz gegenüber Paraphrasen: in rechtlichen, medizinischen, kodierten oder Protokollsuchen. Es ist günstig und schnell und erfordert weder Training noch eine GPU. Seine Schwäche ist das Fehlen jeglichen semantischen Verständnisses, sodass „Auto“ und „Kraftfahrzeug“ für es unverwandt erscheinen.

2. Dichte Vektorabrufung: Bedeutung abgleichen

Dichte Abfrageverarbeitung wandelt Abfragen und Dokumente in Embedding-Vektoren um – Vektoren mit Hunderten bis Tausenden von Dimensionen ohne Nullwerte, die von einem neuronalen Encoder erzeugt werden. Die Ähnlichkeit wird anhand des Kosinus- oder Skalarprodukts bestimmt, und ein annähernder Nahebenachbarn-Index wie HNSW oder IVF findet die am nächsten liegenden Vektoren.

from sentence_transformers import SentenceTransformer
import numpy as np
model = SentenceTransformer("BAAI/bge-small-en-v1.5")
doc_embeddings = model.encode(documents, normalize_embeddings=True)
query_embedding = model.encode([query], normalize_embeddings=True)
scores = np.dot(doc_embeddings, query_embedding.T).flatten()
top_k = np.argsort(-scores)[:5]

Es erfasst semantische Ähnlichkeit: „executive resignation“ findet „CEO stepped down“, obwohl keine gemeinsamen Wörter vorhanden sind. Die Leute bezeichnen das oft als semantische Suche, obwohl streng genommen das dichte Abrufen das Verfahren und die semantische Abgleichung das Ergebnis ist.

Es eignet sich für konversationelle Abfragen, umformulierte Fragen, mehrsprachiges Abrufen oder in Fällen, in denen die Nutzer nicht die genaue Vokabelsprache Ihrer Dokumente verwenden. Es kann bei seltenen Begriffen oder exakten Codes und Zahlen Schwierigkeiten haben, da der Embedding-Raum Details glättet, die BM25 beibehält.

3. Abfragen umformulieren und HyDE: Zuerst die Abfrage reparieren

Manchmal ist das Abrufverfahren in Ordnung, das Problem liegt jedoch bei der Abfrage selbst. Nutzer tippen kurze, vage oder ungewöhnlich formulierte Fragen ein, während sowohl BM25 als auch das dichte Abrufen am besten mit gut strukturiertem, informationsreichen Text arbeiten.

Durch die Umformulierung der Abfrage lässt ein LLM die Frage vor der Suche erweitern oder präzisieren, wodurch „wie schnell“ in eine genaue Frage darüber umgewandelt wird, wie viele Dokumente pro Sekunde der X-Indizierungsprozess in seiner Spitzenleistung verarbeiten kann.

HyDE (Hypothetical Document Embeddings) geht noch weiter. Anstelle der Einbettung der Abfrage wird ein LLM aufgefordert, eine plausibel klingende Antwort zu erfinden; diese erfundene Antwort wird eingebettet und zur Suche nach echten Dokumenten verwendet. Die Intuition dahinter ist, dass eine fabrizierte Antwort oft im Embedding-Raum näher an echten Antworten liegt als die ursprüngliche Frage.

hypothetical_doc = llm.generate(f"Write a short passage answering: {query}")
hyde_embedding = model.encode([hypothetical_doc], normalize_embeddings=True)
scores = np.dot(doc_embeddings, hyde_embedding.T).flatten()

Kurze oder unklare Abfragen gehören zu den häufigsten Ursachen für eine schlechte Ergebnisqualität, da die Abfrage einfach nicht genügend Informationen liefert. Dies ist besonders problematisch in chatbasierten Interfaces sowie in Bereichen, in denen das Vokabular der Benutzer vom Korpus abweicht. Der Nachteil besteht darin, dass vor Beginn der Ergebnissuche eine weitere Aufrufung des LLM erforderlich ist, was zu zusätzlicher Verzögerung und Kosten führt. Zudem kann eine schlechte hypothetische Antwort dazu führen, dass die Suche in die falsche Richtung gelenkt wird.

4. Hybride Suche: Nutzung beider Methoden

BM25 sowie die dichte Ergebnissuche versagen an entgegengesetzten Enden. Bei der hybriden Suche werden beide Methoden parallel ausgeführt und die Ergebnislisten zusammengeführt, wodurch exakte Begriffe sowie Paraphrasen in einer einzigen Durchlaufphase erfasst werden. Das Problem ist jedoch, dass die BM25-Werte sowie die Kosinusähnlichkeiten auf unterschiedlichen Skalen liegen, weshalb ein Durchschnitt dieser Werte nicht funktioniert. Dafür ist die rekursive Rangfusionsmethode zuständig.

5. Rekursive Rangfusion: Kombination der Ränge, nicht der Werte

RRF vermeidet die Skornormalisierung völlig, indem es auf der Rangposition basiert und die Rohwerte ignoriert.

RRF_score(doc) = Σ  1 / (k + rank_r(doc))   for each ranker r

Der Konstante k, meist 60, dämpft die Wirkung, sodass eine einzige Erstplatzerangierung nicht dominieren kann.

def reciprocal_rank_fusion(ranked_lists, k=60):
    scores = {}
    for ranked_list in ranked_lists:
        for rank, doc_id in enumerate(ranked_list):
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
    return sorted(scores.items(), key=lambda x: -x[1])
bm25_ranked = ["doc3", "doc1", "doc5"]
dense_ranked = ["doc1", "doc4", "doc3"]fused = reciprocal_rank_fusion([bm25_ranked, dense_ranked])

Dokumente, die von mehreren Suchalgorithmen hoch bewertet werden, kommen ohne Kalibrierung der Bewertungsskalen automatisch an die Spitze. Das ist die übliche Methode zur Kombination von hybriden Ergebnissen und eignet sich für alle Fälle, in denen zwei oder mehr bewertete Listen von verschiedenen Suchalgorithmen zusammengeführt werden, wie zum Beispiel BM25 in Kombination mit dichten Modellen oder mehreren Embedding-Modellen.

6. Maximale marginale Relevanz: Reduzierung von Redundanzen

Auch eine perfekte Rangliste kann nutzlos sein, wenn die fünf besten Ergebnisse alle dasselbe aussagen. MMR ordnet erneut nach, um sowohl Relevanz als auch Vielfalt zu berücksichtigen.

MMR = λ · Sim(doc, query) − (1 − λ) · max(Sim(doc, selected))

Der Parameter λ bestimmt das Gleichgewicht. Bei λ = 1 erhält man den einfachen top-k-Ansatz, und bei λ = 0 wird die Vielfalt unabhängig von der Relevanz maximiert.

import numpy as np
def mmr(query_vec, doc_vecs, doc_ids, lambda_param=0.5, top_k=5):
    selected, remaining = [], list(range(len(doc_ids)))
    sim_to_query = doc_vecs @ query_vec
    while len(selected) < top_k and remaining:
        if not selected:
            idx = remaining[np.argmax(sim_to_query[remaining])]
        else:
            mmr_scores = []
            for i in remaining:
                sim_to_selected = max(doc_vecs[i] @ doc_vecs[j] for j in selected)
                mmr_scores.append(
                    lambda_param * sim_to_query[i] - (1 - lambda_param) * sim_to_selected
                )
            idx = remaining[np.argmax(mmr_scores)]
        selected.append(idx)
        remaining.remove(idx)
    return [doc_ids[i] for i in selected]

Ohne MMR kann man das gesamte Kontextfenster mit fünf nahezu identischen Abschnitten füllen anstelle von fünf Abschnitten, die unterschiedliche Aspekte der Antwort abdecken. Dies lohnt sich bei Korpora mit überlappendem oder wiederholtem Inhalt, wie FAQ-Seiten, Produktdokumenten mit Standardtexten oder jeder anderen Sammlung nahezu identischer Abschnitte.

7. Cross-Encoder-Reranking: Der Star der Genauigkeit

Alles, was bisher besprochen wurde, gehört zur Welt der bi-Encoder, bei denen Abfragen und Dokumente getrennt eingebettet und anschließend verglichen werden. Ein Cross-Encoder gibt Abfrage und Kandidat gemeinsam in einen einzigen Transformer ein, sodass das Modell auf beide Texte zugreifen kann. Dadurch lassen sich viel präzisere Relevanzbeurteilungen erzielen als durch den Vergleich vorberechneter Vektoren.

from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
pairs = [(query, doc) for doc in candidate_docs]
scores = reranker.predict(pairs)
reranked = [doc for _, doc in sorted(zip(scores, candidate_docs), reverse=True)]

Cross-Encoder schneiden bei der Bewertung der Relevanz konsequent besser ab als Bi-Encoder, da sie sowohl über die Abfrage als auch über das Dokument gemeinsam berücksichtigen. Verwenden Sie einen Cross-Encoder als abschließende Optimierung für eine kleine Auswahl an Kandidaten – in der Regel die besten 20 bis 50 Ergebnisse nach Fusion oder MMR – und niemals auf dem gesamten Korpus, da jedes Dokumentenpaar eine eigene Verarbeitung benötigt. Die Aufrufung des LLM stellt die größte Verzögerung in dieser Kette dar, weshalb sie am Ende des Pipelines und nicht am Anfang erfolgt.

8. Zeit bis zur Antwort: die Kosten für all das

Jede der oben genannten Techniken verbessert die Qualität der Dokumentensuche, doch keine davon ist kostenlos. Jede verursacht zusätzliche Verzögerungen, und die Gesamtzeit von der Abgabe der Abfrage bis zur Erhaltung der endgültigen Antwort entscheidet darüber, ob ein sorgfältig abgestimmter Pipeline in der Produktion einsetzbar ist. Typische Kosten pro Schritt:

  • Abfrageschreibumwandlung oder HyDE, sofern verwendet: etwa 200 ms bis 1 s, eine zusätzliche Aufrufung des LLM vor Beginn der Abfrageausführung.
  • BM25-Suche: etwa 5 bis 20 ms, über einen kostengünstigen invertierten Index im Arbeitsspeicher.
  • Dichte Vektorsuche (ANN): etwa 10 bis 50 ms, abhängig von Größe und Typ des Index, HNSW oder flach.
  • RRF-Fusion: 1 bis 5 ms, einfache arithmetische Operationen an Ranglisten.
  • MMR-Neuranking: etwa 20 bis 100 ms, die Laufzeit steigt mit der Anzahl der Kandidaten, da es iterativ und paarweise abläuft.
  • Cross-Encoder-Neuranking, sofern verwendet: etwa 50 bis 300 ms, oft die größte Einzelkostenkomponente vor der Generierung.
  • LLM-Generierung: etwa 500 ms bis mehrere Sekunden, in der Regel der größte Anteil der Gesamtkosten.

Um diese Gesamtkosten in Schach zu halten:

  • Führen Sie BM25 und dichte Suche parallel aus. Sie sind voneinander unabhängig.
  • Wenden Sie MMR auf eine kleine Gruppe an, beispielsweise die besten 20 bis 30 Ergebnisse nach RRF, da Paarvergleiche der Ähnlichkeit schlecht skalieren.
  • Cachieren Sie Embeddings für häufige oder wiederholte Abfragen.
  • Fügen Sie einen Cross-Encoder nur dann hinzu, wenn der Gewinn an Genauigkeit die Verzögerung eindeutig rechtfertigt.
  • Verfolgen Sie die Antwortzeit als wichtige Metrik neben der Antwortqualität, wie z. B. Treue oder Recall@k. Ein System, das zwei Prozent genauer ist, aber dreimal langsamer arbeitet, lohnt sich möglicherweise nicht für die Veröffentlichung.

Wie die Komponenten zusammenpassen

Ein produktiver hybrider Suchprozess läuft in der Regel wie folgt ab:

  • Die Abfrageumformung oder HyDE bereinigt optional eine vage Abfrage vor der Suche, was jedoch zu Verzögerungen führen kann.
  • BM25 erfasst exakte Begriffe.
  • Die dichte Suche erfasst die Bedeutung.
  • RRF fügt die beiden Listen ohne Umstände der Score-Normalisierung zusammen.
  • MMR kürzt die zusammengeführte Liste auf eine vielfältige, nicht redundante Kandidatenmenge.
  • Ein Cross-Encoder führt eine finale, hochpräzise Bearbeitung dieser kleineren Menge durch.
  • Die Antwortzeit ist der Indikator dafür, ob alles bereit für die Verwendung ist.

Kurzer Entscheidungsführer

  • Kurze, vage oder außerhalb des Wortschatzes liegende Abfragen? Schreiben Sie sie um oder versuchen Sie HyDE.
  • Präzise Treffer für Codes, IDs und seltene Begriffe? BM25.
  • Toleranz gegenüber Paraphrasen? Dichte Suche.
  • Beides gleichzeitig? Verwenden Sie RRF zur Kombination.
  • Redundante oder doppelte Abschnitte im Kontext? MMR.
  • Höchste Relevanz in einer kleinen Endmenge? Neubewertung durch den Cross-Encoder.
  • Genau, aber langsam? Messen Sie jede Phase, bevor Sie blind optimieren.

Keine einzelne Technik ist eine Allheilmethode. Die besten Produktionsysteme kombinieren sie und passen sie an echte Abfragenachweise an – und nicht an Intuition – wobei die richtige Mischung weiterhin stark vom Korpus sowie vom verfügbaren Latenzbudget abhängt.