TL;DR
-
Hybrid search combines keyword matching (BM25) with semantic vector search to get both exact-term precision and meaning-based recall in a single retrieval step.
-
Pure vector search misses exact matches — product SKUs, error codes, CVE IDs, and person names often fail with embeddings alone because dense vectors compress away token-level information.
-
Pure keyword search misses paraphrases — "rate limit exceeded" won't match a document saying "too many requests per minute."
-
Score fusion merges ranked lists from both retrievers using reciprocal rank fusion (RRF), weighted averaging, or learned models. RRF is the safest default.
-
Default to hybrid for production RAG — the storage and complexity cost is modest, and recall improvements are common when queries mix natural language with exact identifiers.
Why This Matters
Retrieval quality is the ceiling for RAG answer quality. If the right document isn't in the top-k results, no LLM can produce a correct answer — no prompt engineering compensates for missing context.
Teams that deploy vector-only search consistently hit a wall: users search for exact identifiers (ERR_CONNECTION_REFUSED, SKU-8842-X, CVE-2024-1234) and get semantically related but wrong results. Users paraphrase ("how to fix connection issues") and keyword-only search returns irrelevant exact matches on common terms.
Hybrid search solves both problems by running both retrieval methods in parallel and merging results. It is one of the highest-impact retrieval improvements after basic vector search, and most dedicated vector databases support it natively — Weaviate, Qdrant, Pinecone, Milvus, and Elasticsearch 8.x all ship hybrid query APIs.
If you're building a deployed RAG pipeline and haven't benchmarked hybrid against dense-only, you're likely leaving recall on the table.
The Problem Hybrid Search Solves
Vector search weakness: exact matching. Embeddings compress meaning into dense vectors, losing exact token information. Searching for GPT-4o may return documents about GPT-4 or "language models" instead of the specific model page. Error codes, UUIDs, version numbers, acronyms, and legal citations are systematically underserved by semantic search alone.
Keyword search weakness: vocabulary mismatch. BM25 scores documents by term frequency but has no concept of meaning. A search for "automobile repair" won't find "car maintenance guide." Users naturally paraphrase, and keyword search punishes them for using different words than the corpus author chose.
The recall-precision gap. Deployed RAG systems need high recall (the right document appears somewhere in candidates) and high precision (top results are actually relevant). Neither method alone achieves both across diverse query types. A support bot receives "429 on batch endpoint" and "why am I being throttled" in the same session — one query is lexical, one is semantic.
Hybrid search runs both retrievers in parallel and fuses their ranked lists, so a document that scores well on either dimension rises to the top. When user queries are vague or use different terminology than the corpus, query transformation before retrieval can improve what each retriever receives. Downstream re-ranking then fixes ordering within the fused candidate pool.
How We Got Here
Information retrieval did not jump from keywords to vectors overnight. Hybrid search is the pragmatic synthesis of two mature research lines:
Diagram: Evolution of hybrid retrieval
flowchart LR
A[BM25 / TF-IDF] --> B[Learning to Rank]
B --> C[Dense retrieval / DPR]
C --> D[Single-index hybrid]
D --> E[RRF at scale]
E --> F[RAG + rerank stacks]
Many production deployments converged on parallel sparse + dense retrieval with rank fusion, then reranking—rather than forcing one index to do everything.
| Era | What shipped | Limitation |
|---|---|---|
| Keyword IR (1990s–2010s) | BM25, inverted indexes, Elasticsearch | No paraphrase; vocabulary mismatch |
| Neural IR (2017–2019) | Bi-encoders, DPR | Strong semantics; weak on rare tokens |
| Hybrid prototypes (2020–2022) | Parallel BM25 + dense, manual score blend | Score normalization pain; fragile tuning |
| Native hybrid DBs (2022–2024) | Weaviate, Pinecone sparse-dense, Qdrant | Ops maturity; filter + hybrid in one API |
| Production RAG (2024+) | Hybrid → metadata filter → rerank | Fusion weights still domain-specific |
The 2021 BEIR benchmark showed dense retrieval winning on semantic tasks but losing on keyword-heavy corpora — hybrid consistently topped either method alone. Frameworks like LangChain hybrid retrievers and LlamaIndex BM25 + vector codified the pattern. The engineering question shifted from whether to hybrid to how to fuse and tune.
What Is Hybrid Search?
Hybrid search executes two retrieval strategies against the same corpus and combines their results:
-
Sparse retrieval (BM25) — Scores documents by term frequency and inverse document frequency. Fast, exact, no GPU required.
-
Dense retrieval (vector) — Scores documents by embedding cosine similarity or dot product. Captures semantic meaning and paraphrases.
The merged result set feeds into downstream stages — metadata filtering, reranking, generation — with better recall than either method alone.
def hybrid_search(query: str, alpha: float = 0.7, top_k: int = 20):
sparse_results = bm25_search(query, top_k=top_k)
dense_results = vector_search(embed(query), top_k=top_k)
return fuse_scores(sparse_results, dense_results, alpha=alpha)
The alpha parameter controls weighted fusion: 0.0 = pure keyword, 1.0 = pure vector, 0.7 = 70% vector weight. Most deployed systems use 0.6–0.75 favoring vector, adjusting based on retrieval evaluation on real queries.
Hybrid search is not a replacement for ANN indexes or reranking — it sits between embedding and precision layers in the retrieval stack.
How Hybrid Search Works
BM25 (Sparse Retrieval)
BM25 (Best Matching 25) is a probabilistic ranking function over an inverted index:
score(D, Q) = Σ IDF(qi) · (f(qi, D) · (k1 + 1)) / (f(qi, D) + k1 · (1 - b + b · |D|/avgdl))
Where f(qi, D) is term frequency of query term qi in document D, IDF is inverse document frequency, and k1, b are tuning parameters (typically k1=1.2, b=0.75). BM25 is fast, interpretable, and requires no GPU. It excels when queries contain rare, discriminative tokens.
Dense Retrieval
The query is embedded with the same model used at indexing time. Approximate nearest neighbor search finds the closest document vectors by cosine similarity or dot product. This captures semantic relationships BM25 cannot — synonyms, paraphrases, and conceptual similarity.
Warning
Hybrid search assumes both indexes cover the same chunk set with aligned IDs. Re-indexing one path without the other breaks fusion.
Score Fusion Methods
| Method | How It Works | Pros | Cons |
|---|---|---|---|
| Weighted sum | score = α · dense_norm + (1-α) · sparse_norm |
Tunable; one parameter | Requires score normalization |
| Reciprocal Rank Fusion (RRF) | score = Σ 1/(k + rank_i) per list |
No normalization; robust default | Less fine-grained weight control |
| Cross-encoder rerank | Separate model scores query-doc pairs | Highest precision | Adds latency (often ~100–300ms in representative deployments; varies by model and candidate count) |
| Learned fusion | ML model combines features | Optimal for large eval sets | Needs training data and retraining |
Tip
Start with RRF (
k=60) — it's parameter-free and works well across domains. Switch to weighted fusion when you have 100+ labeled queries to tune alpha.
Diagram: Hybrid query sequence
sequenceDiagram
participant U as User
participant API as Retriever
participant E as Embedder
participant V as Vector index
participant B as BM25 index
participant F as Fusion
U->>API: query + filters
par Parallel retrieval
API->>E: embed query
E-->>API: query vector
API->>V: ANN top-k
V-->>API: dense ranks
API->>B: tokenize query
B-->>API: sparse ranks
end
API->>F: RRF or weighted merge
F-->>API: fused top-k
API-->>U: chunk IDs + scores
Both indexes should receive identical metadata filters before fusion— asymmetric filtering leaks documents through one path.
Architecture
A hybrid search system maintains dual indexes over the same document chunks:
Diagram: Hybrid search architecture
flowchart TB
Ingest[Ingestion] --> Chunk[Chunker]
Chunk --> Embed[Embedder]
Chunk --> Token[Tokenizer]
Embed --> VIdx[Vector index / HNSW]
Token --> BIdx[Inverted index / BM25]
Query[Query] --> QEmbed[Embed query]
Query --> QToken[Tokenize query]
QEmbed --> VIdx
QToken --> BIdx
VIdx --> Fuse[Score fusion]
BIdx --> Fuse
Fuse --> Filter[Metadata filter]
Filter --> Rerank[Reranker]
Rerank --> LLM[Generator]
Most vector databases co-locate both indexes; you query one API rather than orchestrating two systems.
| Component | Stores | Index Type |
|---|---|---|
| Vector index | Embedding vectors + chunk IDs | HNSW, IVF, or disk-based ANN |
| Inverted index | Term → document mappings | BM25 inverted index |
| Document store | Full chunk text + metadata | Key-value or column store |
Most modern vector databases (Weaviate, Pinecone, Qdrant, Elasticsearch with dense vectors) maintain both indexes internally. You don't need separate Elasticsearch + vector DB unless you already operate both at scale.

