Retrieve-wide then rerank-narrow
WorkedIn 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
- Build the reranker using
build_reranker(top_n). This returns aFastEmbedRerankinstance orNoneif reranking is disabled. - Post-process nodes using the reranker's
postprocess_nodesmethod. The method accepts a list ofNodeWithScoreand aQueryBundle. It returns the toptop_nnodes after cross-encoder scoring. - Return the reranked list.
Complete Code
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.
- roadmap-kg/kg/rerank.py:40-70
- roadmap-kg/kg/rerank.py:1-37
- roadmap-kg/kg/rerank.py:67-106