FlowKit

Filtrer par métadonnées dans un RAG n8n : cibler les bons documents avant la recherche vectorielle

Publié le 29 juillet 2026 · 5 min de lecture

Un RAG qui cherche dans tout le corpus à chaque question finit toujours par se tromper de document : la similarité sémantique remonte un passage convaincant… de la mauvaise version de la doc, du mauvais client ou d'une note obsolète. La parade s'appelle le filtrage par métadonnées : attacher à chaque chunk des attributs structurés (source, date, langue, tenant) à l'ingestion, puis restreindre la recherche vectorielle à la tranche pertinente à la requête. C'est l'amélioration au meilleur rapport effort/impact d'un pipeline RAG — et n8n la gère de bout en bout.

Pourquoi la similarité seule ne suffit pas

La recherche vectorielle classe par proximité sémantique, rien d'autre. Or beaucoup de contraintes métier ne sont pas sémantiques :

  • La version : « comment configurer l'export ? » doit répondre depuis la doc v2, pas la v1 archivée qui lui ressemble à 95 % ;
  • La fraîcheur : une politique tarifaire de 2023 est sémantiquement identique à celle de 2026, mais fausse ;
  • Le périmètre : dans un RAG multi-clients, la question d'un client ne doit jamais toucher les documents d'un autre — un enjeu de sécurité, pas de pertinence ;
  • Le type de document : un contrat signé et un brouillon d'email traitent du même sujet avec une valeur de vérité très différente.

La littérature récente sur le RAG confirme que la qualité du retrieval — pas la taille du modèle — est le facteur limitant : la synthèse de référence de Yunfan Gao et ses coauteurs, « Retrieval-Augmented Generation for Large Language Models: A Survey » (2023, voir sur Google Scholar), classe précisément le filtrage et le routage des sources parmi les techniques distinctives des architectures RAG avancées par rapport au RAG naïf. Autrement dit : avant d'envisager un modèle plus gros, filtrez mieux.

Étape 1 — Enrichir les chunks à l'ingestion

Tout se joue au moment de l'ingestion, dans le Default Data Loader qui accompagne votre node vector store : sa section Metadata attache des paires clé-valeur à chaque chunk produit par le text splitter. Les valeurs peuvent être fixes ou calculées par expression depuis l'item courant :

source        → {{ $json.nom_fichier }}
type          → contrat | doc_produit | faq
date_document → {{ $json.date_modification }}
langue        → fr
tenant_id     → {{ $json.client_id }}
version       → v2

Trois règles d'or : des clés stables (un renommage casse tous les filtres existants), des valeurs normalisées (dates en ISO 8601, énumérations fermées plutôt que du texte libre), et la parcimonie — chaque métadonnée doit correspondre à un filtre que vous poserez réellement. Côté stockage, Supabase/pgvector range tout dans la colonne metadata (jsonb) créée par le schéma standard décrit dans notre guide RAG avec Supabase ; Qdrant utilise son payload, indexable champ par champ.

Étape 2 — Filtrer à la requête

