0Pricing
AI Engineering Academy · Lektion

Warum zweistufiger Abruf funktioniert

Verstehen Sie den Zielkonflikt zwischen Recall und Precision beim einstufigen Abruf und wie ein schneller, grober Abruf gefolgt von einem langsamen, aber präzisen Re-Ranking das Beste aus beiden Ansätzen vereint.

Warum zweistufiger Abruf funktioniert ist eine kostenlose AI Engineering Academy-Lektion auf CoddyKit. Dies ist Lektion 1 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des AI Engineering Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der AI Engineering Academy-Kurs umfasst insgesamt 4 Lektionen.

Teile dieser Lektion wurden noch nicht übersetzt und werden auf Englisch angezeigt.

The Recall-Precision Trade-off in Retrieval

Every retrieval system faces a fundamental trade-off: recall measures how many relevant documents you find (did you miss any?), while precision measures how accurate the top results are (how many retrieved docs are actually relevant?). Maximizing both simultaneously is computationally expensive. Fast retrievers sacrifice precision for recall; precise rankers sacrifice speed for accuracy.

Bi-Encoder vs Cross-Encoder: The Core Distinction

The two types of models at the heart of two-stage retrieval differ in how they see the query and document. A bi-encoder encodes the query and each document independently and measures similarity between their vectors — fast but limited by independent encoding. A cross-encoder sees the query and document concatenated as a single input, enabling deep interaction between them — highly accurate but O(n) complexity over the candidate set.

# Bi-encoder: compute query embedding ONCE, compare to all doc embeddings
# O(1) query encoding + O(n) dot products via ANN index = fast
query_vec = embed(query)  # done once
results = vector_index.search(query_vec, top_k=100)  # fast ANN search

# Cross-encoder: re-scores (query, doc) pairs jointly
# O(k) forward passes for k candidate documents = slow but accurate
for doc in results[:100]:
    score = cross_encoder.score(query, doc.text)  # joint scoring

Stage 1: Fast Coarse Retrieval

The first stage is a fast retriever — typically a bi-encoder with an approximate nearest neighbor index or a BM25 index — that retrieves a large candidate set (50-200 documents) with high recall but modest precision. The goal is not to be accurate; it is to not miss relevant documents. We cast a wide net and accept some false positives, knowing the second stage will clean them up.

from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

# Stage 1: retrieve 100 candidates (high recall, modest precision)
vectorstore = FAISS.from_documents(documents, OpenAIEmbeddings())
coarse_retriever = vectorstore.as_retriever(
    search_kwargs={'k': 100}  # large candidate set
)

candidates = coarse_retriever.invoke(query)
print(f'Stage 1: retrieved {len(candidates)} candidate documents')

Stage 2: Accurate Cross-Encoder Re-ranking

The second stage takes the candidate set from stage 1 and re-scores each (query, document) pair using a cross-encoder that reads both together. Because it processes only 50-200 candidates (not the full corpus), it can afford the expensive joint encoding. The cross-encoder's deep attention over the concatenated input makes it far more accurate at estimating true relevance than a bi-encoder.

from sentence_transformers import CrossEncoder

reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')

def rerank(query: str, candidates: list[str], top_k: int = 5) -> list[str]:
    # Score each (query, document) pair jointly
    pairs = [[query, doc] for doc in candidates]
    scores = reranker.predict(pairs)

    # Sort by score descending
    ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
    return [doc for doc, _ in ranked[:top_k]]

candidate_texts = [doc.page_content for doc in candidates]
final_docs = rerank(query, candidate_texts, top_k=5)
print(f'Stage 2: selected top {len(final_docs)} documents after re-ranking')

Why This Combination Works

The two-stage design exploits a key asymmetry: the first stage's fast ANN search scales to millions of documents in milliseconds, while the second stage's accurate cross-encoder operates only on the small candidate pool. You get the scalability of approximate search with the accuracy of exact joint scoring. The overall pipeline is both fast and highly accurate — something neither stage achieves alone.

Latency Profile of Two-Stage Retrieval

In a typical two-stage pipeline: stage 1 (vector ANN search over 1M documents) takes 5-20ms; stage 2 (cross-encoder over 100 candidates) takes 100-500ms depending on document length and hardware. The total latency budget is 150-600ms — acceptable for most applications. GPU acceleration in stage 2 can reduce re-ranking to under 30ms for short documents, making the pipeline competitive with single-stage retrieval on latency-sensitive applications.

import time

def two_stage_search(query, coarse_retriever, reranker, top_k=5):
    t0 = time.perf_counter()

    candidates = coarse_retriever.invoke(query)        # stage 1
    t1 = time.perf_counter()

    candidate_texts = [c.page_content for c in candidates]
    final_docs = rerank(query, candidate_texts, top_k)  # stage 2
    t2 = time.perf_counter()

    print(f'Stage 1 (retrieval): {(t1-t0)*1000:.1f}ms')
    print(f'Stage 2 (re-ranking): {(t2-t1)*1000:.1f}ms')
    print(f'Total: {(t2-t0)*1000:.1f}ms')
    return final_docs

Choosing the Right Candidate Set Size

