Startseite / Artikel

Fünf Reranking-Techniken für RAG: Vom schnellen Abrufen bis zum präzisen Kontext.

Warum die erste Suchphase keine Antworten liefert und wie man das behebt: Cross-Encoders, RRF, Cohere Rerank, ColBERT sowie LLM-as-a-Judge, zusammen mit einem schichtweisen Pipeline-Ansatz zum Start.

Verlag: REACTAPP.TOP12 Min. Lesezeit2769 Wörter

Quellen: **5 Reranking Techniques in RAG: From Fast Retrieval to Accurate Context**

Stellen Sie sich einen ordnungsgemäß aufgebauten RAG-Pipeline vor: Dokumente sind in Blöcke unterteilt, ein Embedding-Modell wurde ausgewählt und eine Vektordatenbank ist angeschlossen. Eine Frage trifft ein, der Retriever liefert 50 Blöcke, und die fünf besten kommen in den Prompt. Die Antwort ist in Ordnung – aber nicht hervorragend. Manchmal wird die richtige Antwort völlig übersehen.

Betrachten Sie, was abgerufen wurde, und Sie finden oft den Block mit der Antwort an Position 23. Das Embedding-Modell bewertete ihn als mäßig relevant, aber nicht als zu den fünf wichtigsten gehörend, weshalb das LLM ihn nie sah. Das ist keineswegs ein seltener Sonderfall – es handelt sich dabei um einen der häufigsten Fehlermuster bei produktiven RAG-Systemen.

In der Regel liegt das Problem nicht beim Retriever, sondern bei dem, was nach der Abfrage geschieht.

Das Engpassproblem nach der Abfrage

Die erste Stufe der Dokumentensuche ist auf Geschwindigkeit ausgelegt. Dichte Vektoren, spärliche BM25-Methoden oder eine Kombination beider – das Ziel ist es, eine Million Dokumente innerhalb von Millisekunden auf eine handhabbare Anzahl zu reduzieren. Der Preis dafür ist ein schnelles, oberflächliches Urteil über die Relevanz: Das Modell betrachtet die Abfrage sowie jedes Dokument einzeln, bewertet sie und gibt das am nächsten liegende zurück.

