~/wiki

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: .env file configuration
  • Documentation: .env.example advertised 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