Source: Meta AI — RAG paper
Index construction and query execution are usually treated as separate workflows: offline indexing (load, chunk, embed, tokenize, store) and online querying (embed query, parallel search, fuse, filter, rerank, generate).
Step-by-Step Flow
Step 1: Index documents with both representations. During ingestion, embed each chunk (dense) and tokenize for the inverted index (sparse). Store aligned chunk IDs and metadata on both paths. Most vector DBs handle this in a single upsert.
Step 2: At query time, run both searches in parallel. Embed the query for vector search. Tokenize the query for BM25. Request top-k from each (typically k=20–50 per retriever, not k=5).
Step 3: Apply metadata filters to both indexes. Tenant ID, doc type, and date filters must be identical on vector and BM25 paths. Asymmetric filtering is a common data-leak vector.
Step 4: Fuse scores. Apply RRF or weighted fusion to produce a unified ranking. Log which retriever contributed each document for debugging.
Step 5: Rerank (recommended). Pass fused top-20 to a cross-encoder reranker. Return top-5 to the LLM. Hybrid improves recall; reranking improves precision.
Step 6: Measure. Track recall@k on your golden set with vector-only, keyword-only, and hybrid configurations. Document your fusion method and alpha in the runbook.
Real Production Example
An e-commerce support system indexes product docs, FAQs, and error logs. Users search for both semantic questions and exact product codes.
from dataclasses import dataclass
from typing import Optional
@dataclass
class SearchResult:
doc_id: str
text: str
score: float
source: str # "vector", "bm25", or "hybrid"
def reciprocal_rank_fusion(
result_lists: list[list[SearchResult]],
k: int = 60,
) -> list[SearchResult]:
"""RRF: score = sum(1 / (k + rank)) across all lists."""
scores: dict[str, float] = {}
docs: dict[str, SearchResult] = {}
for results in result_lists:
for rank, result in enumerate(results):
doc_id = result.doc_id
scores[doc_id] = scores.get(doc_id, 0) + 1.0 / (k + rank + 1)
docs[doc_id] = result
ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)
return [
SearchResult(doc_id=doc_id, text=docs[doc_id].text, score=score, source="hybrid")
for doc_id, score in ranked
]
class HybridRetriever:
def __init__(self, vector_store, alpha: float = 0.7):
self.store = vector_store
self.alpha = alpha
def search(
self,
query: str,
top_k: int = 20,
filters: Optional[dict] = None,
) -> list[SearchResult]:
vector_results = self.store.vector_search(
query_vector=self.store.embed(query),
top_k=top_k,
filters=filters,
)
bm25_results = self.store.bm25_search(
query_text=query,
top_k=top_k,
filters=filters,
)
fused = reciprocal_rank_fusion([vector_results, bm25_results])
return fused[:top_k]
def search_weighted(self, query: str, top_k: int = 20) -> list[SearchResult]:
vector_results = self.store.vector_search(
query_vector=self.store.embed(query), top_k=top_k * 2,
)
bm25_results = self.store.bm25_search(query_text=query, top_k=top_k * 2)
v_max = max((r.score for r in vector_results), default=1) or 1
b_max = max((r.score for r in bm25_results), default=1) or 1
combined: dict[str, float] = {}
docs: dict[str, SearchResult] = {}
for r in vector_results:
combined[r.doc_id] = self.alpha * (r.score / v_max)
docs[r.doc_id] = r
for r in bm25_results:
combined[r.doc_id] = combined.get(r.doc_id, 0) + (1 - self.alpha) * (r.score / b_max)
docs[r.doc_id] = r
ranked = sorted(combined.items(), key=lambda x: x[1], reverse=True)
return [
SearchResult(doc_id=d, text=docs[d].text, score=s, source="hybrid")
for d, s in ranked[:top_k]
]
# Usage — Weaviate, Qdrant, or Pinecone client as vector_store
retriever = HybridRetriever(vector_store=qdrant_client)
results = retriever.search(
"SKU-8842-X return policy",
top_k=10,
filters={"tenant_id": "acme-corp", "status": "published"},
)
# BM25 catches "SKU-8842-X"; vector catches "return policy" semantics
The tenant_id filter runs on both indexes before fusion. Logging source per result lets you diagnose whether vector or BM25 contributed each hit — essential when tuning alpha.
Design Decisions
| Decision | Option A | Option B | When to choose |
|---|---|---|---|
| Fusion method | RRF | Weighted sum | RRF to start; weighted when you have eval data to tune alpha |
| Alpha (vector weight) | 0.5 (balanced) | 0.7–0.75 (vector-heavy) | Higher alpha for natural language; lower for ID/code-heavy corpora |
| Pre-fusion k | 20 per retriever | 50 per retriever | Higher k when recall is the bottleneck; reranker cuts to top-5 |
| Filter timing | Pre-filter both indexes | Post-filter fused results | Pre-filter when metadata is selective; never asymmetric |
| Single vs dual system | One DB (Weaviate, Qdrant) | Elasticsearch + vector DB | Single DB for new projects; dual when Elasticsearch is already canonical |
| Sparse representation | Native BM25 index | Sparse vectors (SPLADE) | BM25 default; learned sparse when you have budget for another model |
Comparisons
Hybrid vs dense-only vector search
| Dimension | Dense only | Hybrid |
|---|---|---|
| Paraphrase recall | Strong | Strong (same dense path) |
| Exact ID / SKU / CVE | Weak | Strong (BM25 path) |
| Index storage | Lower | Often materially higher (inverted index overhead) |
| Query latency | Baseline | ~Same (parallel execution) |
| When to choose | Pure narrative corpora, no IDs | Default for production RAG |
Hybrid vs keyword-only (BM25)
| Dimension | BM25 only | Hybrid |
|---|---|---|
| Semantic match | None | Dense path handles paraphrases |
| Infrastructure | Elasticsearch, OpenSearch | Vector DB with BM25 or dual stack |
| When to choose | Legacy search with no embeddings budget | Any RAG pipeline with embedding model |
Hybrid vs learned sparse (SPLADE)
| Dimension | BM25 + dense hybrid | SPLADE + dense |
|---|---|---|
| Setup | Built into most vector DBs | Requires sparse embedding model |
| Exact match | BM25 excels | Learned sparse; domain-dependent |
| Ops complexity | Low | Medium — another model to version |
| When to choose | Default | Research corpora where BM25 underperforms |
Hybrid vs late interaction retrieval
Late interaction (ColBERT-style) encodes token-level interactions at query time — higher quality than bi-encoder dense search, higher cost than hybrid fusion. Use hybrid + rerank first; escalate to late interaction when cross-encoder reranking still misses recall targets.
Decision tree: do you need hybrid search?
Decision tree: When to use hybrid search
flowchart TD
A[Building RAG retrieval?] -->|No| B[Keyword or dense alone may suffice]
A -->|Yes| C[Queries contain IDs, SKUs, codes?]
C -->|Yes| D[Enable hybrid + filters]
C -->|No| E[Measure dense-only recall@k]
E -->|Below target| D
E -->|Meets target| F[Dense + rerank baseline]
D --> G[Fuse with RRF]
G --> H[Rerank top-20 to top-5]
F --> H
H --> I[Evaluate weekly on golden set]
If more than ~10% of live queries contain exact tokens, hybrid often wins — measure before skipping it.
Head-to-head vector database tooling
Compare hybrid capabilities in Best Vector Databases: Qdrant vs Pinecone · Qdrant vs Weaviate · Milvus vs Qdrant · pgvector vs Pinecone.
Common Mistakes
-
Not normalizing scores in weighted fusion. Vector scores (0–1 cosine) and BM25 scores (unbounded) aren't comparable without min-max or z-score normalization.
-
Setting alpha without evaluation. Default
alpha=0.5is rarely optimal. Measure recall@k across alpha values on your golden query set. -
Skipping hybrid because "vector is enough." Test it. Most real-world corpora have enough exact-match queries to justify dual indexes.
-
Retrieving too few from each index. If each retriever returns k=5, fusion has little to work with. Retrieve k=20–50 from each, fuse, then cut or rerank.
-
Ignoring BM25 tokenization. Stemming, stop words, and language-specific analyzers affect keyword matching. Match tokenization between indexing and querying.
-
Asymmetric metadata filtering. Filtering vector search by
tenant_idbut not BM25 leaks documents across tenants. Apply identical filters to both paths. -
Not combining with reranking. Hybrid improves recall; re-ranking improves precision. Use both in deployed retrieval pipelines.
Where It Breaks Down
Hybrid search adds index storage and query complexity — you maintain two indexes instead of one. For very small corpora (under 1,000 documents), brute-force search makes the distinction irrelevant.
Multilingual corpora require language-aware BM25 analyzers and multilingual embedding models. Mixing English BM25 with a multilingual embedder creates inconsistent fusion across languages.
Highly semantic corpora (research papers, narrative content with few exact terms) may see minimal hybrid benefit over dense + rerank. Measure before assuming hybrid helps — don't pay storage cost without recall gain.
Score fusion is not magic. If both retrievers miss the relevant document, fusion cannot recover it. Hybrid improves recall at the margins — it doesn't fix bad chunking or wrong embedding models.
Latency-sensitive paths with sub-50ms budgets may skip BM25 on cache hits, falling back to hybrid only on cache miss — but this adds routing complexity.
When NOT to Use Hybrid Search
Skip hybrid search when:
-
Corpus is tiny and static — under ~500 chunks, brute-force dense search with reranking may match hybrid recall without dual indexes.
-
Queries never contain exact tokens — pure narrative Q&A over homogeneous prose (internal essays, literary analysis) may not benefit; measure dense-only first.
-
You cannot maintain index parity — if sparse and dense indexes drift (different chunk sets, stale BM25 after re-embed), fusion returns garbage. Fix ops before adding hybrid.
-
Storage budget is zero — dual indexes add storage overhead beyond vector-only. Vector quantization may be higher priority at billion-vector scale.
-
Single-language keyword search already meets recall@k — don't add complexity without eval evidence.
-
You're replacing reranking with fusion — fusion merges recall lists; cross-encoders fix ordering. Removing reranking because you added hybrid usually drops precision.
Prefer dense-only when eval proves it. Prefer late interaction when hybrid + rerank still misses fine-grained matches.
Running in Production
Best Practice
✅ Best Practices — Log which retriever contributed each result, apply identical metadata filters on both paths, tune fusion on a fixed golden set, and rerank after fusion.
| Dimension | Consideration |
|---|---|
| Scaling | Dual indexes typically increase storage over vector-only (exact overhead depends on sparse index size). Both indexes scale horizontally in Qdrant, Weaviate, and Milvus. |
| Latency | Parallel search adds minimal latency — total retrieval is dominated by the slower path, not their sum. Illustrative combined retrieval often ~30–150ms; actual latency depends on hardware, corpus size, embedding model, and vector database. |
| Cost | No additional API costs for BM25 — compute-only. Embedding costs unchanged. Slightly higher storage. |
| Monitoring | Log per-retriever contribution (vector-only, bm25-only, both). Track alpha sensitivity and fusion method in config. Alert if one retriever dominates unexpectedly. |
| Evaluation | A/B test vector-only vs hybrid on golden set weekly. Benchmark on your own corpus and query distribution — BEIR and other published hybrid results are useful for comparison but should not replace evaluation on production-like workloads. |
| Security | Apply metadata filters to both indexes consistently. Audit filter construction in CI with cross-tenant query tests. |
Important
Always evaluate hybrid search on your actual query distribution. Don't assume it helps — measure recall@k with and without it on 50+ real queries.
Related Guides
-
Foundations: Embeddings · Embedding Models · Chunking Strategies · RAG
-
Retrieval stack: Vector Search · Metadata Filtering · ANN Indexes · Re-ranking · Late Interaction Retrieval
-
Quality & ops: Retrieval Evaluation · Vector Databases · Vector Quantization
-
Vector stores: Qdrant · Weaviate · Pinecone · Milvus · pgvector — compare in Best Vector Databases.
-
Head-to-heads: Qdrant vs Pinecone · Qdrant vs Weaviate · Milvus vs Qdrant · pgvector vs Pinecone
If you understood this topic, read next:
Diagram: Recommended learning path
flowchart LR
A[Embeddings] --> B[Vector Search]
B --> C[Hybrid]
C --> D[Filters]
D --> E[Rerank]
E --> F[Eval]
Prerequisites: Embeddings · Vector Search
Next topics: Re-ranking · Metadata Filtering · Retrieval Evaluation
Estimated time: 45 min · Difficulty: Intermediate
Interview Questions
-
Why does pure vector search fail on error codes and SKUs?
- Expected: embeddings compress token-level information; dense vectors optimize for semantic similarity, not exact match (Vector Search).
-
What is RRF and why use it over weighted fusion?
- Expected: reciprocal rank fusion
Σ 1/(k+rank); no score normalization needed, robust default before you have eval data to tune alpha.
- Expected: reciprocal rank fusion
-
How should metadata filters interact with hybrid search?
- Expected: identical filters on both vector and BM25 paths before fusion; asymmetric filtering leaks documents (Metadata Filtering).
-
What k should each retriever return before fusion?
- Expected: 20–50 per retriever, fuse, then rerank to top-5; k=5 per path gives fusion too little to work with.
-
Hybrid vs reranking — which fixes recall vs precision?
- Expected: hybrid improves recall (right doc in candidate pool); cross-encoder reranking improves precision (ordering) (Re-ranking).
-
When would you lower alpha toward BM25?
- Expected: corpora with many IDs, legal citations, product codes, CVE queries; tune on golden set.
-
How do you debug a hybrid query that returns wrong results?
- Expected: log per-retriever hits; check filter parity, tokenization, chunk alignment, embedding model version.
-
Name two vector databases with native hybrid support.
- Expected: Weaviate, Qdrant, Pinecone, Milvus, Elasticsearch 8.x — compare in Best Vector Databases.
Key Takeaways
- Hybrid search combines BM25 and vector retrieval for recall neither achieves alone.
- Use RRF fusion to start — parameter-free and robust across domains.
- Retrieve k=20–50 from each index, fuse, then rerank to top-5 for generation.
- Apply identical metadata filters on both sparse and dense paths.
- Measure recall@k on real queries before and after enabling hybrid.
- Compare stacks in Best Vector Databases and head-to-head comparison pages before committing.
FAQs
When should I use hybrid search?
Default to hybrid for production RAG. The exception is corpora with purely narrative content and no exact-match queries — but even then, measure dense-only recall@k first.
What alpha value should I use?
Start with RRF (no alpha needed). If using weighted fusion, start at 0.7 (vector-heavy) and tune on your eval set. Lower alpha (0.4–0.5) for corpora with many IDs, codes, and names.
Is RRF better than weighted fusion?
RRF is more robust — no score normalization, no alpha tuning — and works well as a default. Weighted fusion can outperform RRF when you have enough eval data to tune alpha precisely.
Does hybrid search work with all vector databases?
Most modern vector DBs support it natively: Weaviate, Pinecone, Qdrant, Elasticsearch 8.x, Milvus 2.x+. pgvector requires a separate BM25 extension (ParadeDB, pg_search).
How much does hybrid improve recall?
Typically modest recall@k improvement over vector-only, depending on query distribution. Biggest gains often appear when users search for exact terms, error codes, or product identifiers.
Can I use hybrid search without a reranker?
Yes, but reranking after hybrid fusion is a common and effective approach. Hybrid improves recall; reranking improves precision.
Does hybrid search increase latency?
Minimal — both searches run in parallel. Total retrieval latency is dominated by the slower path, not their sum. Expect 30–150ms for both combined.
How do I handle multilingual hybrid search?
Use language-specific BM25 analyzers and a multilingual embedding model (e.g., multilingual-e5-large). Apply language detection at query time to select the right analyzer.
Should I pre-filter or post-filter in hybrid search?
Pre-filter when metadata filters are highly selective (tenant ID eliminates 99% of docs). Apply the same pre-filter to both indexes. Post-filter risks empty or incomplete result sets.
What is sparse-dense vector representation?
Some systems (Pinecone, Elasticsearch) store BM25 term weights as sparse vectors alongside dense embeddings, enabling single-index hybrid search without maintaining separate inverted indexes.
Does Elasticsearch replace a vector database for hybrid search?
Elasticsearch 8.x with dense vectors and BM25 in one index is viable, especially if you already run Elasticsearch. Dedicated vector DBs often provide simpler APIs and better ANN tuning for RAG workloads — see Qdrant vs Weaviate.
References
- BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation (Thakur et al., 2021)
- Dense Passage Retrieval (Karpukhin et al., 2020)
- Weaviate Hybrid Search Documentation
- Qdrant Hybrid Queries Documentation
- Pinecone Hybrid Search Guide