„Am nächsten liegend“ und „am nützlichsten zur Beantwortung dieser Frage“ sind unterschiedliche Begriffe. Die anfängliche Dokumentensuche stößt aus vier Gründen auf Schwierigkeiten:

  • Oberflächliche Bewertung. Embeddings messen die Ähnlichkeit, nicht die Nützlichkeit – daher kann ein Dokumentteil zwar in der Nähe der Abfrage liegen, aber dennoch nichts Nützliches enthalten.
  • Keine Interaktion zwischen Abfrage und Dokumenten. Bi-Encoder kodieren die Abfrage sowie die Dokumente getrennt voneinander und sehen sie niemals zusammen, wodurch subtile Beziehungen verborgen bleiben.
  • Kontextblindheit. Der Retriever hat keine Ahnung, was das LLM benötigt, weshalb er nicht vorhersagen kann, ob ein bestimmter Textabschnitt zu einer guten Antwort führen wird.
  • Falschpositive Ergebnisse. Allgemeine Abschnitte zu einem Thema erzielen oft bessere Ergebnisse als spezifische, faktenbasierte Abschnitte, weil sie eine „sicherere“ Übereinstimmung darstellen.
  • Reranking ist die Lösung.

    Was Reranking bewirkt

    Reranking ist ein zweiter Durchlauf. Nachdem eine schnelle, umfassende Suche eine Liste von Kandidatenabschnitten ergeben hat, ordnet ein leistungsstärkeres Modell diese nach ihrer tatsächlichen Passgenauigkeit zur jeweiligen Anfrage um. Man kann es sich als Qualitätskontrolle vor dem Erreichen des Kontexts beim LLM vorstellen.

    Der Ablauf umfasst drei Schritte:

    1. Erste Suche: Eine schnelle, umfassende Suche liefert 50 bis 100 Kandidatenabschnitte.
    2. Reranking: Ein intelligenteres Modell bewertet jeden Kandidaten im Vergleich zur Anfrage und ordnet die Liste neu.
  • Endauswahl: Die 3 bis 5 bestplatzierten neu rangierten Abschnitte werden an den LLM übergeben.
  • Man erhält die Geschwindigkeit einer annähernden Suche zusammen mit der Genauigkeit einer detaillierten Bewertung. Der Retriever wird nicht ersetzt, sondern nur ergänzt.

    Rückholung und Neuranking nebeneinander

    | Aspect              | Initial Retrieval        | Reranking                     |
    | ------------------- | ------------------------ | ----------------------------- |
    | Goal                | Find candidates fast     | Judge true relevance          |
    | Speed               | Milliseconds             | Tens to hundreds of milliseconds |
    | Input               | Query + index            | Query + top-k candidates      |
    | Scoring depth       | Shallow (embedding dot product) | Deep (cross-attention, token interaction) |
    | Cost                | Low (local compute)      | Higher (model inference)      |
    | When to use         | Every query              | On top-k candidates only      |
    

    Die Idee ist es, keine hohe Rechenleistung für eine Million Dokumente aufzuwenden, sondern sie stattdessen für die 50 vielversprechendsten einzusetzen.

    Fünf Techniken zur Neubewertung

    Neubewertungsalgorithmen reichen von günstigen und schnellen bis hin zu ressourcenintensiven und leistungsstarken Lösungen. Die richtige Wahl hängt von Ihrem Latenzbudget, Ihren Genauigkeitsanforderungen und Ihren betrieblichen Grenzen ab. Diese fünf Methoden decken diesen Bereich ab.

    1. Neubewertung mit Cross-Encoder

    Cross-Encoder gelten als Maßstab für die Genauigkeit. Ein Bi-Encoder embeddet Abfragen und Dokumente getrennt voneinander, während ein Cross-Encoder sie gemeinsam als eine einzige Eingabe verarbeitet. Dadurch kann er tokenbasierte Interaktionen wie Pronomenauflösung, Negation und implizierte Beziehungen erfassen, die herkömmliche Suchalgorithmen völlig übersehen.

    Die Schritte:

    1. Der Suchalgorithmus gibt 50 potenzielle Abschnitte zurück.
    2. Für jeden Abschnitt werden Abfrage und Abschnitt als [CLS] Abfrage [SEP] Abschnitt [SEP] kombiniert.
  • Führen Sie das Paar in ein Cross-Encoder-Modell ein, beispielsweise cross-encoder/ms-marco-MiniLM-L-6-v2.
  • Damit erhält man einen Relevanzwert pro Paar.
  • Sortieren Sie nach dem Wert und behalten Sie die besten 5.
  • from sentence_transformers import CrossEncoder
    
    # Load a cross-encoder reranker
    # ms-marco-MiniLM-L-6-v2 is fast and accurate for general use
    cross_encoder = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
    
    def rerank_with_cross_encoder(query: str, retrieved_docs: list[str], top_k: int = 5):
        """
        Rerank retrieved documents using a cross-encoder.
    
        Args:
            query: The user question
            retrieved_docs: List of document chunks from initial retrieval
            top_k: Number of documents to return after reranking
    
        Returns:
            List of (document, score) tuples, sorted by relevance
        """
        # Create query-document pairs
        pairs = [[query, doc] for doc in retrieved_docs]
    
        # Get relevance scores
        scores = cross_encoder.predict(pairs)
    
        # Combine docs with scores and sort
        scored_docs = list(zip(retrieved_docs, scores))
        scored_docs.sort(key=lambda x: x[1], reverse=True)
    
        return scored_docs[:top_k]
    
    # Example usage
    query = "What are the side effects of amoxicillin?"
    retrieved = [
        "Amoxicillin is a penicillin antibiotic used to treat bacterial infections.",
        "Common side effects include nausea, vomiting, and diarrhea.",
        "The drug was first discovered in 1958 by researchers at Beecham.",
        "Patients with penicillin allergies should avoid amoxicillin.",
        "Side effects may include rash, itching, and in rare cases, anaphylaxis.",
    ]
    
    top_docs = rerank_with_cross_encoder(query, retrieved, top_k=3)
    for doc, score in top_docs:
        print(f"Score: {score:.4f} | {doc}")
    

    Wählen Sie Cross-Encoder, wenn Genauigkeit an erster Stelle steht und zusätzliche 50 bis 200 ms in Ordnung sind. Für die meisten produktiven RAG-Anwendungen ist dies der beste Ausgangspunkt. Die Einschränkung besteht darin, dass jedes Abfragen-Dokument-Paar separat verarbeitet wird, wodurch die Latenz mit der Anzahl der Kandidaten zunimmt.

    2. Reciprocal Rank Fusion (RRF)

    Nehmen wir an, Sie möchten überhaupt kein neuronales Modell verwenden. Reciprocal Rank Fusion fügt die Ranglisten mehrerer Suchmechanismen mithilfe einer deterministischen Formel zusammen: kein Training, keine Inferenz, nur Arithmetik.

    RRF eignet sich am besten, wenn bereits eine hybride Suchmethode vorhanden ist, wie beispielsweise dichte Vektorabfragen zusammen mit BM25-Suchfunktionen nach Schlüsselwörtern. Jeder Suchmechanismus liefert seine eigene Rangliste, und RRF fügt diese zu einer besseren Liste zusammen.

    Die Formel lautet:

    RRF_score(d) = Summe über alle Suchmechanismen von 1 / (k + rank_r(d))

    Hier ist k eine Konstante, in der Regel 60, und rank_r(d) ist die Position des Dokuments d im Suchmechanismus r. Dokumente, die von mehreren Suchmechanismen hoch eingestuft werden, rücken an die Spitze.

    def reciprocal_rank_fusion(rankings: list[list[str]], k: int = 60) -> list[tuple[str, float]]:
        """
        Merge multiple document rankings using Reciprocal Rank Fusion.
    
        Args:
            rankings: List of rankings, where each ranking is a list of document IDs
                      ordered from most to least relevant
            k: RRF constant (default 60, as recommended in the original paper)
    
        Returns:
            List of (document_id, rrf_score) tuples, sorted by fused score
        """
        scores = {}
    
        for ranking in rankings:
            for rank, doc_id in enumerate(ranking, start=1):
                if doc_id not in scores:
                    scores[doc_id] = 0.0
                # RRF formula: 1 / (k + rank)
                scores[doc_id] += 1.0 / (k + rank)
    
        # Sort by score descending
        return sorted(scores.items(), key=lambda x: x[1], reverse=True)
    
    # Example: merging BM25 and vector search results
    bm25_results = ["doc_5", "doc_2", "doc_8", "doc_1", "doc_9"]
    vector_results = ["doc_1", "doc_5", "doc_3", "doc_8", "doc_7"]
    
    fused = reciprocal_rank_fusion([bm25_results, vector_results])
    
    print("Fused ranking:")
    for doc_id, score in fused:
        print(f"  {doc_id}: {score:.4f}")
    
    # Notice: doc_5 and doc_1 appear in both retrievers and get boosted to the top
    

    RRF ist hier die schnellste Option, da es im Grunde keine zusätzliche Latenz verursacht. Verwenden Sie es, wenn Sie bereits eine hybride Suchmethode nutzen und eine günstige Verbesserung ohne Modellinferenz wünschen. In Bezug auf die reine Genauigkeit kann es zwar keinen Cross-Encoder übertreffen, funktioniert aber erstaunlich gut – obwohl es keine maschinelle Lernfunktion enthält.

    3. Cohere Rerank API

    Einige Teams möchten kein Reranking-Modell hosten und anpassen. Wenn Ihr Schwerpunkt auf dem Produkt und nicht auf der Modellverwaltung liegt, ist eine verwaltete Rerank-API attraktiv – und Coheres Lösung ist in diesem Bereich am weitesten verbreitet.

    Die API nimmt eine Abfrage sowie eine Liste von Dokumenten entgegen und gibt sie in Relevanzreihenfolge zurück. Hinter ihr steht ein modernes Reranking-Modell mit Optimierungen, deren Entwicklung selbst Wochen in Anspruch nehmen würde. Es kann mehrsprachigen Inhalt sowie lange Dokumente verarbeiten und skaliert nach Bedarf.

    import cohere
    from dotenv import load_dotenv
    import os
    
    load_dotenv()
    
    # Initialize Cohere client
    co = cohere.Client(os.getenv("COHERE_API_KEY"))
    
    def rerank_with_cohere(query: str, documents: list[str], top_k: int = 5):
        """
        Rerank documents using Cohere's managed Rerank API.
    
        Args:
            query: The user question
            documents: List of document chunks from initial retrieval
            top_k: Number of documents to return
    
        Returns:
            List of (document, relevance_score) tuples
        """
        response = co.rerank(
            model="rerank-v3.5",
            query=query,
            documents=documents,
            top_n=top_k,
            return_documents=True
        )
    
        results = []
        for result in response.results:
            results.append((
                result.document.text,
                result.relevance_score
            ))
    
        return results
    
    # Example usage
    query = "How do I handle authentication in a FastAPI app?"
    docs = [
        "FastAPI is a modern web framework for building APIs with Python.",
        "To add authentication, use OAuth2PasswordBearer and JWT tokens.",
        "Pydantic models in FastAPI provide automatic request validation.",
        "The OAuth2PasswordBearer class expects a token URL endpoint.",
        "FastAPI was created by Sebastián Ramírez and released in 2018.",
    ]
    
    ranked = rerank_with_cohere(query, docs, top_k=3)
    for doc, score in ranked:
        print(f"Score: {score:.4f} | {doc}")
    

    Wählen Sie diese Option, wenn Sie eine hohe Genauigkeit ohne zusätzliche Betriebsbelastung wünschen. Die Nachteile sind die Kosten pro Anfrage sowie eine externe Abhängigkeit; bei sehr hohem Traffic kann die Latenz einer Netzwerkanfrage von Bedeutung sein. Für die meisten Teams überwiegen die durch Hosting und Optimierung eingesparten Zeitvorteile diese Kosten.

    4. ColBERT

    Cross-Encoders sind genau, aber langsam, während Bi-Encoders schnell, aber oberflächlich sind. ColBERT findet einen Mittelweg durch „späte Interaktion“.

    1. Offline: Jedes Dokument wird Token für Token in kontextuelle Embeddings kodiert, die im Voraus berechnet und in einem Index gespeichert werden.
    2. Online: Wenn eine Anfrage eintrifft, wird sie auf derselben Token-Ebene verarbeitet.
    3. Bewertung: Jedes Anfragen-Token wird mit seinem nächsten Dokument-Token abgeglichen (Schritt MaxSim), und die maximalen Ähnlichkeiten aller Anfragen-Tokens werden addiert, um die Relevanzbewertung zu ermitteln.

    Weil Dokumenteingebettungen im Voraus berechnet werden, ist dies viel schneller als ein Cross-Encoder. Da er tokenbasierte Interaktionen beibehält, übertrifft er Bi-Encoder in Bezug auf die Genauigkeit.

    from colbert import Searcher
    from colbert.infra import Run, RunConfig
    
    def setup_colbert_searcher(index_path: str, checkpoint: str):
        """
        Initialize a ColBERT searcher for late-interaction reranking.
    
        Args:
            index_path: Path to the pre-built ColBERT index
            checkpoint: Path to the ColBERT model checkpoint
    
        Returns:
            Configured Searcher instance
        """
        with Run().context(RunConfig(nranks=1, experiment="reranking")):
            searcher = Searcher(
                index=index_path,
                checkpoint=checkpoint
            )
        return searcher
    
    def rerank_with_colbert(searcher, query: str, doc_ids: list[str], top_k: int = 5):
        """
        Rerank documents using ColBERT's late interaction.
    
        Args:
            searcher: Initialized ColBERT Searcher
            query: The user question
            doc_ids: List of document IDs from initial retrieval
            top_k: Number of documents to return
    
        Returns:
            List of (doc_id, score) tuples
        """
        # Search within the candidate set
        results = searcher.search(
            query,
            k=top_k,
            filter_fn=lambda pid: pid in doc_ids  # Only rerank candidates
        )
    
        return list(zip(results[0], results[2]))  # doc_ids, scores
    
    # Note: ColBERT requires a pre-built index and model checkpoint.
    # For production use, build the index once and load it at startup.
    

    ColBERT eignet sich für großskalige Systeme, die eine höhere Genauigkeit als Bi-Encoder und eine geringere Latenz als Cross-Encoder benötigen. Der Nachteil besteht in der Arbeit, den Index aufzubauen und zu warten. Wenn Sie sich mit Vektordatenbanken auskennen und eine bessere Qualität ohne eine linear mit der Anzahl der Kandidaten steigende Latenz wünschen, ist dies die ideale Lösung.

    5. LLM-as-a-Judge

    In hochriskanten Bereichen wie medizinischer Diagnose, Recherchen im Rechtsbereich und finanzieller Konformität kann selbst ein Cross-Encoder unzureichend sein. Die Abfragen sind komplex, der Kontext subtil und eine falsche Antwort hat hohe Kosten. In solchen Fällen kann das präziseste Neuranking-Modul das LLM selbst sein.

    Die Idee besteht darin, ein großes Modell (GPT-4, Claude oder Gemini) anzuweisen, die Relevanz jedes Kandidaten zur Abfrage einzuschätzen. Dank umfassender Weltkenntnisse und starker Schlussfolgerungsfähigkeit kann es Relevanz auf Weisen beurteilen, die kleinere Neuranking-Modelle nicht können. Ein typischer Prompt:

    You are evaluating documents for a retrieval system.
    
    Query: {query}
    Document: {document}
    
    Rate how relevant this document is for answering the query.
    Respond with a single integer from 1 to 10, where 10 means perfectly relevant.
    
    Relevance score:
    

    Führen Sie es für jeden Kandidaten aus, analysieren Sie die Ergebnisse und geben Sie die besten k zurück.

    from openai import OpenAI
    import os
    from dotenv import load_dotenv
    
    load_dotenv()
    client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
    
    def score_document_with_llm(query: str, document: str) -> int:
        """
        Ask an LLM to score a document's relevance to a query.
    
        Args:
            query: The user question
            document: A candidate document chunk
    
        Returns:
            Integer relevance score from 1-10
        """
        prompt = f"""You are evaluating documents for a retrieval system.
    
    Query: {query}
    Document: {document}
    
    Rate how relevant this document is for answering the query.
    Respond with a single integer from 1 to 10, where 10 means perfectly relevant.
    Be strict: only give high scores to documents that directly help answer the query.
    
    Relevance score:"""
    
        response = client.chat.completions.create(
            model="gpt-4.1-mini",
            messages=[{"role": "user", "content": prompt}],
            temperature=0,
            max_tokens=5
        )
    
        try:
            score = int(response.choices[0].message.content.strip())
            return max(1, min(10, score))  # Clamp to 1-10
        except ValueError:
            return 5  # Default on parse failure
    
    def rerank_with_llm_judge(query: str, documents: list[str], top_k: int = 3):
        """
        Rerank documents using an LLM as a relevance judge.
    
        Args:
            query: The user question
            documents: List of candidate document chunks
            top_k: Number of documents to return
    
        Returns:
            List of (document, score) tuples, sorted by relevance
        """
        scored = []
        for doc in documents:
            score = score_document_with_llm(query, doc)
            scored.append((doc, score))
    
        scored.sort(key=lambda x: x[1], reverse=True)
        return scored[:top_k]
    
    # Example usage
    query = "What are the tax implications of RSU vesting for employees in California?"
    docs = [
        "RSUs are restricted stock units granted to employees as part of compensation.",
        "In California, RSU income is taxed as ordinary income at vesting, not at grant.",
        "Employers typically withhold federal and state taxes at vesting time.",
        "Stock options and RSUs have different tax treatments under IRS rules.",
        "California has one of the highest state income tax rates in the US.",
    ]
    
    ranked = rerank_with_llm_judge(query, docs, top_k=3)
    for doc, score in ranked:
        print(f"Score: {score}/10 | {doc}")
    

    Das ist hier die genaueste Methode – doch gleichzeitig auch die langsamste und teuerste. Verwenden Sie sie für komplexe, wertvolle Abfragen, bei denen eine falsche Antwort echtes Geld kostet oder echte Risiken mit sich bringt. Für die meisten Anwendungen liefern Cross-Encoder oder verwaltete APIs etwa 90 % der Genauigkeit zu 10 % der Kosten (ein Richtwert, kein Vergleichswert). Behalten Sie LLM-Bewerter für die Fälle bei, in denen sie sich wirklich rechtfertigen.

    Wahl zwischen ihnen

    Kein Neuranking-Tool ist überall der Beste. Diese Tabelle fasst die Einschränkungen zusammen:

    | Technique             | Best For                                          | Latency      | Cost           |
    | --------------------- | ------------------------------------------------- | ------------ | -------------- |
    | Cross-Encoder         | Maximum quality on top-k candidates               | 50-200ms     | Local GPU/CPU  |
    | RRF                   | Hybrid retrieval without adding model inference   | ~0ms         | Free           |
    | Cohere Rerank API     | Speed without operational overhead                | 100-300ms    | Per API call   |
    | ColBERT               | Large-scale, low-latency use cases                | 20-100ms     | Index + GPU    |
    | LLM-as-a-Judge        | Complex, high-value queries (medical, legal)      | 1-5 seconds  | Per API call   |
    

    Ein sinnvoller Ansatz ist es, mit einem Cross-Encoder zu beginnen, da er bei geringster Komplexität den größten Genauigkeitszuwachs liefert. Wenn Latenz ein Problem darstellt, sollten Sie ColBERT oder eine verwaltete API wie Cohere in Betracht ziehen. Fügen Sie RRF hinzu, wenn Sie eine hybride Suchmethode verwenden, und reservieren Sie den LLM-Judge für Anfragen, bei denen die maximale Genauigkeit die Kosten rechtfertigt.

    Teams müssen selten nur einen Ansatz wählen. Eine gängige Produktionsarchitektur sieht wie folgt aus:

    1. Hybride Suche (dichtes Vektorverfahren plus BM25) mit RRF-Fusion.
    2. Cross-Encoder zur Neubewertung der 50 besten Kandidaten.
    3. Ein LLM-as-a-Judge als Rückfalllösung für besonders wichtige Anfragen.

    Dies bietet in den normalen Fällen Geschwindigkeit und bei entscheidenden Anfragen Genauigkeit.

    Zusammenfassende Gedanken

    Reranking ist vermutlich der am meisten unterschätzte Aspekt bei der Erstellung von RAG-Systemen. Teams verbringen Monate damit, die Größe der Datenblöcke sowie die Embedding-Modelle zu optimieren, während der beste Kontext an Position 23 ungenutzt bleibt – ein guter Reranker kann das mit geringem technischem Aufwand beheben.

    Die verfügbaren Techniken reichen von kostenlosen und schnellen Methoden (RRF) bis hin zu kostspieligen, leistungsstarken Ansätzen (LLM-as-a-Judge), und bei Reranking muss es nicht immer entweder alles oder nichts sein. Beginnen Sie einfach, fügen Sie Methoden schrittweise hinzu und erhöhen Sie die Komplexität nur dann, wenn es Ihre Metriken erfordern.

    • Die erste Suchphase sammelt schnell mögliche Kandidaten – sie entscheidet jedoch noch nicht über die richtigen Antworten.
    • Reranking dient als Qualitätskontrolle, als zweite Überprüfung, die die tatsächliche Relevanz bewertet.
    • Cross-Encoders bilden den besten Ausgangspunkt, da sie eine hohe Genauigkeit bei mäßiger Komplexität bieten.
    • RRF liefert kostenlose Vorteile, wenn Sie bereits eine hybride Suchmethode verwenden.
  • Gemanagte APIs sparen Zeit bei der Entwicklung, und Cohere Rerank funktioniert bereits aus der Box heraus gut.
  • ColBERT vereint Geschwindigkeit und Genauigkeit für große Systeme.
  • LLM-as-a-Judge ist die anspruchsvollere Option, wenn Genauigkeit wichtiger ist als Kosten.
  • Falls Sie RAG in der Produktion ohne Neubewertung der Ergebnisse einsetzen, wird die Qualität der Antworten vernachlässigt. Wählen Sie eine Methode, implementieren Sie sie noch diese Woche und messen Sie den Unterschied.