📖 Accompanying Technical Deep-Dive Article:
This repository provides the production reference implementation and empirical benchmarking suite for the article:
Building Hybrid Search for Financial Intelligence (From Pure BM25 to Dense Vectors and Splade With Qdrant, Why a Single Retrieval Method Will Never Be Enough, Highlighting Where Each One Excels and Where Each Falls Short)
(Covers theoretical mechanics, failure modes of single-vector systems, and vector database evaluation criteria).
A production-grade, zero-paid-API reference architecture built on Qdrant Vector Database demonstrating why financial document retrieval requires hybrid search. The system indexes 8,609 contextualized chunks (2.54M words) across 47 SEC EDGAR filings (Forms 10-K, 10-Q, 20-F) for 10 major enterprise technology companies, comparing 5 Qdrant-native retrieval configurations and a downstream Stage-2 cross-feature neural reranker.
graph TD
User([Analyst / Developer Query]) --> Router[CLI / REST API / Web UI]
subgraph "Query Encoding (100% Local CPU ONNX via FastEmbed)"
Router --> EncDense["Dense Encoder<br/>(BAAI/bge-small-en-v1.5, 384d)"]
Router --> EncBM25["BM25 Lexical Encoder<br/>(Qdrant/bm25 Sparse)"]
Router --> EncSplade["SPLADE Sparse Encoder<br/>(prithivida/Splade_PP_en_v1)"]
end
subgraph "Qdrant Vector Database (Single Collection: 'financial_docs')"
EncDense --> QdrantDense[("Dense Vector Space<br/>(Cosine Metric, HNSW)")]
EncBM25 --> QdrantBM25[("BM25 Vector Space<br/>(Sparse Modifier.IDF)")]
EncSplade --> QdrantSparse[("Sparse Vector Space<br/>(Sparse Modifier.IDF)")]
QdrantDense -. "Prefetch (k=50)" .-> RRF["Server-Side Reciprocal Rank Fusion (RRF)<br/>k = 60"]
QdrantBM25 -. "Prefetch (k=50)" .-> RRF
QdrantSparse -. "Prefetch (k=50)" .-> RRF
end
subgraph "Qdrant Retrieval Outputs (5 Core Configurations)"
QdrantBM25 --> Res1["1. BM25 (Pure Lexical)"]
QdrantDense --> Res2["2. Dense (BGE-Small)"]
RRF --> Res3["3. BM25 + Dense Hybrid"]
QdrantSparse --> Res4["4. Sparse (SPLADE)"]
RRF --> Res5["5. Sparse + Dense Hybrid (Recall: 0.9600)"]
end
subgraph "Downstream Stage-2 Precision RAG Pipeline"
Res5 -. "Top Candidates (k=20)" .-> Rerank["NeuralReranker & AnswerExtractor<br/>(Cross-Feature Table & Metric Alignment)"]
Rerank --> Res6["6. Direct Extracted Answer (MRR: 1.0000)"]
end
All five retrieval configurations operate directly against a single unified Qdrant collection (financial_docs) containing 3 named vector spaces, avoiding the latency and maintenance overhead of multi-database setups:
| # | Configuration | Representation / Engine | Core Behavioral Strength | Where It Does Justice | Where It Fails |
|---|---|---|---|---|---|
| 1 | BM25 (Lexical) |
Qdrant/bm25 Sparse Vector with dynamic Modifier.IDF
|
Verbatim token matching without neural hallucination. | Exact product SKUs (H200), ticker symbols (NVDA), CIK codes, and statutory accounting rules (ASC 606). |
Vocabulary Mismatch: Fails when documents express the concept using synonyms ("capex reduction" vs "slowing infrastructure investment"). |
| 2 | Dense Vector |
BAAI/bge-small-en-v1.5 (384d Cosine) |
Continuous semantic geometry and conceptual generalization. | Broad conceptual research, thematic inquiries, and paraphrased questions. | Semantic Drift / Token Blindness: Fails on exact numbers, rare alphanumeric codes, dates, and fiscal period boundaries. |
| 3 | BM25 + Dense Hybrid | Server-Side RRF (prefetch bm25 + dense, |
Fuses exact lexical precision with semantic recall in a single database call. | Mixed financial queries combining a specific corporate entity/SKU with conceptual themes. | Misses synonyms that are not in the query text unless retrieved by the dense candidate pool. |
| 4 | Sparse (SPLADE) |
prithivida/Splade_PP_en_v1 Learned Sparse Vector |
Neural term expansion over a 30k BERT vocabulary directly into sparse weights. | Domain jargon and plain-English financial questions with synonym divergence. | Over-Expansion: Can introduce false-positive lexical matches on highly generic financial terms (e.g. expanding revenue into debt notes). |
| 5 | Sparse + Dense Hybrid | Server-Side RRF (prefetch sparse + dense, |
State-of-the-art hybrid combining learned expansion and dense geometry. | Complex, multi-clause natural language analyst research inquiries. | Higher indexing time and vector storage footprint compared to pure BM25+Dense. |
| 6 | Downstream Neural Reranking (Stage-2 RAG) |
NeuralReranker + AnswerExtractor over Qdrant Candidates |
Cross-feature token alignment, multi-column table parsing, and year disambiguation. | Enterprise financial Q&A where pinpointing multi-year table facts at Rank #1 is mandatory. | Higher latency post-processing step; requires Stage-1 candidates from Qdrant. |
Financial intelligence documents (SEC filings, 10-Ks, earnings releases) contain a mix of exact alphanumeric identifiers, complex narrative disclosures, and multi-column financial tables.
-
Pure Lexical (BM25) Blind Spot: Matches exact keywords (
H200,ASC 606,Item 1A), but completely misses paraphrased concepts (e.g. “slowing data center capex” vs “moderation in hyperscaler infrastructure spending”). -
Pure Dense (Vector Embedding) Blind Spot: Understands high-level semantics, but suffers from token dilution & number blindness—frequently confusing close product names (e.g., retrieving
H100for anH200query) or blending different fiscal years (FY2023vsFY2024). - Learned Sparse (SPLADE) Trade-Off: Neural term expansion bridges informal vocabulary with GAAP terminology, but can introduce false-positive expansion noise on generic accounting terms.
-
Server-Side Hybrid Fusion (The Solution): Combining sparse and dense vector spaces with Qdrant's Rust-native Reciprocal Rank Fusion (
$RRF = \sum \frac{1}{60 + \text{rank}}$ ) eliminates score incompatibility and lifts Recall@10 from ~81% to 96.00%. - Stage-2 Neural Reranker (The Precision Finisher): Re-scores hybrid candidates to align multi-column table cells, filter quarterly noise, and pinpoint the exact answering sentence at Rank #1 (MRR = 1.0000).
A collage of screenshots from SEC filings highlights the type of data we're working with on this project | Table of Contents, links, separators, embedded iXBRL tags, and dense footnote markers. This kind of data is challenging to break into chunks and retrieve accurately from millions or even billions of embeddings.
- Corpus Scale: 8,609 contextualized chunks (2,542,471 words, ~500 tokens/chunk with 100-token overlap).
- Filings Ingested: 47 SEC EDGAR filings spanning FY2023–FY2025.
- 10 Enterprise Companies:
NVDA(NVIDIA Corporation) — Semiconductors & AI ComputeAAPL(Apple Inc.) — Consumer Electronics & ServicesMSFT(Microsoft Corporation) — Cloud & Enterprise SoftwareGOOGL(Alphabet Inc.) — Search, Cloud & AI InfrastructureAMZN(Amazon.com, Inc.) — E-Commerce & AWS CloudMETA(Meta Platforms, Inc.) — Digital Advertising & AI InfrastructureTSLA(Tesla, Inc.) — Automotive & Energy StorageAMD(Advanced Micro Devices) — x86 CPUs & AI AcceleratorsINTC(Intel Corporation) — Processors & Foundry ServicesTSM(Taiwan Semiconductor Manufacturing Co.) — Pure-Play Semiconductor Foundry (Form 20-F)
- Key Sections Parsed (TOC-Filtered):
- Form 10-K: Item 1 (Business), Item 1A (Risk Factors), Item 1C (Cybersecurity), Item 7 (MD&A), Item 7A (Market Risk), Item 8 (Financial Statements & Notes), Item 9A (Controls).
- Form 10-Q: Part I Item 1 (Financial Statements & Notes), Part I Item 2 (MD&A), Part II Item 1A (Risk Factors).
- Form 20-F: Item 3 (Key Info & Risk Factors), Item 4 (Company Information), Item 5 (Operating Review), Item 18 (Financial Statements).
- Context Header Injection: Every passage is prepended with
[Company (Ticker) | Form Period | Section: ...]before embedding, preserving document provenance across all vector spaces.
You can use the modular components directly in Python applications:
from hybrid_search.chunking.chunker import DocumentChunker
from hybrid_search.models.document import Document, DocumentSection
# Initialize sentence-aware chunker with overlap
chunker = DocumentChunker(chunk_size_tokens=500, chunk_overlap_tokens=100)
doc = Document(
document_id="0001045810_0001045810-24-000029",
company="NVIDIA Corporation",
ticker="NVDA",
cik="0001045810",
accession_number="0001045810-24-000029",
document_type="10-K",
filing_date="2024-02-21",
report_date="2024-01-28",
fiscal_period="FY2024",
source_url="https://www.sec.gov/Archives/edgar/data/1045810/...",
sections=[DocumentSection(section_name="Item 1A. Risk Factors", section_code="ITEM_1A", text="Our GPU supply constraints...")]
)
chunks = chunker.chunk_document(doc)
print(f"Generated {len(chunks)} chunks with context header: {chunks[0].text[:80]}...")from hybrid_search.embeddings.dense import DenseEmbeddingGenerator
from hybrid_search.embeddings.bm25 import BM25EmbeddingGenerator
from hybrid_search.embeddings.sparse import SpladeEmbeddingGenerator
# 100% local CPU ONNX inference via FastEmbed
dense_gen = DenseEmbeddingGenerator(model_name="BAAI/bge-small-en-v1.5")
bm25_gen = BM25EmbeddingGenerator(model_name="Qdrant/bm25")
splade_gen = SpladeEmbeddingGenerator(model_name="prithivida/Splade_PP_en_v1")
query = "NVIDIA H200 supply constraints"
dense_vec = dense_gen.embed_query(query) # List[float] (384 dimensions)
bm25_indices, bm25_values = bm25_gen.embed_query(query) # Lexical term frequencies
splade_indices, splade_values = splade_gen.embed_query(query) # Neural term expansionsfrom hybrid_search.indexing.qdrant_index import QdrantIndexManager
manager = QdrantIndexManager()
# Creates single collection 'financial_docs' with 1 dense and 2 sparse vector spaces + payload schemas
manager.create_collection(recreate=False)
# Multi-threaded streaming batch indexer with real-time ETA
indexed_count = manager.index_chunks(chunks, batch_size=100)
print(f"Indexed {indexed_count} points into Qdrant collection '{manager.collection_name}'.")from hybrid_search.retrieval.engine import SearchEngine
from hybrid_search.models.filter import MetadataFilter
engine = SearchEngine()
# Execute Server-Side SPLADE + Dense Hybrid Search
results = engine.search(
query="Microsoft Azure OpenAI infrastructure capex",
method="sparse_dense",
k=5,
filters=MetadataFilter(tickers=["MSFT"], document_types=["10-K"])
)
for r in results:
print(f"Rank {r.rank} [{r.score:.4f}] - {r.company} ({r.fiscal_period}): {r.section}")
# Execute Stage-2 Neural Rerank + Table Fact Extraction
reranked = engine.search(
query="What was Apple's total Services revenue in fiscal year 2024?",
method="rerank",
k=1
)
print(f"Pinpointed Fact: {reranked[0].exact_answer}").
├── config/
│ ├── companies.yaml # Target company metadata (CIKs, tickers, sectors, forms)
│ └── default.yaml # System configuration (Qdrant, models, chunking, rate limits)
├── data/
│ ├── benchmark_report.md # Complete generated benchmark report with case studies
│ ├── processed/ # Ingested & normalized corpus
│ │ ├── chunks.jsonl # 8,609 contextualized chunks
│ │ ├── documents.jsonl # 47 parsed SEC document models
│ │ └── manifest.json # Corpus metadata manifest
│ └── raw/edgar/ # Local cached SEC HTML/iXBRL filings
├── docs/
│ ├── adr/ # Architecture Decision Records
│ │ ├── 001-qdrant-selection.md # ADR-001: Unified Qdrant multi-vector selection
│ │ ├── 002-qdrant-bm25-architecture.md # ADR-002: Native BM25 sparse vectors
│ │ ├── 003-corpus-strategy.md # ADR-003: Corpus scope & section parsing
│ │ ├── 004-chunking-strategy.md # ADR-004: Sentence-aware contextual chunking
│ │ ├── 005-embedding-models.md # ADR-005: Local CPU ONNX embedding models
│ │ ├── 006-server-side-fusion.md # ADR-006: Server-side RRF fusion strategy
│ │ ├── 007-evaluation-design.md # ADR-007: Evaluation metrics & golden dataset
│ │ └── 008-downstream-reranking-pipeline.md # ADR-008: Stage-2 cross-feature reranking
│ ├── architecture.md # Full system architecture specification
│ └── debugging_and_architecture_guide.md # Diagnostic runbooks and engine internals
├── evaluation/
│ └── golden_dataset.yaml # 25 hand-curated multi-category financial test queries
├── src/hybrid_search/
│ ├── chunking/
│ │ └── chunker.py # Token-aware sliding chunker with metadata header injection
│ ├── embeddings/
│ │ ├── bm25.py # FastEmbed Qdrant/bm25 lexical sparse generator
│ │ ├── dense.py # FastEmbed BAAI/bge-small-en-v1.5 (384d) dense generator
│ │ └── sparse.py # FastEmbed prithivida/Splade_PP_en_v1 sparse generator
│ ├── evaluation/
│ │ ├── benchmark.py # BenchmarkRunner across golden dataset
│ │ ├── latency.py # LatencyTracker (p50, p95, p99 profiling)
│ │ ├── metrics.py # IR Metrics (Recall@K, Precision@K, MRR, NDCG@K, Hit Rate)
│ │ └── report.py # Rich CLI formatting & Markdown report exporter
│ ├── filtering/
│ │ └── metadata_filter.py # Translates MetadataFilter into native Qdrant filter AST
│ ├── indexing/
│ │ └── qdrant_index.py # Qdrant collection manager & multithreaded batch indexer
│ ├── ingestion/
│ │ ├── downloader.py # Token-bucket rate limited (8 req/s) SEC downloader
│ │ ├── edgar_client.py # SEC Submissions API client & URL resolver
│ │ ├── html_parser.py # BeautifulSoup iXBRL parser preserving table rows
│ │ ├── normalizer.py # Financial symbol, number & unicode normalizer
│ │ ├── pipeline.py # End-to-end ingestion pipeline orchestrator
│ │ └── section_extractor.py # Proximity-based TOC-filtering section extractor
│ ├── models/
│ │ ├── document.py # Document, DocumentSection, Chunk Pydantic models
│ │ ├── filter.py # MetadataFilter Pydantic model
│ │ └── search.py # SearchQuery, SearchResult Pydantic models
│ ├── retrieval/
│ │ ├── base.py # BaseRetriever abstract class & result mapping
│ │ ├── bm25_dense_retriever.py # Configuration 3: BM25 + Dense Hybrid (RRF)
│ │ ├── bm25_retriever.py # Configuration 1: Pure BM25 Lexical
│ │ ├── dense_retriever.py # Configuration 2: Pure Dense Semantic
│ │ ├── engine.py # Unified SearchEngine interface
│ │ ├── extractor.py # Multi-column table fact extractor & text highlighter
│ │ ├── reranker.py # Stage-2 Cross-Feature Neural Reranker
│ │ ├── sparse_dense_retriever.py # Configuration 5: SPLADE + Dense Hybrid (RRF)
│ │ └── sparse_retriever.py # Configuration 4: Pure SPLADE Neural Sparse
│ ├── api.py # FastAPI REST API server (/api/status, /api/search, /api/benchmark)
│ ├── cli.py # Rich CLI commands (ingest, index, search, benchmark, info, serve)
│ └── config.py # Pydantic v2 application configuration loaders
├── tests/
│ ├── conftest.py # Pytest fixtures and mock objects
│ ├── e2e/
│ │ └── test_smoke.py # End-to-end pipeline smoke test
│ ├── integration/
│ │ ├── test_embedding_pipeline.py # Embedding generators integration tests
│ │ ├── test_qdrant_index.py # Qdrant index manager tests
│ │ └── test_retrieval_pipeline.py # All 5 retrievers & reranker integration tests
│ └── unit/
│ ├── test_api.py # REST API endpoints unit tests
│ ├── test_chunker.py # Sentence splitting & chunking tests
│ ├── test_config.py # Configuration loading tests
│ ├── test_edgar_client.py # SEC EDGAR client tests
│ ├── test_html_parser.py # iXBRL/HTML table parsing tests
│ ├── test_latency.py # Latency statistics tests
│ ├── test_metadata_filter.py # Qdrant filter translation tests
│ ├── test_metrics.py # IR metrics computation tests
│ ├── test_models.py # Pydantic data models validation tests
│ ├── test_normalizer.py # Text normalization tests
│ └── test_section_extractor.py # Section extraction & TOC filtering tests
├── CONTRIBUTING.md # Open source contribution guidelines
├── docker-compose.yml # Optional Qdrant Docker Compose configuration
├── index.html # Interactive Single-Page Web Dashboard UI
├── pyproject.toml # Package build configuration & dependencies
└── README.md
# Clone repository
git clone https://github.com/satyam671/financial-hybrid-search-qdrant.git
cd financial-hybrid-search-qdrant
# Create virtual environment & activate
python -m venv .venv
.\.venv\Scripts\activate # Windows PowerShell
# source .venv/bin/activate # Linux / macOS
# Install package in editable mode
pip install -e .# Optional: Launch Qdrant server with Web Dashboard at http://localhost:6333/dashboard
docker compose up -dAutomatic Local Fallback: If Docker is not running, the system automatically uses embedded on-disk storage at
./qdrant_data. Zero external infrastructure is required.
Downloads 10-K, 10-Q, and 20-F filings across all 10 companies for FY2023–FY2025 via rate-limited SEC EDGAR API:
python -m hybrid_search.cli ingestExtracts full narrative sections, injects context headers, computes 3 vector spaces (dense, bm25, sparse), and indexes all 8,609 points:
python -m hybrid_search.cli indexLaunch the local interactive web interface and FastAPI backend:
python -m hybrid_search.cli serve --port 8000
Open http://localhost:8000 in your browser to access:
- Side-by-Side Retrieval Comparator: Compare results across all 5 search methods + Stage-2 reranking in real time.
- Interactive Metadata Filters: Filter candidates by ticker (
NVDA,AAPL), filing form (10-K,10-Q), or SEC item. - Pinpointed Table Answer Viewer: Visual direct fact highlighting and multi-column table extraction.
- Live Empirical Benchmark Dashboard: Run and view evaluation metrics directly from the browser.
Run these paired empirical demonstration queries to observe firsthand where each search method succeeds and where single-vector methods fail:
-
✅ Winning Query (Exact Accounting Codification & Technical Policy):
python -m hybrid_search.cli search --query "Accounting Standards Codification ASC 606 revenue recognition" --method bm25 --ticker TSLA -k 3 -v
-
❌ Failing Query (Vocabulary Mismatch / Synonyms):
python -m hybrid_search.cli search --query "What were the primary legal disputes facing automated driving systems?" --method bm25 --ticker TSLA -k 3 -v
-
✅ Winning Query (Broad Thematic & Operational Risk):
python -m hybrid_search.cli search --query "What supply chain bottlenecks could disrupt advanced chip manufacturing?" --method dense --ticker TSM -k 3 -v
-
❌ Failing Query (Exact Product SKU Blindness):
python -m hybrid_search.cli search --query "Find disclosures regarding MI300 accelerator shipments and architecture" --method dense --ticker AMD -k 3 -v
-
✅ Winning Query (Vocabulary Expansion across Architecture):
python -m hybrid_search.cli search --query "How does NVIDIA expand its computing platform through graphics processing units and networking architecture?" --method sparse --ticker NVDA -k 3 -v
-
❌ Failing Query (Temporal Confusion on General Queries):
python -m hybrid_search.cli search --query "What were Apple's primary hardware products and platform services described in fiscal year 2024?" --method sparse --ticker AAPL -k 3 -v
-
✅ Winning Query (Named Regulatory Entity + Conceptual Scrutiny):
python -m hybrid_search.cli search --query "What legal proceedings and antitrust investigations did Meta face from regulators?" --method bm25_dense --ticker META -k 3 -v
-
❌ Failing Query (Capital Allocation Schedules):
python -m hybrid_search.cli search --query "How much did Microsoft spend under the Stock Repurchase Program in fiscal year 2024?" --method bm25_dense --ticker MSFT -k 3 -v
- ✅ Winning Query (High-Recall Candidate Retrieval):
python -m hybrid_search.cli search --query "How did data center GPU demand drive Nvidia's compute and networking revenue in fiscal year 2024?" --method sparse_dense --ticker NVDA -k 3 -v
- ❌ Failing Query (Table Row Extraction Ordering):
python -m hybrid_search.cli search --query "What was Apple's total Services revenue in fiscal year 2024 compared to 2023?" --method sparse_dense --ticker AAPL -k 3 -v
- ✅ Winning Query (Tabular Precision & Year Alignment):
python -m hybrid_search.cli search --query "What was Google Cloud revenue in fiscal year 2023?" --method rerank --ticker GOOGL -k 3 -v
Executes a single query across all retrieval methods to inspect ranking differences in real time:
python -m hybrid_search.cli search --query "What was Google Cloud revenue in fiscal year 2023?" --method all --ticker GOOGL -k 3
Combines vector retrieval with hard metadata payload constraints:
# Filter filings exclusively for Apple (AAPL) Form 10-K
python -m hybrid_search.cli search "share repurchase and dividend authorization" --ticker AAPL --form 10-K -k 5
# Filter filings exclusively for NVIDIA (NVDA) Form 10-Q under Risk Factors
python -m hybrid_search.cli search "export controls and licensing restrictions" --ticker NVDA --form 10-Q --section "Item 1A" -k 5Execute the 25-query golden evaluation benchmark across all 5 retrieval configurations:
python -m hybrid_search.cli benchmark --k 10 --runs 3 --output data/benchmark_report.mdEvaluation conducted against 8,609 SEC financial filing chunks across 25 curated queries in evaluation/golden_dataset.yaml:
| Retrieval Method | Architecture / Vector Space | Recall@10 | Precision@10 | MRR | NDCG@10 | Hit Rate@10 |
|---|---|---|---|---|---|---|
| BM25 (Pure Lexical) |
Qdrant/bm25 (Sparse with IDF) |
0.8133 | 0.2440 | 0.8400 | 0.7208 | 1.0000 |
| Dense (BGE-Small) |
BAAI/bge-small-en-v1.5 (384d Cosine) |
0.8267 | 0.2480 | 0.8667 | 0.7516 | 1.0000 |
| BM25 + Dense | Server-Side RRF ( |
0.9600 | 0.2880 | 0.9600 | 0.8631 | 1.0000 |
| Sparse (SPLADE) |
prithivida/Splade_PP_en_v1 (Sparse) |
0.7733 | 0.2320 | 0.8133 | 0.6974 | 1.0000 |
| Sparse + Dense | Server-Side RRF ( |
0.9600 | 0.2880 | 0.9600 | 0.8624 | 1.0000 |
| Stage-2 Neural Rerank | Cross-Feature Scoring + Table Extractor | 0.9600 | 0.2920 | 1.0000 | 0.9312 | 1.0000 |
| Configuration | p50 Median (ms) | p95 Tail (ms) | Mean (ms) | Speed Rank | Recommended Use Case |
|---|---|---|---|---|---|
| BM25 Only | 12.4 ms | 18.2 ms | 13.1 ms | #1 | Exact SKU/code queries, CIK lookup, statutory audits |
| Dense Only | 15.6 ms | 22.1 ms | 16.4 ms | #2 | Fast UI autocomplete, high-level thematic browsing |
| BM25 + Dense | 25.8 ms | 34.5 ms | 27.2 ms | #3 | General-purpose low-latency hybrid search |
| Sparse Only | 28.4 ms | 38.0 ms | 29.7 ms | #4 | Domain synonym expansion without manual thesaurus |
| Sparse + Dense | 39.2 ms | 51.4 ms | 41.0 ms | #5 | Best single-stage candidate generator (Recall: 0.9600) |
| Stage-2 Rerank | 58.6 ms | 74.2 ms | 61.3 ms | #6 | Production RAG & Financial Fact Extraction (MRR: 1.0000) |
-
Hybrid Retrieval is Non-Negotiable for Financial Recall: Both
BM25 + DenseandSparse + Denselift Recall@10 from ~81% to 96.00%, proving that combining lexical and semantic signals eliminates the single-method blind spots. -
Server-Side RRF Eliminates Fusion Latency: Qdrant's Rust-native server-side RRF executes in
$<1\text{ ms}$ on top of candidate vector searches, completely bypassing the network overhead of multi-database client-side fusion. -
BM25 vs SPLADE Trade-Off on CPU: BM25 is ~2.2x faster than SPLADE on CPU while achieving superior precision on exact alphanumeric codes (
H200,ASC 606). SPLADE excels on plain-English user terminology without maintaining manual synonym dictionaries. - Stage-2 Neural Reranking Delivers Perfect First-Rank Accuracy: Cross-feature scoring resolves multi-year temporal ambiguity and extracts pinpointed table rows at Rank #1 (MRR = 1.0000).
Detailed architectural justifications for every technical decision in this system:
- ADR-001: Selection of Qdrant as Unified Multi-Vector Infrastructure
- ADR-002: Implementation of BM25 as a Qdrant-Native Sparse Vector
- ADR-003: Financial Corpus Scope & Targeted Section Extraction
- ADR-004: Sentence-Aware Deterministic Chunking Architecture
- ADR-005: Local CPU ONNX Embedding Models & Licensing Analysis
- ADR-006: Server-Side Reciprocal Rank Fusion (RRF) Strategy
- ADR-007: Evaluation Methodology, Golden Dataset Design & Metrics
- ADR-008: Downstream Neural Cross-Feature Reranking & Answer Extraction in Production RAG
Run the automated test suite covering unit tests, integration tests, and end-to-end smoke verification:
# Run all unit tests
pytest tests/unit/ -v
# Run integration tests (FastEmbed ONNX & in-memory Qdrant)
pytest tests/integration/ -v
# Run end-to-end pipeline smoke test
pytest tests/e2e/ -v
# Run complete 44-test suite
pytest tests/ -vWe welcome contributions from the open-source and AI engineering community! Whether you want to add new embedding models, test vector quantization benchmarks, expand SEC filing parsers, or improve the evaluation dataset, we would love your help.
- Please read our Contributing Guide (CONTRIBUTING.md) for local setup, development workflows, and coding conventions.
- Feel free to open an Issue to propose improvements or report bugs.
- Pull requests are warmly welcomed!
- Source Code: Apache 2.0 License.
- Financial Filings Data: Public domain corporate disclosures provided by the United States Securities and Exchange Commission (SEC EDGAR).