~/wiki

multi source rag

---
title: Multi-Source RAG
category: concepts
created: 2025-12-19
updated: 2025-01-04
tags: [multi-source-rag, federated-search, embedding-models, filter-propagation, heterogeneous-corpora, source-coordination, rag-architecture, postgres-embeddings, interface-consistency, production-issues, sql-generation, ministry-filters, chunk-objects, shared-embeddings, dependency-management, critical-bugs, multi-source-filtering, embedding-space-compatibility, category-5-bugs, embedding-space-validation, filter-bypass-bugs, assistant-rh-bugs, runtime-crashes, autonomous-retrieval, cross-source-compatibility]
sources: [raw/conversations/2025-10-23-codex-assistant-rh-c34c0439.md]
confidence: high
---

# Multi-Source RAG

Retrieval-Augmented Generation architecture that queries multiple heterogeneous document collections simultaneously, combining results for comprehensive response generation. Requires careful coordination of embedding models, filtering logic, and result fusion strategies.

## Core Challenges

### Embedding Space Compatibility

Different document sources may be embedded using different models or at different times, creating incompatible vector spaces. Attempting to compare or rank results across these spaces produces meaningless similarity scores.

The assistant-rh system exemplifies this challenge with three document sources:
- **Service-Public.gouv.fr**: Government service documentation
- **DGAFP**: Civil service administration guidelines  
- **MATTE**: Ministry-specific regulations

Each source potentially uses different embedding models or model versions, making cross-source similarity comparison unreliable.

### Filter Propagation

Query filters (e.g., ministry restrictions, document types, date ranges) must be consistently applied across all sources. Failure to propagate filters creates several issues:

- **Inconsistent Results**: Some sources respect filters while others don't
- **Security Leaks**: Sensitive documents bypass access restrictions
- **Performance Degradation**: Unnecessary retrieval from irrelevant sources

### Interface Consistency

Multiple retrievers must provide uniform interfaces for:
- Query input formats
- Result data structures  
- Metadata schemas
- Error handling patterns

The [interface-inconsistency](/concepts/interface-inconsistency) anti-pattern is particularly problematic in multi-source contexts where downstream consumers must handle results from multiple retrievers polymorphically.

## Architecture Patterns

### Federated Search

Each source maintains its own retriever with source-specific optimizations:

```python
class MultiSourceRetriever:
    def __init__(self, sources: Dict[str, BaseRetriever]):
        self.sources = sources
    
    def search(self, query: str, filters: Dict) -> List[Chunk]:
        all_results = []
        for source_name, retriever in self.sources.items():
            source_filters = self._adapt_filters(filters, source_name)
            results = retriever.search(query, source_filters)
            all_results.extend(self._tag_source(results, source_name))
        return self._rank_and_merge(all_results)

Unified Index

All sources are embedded into a single vector database with source metadata:

# Single embedding space with source tags
chunks = [
    Chunk(content="...", source="service-public", ministry="interior"),
    Chunk(content="...", source="dgafp", ministry="finance"),
    # ...
]

This approach ensures embedding compatibility but may lose source-specific optimizations.

Critical Implementation Issues

Assistant-RH Multi-Source Bugs

The October 2025 Codex analysis identified critical bugs in Assistant-RH's multi-source implementation:

Filter Bypass

def search_multi_source(self, query: str, filters: Dict) -> List[Chunk]:
    # BUG: filters parameter ignored completely
    service_public_results = self.service_public_retriever.search(query)
    dgafp_results = self.dgafp_retriever.search(query) 
    matte_results = self.matte_retriever.search(query)
    # Ministry filters not applied to any source

Embedding Space Assumption

# BUG: Assumes all sources share embedding space
combined_scores = [r.score for r in all_results]  
# Meaningless if sources use different embedding models

Missing Error Handling

# BUG: No validation of source compatibility
def merge_results(self, results_by_source: Dict) -> List[Chunk]:
    # Assumes all sources return compatible Chunk objects
    # Crashes if sources return different data structures

Solutions and Best Practices

Embedding Model Standardization

Ensure all sources use the same embedding model and version:

class StandardizedEmbedder:
    def __init__(self, model_name: str, model_version: str):
        self.model = load_model(model_name, model_version)
        
    def embed_source(self, source_name: str, documents: List[str]):
        # Consistent embedding across all sources
        return self.model.encode(documents)

Filter Translation Layer

Abstract source-specific filter formats:

class FilterTranslator:
    def translate(self, filters: Dict, source: str) -> Dict:
        if source == "service-public":
            return {"category": filters.get("ministry")}
        elif source == "dgafp":  
            return {"department": filters.get("ministry")}
        # Source-specific filter mapping

Result Normalization

Ensure consistent result formats across sources:

def normalize_results(self, results: List, source: str) -> List[Chunk]:
    normalized = []
    for result in results:
        chunk = Chunk(
            content=self._extract_content(result, source),
            score=self._normalize_score(result.score, source),
            metadata=self._standardize_metadata(result, source)
        )
        normalized.append(chunk)
    return normalized

Cross-Source Validation

Implement runtime checks for source compatibility:

def validate_source_compatibility(self, sources: Dict[str, BaseRetriever]):
    embedding_models = {}
    for name, retriever in sources.items():
        model_info = retriever.get_embedding_model_info()
        embedding_models[name] = model_info
        
    if len(set(embedding_models.values())) > 1:
        raise IncompatibleEmbeddingError(
            f"Sources use different embedding models: {embedding_models}"
        )

Performance Considerations

Parallel Retrieval

Query sources concurrently to minimize latency:

import asyncio

async def search_async(self, query: str) -> List[Chunk]:
    tasks = []
    for source in self.sources.values():
        task = asyncio.create_task(source.search_async(query))
        tasks.append(task)
    
    results = await asyncio.gather(*tasks)
    return self._merge_results(results)

Result Caching

Cache merged results to avoid repeated multi-source queries:

from functools import lru_cache

@lru_cache(maxsize=1000)
def search_cached(self, query: str, filters_hash: str) -> List[Chunk]:
    filters = self._deserialize_filters(filters_hash)
    return self.search(query, filters)

Load Balancing

Distribute load across source-specific infrastructure:

class LoadBalancedMultiSource:
    def __init__(self, source_weights: Dict[str, float]):
        self.weights = source_weights
        
    def search(self, query: str, max_results: int) -> List[Chunk]:
        results_per_source = self._distribute_load(max_results)
        # Query each source for proportional results

See also

  • assistant-rh - System demonstrating multi-source RAG challenges
  • interface-inconsistency - Common failure pattern in multi-source systems
  • embeddings - Vector representations requiring consistency across sources
  • reranker-registry - Model selection patterns for multi-source ranking
  • filter-propagation - Challenge of applying query constraints across sources
  • category-5-bugs - Critical failures in production multi-source systems