
La recherche d'un RAG — dense, lexicale ou hybride — est rapide mais approximative : elle compare des représentations calculées séparément pour la question et pour les documents, sans jamais les confronter vraiment. Résultat : un document hors sujet remonte parfois en tête. Le re-ranking corrige cela par une seconde passe : un modèle cross-encoder lit chaque paire (question, candidat) ensemble et la note finement. Ce guide montre le schéma en deux temps — recherche large, puis re-ranking du lot —, son implémentation avec un cross-encoder, et le critère pour décider s'il vaut son coût. Public visé : développeur dont le RAG rate encore des réponses malgré une recherche correcte.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Pourquoi la recherche seule reste approximative.
- La différence entre bi-encoder et cross-encoder.
- Le schéma en deux temps : présélection large, puis re-ranking.
- Implémenter un re-ranking avec un cross-encoder.
- Quand le re-ranking vaut son coût.
Prérequis
Section intitulée « Prérequis »- Avoir un RAG fonctionnel à améliorer.
- Une instance Ollama avec
nomic-embed-text. - Python 3.10+ — le cross-encoder se télécharge au premier lancement.
Pourquoi la recherche reste approximative
Section intitulée « Pourquoi la recherche reste approximative »La recherche d'un RAG repose sur un bi-encoder : un modèle qui calcule, séparément, le vecteur de la question et le vecteur de chaque document. La comparaison se fait ensuite entre ces vecteurs déjà figés.
Cette séparation est ce qui rend la recherche rapide — les vecteurs des documents sont calculés une fois, à l'indexation. Mais c'est aussi sa faiblesse : la question et le document ne sont jamais « lus ensemble ». Le modèle ne voit jamais la question à la lumière d'un document précis. Il compare deux résumés indépendants.
Conséquence concrète, observée dans le lab de ce guide : sur la question « comment garder les données quand on supprime un conteneur ? », la recherche dense place en tête un document sur la commande prune — hors sujet —, et relègue plus bas le document qui parle réellement de conserver les données. La recherche a échoué, alors que le bon document était bien dans l'index.
Bi-encoder et cross-encoder
Section intitulée « Bi-encoder et cross-encoder »Le cross-encoder travaille autrement. Au lieu de calculer deux vecteurs séparés, il prend la paire complète — question et document concaténés — et la passe en une fois dans le modèle. Il en sort un score de pertinence unique.
La différence est décisive. Le cross-encoder « lit » la question en présence du document : il peut juger si ce document précis répond à cette question précise. Là où le bi-encoder compare deux photos prises séparément, le cross-encoder regarde les deux côte à côte.
| Bi-encoder (recherche) | Cross-encoder (re-ranking) | |
|---|---|---|
| Calcul | Vecteurs séparés | Paire lue ensemble |
| Vitesse | Rapide | Lent |
| Précision | Approximative | Élevée |
| Passe à l'échelle | Tout le corpus | Une présélection |
Le cross-encoder est plus juste — mais bien plus lent. L'appliquer à un corpus entier serait inenvisageable : il faudrait, à chaque question, le faire tourner sur chaque document. D'où le schéma en deux temps.
Le schéma en deux temps
Section intitulée « Le schéma en deux temps »L'idée : combiner les deux. On utilise le bi-encoder pour ce qu'il fait bien — présélectionner vite —, puis le cross-encoder pour ce qu'il fait bien — classer juste.
La recherche dense remonte une présélection large — pas le top 3, mais une douzaine de candidats. Le cross-encoder note ensuite chaque paire (question, candidat) de ce petit lot, et on garde le top 3 ou 5 de ce reclassement.
def recherche_avec_reranking(question: str, k: int = 3) -> list[str]: """Recherche large, puis re-ranking : le pipeline en deux temps.""" candidats = recherche_dense(question, n=5) # présélection large return reclasser(question, candidats, k) # reclassement finLe cross-encoder ne tourne que sur quelques candidats — pas sur le corpus entier. Sa lenteur devient négligeable, et sa précision profite à la sélection finale. C'est le meilleur des deux modèles.
Implémenter le re-ranking
Section intitulée « Implémenter le re-ranking »Le cross-encoder s'utilise via la classe CrossEncoder de sentence-transformers. On lui donne des paires (question, document) ; il renvoie un score par paire.
from sentence_transformers import CrossEncoder
MODELE_RERANK = "BAAI/bge-reranker-v2-m3" # cross-encoder multilingue
def reclasser(question: str, candidats: list[str], k: int = 3) -> list[str]: """Seconde passe : le cross-encoder note chaque paire et reclasse.""" paires = [(question, candidat) for candidat in candidats] scores = CrossEncoder(MODELE_RERANK).predict(paires) classement = sorted(zip(candidats, scores), key=lambda c: c[1], reverse=True) return [texte for texte, _ in classement[:k]]On construit les paires, le cross-encoder les note, on trie par score décroissant. Sur le cas qui mettait la recherche dense en échec, le re-ranking rétablit le bon ordre :
Après recherche dense (1er) : Pour purger les conteneurs arrêtés...Après re-ranking (1er) : Un volume Docker conserve les données après la suppression du conteneur.Le document pertinent, que la recherche dense avait relégué, remonte en première place. C'est exactement le rôle du re-ranking : rattraper les erreurs de la première passe.
Quand le re-ranking vaut son coût
Section intitulée « Quand le re-ranking vaut son coût »Le re-ranking améliore la qualité — mais il coûte. Le cross-encoder est un modèle à charger et à exécuter ; chaque question ajoute une passe d'inférence. Ce coût n'est pas toujours justifié.
Le re-ranking se justifie quand la qualité du retrieval, mesurée, plafonne malgré une recherche correcte — un recall honnête, mais le bon document trop souvent en deuxième ou troisième position plutôt qu'en première. C'est le symptôme classique qu'une seconde passe peut corriger.
Il ne se justifie pas par principe. Si la recherche — surtout en mode hybride — place déjà le bon document en tête, ajouter un cross-encoder alourdit sans gagner. La règle est constante dans tout le parcours RAG : on mesure d'abord, on optimise ensuite. Le re-ranking est un levier qu'on actionne sur la foi d'un chiffre, pas d'une intuition.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
| Le re-ranking ne change rien | Recherche déjà bonne | Normal — le re-ranking n'a rien à corriger |
| Le re-ranking dégrade le classement | Cross-encoder faible ou non multilingue | Choisir bge-reranker-v2-m3 ou équivalent |
| Latence trop forte | Cross-encoder sur trop de candidats | Réduire la présélection (n) avant re-ranking |
| Premier lancement très long | Téléchargement du modèle | Normal — le modèle est mis en cache ensuite |
| Aucun gain mesuré | Re-ranking ajouté sans diagnostic | Mesurer le recall avant de l'ajouter |
À retenir
Section intitulée « À retenir »- La recherche s'appuie sur un bi-encoder : rapide, mais elle compare des vecteurs figés séparément.
- Le cross-encoder lit la paire (question, document) ensemble — plus juste, mais plus lent.
- Le schéma en deux temps : la recherche présélectionne large, le cross-encoder reclasse le lot.
- Le re-ranking rattrape les erreurs de la première passe — il remonte le bon document en tête.
- La qualité du reranker est déterminante — un modèle faible se trompe encore.
- Le re-ranking s'ajoute sur la foi d'une mesure, pas par principe.
Prochaines étapes
Section intitulée « Prochaines étapes »Pour aller plus loin
Section intitulée « Pour aller plus loin »- Cross-Encoders — sentence-transformers — la référence sur les cross-encoders.