Aller au contenu
Développement medium

RAG en production : sécurité, évaluation, observabilité

10 min de lecture

logo python

Un RAG qui marche sur votre poste n'est pas un RAG en production. Trois exigences l'en séparent. La sécurité : un utilisateur ne doit récupérer que les documents auxquels il a droit. L'évaluation : sans mesure, impossible de savoir si le RAG s'améliore ou se dégrade. L'observabilité : en production, il faut voir ce qui se passe à chaque requête. Ce guide traite les trois — le filtrage par droits au moment de la recherche, une évaluation par recall et exactitude sur un jeu de test, et la journalisation structurée des requêtes. Public visé : développeur qui veut exploiter un RAG, pas seulement le démontrer.

  • Filtrer la recherche par droits d'accès — le multi-tenant.
  • Construire un jeu de test et mesurer un RAG.
  • Distinguer recall du retrieval et exactitude de la réponse.
  • Journaliser chaque requête pour l'observabilité.
  • Intégrer l'évaluation dans une démarche d'amélioration continue.

Un RAG d'entreprise indexe des documents de sensibilités différentes : des notes publiques, des documents d'équipe, des données confidentielles. Un RAG naïf cherche dans tout — et peut donc faire fuiter, dans une réponse, un passage qu'un utilisateur n'avait pas le droit de voir.

La parade ne se joue pas dans le prompt — espérer que le modèle « ne révèle pas » est illusoire. Elle se joue à la recherche : on ne récupère, dès le départ, que les documents autorisés.

Le mécanisme est le filtrage par métadonnée, vu avec Chroma. À l'indexation, chaque document reçoit une métadonnée d'accès — une équipe, un niveau, un identifiant de client. À la recherche, on filtre sur cette métadonnée.

# L'utilisateur appartient à l'équipe « infra » :
# la recherche sémantique ne s'applique qu'à ses documents.
filtre = {"equipe": "infra"}
resultats = collection.query(
query_embeddings=vectoriser([question]),
n_results=k,
where=filtre,
)

C'est le principe du multi-tenant : plusieurs « locataires » — équipes, clients — partagent la même base vectorielle, mais chaque recherche est cloisonnée à un périmètre. Le document hors périmètre n'est jamais récupéré, donc jamais injecté dans le prompt, donc jamais dans la réponse.

« Le RAG a l'air de mieux marcher » n'est pas un constat exploitable. Pour piloter un RAG — décider si un nouveau chunking aide, si un changement de modèle régresse —, il faut des chiffres.

L'évaluation repose sur un jeu de test : une liste de questions de référence, chacune avec ce qu'on attend — la source qui devrait être trouvée, un fait qui devrait figurer dans la réponse.

JEU_TEST = [
{"question": "Comment conserver les données d'un conteneur ?",
"source": "volumes.md", "terme": "volume"},
{"question": "Comment des conteneurs communiquent par leur nom ?",
"source": "reseau.md", "terme": "réseau"},
]

Ce jeu se construit à la main, à partir de vraies questions d'utilisateurs. C'est un investissement — mais c'est lui qui rend toute mesure possible. Sans jeu de test, on pilote à l'aveugle.

Un RAG peut échouer à deux endroits : la recherche ne remonte pas le bon document, ou la réponse déforme un document pourtant correct. Deux métriques séparent ces deux causes.

Le recall du retrieval mesure la recherche : la source attendue figure-t-elle parmi les chunks récupérés ?

def recall_at_k(source_attendue: str, sources_trouvees: list[str]) -> bool:
"""Vrai si la source attendue figure parmi les sources récupérées."""
return source_attendue in sources_trouvees

L'exactitude de la réponse mesure la génération : le fait attendu est-il présent dans la réponse finale ?

def contient_terme(reponse: str, terme: str) -> bool:
"""Vrai si la réponse contient le terme factuel attendu."""
return terme.lower() in reponse.lower()

La distinction est opérationnelle. Un recall bas pointe vers le retrieval : chunking à revoir, recherche hybride à ajouter. Une exactitude basse malgré un bon recall pointe vers la génération : le prompt à durcir, le modèle à changer. Mesurer les deux séparément dit agir.

def evaluer(index, jeu_test, k=3):
"""Évalue le RAG sur le jeu de test : recall et exactitude."""
recalls, exactitudes = [], []
for cas in jeu_test:
resultat = repondre(index, cas["question"], k)
recalls.append(recall_at_k(cas["source"], resultat["sources"]))
exactitudes.append(contient_terme(resultat["reponse"], cas["terme"]))
n = len(jeu_test)
return {"recall_at_k": sum(recalls) / n, "exactitude": sum(exactitudes) / n}

Intégrer l'évaluation à l'amélioration continue

Section intitulée « Intégrer l'évaluation à l'amélioration continue »

Mesurer une fois ne sert à rien : la valeur vient de la répétition. L'évaluation devient utile quand elle tourne à chaque changement — nouveau chunking, autre modèle d'embedding, prompt modifié.

Le réflexe : faire de l'évaluation une étape automatisée. Avant de déployer une modification du RAG, on relance le jeu de test ; si le recall ou l'exactitude baissent, le changement est une régression — on ne déploie pas. C'est exactement le rôle d'un test en intégration continue, appliqué à la qualité du RAG et non plus seulement à son code.

Cette boucle — modifier, mesurer, comparer — est ce qui transforme un RAG figé en un RAG qui progresse.

En production, le RAG répond à des questions qu'on n'a pas anticipées. Pour comprendre son comportement réel — et repérer les dérives —, il faut journaliser chaque requête sous une forme exploitable.

def journaliser(question: str, resultat: dict) -> dict:
"""Construit un enregistrement structuré d'une requête."""
return {
"question": question,
"sources": resultat["sources"],
"latence_ms": resultat["latence_ms"],
"longueur_reponse": len(resultat["reponse"]),
}

Un journal structuré — un dictionnaire, sérialisable en JSON — vaut infiniment mieux qu'une ligne de texte libre : il s'agrège, se filtre, s'analyse. Trois informations méritent d'y figurer systématiquement : les sources récupérées (pour repérer un document jamais utile, ou au contraire surreprésenté), la latence (pour suivre les performances), et de quoi relier la requête à un éventuel retour utilisateur.

Agrégés, ces journaux révèlent ce qu'aucun test ne montre : les questions fréquentes mal couvertes, les pics de latence, les documents morts. C'est la matière première de l'amélioration en production.

SymptômeCause probableSolution
Une réponse cite un document interditFiltrage absent ou fait dans le promptFiltrer par métadonnée à la recherche
Impossible de comparer deux versionsPas de jeu de testConstruire un jeu de questions de référence
Recall basRetrieval défaillantRevoir le chunking, ajouter la recherche hybride
Exactitude basse, recall correctGénération défaillanteDurcir le prompt, changer de modèle
Dérive invisible en productionPas de journalisationJournaliser chaque requête en structuré
  • La sécurité d'un RAG se joue à la recherche : filtrer par droits, jamais dans le prompt.
  • Le multi-tenant cloisonne chaque recherche à un périmètre, sur une base partagée.
  • L'évaluation exige un jeu de test : des questions de référence aux réponses connues.
  • Le recall mesure le retrieval, l'exactitude mesure la réponse — deux problèmes distincts.
  • L'évaluation prend sa valeur répétée : à chaque changement, pour détecter les régressions.
  • La journalisation structurée des requêtes est la base de l'observabilité en production.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn