Hybrid Retrieval Systems
Confiance : high
hybrid-searchragmeilisearchqdrantlexical-searchvector-searchfault-tolerancerank-fusionreciprocal-rank-fusionconfiguration-managementsystem-reliabilitycollection-naminghard-coded-dependencies
Retrieval architectures combining multiple search modalities (typically lexical and semantic) to improve both precision and recall in document retrieval tasks. Common in production RAG systems where different query types benefit from different search approaches.
Core Architecture
Search Modalities
- Lexical Search: Traditional keyword-based search using BM25 or TF-IDF
- Vector Search: Semantic similarity using embedding models and approximate nearest neighbor
- Rank Fusion: Algorithmic combination of results from multiple modalities
Common Implementations
- meilisearch + qdrant: Popular combination for French legal tech applications
- Elasticsearch + Pinecone: Enterprise-focused hybrid search
- Weaviate hybrid mode: Single system supporting both modalities
Configuration Challenges
Collection Naming Consistency
A common anti-pattern revealed in assistant-rh code review:
# Ingestion phase - configurable
MEILISEARCH_INDEX = os.getenv("MEILISEARCH_INDEX", "legal_docs")
QDRANT_COLLECTION = os.getenv("QDRANT_COLLECTION", "legal_embeddings")
# Search phase - hard-coded (WRONG)
def hybrid_search(query):
lexical_results = meilisearch.search("legi", query) # Hard-coded!
vector_results = qdrant.search("legi", query) # Hard-coded!
return merge_results(lexical_results, vector_results)
This creates configuration drift where ingestion and retrieval target different collections.
Environment Variable Management
Critical to maintain consistency between:
- Development:
.envfile configuration - Documentation:
.env.exampleadvertised variables - Runtime: Actual variables read by application code
Fault Tolerance Patterns
Independent Modality Operation
Systems should gracefully handle partial service availability:
def fault_tolerant_hybrid_search(query, mode="hybrid"):
if mode == "lexical":
return search_meilisearch_only(query)
lexical_results = search_meilisearch(query)
try:
vector_results = search_qdrant(query)
return rank_fusion(lexical_results, vector_results)
except VectorStoreException:
log.warning("Vector search unavailable, using lexical only")
return lexical_results
Payload Hydration Strategies
- Eager Hydration: Store full documents in vector database (expensive)
- Lazy Hydration: Store only IDs, fetch documents from primary source
- Hybrid Hydration: Cache frequently accessed documents, lazy-load others
Production Considerations
Monitoring
- Latency Tracking: Monitor each modality separately
- Availability Metrics: Track uptime of vector and lexical services
- Quality Metrics: Compare hybrid vs single-modality result quality
Scaling
- Independent Scaling: Vector and lexical services may have different resource needs
- Cache Warming: Pre-populate frequently accessed results
- Geographic Distribution: Consider data locality for large document corpora
See also
- rag
- meilisearch
- qdrant
- fault-tolerant-ai-systems
- rank-fusion
- assistant-rh