Aller au contenu principal
Leçon 52 sur 95 · Construire avec l'API Claude

Un pipeline RAG multi-index

Leçon 52 sur 95 Mis à jour le 5 novembre 2026 Développeur
Sur cette page
  1. Une interface commune
  2. Fusionner des rangs, pas des scores
  3. Le Retriever
  4. Le résultat
  5. Un pipeline extensible
  6. À vous de jouer
  7. Points clés à retenir

On dispose de deux index, l’un sémantique, l’autre lexical. Cette leçon les réunit dans un même objet, le Retriever, qui interroge les deux et fusionne leurs classements par la fusion des rangs réciproques (Reciprocal Rank Fusion, ou RRF).

Une interface commune

VectorIndex (leçon 50) et BM25Index (leçon 51) offrent les mêmes méthodes : add_document(document) pour indexer, et search(query, k), qui renvoie des paires (document, valeur) de la meilleure à la moins bonne. Cette interface commune permet de les envelopper dans une classe qui ignore tout de leur fonctionnement interne.

Fusionner des rangs, pas des scores

On ne peut pas additionner une distance cosinus et un score BM25 : ils n’ont ni la même échelle ni le même sens de lecture. La fusion des rangs réciproques ne regarde donc que la place de chaque document dans chaque classement :

score_RRF(d) = Σ 1 / (k + rang_i(d))

rang_i(d) est la place du document d dans le classement de l’index i, 1 pour le premier, et k une constante qui atténue l’écart entre les premières places. La valeur 60 est la plus répandue ; le cours officiel prend 1 dans son exemple, pour rendre les écarts visibles.

Fusionner deux classements Trois colonnes : le classement de l'index vectoriel (sections 2, 7, 6), celui de BM25 (sections 6, 2, 7), et la fusion des rangs réciproques avec k = 1 (section 2 : 0,833 ; section 6 : 0,75 ; section 7 : 0,583). MULTI-INDEX Fusionner deux classements Index vectoriel Rang 1 Section 2 Rang 2 Section 7 Rang 3 Section 6 Index BM25 Rang 1 Section 6 Rang 2 Section 2 Rang 3 Section 7 Fusion, k = 1 0,833 Section 2 0,75 Section 6 0,583 Section 7 Score RRF : somme de 1 / (k + rang). La section 2, bien classée des deux côtés, passe en tête.
D'après la leçon « A multi-index RAG pipeline » (Claude Academy, Building with the Claude API).
SectionRang vectorielRang BM25Score RRF, k = 1
Section 2121/2 + 1/3 = 0,833
Section 6311/4 + 1/2 = 0,75
Section 7231/3 + 1/4 = 0,583

La section 2, bien classée par les deux index, passe en tête. Avec k = 60, l’ordre reste le même, mais les écarts se resserrent : 0,0325, 0,0323 et 0,0320.

Le Retriever

Le code du cours officiel laisse la fusion en commentaires. En voici une version complète :

class Retriever:
    def __init__(self, *indexes):
        if not indexes:
            raise ValueError("il faut au moins un index")
        self.indexes = list(indexes)

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

    def search(self, query, k=1, k_rrf=60, candidates=20):
        scores, documents = {}, {}
        for index in self.indexes:
            for rank, (doc, _) in enumerate(index.search(query, k=candidates), start=1):
                key = id(doc)
                documents[key] = doc
                scores[key] = scores.get(key, 0.0) + 1 / (k_rrf + rank)
        best = sorted(scores, key=scores.get, reverse=True)[:k]
        return [(documents[key], scores[key]) for key in best]
  • add_document ajoute le même objet document à tous les index. C’est ce qui permet de le reconnaître d’un classement à l’autre, avec id(doc).
  • Chaque index renvoie ses candidates meilleurs résultats. Un document absent d’une liste ne reçoit rien de cet index : BM25 ne renvoie que les documents qui contiennent au moins un terme de la question.
  • Les valeurs propres à chaque index, distance ou score, sont ignorées : seul le rang compte.
retriever = Retriever(VectorIndex(generate_embedding), BM25Index())
for chunk in chunk_by_section(text):
    retriever.add_document({"content": chunk})

for doc, score in retriever.search("Que s'est-il passé avec INC-2023-Q4-011 ?", k=3):
    print(round(score, 4), doc["content"][:120])

Ici, VectorIndex.add_document vectorise les morceaux un par un, avec un appel par morceau. Pour un gros corpus, vectorisez par lots comme à la leçon 50, puis ajoutez chaque vecteur avec add_vector et le même objet document à l’index BM25.

Le résultat

Dans la démonstration du cours officiel, la recherche vectorielle seule plaçait la section 10 (cybersécurité) en tête, puis la section 3 (analyse financière), qui ne mentionne pas l’incident. Avec le Retriever hybride, on obtient la section 10 (rapport d’incident de cybersécurité), puis la section 2 (ingénierie logicielle, stabilisation du projet Phoenix), puis la section 5 (développements juridiques).

Un pipeline extensible

Tout index qui offre add_document et search peut rejoindre le Retriever : un index par mots-clés, un index de graphe, un index spécialisé dans un domaine. La fusion n’a pas à changer. C’est aussi l’architecture que l’article d’Anthropic décrit pour une RAG standard : embeddings et BM25 en parallèle, puis fusion des rangs et dédoublonnage des résultats.

À vous de jouer

Comparer trois recherches. Sur votre document, préparez trois questions : une conceptuelle, une avec un terme exact, une qui mélange les deux. Pour chacune, comparez les trois premiers résultats de VectorIndex seul, de BM25Index seul et du Retriever. Refaites l’essai avec k_rrf=1 puis k_rrf=60, et notez si l’ordre change.

Sources officielles : A multi-index RAG pipeline (Claude Academy) · Contextual Retrieval (Anthropic : embeddings, BM25 et fusion des rangs)

Points clés à retenir

  • Le Retriever interroge plusieurs index qui partagent la même interface.
  • On fusionne des rangs, pas des scores : une distance et un score BM25 ne se comparent pas.
  • Score RRF : somme de 1 / (k + rang) ; k = 60 est la valeur la plus répandue.
  • Le même objet document est ajouté à tous les index, ce qui permet de le reconnaître.
  • Un nouvel index rejoint la fusion sans que le Retriever change.