Recherche hybride pour le RAG dans n8n : combiner mots-clés (BM25) et vecteurs
Publié le 27 juillet 2026 · 8 min de lecture
Un assistant RAG branché sur Supabase répond correctement à « comment résilier mon abonnement ? » mais sèche sur « que signifie l'erreur E-4012 ? » — alors que la page qui documente précisément ce code existe dans la base. Le coupable n'est ni le LLM ni le découpage des documents : c'est la recherche vectorielle elle-même, qui n'a aucune idée de ce que représente « E-4012 » et remonte des chunks vaguement liés aux erreurs en général. La recherche hybride corrige ce point aveugle en combinant deux moteurs complémentaires : une recherche par mots-clés (type BM25) et une recherche vectorielle, dont les résultats sont fusionnés en une seule liste. Voici comment la mettre en place dans un pipeline RAG n8n avec Supabase/Postgres, sans ajouter le moindre service externe.
Pourquoi la recherche vectorielle pure rate les requêtes exactes
Un modèle d'embedding encode le sens d'un texte, pas ses caractères. C'est sa force pour les reformulations — et sa faiblesse pour tout ce qui n'a pas de sens linguistique : références produit (REF-2024-118), codes d'erreur (E-4012), numéros d'article de loi, noms propres rares, jargon interne. Ces tokens sont quasi absents des données d'entraînement du modèle d'embeddings, et leur vecteur ne porte presque aucune information discriminante. Résultat : le chunk qui contient exactement E-4012 n'a aucune raison de sortir devant dix chunks qui parlent d'erreurs en termes généraux.
Ce n'est pas une anecdote d'implémentation, c'est une limite documentée. Le papier fondateur du dense retrieval moderne, « Dense Passage Retrieval for Open-Domain Question Answering » de Karpukhin et al. (EMNLP 2020 — voir sur Google Scholar), montre que la recherche dense bat BM25 en moyenne sur des benchmarks de question-réponse, tout en observant que les deux approches restent complémentaires : les auteurs relèvent des cas où BM25 conserve l'avantage, précisément quand la requête repose sur un recouvrement exact de termes saillants — et constatent qu'en combinant les deux signaux, les résultats s'améliorent encore.
Et pourquoi le full-text pur rate les reformulations
Le problème inverse est tout aussi réel. Un moteur lexical comme BM25 note un document en fonction des mots exacts partagés avec la requête, pondérés par leur rareté dans le corpus et la longueur du document — le cadre théorique est posé dans « The Probabilistic Relevance Framework: BM25 and Beyond » de Robertson et Zaragoza (Foundations and Trends in Information Retrieval, 2009 — voir sur Google Scholar). C'est redoutable d'efficacité sur les termes exacts, mais aveugle aux synonymes : « je n'arrive plus à me connecter » ne partage aucun mot significatif avec un chunk intitulé « échec d'authentification », et un utilisateur qui parle de « facture » ne trouvera jamais le document qui ne dit que « avoir » ou « note de débit ».
Un RAG en production reçoit les deux types de requêtes, souvent dans la même journée. D'où l'idée simple de la recherche hybride : lancer les deux recherches en parallèle et fusionner les résultats, pour que chaque moteur couvre l'angle mort de l'autre.
Fusionner les deux listes avec Reciprocal Rank Fusion (RRF)
Le piège classique de la fusion : les scores des deux moteurs ne sont pas comparables. Une similarité cosinus entre 0 et 1 et un score lexical non borné ne s'additionnent pas proprement. Reciprocal Rank Fusion contourne le problème en ignorant les scores et en ne gardant que les rangs : chaque document reçoit, pour chaque liste où il apparaît, un score de 1 / (k + rang), et on additionne. La constante k (typiquement 50 ou 60) amortit l'écart entre les premières positions.
Exemple avec k = 50 : un chunk classé 3ᵉ en full-text et 7ᵉ en vectoriel obtient 1/53 + 1/57 ≈ 0,0364. Un chunk classé 1ᵉʳ en vectoriel mais absent du full-text obtient 1/51 ≈ 0,0196. Le premier gagne : un document jugé pertinent par les deux moteurs passe devant un document plébiscité par un seul. C'est exactement le comportement recherché, obtenu avec une formule qui tient en une ligne et ne demande aucune normalisation ni apprentissage.
Mise en œuvre avec Supabase/Postgres dans n8n
L'atout de la pile Supabase + pgvector : Postgres sait déjà tout faire. Le full-text est natif (tsvector), le vectoriel est fourni par pgvector, et une seule fonction SQL peut exécuter les deux requêtes et fusionner par RRF — n8n n'a plus qu'un seul appel à faire.
1. Ajouter la colonne full-text et son index
Sur la table documents qui contient déjà vos chunks et leurs embeddings, ajoutez une colonne tsvector générée automatiquement et un index GIN :
alter table documents
add column fts tsvector
generated always as (to_tsvector('french', content)) stored;
create index documents_fts_idx on documents using gin (fts);
La configuration 'french' applique la racinisation française (« connexion », « connecter » et « connecté » se rejoignent). La colonne étant générée, chaque nouveau chunk ingéré est indexé sans modifier le workflow d'ingestion. Postgres n'implémente pas BM25 au sens strict — son ts_rank_cd est un cousin plus simple — mais le principe lexical est identique et suffit comme étage mots-clés d'un RAG. Côté vectoriel, l'index HNSW ou IVFFlat sur la colonne embedding reste celui décrit dans notre guide pgvector.
2. La fonction SQL qui interroge et fusionne
Une fonction RPC unique exécute les deux recherches et applique RRF :
create or replace function hybrid_search(
query_text text,
query_embedding vector(1536),
match_count int default 10,
rrf_k int default 50
)
returns setof documents
language sql
as $$
with full_text as (
select id, row_number() over (
order by ts_rank_cd(fts, websearch_to_tsquery('french', query_text)) desc
) as rank_ix
from documents
where fts @@ websearch_to_tsquery('french', query_text)
limit match_count * 2
),
semantic as (
select id, row_number() over (
order by embedding <=> query_embedding
) as rank_ix
from documents
limit match_count * 2
)
select d.*
from full_text
full outer join semantic on full_text.id = semantic.id
join documents d on d.id = coalesce(full_text.id, semantic.id)
order by
coalesce(1.0 / (rrf_k + full_text.rank_ix), 0.0) +
coalesce(1.0 / (rrf_k + semantic.rank_ix), 0.0)
desc
limit match_count;
$$;
Le full outer join est le détail qui compte : un document présent dans une seule des deux listes reste candidat (son score de l'autre liste vaut simplement 0), tandis qu'un document présent dans les deux cumule ses deux scores et remonte mécaniquement.
3. L'appel depuis n8n
Dans le workflow de question-réponse, l'enchaînement remplace l'appel habituel à match_documents :
- Générer l'embedding de la question — avec le même modèle que celui utilisé à l'ingestion, via le node Embeddings ou un HTTP Request vers l'API d'embeddings.
- Appeler la fonction hybride — deux options équivalentes :
- Node HTTP Request vers l'endpoint RPC auto-généré par Supabase :
POST https://<projet>.supabase.co/rest/v1/rpc/hybrid_search, avec les en-têtesapikeyetAuthorization: Bearer <clé>(voir notre guide de connexion n8n ↔ Supabase) et un corps JSON{"query_text": "...", "query_embedding": [...], "match_count": 20}. - Node Postgres en connexion directe :
select id, content, metadata from hybrid_search($1, $2, 20);— utile si l'instance n8n a déjà un accès Postgres et que vous préférez éviter la couche REST.
- Node HTTP Request vers l'endpoint RPC auto-généré par Supabase :
- Construire le prompt — injecter les passages retournés avec leurs métadonnées de source, comme dans le workflow d'API question-réponse RAG.
Le node Supabase natif de n8n couvre les opérations de table (select, insert, update) mais pas l'appel de fonctions arbitraires : pour une RPC personnalisée, le HTTP Request vers /rest/v1/rpc/ ou le node Postgres sont les deux voies à retenir.
Quand la recherche hybride vaut le coût
Le surcoût est modeste — une colonne, un index, une fonction — mais il n'est pas nul : c'est du SQL à maintenir et un paramètre de plus à passer depuis n8n. Le gain est net dans les cas suivants :
- Bases documentaires techniques : documentation produit avec codes d'erreur, références de pièces, numéros de version, tickets de support. Les requêtes exactes y sont fréquentes et le vectoriel seul y échoue de façon visible.
- Catalogues : recherche produit où l'utilisateur tape aussi bien « chaussure de running légère » (sémantique) que « SKU 8842-B » (exact).
- Corpus juridiques ou normatifs : numéros d'articles, références de normes, intitulés officiels.
- Mémoire d'agent : la mémoire long terme d'un agent IA bénéficie du même schéma, un agent devant retrouver aussi bien « ce que l'utilisateur a dit sur ses préférences » (sémantique) que la mention exacte d'un nom de projet.
À l'inverse, sur une petite base homogène et rédactionnelle (une FAQ, une documentation sans identifiants techniques), où les requêtes sont des questions en langage naturel, le vectoriel seul remonte déjà les bons passages : l'étage lexical n'y ajouterait presque rien. Commencez simple, et ajoutez l'hybride quand les logs montrent des échecs sur des termes exacts.
Et le reranking dans tout ça ?
Recherche hybride et reranking ne jouent pas au même étage. L'hybride améliore le rappel du premier étage : il augmente les chances que le bon passage figure dans la liste de candidats, quel que soit le style de la requête. Le reranking améliore la précision du second étage : il reclasse finement ces candidats avec un cross-encoder qui lit la question et chaque passage ensemble. Les deux se cumulent naturellement : recherche hybride pour sortir 20 à 30 candidats, reranking pour n'en garder que les 5 meilleurs avant le prompt. Sur une base technique volumineuse, c'est cette combinaison — et non l'un ou l'autre isolément — qui donne les réponses les plus fiables.
Pour aller plus loin
Le Pack Assistant RAG (119 €) fournit les workflows d'ingestion, de chatbot avec citations et d'API question-réponse construits sur Supabase pgvector : la fonction hybrid_search ci-dessus se substitue à l'appel match_documents fourni, sans toucher au reste du pipeline. Si votre assistant échoue systématiquement sur les références produit ou les codes d'erreur alors que les documents existent, c'est le premier chantier à ouvrir — avant de changer de modèle d'embeddings ou de revoir tout le découpage. Et si votre base documentaire est faite de contrats et de rapports plutôt que de pages web, notre guide du RAG sur des PDF avec n8n couvre l'étage d'extraction et d'OCR qui précède tout ce pipeline.
FAQ
Questions fréquentes
La recherche hybride remplace-t-elle le reranking ?
Non, les deux se complètent. La recherche hybride améliore le rappel au premier étage : elle augmente les chances que le bon passage figure dans la liste de candidats, notamment pour les requêtes contenant des termes exacts (références, codes d'erreur). Le reranking intervient ensuite pour reclasser finement ces candidats. Un pipeline complet enchaîne recherche hybride (20-30 candidats) puis reranking (top 5).
Faut-il un moteur dédié type Elasticsearch pour faire du BM25 dans un RAG n8n ?
Non. Pour un RAG classique, le full-text natif de Postgres (colonne tsvector + index GIN) suffit largement comme étage lexical. Son ts_rank n'est pas exactement BM25 au sens strict, mais il repose sur les mêmes principes de correspondance par mots-clés. L'avantage est décisif : tout reste dans la même base Supabase/Postgres que pgvector, une seule fonction SQL fait les deux requêtes et la fusion.
Quelle valeur choisir pour la constante k dans Reciprocal Rank Fusion ?
La valeur historique est k = 60, et les exemples Supabase utilisent souvent 50. En pratique, le résultat est peu sensible à ce paramètre tant qu'il reste dans cet ordre de grandeur : un k plus élevé lisse les différences entre rangs, un k plus faible donne plus de poids aux tout premiers résultats de chaque liste. Commencez à 50-60 et n'y touchez que si vous constatez un biais mesurable.
Quand la recherche vectorielle seule suffit-elle ?
Quand la base documentaire est petite, homogène, et que les questions des utilisateurs sont des reformulations en langage naturel sans termes exacts à retrouver (FAQ produit, documentation rédactionnelle). Dès que les requêtes contiennent des références produit, des codes d'erreur, des noms propres rares ou du jargon métier très spécifique, l'étage lexical de la recherche hybride devient rentable.
Bundle FlowKit Complet
269 €