Back to Practice

Retrieve-wide then rerank-narrow

Worked

In this worked example, you will implement the core reranking logic used in the grounding engine. The goal is to take a wide candidate set of nodes (retrieved by a bi-encoder) and narrow it down using a cross-encoder reranker (FastEmbedRerank). A cross-encoder scores each (query, chunk) pair together, giving more precise relevance than a bi-encoder that scores independently. This is crucial for high-quality retrieval for grounding.

Why Cross-Encoder is Better

A bi-encoder encodes query and chunk separately and computes cosine similarity. It is fast but can miss relevant chunks if the query and chunk are not independently similar in embedding space. A cross-encoder (like Xenova/ms-marco-MiniLM-L-6-v2) reads the pair jointly, capturing interactions. It is slower but more accurate. Therefore the architecture does wide bi-encoder retrieval (cheap recall) and then narrow reranking with a cross-encoder (precise ordering).

Implementation Steps

  1. Build the reranker using build_reranker(top_n). This returns a FastEmbedRerank instance or None if reranking is disabled.
  2. Post-process nodes using the reranker's postprocess_nodes method. The method accepts a list of NodeWithScore and a QueryBundle. It returns the top top_n nodes after cross-encoder scoring.
  3. Return the reranked list.

Complete Code

python
from typing import List
from llama_index.core.schema import NodeWithScore, QueryBundle
from .rerank import build_reranker

def rerank_nodes(nodes: List[NodeWithScore], query: str, top_n: int = 6) -> List[NodeWithScore]:
    """
    Rerank a list of nodes using a cross-encoder reranker.

    Args:
        nodes: Wide candidate list from bi-encoder retrieval.
        query: The query string.
        top_n: Number of top nodes to return after reranking.

    Returns:
        List of reranked nodes (highest score first), truncated to top_n.
    """
    reranker = build_reranker(top_n=top_n)
    if reranker is None:
        # Graceful degradation: return plain top-k if reranker unavailable
        return nodes[:top_n]
    query_bundle = QueryBundle(query_str=query)
    reranked = reranker.postprocess_nodes(nodes, query_bundle=query_bundle)
    # postprocess_nodes already sorts by descending score and returns top_n
    return reranked

Now your task: In the provided starter skeleton, write the implementation of rerank_nodes following the above code. Additionally, add a comment explaining why a cross-encoder is superior to a bi-encoder for the final ranking step.

Your code
Sources
  • roadmap-kg/kg/rerank.py:40-70
  • roadmap-kg/kg/rerank.py:1-37
  • roadmap-kg/kg/rerank.py:67-106