Recherche hybride : BM25 + vectoriel
La recherche hybride lance en parallèle une recherche par mots-clés (BM25) et une recherche vectorielle sur la même question, puis fusionne les deux classements, le plus souvent par Reciprocal Rank Fusion (RRF). Elle rattrape les cas où l'embedding échoue (identifiants, noms propres, termes rares) sans perdre la compréhension des paraphrases. Qdrant, Weaviate et Elasticsearch la proposent nativement ; avec ChromaDB, on la construit en trente lignes de Python.
Une base vectorielle retrouve le sens, pas la lettre : une référence comme « RG/2024-117 » ou un numéro de ticket lui échappe. À l'inverse, la recherche par mots-clés ne comprend pas qu'« automobile » et « voiture » désignent la même chose. Ce guide montre comment combiner les deux, quelle méthode de fusion choisir, ce que font Qdrant et Weaviate, et un piège de tokenisation en français qui ruine silencieusement la partie BM25.
#Pourquoi le pur vectoriel rate certaines questions
La recherche vectorielle transforme chaque passage en un vecteur qui résume son sens général, puis renvoie les passages dont le vecteur est le plus proche de celui de la question. Cette compression fonctionne très bien pour les paraphrases, et mal pour tout ce qui doit être retrouvé à la lettre : un identifiant, un code d'erreur, un nom de personne, un sigle interne. Pour l'embedding, « PROD-4817 » et « PROD-4871 » se ressemblent, et un ticket sans rapport peut arriver devant le bon. La recherche hybride répond à ce défaut : elle ajoute un second classement fondé sur la présence exacte des mots, et fusionne les deux pour que chaque méthode compense les angles morts de l'autre.
- Identifiants et références
- Numéros de ticket, de dossier, de contrat, SKU, codes d'erreur : ce sont des chaînes qu'un embedding ne garde pas de façon fiable et que BM25 retrouve dès qu'elles figurent dans le passage.
- Vocabulaire de spécialité rare
- Termes médicaux, juridiques ou techniques peu fréquents : le poids d'un mot rare est élevé en BM25, alors que son embedding peut être flou.
- Requêtes très courtes
- Deux mots comme « facture 2024 » donnent peu de matière à un embedding ; les mots-clés, eux, se comparent tels quels.
- Paraphrases et reformulations
- Le cas inverse : « contrat à durée déterminée » et « CDD » ou « voiture » et « automobile » ne partagent aucun mot ; seule la recherche vectorielle les rapproche.
#BM25 : ce que fait la recherche par mots-clés
Tes documents, ton IA : un RAG local fiable sur tes PDF, notes et mails — sans rien envoyer dans le cloud.
- Espace en ligne à vie
- PDF + fichiers
- Mises à jour à vie
BM25 est la fonction de classement lexical qui équipe Lucene, Elasticsearch et OpenSearch, et que SQLite propose dans son module FTS5 (la documentation décrit sa fonction bm25() comme renvoyant une valeur qui indique la qualité de correspondance de la ligne avec la requête). Elle repose sur trois idées : un mot rare pèse plus qu'un mot fréquent, un mot répété compte de moins en moins au-delà d'un certain nombre d'occurrences, et un long passage est légèrement pénalisé par rapport à un court. Le paramètre k1 règle cette saturation : selon la description d'Elastic, il limite l'influence qu'un seul terme de la requête peut avoir sur le score d'un document.
- Forces
- Termes rares, identifiants, requêtes courtes, vocabulaire de spécialité, aucun modèle à charger ni à entraîner, index compact, résultat explicable (on sait quel mot a fait remonter le passage).
- Faiblesses
- Synonymes, reformulations, paraphrases, fautes de frappe ; sans lemmatisation, « signé » et « signature » sont deux mots différents.
- Pré-requis
- Une tokenisation soignée : minuscules, retrait des accents, éventuellement racinisation. C'est le point où la plupart des implémentations maison se trompent, voir plus bas.
#Recherche hybride : le principe
On exécute les deux recherches sur la même question, chacune renvoie sa liste de candidats (20 à 50 passages), puis on fusionne les deux listes en un seul classement. Le gain vient d'une observation simple : les passages qui apparaissent dans les deux listes sont presque toujours pertinents, et chaque méthode ramène en plus des passages que l'autre a manqués. Weaviate définit la recherche hybride en ces termes : elle combine les résultats d'une recherche vectorielle et d'une recherche par mots-clés en fusionnant les deux jeux de résultats, avec une méthode de fusion et des poids relatifs configurables. Le gain chiffré dépend du corpus et des questions : aucun chiffre général n'est fiable, et il faut le mesurer sur vos propres documents (voir plus bas).
#Fusionner sans normaliser : Reciprocal Rank Fusion
Le piège classique consiste à additionner les scores bruts. Un score BM25 est un nombre positif sans borne ; un score de similarité vectorielle est une distance ou un cosinus, dans une plage bornée : les deux échelles sont incomparables, et le moindre changement de corpus les décale. La Reciprocal Rank Fusion contourne le problème en n'utilisant que les rangs. La documentation d'Elasticsearch la présente comme une méthode qui n'exige aucun réglage et dont les indicateurs de pertinence n'ont pas besoin d'être liés entre eux.
Un passage classé premier par BM25 et cinquième par le vectoriel obtient 1/61 + 1/65, soit environ 0,0318 ; un passage classé quinzième dans les deux listes obtient 2/75, soit environ 0,0267. La constante k atténue l'avantage du tout premier rang : plus elle est grande, plus les rangs lointains comptent. Elasticsearch documente cette constante sous le nom rank_constant, avec 60 par défaut, et une taille de fenêtre (rank_window_size) qui fixe la longueur de chaque liste avant fusion. Une fenêtre plus grande améliore la pertinence au prix de la performance, précise la documentation.
#Et la fusion par scores ?
Certains moteurs proposent l'alternative : normaliser les scores de chaque liste puis les combiner par un poids. Weaviate documente les deux méthodes, un classement par rangs et une fusion par scores relatifs, cette dernière étant la méthode par défaut depuis la version 1.24 ; elle est requise pour utiliser l'autocut avec l'opérateur hybride. Qdrant propose RRF et DBSF, cette dernière conservant les scores bruts mais normalisant leur distribution (moyenne et écart-type) avant combinaison. La fusion par rangs est plus robuste quand on ne connaît pas la distribution des scores ; la fusion par scores permet un réglage plus fin quand on peut mesurer.
#Quel outil : moteurs qui font l'hybride nativement
| Outil | Hybride natif | Fusion | Réglage du poids |
|---|---|---|---|
| Qdrant | Oui, via l'API Query (disponible depuis la version 1.10) | RRF ou DBSF | Poids par requête et constante k réglables dans les versions récentes |
| Weaviate | Oui, opérateur hybrid | Rangs ou scores relatifs (défaut depuis la 1.24) | Paramètre alpha : 1 = vectoriel pur, 0 = mots-clés purs |
| Elasticsearch | Oui, récupérateur rrf | RRF | rank_constant (60 par défaut) et rank_window_size |
| SQLite FTS5 + extension vectorielle | À assembler | À écrire | À vous |
| ChromaDB + rank_bm25 | À assembler en Python | À écrire (RRF en 6 lignes) | À vous |
La différence de fond n'est pas la fusion elle-même, qui tient en quelques lignes, mais l'index : un moteur natif tient les deux index à jour ensemble, alors que l'assemblage maison garde l'index BM25 en mémoire et doit le reconstruire à chaque ajout de document. Pour un corpus de quelques milliers de passages qui change rarement, l'assemblage maison suffit largement. Au-delà, ou dès que les documents changent chaque jour, un moteur natif évite les incohérences entre les deux index. Le guide sur Weaviate présente cet outil en détail.
#Implémentation maison : ChromaDB, rank_bm25 et RRF
Le code suivant assemble les trois briques. Il corrige un défaut fréquent dans les exemples : la fonction de normalisation doit retirer les signes diacritiques avant de filtrer, sinon chaque lettre accentuée coupe un mot en deux.
Deux précautions pratiques. D'abord, les identifiants doivent être identiques dans les deux index : ici l'indice de la liste, converti en chaîne, sert d'identifiant Chroma. Ensuite, l'index BM25 en mémoire disparaît à l'arrêt du programme : reconstruisez-le au démarrage, ce qui prend quelques secondes pour des dizaines de milliers de passages, ou enregistrez la liste des documents à côté de la base.
#Régler l'équilibre entre BM25 et vectoriel
Par défaut, RRF traite les deux listes à égalité. Si votre corpus est riche en références (jurisprudence, tickets, catalogues), donnez plus de poids à BM25 ; si les questions sont conversationnelles, laissez l'équilibre ou favorisez le vectoriel. Dans la fonction précédente, il suffit de passer weights=[0.6, 0.4] pour un poids de 60 % sur BM25. Qdrant permet le même réglage : sa documentation indique que le poids de chaque requête vaut 1 par défaut, ce qui redonne la formule RRF d'origine, et que la constante k est réglable dans les versions récentes. Dans Weaviate, on règle alpha : 1 revient au vectoriel pur, 0 aux mots-clés purs.
| Corpus et questions | Poids de départ BM25 / vectoriel | Ce que vous surveillez |
|---|---|---|
| Références, numéros, noms propres (juridique, tickets, catalogues) | 60 / 40 | Les questions par identifiant remontent-elles en premier ? |
| Documentation rédigée, questions en langage naturel | 40 / 60 | Les paraphrases retrouvent-elles le bon passage ? |
| Corpus mixte ou inconnu | 50 / 50 | Rappel@5 sur 30 à 50 questions réelles |
| Sigles et jargon de métier | 55 / 45, avec un dictionnaire de synonymes côté BM25 | Sigle et forme longue retrouvent-ils le même passage ? |
#Après la fusion : ajouter un reranker
La fusion donne un lot de candidats plus varié que chaque méthode seule. Un reranker peut ensuite trier ce lot en lisant chaque passage avec la question : les deux techniques se cumulent. L'ordre habituel est recherche hybride, puis fusion, puis reranker sur les 20 à 50 premiers, puis les 3 à 5 meilleurs passages dans le prompt. Le guide sur le reranker détaille cette dernière étape, et celui sur le chunking explique pourquoi la taille des passages influence autant BM25 que les embeddings.
#Questions fréquentes sur la recherche hybride
Qu'est-ce que la recherche hybride dans un RAG ?+
Faut-il normaliser les scores BM25 et vectoriels avant de les additionner ?+
Quelle valeur de k choisir dans RRF ?+
Peut-on faire de la recherche hybride avec ChromaDB ?+
La recherche hybride ralentit-elle beaucoup les requêtes ?+
Comment savoir si l'hybride vaut le coup sur mes documents ?+
- Ajouter un reranker à son pipeline
- Stratégies de chunking
- Weaviate : recherche hybride et multi-location
- RAG en local avec ChromaDB et Ollama
- Les meilleurs modèles d'embeddings FR
- Source : Qdrant, requêtes hybrides
- Source : Weaviate, recherche hybride
- Source : Elasticsearch, Reciprocal Rank Fusion
- Source : SQLite FTS5, fonction bm25()
Un retour, une erreur, une précision ? Faites-nous signe, ça améliore le guide pour tout le monde.