Aller au contenu principal

Pipeline RAG multi-recherche

Mis à jour le 30 juillet 2026

Nous avons deux moteurs qui fonctionnent indépendamment — la recherche sémantique (embeddings vectoriels) et la recherche lexicale (BM25) — et une promesse à tenir : les combiner dans un pipeline de recherche unifié. Deux problèmes à résoudre pour y arriver : une architecture qui coordonne les index, et un algorithme qui fusionne leurs classements.

Interface unifiée

L’architecture d’abord. Les deux implémentations partagent des API quasi identiques — add_document() et search() — et ce n’était pas un hasard : cette interface commune permet de les encapsuler dans une seule classe Retriever qui coordonne les deux approches :

  1. Recevoir la question de l’utilisateur
  2. La transmettre au VectorIndex et au BM25Index
  3. Collecter les résultats des deux systèmes
  4. Fusionner les résultats avec un algorithme de classement

Vu de l’application, le Retriever est un moteur de recherche unique ; le fait qu’il interroge deux index en coulisses est un détail d’implémentation invisible.

Reciprocal Rank Fusion (RRF)

Reste le défi central : fusionner des résultats de méthodes de recherche différentes. Chaque système renvoie ses résultats avec son propre mécanisme de notation — similarité cosinus d’un côté, scores BM25 de l’autre — donc les scores ne sont pas combinables directement : additionner des grandeurs sans échelle commune ne veut rien dire. La technique qui contourne l’obstacle : Reciprocal Rank Fusion (RRF), qui ignore les scores bruts et ne raisonne que sur les positions dans chaque classement.

Fonctionnement du RRF

Supposons que le VectorIndex renvoie : Section 2, Section 7, Section 6 — et le BM25Index : Section 6, Section 2, Section 7.

La formule RRF calcule un score pour chaque document :

RRF_score(d) = sum(1 / (k + rank_i(d)))

Où k est une constante (typiquement 60) et rank_i(d) est le rang du document dans le i-ème système. Exemple avec k=1 :

  • Section 2 : 1/(1+1) + 1/(1+2) = 0.833
  • Section 6 : 1/(1+3) + 1/(1+1) = 0.750
  • Section 7 : 1/(1+2) + 1/(1+3) = 0.583

Classement final : Section 2 (1er), Section 6 (2e), Section 7 (3e). Lisez ce que la formule récompense : la section 2 l’emporte parce qu’elle est bien classée des deux côtés — le consensus entre moteurs pèse plus qu’une première place isolée. C’est exactement l’intuition qu’on attend d’une fusion : un document que les deux approches jugent pertinent mérite la tête. La constante k amortit l’écart entre les premiers rangs — avec la valeur usuelle de 60, être 1er ou 3e diffère peu, ce qui évite qu’un seul moteur n’écrase l’autre.

Implémentation

class Retriever:
    def __init__(self, *indexes):
        self._indexes = list(indexes)

    def add_document(self, document):
        for index in self._indexes:
            index.add_document(document)

    def search(self, query_text, k=1, k_rrf=60):
        # Obtenir les résultats de tous les index
        # Calculer les scores RRF
        # Renvoyer les résultats fusionnés et triés

Le constructeur accepte un nombre variable d’index (*indexes) — pas spécifiquement deux. add_document propage chaque document vers tous les index, qui l’indexent chacun à leur façon ; search interroge tout le monde puis applique le RRF. Rien dans cette classe ne connaît les détails d’un index particulier : elle ne parle qu’à l’interface commune.

Extensibilité

C’est ce qui rend le design durable : la modularité permet d’ajouter facilement de nouvelles méthodes de recherche. Tant qu’un index implémente add_document() et search(), il s’intègre naturellement dans le pipeline hybride — un index par mots-clés métier, un index de titres, ce que votre application exigera — et le RRF fusionne trois classements aussi bien que deux, sans changement.

L’approche hybride combine ainsi la compréhension sémantique de la recherche vectorielle avec la précision de la correspondance de mots-clés, le tout unifié par un algorithme de fusion mathématiquement solide. Notre pipeline répond désormais correctement aux deux familles de questions — celles qui reformulent, et celles qui citent exactement.

Points clés à retenir

  • L’interface commune (add_document/search) permet la façade Retriever : un moteur unique vu de l’application
  • Les scores cosinus et BM25 sont incomparables — le RRF fusionne sur les rangs, pas sur les scores
  • RRF récompense le consensus : bien classé partout bat premier quelque part
  • k (≈60) amortit les écarts de tête et équilibre l’influence des moteurs
  • Extensible par construction : tout index respectant l’interface rejoint le pipeline sans modification