The first stage candidate set size is a critical hyperparameter. Too small (say, 10) and relevant documents may be missed before re-ranking even starts. Too large (say, 500) and stage 2 latency blows up. The recall at N curve — how many relevant documents are captured at different values of N — guides this choice. Typical sweet spots are between 50 and 150 candidates, where recall is near-saturation but latency remains manageable.

def recall_at_n(coarse_retriever, test_queries, golden_relevant, n_values):
    for n in n_values:
        recalls = []
        for query, relevant in zip(test_queries, golden_relevant):
            # Temporarily set k to n
            coarse_retriever.search_kwargs['k'] = n
            results = coarse_retriever.invoke(query)
            retrieved_ids = {r.metadata.get('id') for r in results}
            relevant_found = len(set(relevant) & retrieved_ids)
            recalls.append(relevant_found / len(relevant))
        avg = sum(recalls) / len(recalls)
        print(f'N={n}: recall={avg:.3f}')

Hybrid First Stage + Cross-Encoder Second Stage

The most powerful two-stage configuration pairs a hybrid retriever (dense + BM25) as the first stage with a cross-encoder as the second stage. Hybrid retrieval maximizes first-stage recall by combining semantic and keyword matching, and the cross-encoder then accurately selects the most relevant documents from the combined candidate pool. This configuration consistently achieves state-of-the-art retrieval quality on benchmarks.

from langchain.retrievers import EnsembleRetriever

# Stage 1: hybrid retrieval for maximum recall
hybrid_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6],
)

# Stage 2: cross-encoder re-ranking for high precision
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder

cross_encoder_model = HuggingFaceCrossEncoder(model_name='cross-encoder/ms-marco-MiniLM-L-6-v2')
compressor = CrossEncoderReranker(model=cross_encoder_model, top_n=5)

from langchain.retrievers import ContextualCompressionRetriever
two_stage = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=hybrid_retriever,
)

Commercial Re-ranking APIs

If you want cross-encoder accuracy without managing your own model, both Cohere Rerank and Jina AI Reranker offer cloud-hosted re-ranking APIs. You send a query and a list of document texts, and receive relevance scores back. These APIs use large cross-encoder models (often 500M+ parameters) that outperform self-hosted small cross-encoders, at the cost of additional API latency (50-300ms) and pricing per reranked document.

import cohere

co = cohere.Client('YOUR_API_KEY')

def cohere_rerank(query: str, documents: list[str], top_k: int = 5):
    response = co.rerank(
        model='rerank-english-v3.0',
        query=query,
        documents=documents,
        top_n=top_k,
    )
    return [
        {'text': documents[r.index], 'score': r.relevance_score}
        for r in response.results
    ]

final = cohere_rerank(query, candidate_texts, top_k=5)
for doc in final:
    print(f'Score {doc["score"]:.3f}: {doc["text"][:80]}')

When Two-Stage Is Overkill

Two-stage retrieval adds complexity and latency compared to single-stage. It is not always necessary. For small corpora under 10,000 documents, a single cross-encoder over the full set may be fast enough. For applications where latency under 100ms is critical and accuracy gains are modest, single-stage dense retrieval may be preferable. Use two-stage when you have a large corpus, high accuracy requirements, and can afford 200-500ms retrieval latency.

Three-Stage Retrieval for Extreme Scale

For corpora of tens of millions of documents, a three-stage pipeline is sometimes used: first stage retrieves 10,000 candidates with ANN, second stage re-ranks to 100 using a fast small cross-encoder, and third stage re-ranks to 5 using a large powerful cross-encoder. Each stage applies a more expensive and accurate model to a smaller set. This architecture is used by large-scale search engines and document Q&A systems.

Quick Check

Test your understanding of why two-stage retrieval works from this lesson.

Lesson Recap

In this lesson you learned: bi-encoders are fast but limited to independent query-document encoding, cross-encoders are accurate through joint encoding but too slow for full-corpus search, and two-stage retrieval combines both: a fast first stage for high recall followed by an accurate second stage for high precision. The first stage retrieves many more candidates than needed to avoid missing relevant documents. Next up we implement cross-encoder re-ranking with Cohere and BGE.

Häufig gestellte Fragen

Ist die Lektion „Warum zweistufiger Abruf funktioniert“ kostenlos?

Ja — der vollständige Text von „Warum zweistufiger Abruf funktioniert“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des AI Engineering Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der AI Engineering Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Warum zweistufiger Abruf funktioniert“?

Verstehen Sie den Zielkonflikt zwischen Recall und Precision beim einstufigen Abruf und wie ein schneller, grober Abruf gefolgt von einem langsamen, aber präzisen Re-Ranking das Beste aus beiden Ansä… Du übst AI Engineering Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um AI Engineering Academy zu starten?

Keine Vorkenntnisse erforderlich. AI Engineering Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 1 von 4.

Wie lange dauert die Lektion „Warum zweistufiger Abruf funktioniert“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser AI Engineering Academy-Lektion Code schreiben und ausführen?

Ja. Jede AI Engineering Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Warum zweistufiger Abruf funktioniert
  2. Cross-Encoder-Re-Ranking mit Cohere und BGE
  3. Kontextuelle Komprimierung und Relevanzfilterung
  4. Die Auswirkungen des Re-Rankings messen
← Zurück zu AI Engineering Academy