Dans le node Supabase Vector Store (en mode retrieve, ou monté en tool d'un agent), l'option Metadata Filter transmet vos critères à la fonction match_documents, qui filtre le jsonb avant de classer par similarité :

-- extrait type de match_documents : le filtre s'applique en amont du tri vectoriel
select id, content, metadata,
       1 - (embedding <=> query_embedding) as similarity
from documents
where metadata @> filter          -- le filtre jsonb passé par n8n
order by embedding <=> query_embedding
limit match_count;

Pour des conditions plus riches qu'une égalité — plage de dates, liste de valeurs, négation — adaptez match_documents : c'est une fonction SQL ordinaire, et metadata->>'date_document' >= '2025-01-01' reste du PostgreSQL standard. Sur de gros volumes, pensez à indexer les clés filtrées (create index on documents ((metadata->>'tenant_id'))) : un filtre non indexé qui écarte 95 % des lignes ruine l'intérêt de l'index HNSW ou IVFFlat construit à côté. Côté Qdrant, le node expose l'équivalent via les filtres de payload (must/should), particulièrement efficaces car indexés nativement — un point de plus dans le match Qdrant vs pgvector.

Étape 3 — Rendre le filtre dynamique : le pattern self-query

Le niveau au-dessus : déduire le filtre de la question elle-même. « Que dit le contrat Dupont signé cette année ? » contient deux filtres implicites (type=contrat, date >= 2026-01-01) et une requête sémantique (« clauses du contrat Dupont »). Le pattern, dit self-query, se construit simplement dans n8n :

  1. Un premier appel LLM avec un Structured Output Parser extrait de la question un JSON {filtres: {...}, requete: "..."} ;
  2. Les filtres alimentent l'option Metadata Filter (ou la requête SQL personnalisée) ;
  3. La requête réécrite part dans la recherche vectorielle, éventuellement complétée par une recherche hybride et un reranking pour affiner le classement final.

Gardez un garde-fou : si l'extraction ne trouve aucun filtre fiable, cherchez sans filtre plutôt que d'inventer une contrainte — un filtre erroné produit des réponses « aucun document trouvé » plus frustrantes qu'un résultat un peu large.

Cas particulier : le multi-tenant, où le filtre devient sécurité

Dès que plusieurs clients ou équipes partagent un index, le tenant_id filtré à chaque requête n'est plus une optimisation mais une exigence de sécurité. Et un filtre applicatif reste fragile : il suffit d'un workflow copié sans son filtre pour exposer des données croisées. Défense en profondeur :

  • Côté base : Row Level Security de Postgres sur la table des embeddings, avec un rôle par tenant — la requête ne peut pas voir les lignes d'un autre client, même si le filtre n8n est oublié. Notre article sur la piste d'audit RGPD avec Supabase montre la même philosophie appliquée à la traçabilité ;
  • Ou l'isolation physique : une collection Qdrant (ou une table) par client, au prix d'une gestion plus lourde des ingestions.

Le filtre métadonnées cible ; la barrière base de données garantit. Les deux ensemble font un RAG multi-tenant présentable à un audit.

En résumé

Le filtrage par métadonnées transforme un RAG « qui cherche partout » en RAG qui cherche au bon endroit : enrichissez chaque chunk à l'ingestion (source, type, date, langue, tenant), filtrez à la requête via l'option Metadata Filter ou une fonction match_documents adaptée, passez au self-query quand les questions portent leurs propres contraintes, et doublez le filtre d'une vraie barrière (RLS, collections isolées) dès que plusieurs clients cohabitent. C'est exactement l'architecture packagée dans notre Pack Assistant RAG — prête à brancher sur vos propres métadonnées.

FAQ

Questions fréquentes

À quoi servent les métadonnées dans un pipeline RAG ?

À restreindre la recherche vectorielle à un sous-ensemble pertinent du corpus avant le calcul de similarité : ne chercher que dans la documentation produit v2, que dans les contrats d'un client donné, que dans les documents de moins d'un an. Sans filtre, la similarité sémantique seule peut remonter un passage très ressemblant mais issu de la mauvaise source, de la mauvaise version ou du mauvais client.

Comment ajouter des métadonnées à mes chunks dans n8n ?

Au moment de l'ingestion, dans le node Default Data Loader : la section Metadata permet d'attacher des paires clé-valeur à chaque chunk (source, type de document, date, langue, tenant_id…), fixes ou calculées par expression depuis l'item courant. Elles sont stockées avec le vecteur — en jsonb dans la colonne metadata pour Supabase/pgvector, en payload pour Qdrant.

Comment filtrer par métadonnées à la requête dans le node Supabase Vector Store ?

L'option Metadata Filter du node (en mode retrieve ou en tool d'agent) transmet vos paires clé-valeur à la fonction match_documents, qui filtre la colonne jsonb metadata avant de classer par similarité. Pour des filtres plus riches (plages de dates, listes de valeurs), adaptez la fonction SQL match_documents dans Supabase — c'est du PostgreSQL standard.

Le filtrage par métadonnées suffit-il pour isoler les données de plusieurs clients dans un même index ?

C'est la base indispensable (un tenant_id filtré à chaque requête), mais pour des données réellement sensibles, ne reposez pas uniquement sur un filtre applicatif que le workflow peut oublier : ajoutez une barrière côté base, comme la Row Level Security de Postgres sur la table des embeddings, ou des collections séparées par client dans Qdrant. Défense en profondeur : le filtre cible, la RLS garantit.

Bundle FlowKit Complet

269 €