Recherche hybride : BM25 + vectoriel
Le RAG vectoriel est génial pour la sémantique, catastrophique pour les identifiants. "Référence RG/2024-117" cherché en pur vectoriel ne retrouve rien. Ajouter un moteur de recherche lexicale (BM25) en parallèle, puis fusionner les résultats, donne un système robuste sur les deux dimensions. Ce guide explique comment en pratique.
#Le problème du pur vectoriel
- Noms propres et identifiants
- "Dupont contre Martin", "SKU ABC-1234", "RG/2024-117". L'embedding les traite comme du bruit. Pourtant ce sont souvent les termes exacts que vous cherchez.
- Vocabulaire de domaine rare
- Médical, juridique, technique : les termes spécifiques ont des embeddings fragiles parce que peu représentés dans les corpus d'entraînement.
- Requêtes très courtes
- "facture 2024" — trop peu de signal pour un embedding. BM25 gère nativement.
#BM25, le retour
BM25 est un algorithme de ranking lexical inventé dans les années 80, qui équipe Elasticsearch, Lucene, SQLite FTS5, Postgres via pg_trgm. Il mesure la correspondance exacte entre termes, pondérée par TF-IDF.
- Forces
- Termes rares (+poids), requêtes courtes, identifiants exacts, vocabulaire de domaine.
- Faiblesses
- Synonymes ("voiture" ≠ "automobile"), reformulations ("CDD" ≠ "contrat à durée déterminée"), paraphrases.
- Pré-requis
- Tokenisation + normalisation (minuscules, accents, stemming). Pas de modèle à entraîner.
#L'hybride : le meilleur des deux
On exécute les deux en parallèle — BM25 et embedding — sur la même requête, chacun produit son top-20. On fusionne les deux listes pour obtenir un top-20 hybride, souvent meilleur que chacun séparément.
#RRF : fusionner sans trafiquer
Le piège classique : normaliser les scores de BM25 et du vectoriel pour les additionner. Ça ne marche pas — les échelles sont incomparables. La méthode qui marche : Reciprocal Rank Fusion.
#Stack pratique
- Qdrant 1.10+
- Supporte la recherche hybride native avec RRF. Un seul appel API.
- Weaviate
- Recherche hybride native, pondération alpha réglable entre lexical et vectoriel.
- Elasticsearch 8.x
- kNN + BM25 combinés via RRF. Plus lourd à déployer mais très complet.
- ChromaDB + rank_bm25
- Recherche vectorielle via Chroma, BM25 via la lib Python rank_bm25, fusion manuelle en Python. Solution DIY.
#Implémentation custom (ChromaDB + rank_bm25)
#Régler le ratio BM25 / vectoriel
Par défaut RRF traite les deux listes à égalité. Si votre corpus est très spécialisé (beaucoup d'identifiants), vous pouvez favoriser BM25 en divisant son rank par un facteur < 1 (donc en augmentant sa contribution). À l'inverse, sur corpus libre, favorisez le vectoriel.
Un retour, une erreur, une précision ? Faites-nous signe, ça améliore le guide pour tout le monde.