
Je suis Jules Ginhac, Co-Founder & ingénieur IA chez Apogée Consult à Lyon. Je conçois et déploie des architectures IA génératives (RAG, agents, LLMOps) pour des PME, startups et organisations publiques.
Métadonnées et filtres pré-retrieval : le levier de précision le plus sous-estimé
- rag
Améliorer l'embedding n'est pas toujours la bonne réponse. Les filtres sur métadonnées avant le retrieval changent radicalement la précision d'un RAG.
Métadonnées et filtres pré-retrieval : le levier de précision le plus sous-estimé
Quand un RAG renvoie de mauvaises réponses, le premier réflexe est de changer le modèle d'embedding. Le deuxième est d'ajuster le chunking. Rarement le troisième est le bon : regarder ce qu'on a mis dans les métadonnées et ce qu'on en fait.
Ce que le retrieval purement sémantique ne peut pas résoudre
La recherche par similarité vectorielle est une recherche de proximité dans un espace de haute dimension. Elle fonctionne bien pour les questions de sens, mal pour les contraintes discrètes.
Prenons un exemple concret : un RAG sur de la documentation technique interne avec des versions de produit. L'utilisateur pose une question sur la v3.2. L'embedding ne sait pas distinguer "v3.2" de "v3.1" si les deux documents parlent du même concept avec un vocabulaire quasi-identique. Le retrieval va potentiellement ramener des chunks des deux versions, et le LLM va devoir arbitrer, ce qu'il fait mal.
La similarité cosinus entre le vecteur de "configuration réseau v3.2" et "configuration réseau v3.1" peut être supérieure à 0.95. Aucun score de seuil ne règle ce problème.
Les métadonnées comme espace de filtrage structuré
La solution est de traiter les contraintes discrètes hors de l'espace vectoriel.
À l'ingestion, chaque chunk doit porter les métadonnées qui permettent de le filtrer avant même la recherche sémantique. Cette décision d'architecture se fait une seule fois mais conditionne toute la précision future du système.
# Ingestion avec métadonnées structurées
from qdrant_client.models import PointStruct, VectorParams
def build_point(chunk: str, embedding: list[float], meta: dict) -> PointStruct:
return PointStruct(
id=meta["chunk_id"],
vector=embedding,
payload={
"text": chunk,
"source": meta["source"], # nom du document source
"product_version": meta["version"], # "3.2", "3.1", etc.
"lang": meta["lang"], # "fr", "en"
"doc_type": meta["type"], # "user_guide", "release_note", "api_ref"
"updated_at": meta["updated_at"], # timestamp ISO
},
)À la requête, le filtre est appliqué avant la recherche vectorielle, ce que Qdrant appelle un pre-filtering. L'espace de recherche est d'abord réduit aux candidats pertinents, puis la similarité est calculée dans cet espace réduit.
from qdrant_client.models import Filter, FieldCondition, MatchValue, Range
from datetime import datetime, timezone
def build_filter(version: str | None, lang: str, doc_types: list[str]) -> Filter:
conditions = [
FieldCondition(key="lang", match=MatchValue(value=lang)),
FieldCondition(key="doc_type", match=MatchValue(value=t))
for t in doc_types
]
if version:
conditions.append(
FieldCondition(key="product_version", match=MatchValue(value=version))
)
return Filter(must=conditions)
results = client.search(
collection_name="docs",
query_vector=query_embedding,
query_filter=build_filter(version="3.2", lang="fr", doc_types=["user_guide"]),
limit=8,
)Concevoir les métadonnées avant l'ingestion
La plupart des erreurs de metadata RAG viennent d'une réflexion trop tardive. On ingère d'abord, on réalise ensuite que les filtres nécessaires n'ont pas été stockés.
Quelques questions à se poser avant tout indexation :
- Quelles contraintes discrètes les utilisateurs vont-ils exprimer (période, langue, type de document, auteur, statut) ?
- Quelles contraintes le système doit-il appliquer de manière transparente (permissions, tenant, langue préférée) ?
- Quelles métadonnées peuvent changer sans réingestion (tags, statut) et lesquelles sont immuables (source, version, date de création) ?
Les métadonnées muables justifient un modèle de mise à jour partielle (update payload sans réembedder). Les métadonnées immuables doivent être correctes dès l'ingestion.
L'effet sur les métriques
Sur un projet récent (documentation interne d'un éditeur logiciel, ~80 000 chunks), l'ajout de filtres sur product_version et doc_type a réduit le taux de "mauvaise source citée" de 34% à 6% sur un jeu de test de 200 questions évaluées manuellement.
Le modèle d'embedding n'a pas changé. Le prompt n'a pas changé. Seule la capacité à exclure les candidats hors-scope avant la recherche sémantique a été ajoutée.
Ce que ça ne résout pas
Les filtres pré-retrieval nécessitent que les métadonnées soient disponibles au moment de la requête. Si l'utilisateur ne spécifie pas de version, le système doit soit inférer le contexte (via l'historique de conversation ou le profil utilisateur), soit rester ambigu.
L'inférence automatique des filtres à partir d'une question en langage naturel, "quelle est la procédure de migration ?", est un problème d'extraction d'entités supplémentaire. Il se gère avec un LLM ou un modèle classique de NER, mais ajoute une couche de latence et de fragilité.
La question ouverte reste donc : comment rendre le contexte de filtrage disponible de manière fiable à la requête quand l'utilisateur ne l'exprime pas explicitement ?