Component · L2
Hybrid Retrieval
github.com/juanmmm21/hybrid-search-retrieval-pipeline
A hybrid retrieval engine combining dense vector search and sparse BM25 keyword matching.
- Lines
- 629
- Files
- 8
- Test files
- 1
- Commits
- 16
- Started
- 26 Jun 2026
- Last commit
- 6 Jul 2026
- Python 100%
● Where it sits
Layer 2 of AI Core — representation & retrieval.
L1 Data & input
L2 Representation & retrieval
L3 Inference & control
L4 Agents
L5 MLOps & quality
↳ Flagship
Fed by
Vector DBFeeds into
Cross-EncoderREADME · 4 min read
Commits per day
Hybrid retrieval pipeline that combines classic lexical search (Okapi BM25) and semantic vector search (NanoVectorDB) using advanced ranking fusion algorithms (RRF and Score Normalization).
This module is a core piece in production Retrieval-Augmented Generation (RAG) architectures, solving the limitation of searching only by semantic similarity (which tends to ignore error codes, acronyms, serial numbers, or product IDs) or searching only by keywords (which lacks contextual understanding and synonymy).
Architecture and Theoretical Foundations
The pipeline implements a two-way parallel search strategy (Dense + Sparse) and consolidates its results through ranking alignment techniques.
graph TD
Query[User Query] --> Tokenizer[BM25 Tokenizer]
Query --> Embedder[Embedding Encoder]
subgraph Sparse Branch
Tokenizer --> BM25Engine[BM25 Retriever]
BM25Engine --> SparseScores[Lexical Rankings]
end
subgraph Dense Branch
Embedder --> LocalModel[Local Contrastive Model]
Embedder -. Fallback .-> HashSim[Deterministic Hash Vectorizer]
LocalModel --> VectorQuery[Query Vector]
HashSim --> VectorQuery
VectorQuery --> NanoVectorDB[HNSW NanoVectorDB]
NanoVectorDB --> DenseScores[Vector Rankings]
end
SparseScores --> FusionManager[Fusion Manager]
DenseScores --> FusionManager
FusionManager --> RRFAlg[Reciprocal Rank Fusion]
FusionManager --> ScoreNormAlg[Score Normalization & Fusion]
RRFAlg --> FinalRank[Top-K Retrieved Documents]
ScoreNormAlg --> FinalRank1. Sparse Search (Okapi BM25)
The BM25Retriever module implements the unweighted Okapi BM25 algorithm over a locally indexed static corpus. The score of a document with respect to a query composed of terms is formally defined as:
Where:
- represents the absolute frequency of term in document .
- is the length in tokens of the analyzed document.
- is the average length of all documents within the indexed corpus.
- is the term saturation parameter (defaults to
1.5), which limits the influence of the non-linear growth of the same term's frequency. - is the document length normalization coefficient (defaults to
0.75), penalizing excessively long documents if they don't add relevant information.
The inverse document frequency () calculation applies smoothing to prevent indeterminate or negative values for terms that are excessively common in the corpus:
Where is the corpus size and is the number of documents containing at least one occurrence of term . If a term produces a theoretically negative IDF, a floor of 0.0001 is applied to ensure the ranking's numerical stability.
The pipeline preprocesses the query and corpus by applying tokenization that lowercases the text, extracts words via the word-level regular expression \b\w+\b, and removes stop-words from a built-in Spanish and English list to mitigate statistical noise.
2. Reciprocal Rank Fusion (RRF)
RRF is an empirical ranking fusion method based on the relative order of items rather than the absolute magnitude of their scores. It allows combining different probability distributions and metric distances without requiring calibration or scale adjustment:
Where:
- is the set of retrievers (in this case, Sparse and Dense).
- is the position (rank) of document in the list returned by retriever (1-indexed). If a document doesn't appear in a retriever's ranking, its rank is treated as infinite ().
- is a smoothing constant (defaults to
60), responsible for reducing the disproportionate influence of the top ranking positions and stabilizing the fusion against minor variations in lower positions.
3. Weighted Linear Fusion (Score Normalization Fusion)
When the source scores are needed for further analysis, this method converts the dense search distances and BM25 scores to a normalized range using the Min-Max technique:
Beforehand, the distances returned by NanoVectorDB (which depend on the configured metric) are projected onto similarity indices:
- Cosine Similarity: Assumes .
- Euclidean Similarity (L2): Transformed via the monotonic function .
- Dot Product Similarity: Since
NanoVectorDBstores the negative of the dot product to minimize it as a distance, is applied.
Once the normalized similarities for both branches are obtained, the combined score is calculated via a parameterizable weighting:
Where is the dense weighting coefficient (defaults to 0.5).
Connection to the Ecosystem
This module acts as an orchestrator that connects the following subprojects:
- nano-vector-db: Retrieves dense vector candidates through multi-layer HNSW graph searches. The pipeline passes it query vectors and optional metadata filters, extracting the documents' distances and IDs.
- contrastive-embedding-trainer: The pipeline checks whether a trained PyTorch/Transformers model exists at the relative path
../contrastive-embedding-trainer/model_output. If available, it automatically loads it into memory to encode incoming queries via Mean Pooling and L2 normalization techniques. If absent, it uses a deterministic pseudo-random generator based on the text string's hash to avoid interrupting the execution flow.
File Structure
bm25.py: Contains theBM25Retrieverclass, responsible for tokenization, frequency extraction, and Okapi BM25/IDF score calculation.fusion.py: Module with the implementations of thereciprocal_rank_fusionandscore_normalization_fusionalgorithms.pipeline.py:HybridSearchPipelineclass that unifies the retrievers and coordinates parallel search execution.test_pipeline.py: Unit test suite that validates the behavior and accuracy of the BM25, RRF, and normalization math formulas.example.py: Interactive demo code that illustrates the difference in results when performing pure lexical search, pure vector search, RRF, and hybrid Score Normalization search.
Installation and Usage
1. Set Up the Environment and Install Dependencies
Python 3.9 or higher is required. Initialize the virtual environment and ensure the required dependencies are present:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
2. Run the Unit Tests
The test suite validates the mathematical correctness of the ranking fusion formulas and the coherence of BM25 search:
.venv/bin/python -m unittest test_pipeline.py
3. Run the Hybrid Search Demo
To see the pipeline in action, run:
.venv/bin/python example.py
The script populates a NanoVectorDB vector database, concurrently indexes the text corpus into the BM25 engine, generates example searches, and shows in detail the transition of scores and resulting positions under RRF and Score Normalization.