RAG Reranking: How to Add a Second-Stage Reranker to Retrieval

First-stage retrieval (vector ANN or TF-IDF) casts a wide net for recall, but the most relevant passage often lands second or third - not first. A second-stage reranker rescores only the shortlist with a heavier relevance function and reorders it, so the best answer reaches the LLM at position 1.

AI Engineerairagreranking

Why retrieval order matters

When you pass retrieved passages to an LLM, it weights the top context most. If the most relevant document is ranked third, answer quality drops - even though it was fetched. Retrieval and ranking are two separate jobs:

The two-stage shape is the industry standard pattern because applying a heavy cross-encoder to the full corpus is too slow; running it on just the top-k is fast.

Implementing rerank()

Given a list of candidate passages from the retriever, score each one against the query and sort by score descending:

def relevance(query: str, doc: str) -> float:
    """Score one (query, doc) pair. Higher = more relevant."""
    q_terms = set(query.lower().split())
    d = doc.lower()
    # exact word match scores higher than substring match
    return sum(3 if t in d.split() else (1 if t in d else 0) for t in q_terms)


def rerank(query: str, candidates: list[str]) -> list[str]:
    """Return candidates sorted by relevance score, highest first."""
    return sorted(candidates, key=lambda doc: relevance(query, doc), reverse=True)

Then wire it into your search() function as a second stage:

def search(query: str, k: int = 2) -> list[str]:
    candidates = retrieve(query, k=10)   # broad first-stage fetch
    ranked = rerank(query, candidates)   # precision reorder
    return ranked[:k]                    # return top k

Fetching more candidates at the first stage (e.g. 10) and trimming after reranking (to 2) is the right shape - more recall, then higher precision.

Production rerankers

In production, the relevance() function is typically a cross-encoder - a model that reads both the query and the document together before scoring, which gives far better precision than a bi-encoder or lexical score:

All of them plug into the same rerank(query, candidates) slot; swapping the relevance function does not change the pipeline shape.

What to watch for

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

What is reranking in RAG?

Reranking is a second retrieval stage that takes the top-k candidates returned by the first-stage retriever, scores each one against the query with a more precise relevance function, and reorders them so the most relevant passage is first. It improves answer quality without changing what the retriever fetches.

Why not just use a better retriever instead of reranking?

Fast retrievers (bi-encoders, BM25) trade precision for speed across a large corpus. A cross-encoder reranker is much more accurate but too slow to run on every document - so you use both: retriever for recall on the full corpus, reranker for precision on the shortlist.

What cross-encoder rerankers are used in production?

Common choices are the Cohere Rerank API (managed, no GPU needed), BGE-Reranker or a fine-tuned MiniLM via sentence-transformers (self-hosted), and ColBERT for a good accuracy/speed balance. All accept (query, doc) pairs and return a relevance score.

Keep learning

Restore a Broken LLM API IntegrationAI/ML projectHarden an LLM Pipeline Against API FailuresAI/ML projectParse JSON From an LLM (Strip Markdown Fences)AI/ML projectAI/ML roadmapStep by step to hiredAI/ML interview questionsSTAR answersAll AI/ML projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →