Pipeline RAG multi-recherche
Sur cette page
La recherche sémantique et la recherche lexicale fonctionnent chacune de leur côté. Reste à les combiner en une seule recherche, qui profite des forces des deux.
Un coordinateur : le Retriever
Les deux index ont la même interface, add_document et search. On peut donc les envelopper dans une classe qui coordonne :
- elle reçoit la question de l’utilisateur ;
- elle la transmet à chaque index ;
- elle recueille les deux classements ;
- elle les fusionne en un seul.
Fusionner des classements : la RRF
Les deux index ne notent pas sur la même échelle : une distance d’un côté, un score BM25 de l’autre. On ne peut pas additionner ces nombres. La fusion par rang réciproque (Reciprocal Rank Fusion, RRF) ne regarde que les rangs :
RRF(d) = Σ 1 / (k + rang_i(d))
Pour chaque document d, on additionne 1 / (k + rang) sur tous les index ; k est une constante, souvent 60. L’exemple du cours officiel, avec k = 1 pour des chiffres plus lisibles : l’index vectoriel classe les sections 2, 7, 6 ; BM25 classe les sections 6, 2, 7.
| Section | Rang vectoriel | Rang BM25 | Score RRF (k = 1) |
|---|---|---|---|
| Section 2 | 1 | 2 | 1/2 + 1/3 = 0,833 |
| Section 6 | 3 | 1 | 1/4 + 1/2 = 0,75 |
| Section 7 | 2 | 3 | 1/3 + 1/4 = 0,583 |
Classement final : section 2, puis 6, puis 7. Un document bien placé dans les deux classements l’emporte sur un document premier d’un seul côté.
L’implémentation
Le cours officiel ne montre qu’une esquisse de la méthode search. 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]
Chaque index renvoie ses candidates meilleurs résultats, et seuls les rangs comptent. Le même dictionnaire document est passé à tous les index par add_document : son identifiant Python, id(doc), sert de clé commune pour additionner les contributions. VectorIndex reçoit ici la question en texte et la vectorise lui-même avec sa fonction embed_fn.
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])
add_document vectorise chaque morceau avec Titan, un appel par morceau, et l’indexe aussi pour BM25. Dans la démonstration du cours officiel, la recherche hybride place en tête le rapport d’incident de cybersécurité, suivi de la section d’ingénierie logicielle : mieux que chacune des deux méthodes seule.
Une architecture extensible
Tout index qui respecte l’interface add_document et search peut rejoindre le Retriever : un autre modèle d’embedding, une recherche spécialisée dans un domaine, ou toute autre technique. La fusion par rangs n’a pas besoin de connaître leurs échelles de notes.
À vous de jouer
Régler la fusion. Sur votre document, comparez les trois premiers résultats de VectorIndex, de BM25Index et du Retriever, pour une question avec un terme exact et une question formulée librement. Essayez ensuite k_rrf=1 et k_rrf=60 : le classement change-t-il ?
Sources officielles : A multi-search RAG pipeline (Claude Academy) · Contextual Retrieval (Anthropic : recherche hybride)
Points clés à retenir
- Le
Retrieverenvoie la question à chaque index et fusionne leurs classements. - Les échelles de notes diffèrent : la RRF ne regarde que les rangs, RRF(d) = Σ 1 / (k + rang).
- Un document bien classé partout l’emporte sur un document premier d’un seul côté.
- Tout index qui respecte
add_documentetsearchpeut rejoindre la fusion.