store_nodes() for BM25 hybrid
WorkedWorked Example: Hybrid Retrieval with BM25 Fusion using store_nodes()
In this worked example, you will learn how to combine a dense retriever (semantic search) with a BM25 sparse retriever (lexical/term-matching) using Reciprocal Rank Fusion (RRF). The key challenge: when using Qdrant as the vector store, the LlamaIndex docstore is deliberately empty – nodes are stored only in Qdrant point payloads. Therefore, to build the BM25 index (which needs the full corpus in-process), you must extract the nodes from the vector store itself. The function store_nodes(index) handles this: it first tries index.docstore.docs (populated only for in-memory indices), and if that is empty, it falls back to index.vector_store.get_nodes().
Your task (study this worked solution): Complete the function build_fusion_retriever so that it:
- Retrieves the corpus nodes by calling
store_nodes(index). - Creates a
BM25Retrieverfrom those nodes, withsimilarity_top_kclamped tomin(top_k, len(nodes))(BM25 raises iftop_kexceeds corpus size). - Fuses the
dense_retrieverand the BM25 retriever usingQueryFusionRetrieverin'reciprocal_rerank'mode. Setnum_queries=1and passllm=None(so no LLM call is made). Keepuse_async=Falseandverbose=False. - Return the fused retriever, or if any exception occurs (e.g., missing stemmer), return the original
dense_retrieverunchanged (graceful degradation).
This exact pattern is used in the real codebase (see build_fusion_retriever and _build_retriever). The solution below is the fully realized version for you to study.
Why this matters: The dense retriever excels at semantic similarity but can miss exact identifiers (e.g., NativeD1Saver, route_for, error codes). BM25 catches those lexical hits. RRF merges the ranked lists without normalizing scores, producing a stronger candidate set for downstream reranking. The use of store_nodes() guarantees the BM25 index is built from the actual stored documents, even on the Qdrant path.
- roadmap-kg/kg/rerank.py:188-225
- roadmap-kg/kg/rerank.py:151-186
- roadmap-kg/kg/memory_common.py:975-990