Sommaire
Un POC RAG prend un week-end. Un RAG en production prend six mois. L’écart, ce n’est pas le modèle ni la base vectorielle — c’est le chunking qui survit aux changements de schéma, les pipelines de fraîcheur qui ne perdent pas les tickets de demain, les suites d’eval qui détectent les régressions avant les utilisateurs, et l’observabilité qui pointe la bonne couche quand les réponses dérapent.
Cet article cartographie cet écart sur VCF et VKS. On utilise VCF Private AI Foundation comme runtime, mais les patterns sont transposables. L’objectif n’est pas « bâtir le RAG parfait » — c’est « bâtir un RAG qui survit au lundi matin ».
TL;DR
- Chunking et fraîcheur sont les points où les POCs s’effondrent — fixer ça avant de tuner le modèle.
- Une suite d’eval n’est pas négociable : golden queries, détection de drift, alerting.
- L’observabilité doit couvrir ingestion, retrieval, prompt et réponse — un seul trace ID de bout en bout.
Un chunking qui survit aux changements de schéma
Un chunker de POC, c’est quarante lignes : tu lis le fichier, tu découpes tous les 512 tokens avec un peu de recouvrement, tu embeddes, terminé. Ça démo très bien parce que le corpus de démo, c’est une douzaine de PDF propres. Un corpus de production n’est jamais propre. C’est un espace Confluence qui porte trois générations de gabarits de page, un export d’outil de ticketing où le contenu utile vit dans des champs personnalisés, des runbooks en Markdown, et des PDF constructeur dont les tableaux deviennent de la soupe de caractères dès qu’on les aplatit en texte. Le découpage à fenêtre fixe traite tout ça comme un flux indifférencié et détruit exactement la structure dont le retrieval a besoin.
Le chunking structurel inverse l’ordre des opérations. Tu parses d’abord le document en unités naturelles — un titre et son sous-arbre, un champ de ticket, une ligne de tableau à laquelle tu réattaches sa ligne d’en-tête, un bloc de code gardé entier — et seulement ensuite tu empaquettes ces unités dans un budget de taille. Une section qui rentre reste intacte. Une section qui déborde se coupe à la frontière structurelle suivante, jamais au milieu d’une phrase. Le prix à payer, c’est un parser par type de source : du travail ingrat qui se rembourse la première fois qu’on te pose une question dont la réponse est un tableau.
Ce qui te rattrape six mois plus tard, c’est l’identité des chunks. Si la clé primaire d’un chunk est un hash de son propre texte, alors n’importe quel changement en amont — une migration de gabarit, une montée de version du renderer, quelqu’un qui normalise les espaces — décale toutes les frontières et l’index entier a l’air neuf. Tu ré-embeddes des millions de vecteurs, tu crames un week-end de GPU, et tu es toujours incapable de dire quels chunks ont réellement changé. Dérive plutôt la clé d’une identité source stable, et garde le hash de contenu à côté, uniquement comme détecteur de modification.
# Un enregistrement de chunk. L'id survit au re-parsing ; le hash détecte les vraies édits.
id: "confluence:SPACE-KB:page-41927:sec:install/prereqs:2"
source_system: confluence
document_id: page-41927
section_path: ["Install", "Prerequisites"]
ordinal: 2
content_hash: "sha256:1f0c…" # on ré-embedde seulement si ça bouge
embedding_model: "bge-m3@2026-04" # les rebuilds d'index sont indexés là-dessus
authz_scope: ["group:platform", "group:sre"]
source_updated_at: 2026-06-30T09:12:00Z
Les métadonnées ne sont pas de la décoration, elles font la moitié de la qualité de retrieval. Système source, type de document, langue, horodatage de fraîcheur et — surtout — périmètre d’autorisation doivent vivre sur chaque chunk, parce que filtrer avant la recherche vectorielle est en général moins coûteux et toujours plus sûr que filtrer après. C’est là que le choix de ta base vectorielle se révèle : pgvector te donne des prédicats SQL ordinaires et des jointures avec des tables que tu possèdes déjà, tandis que Milvus et Weaviate exposent leurs propres moteurs de filtrage, avec leurs propres bizarreries de sélectivité. Aucun n’a tort ; ils échouent différemment sur les filtres très sélectifs, donc teste avec ta vraie distribution d’ACL plutôt qu’avec une distribution uniforme.
Les permissions vivent dans le chunk, pas dans le prompt
Demander au modèle de « ne répondre qu’à partir des documents visibles par l’utilisateur » n’est pas du contrôle d’accès. Applique le périmètre d’autorisation comme pré-filtre dur dans le retriever, et revalide les chunk IDs retournés contre l’ACL du système source avant qu’ils n’atteignent le prompt. Un paragraphe fuité dans une réponse générée reste une fuite — et il est bien plus difficile à auditer qu’un fichier fuité.
La taille des chunks mérite moins d’angoisse qu’on ne lui en accorde d’habitude. Des chunks larges portent plus de contexte et il en rentre moins dans le prompt ; des chunks courts retrouvent avec précision mais fragmentent le raisonnement. Les deux effets sont réels, les deux dépendent du corpus, et la réponse honnête c’est que tu dois balayer deux ou trois configurations contre le jeu d’eval décrit plus bas plutôt qu’adopter le chiffre recommandé par un article de blog. Ce qui se généralise, c’est la forme : retrouver petit, puis remonter à la section parente avant la génération.
Pipelines de fraîcheur sur VKS
Chaque corpus a un taux de péremption, et ce n’est presque jamais celui que le métier avait imaginé. La documentation produit bouge au trimestre ; les tickets d’incident bougent à l’heure. Un unique job nocturne qui ré-indexe tout est à la fois trop lent pour les tickets et absurdement coûteux pour la doc. Les pipelines de production font tourner deux chemins en parallèle.
Le chemin incrémental est événementiel : les webhooks ou les change feeds des sources atterrissent sur une queue, un worker Deployment sur VKS consomme par lots, ré-embedde uniquement les chunks dont le hash de contenu a bougé, et fait un upsert. Le chemin de réconciliation est un balayage planifié qui parcourt l’inventaire source et le compare à l’index. Ce balayage existe parce que le chemin événementiel perdra des messages — un webhook rejoué dans une queue pleine, une panne d’API, un système source qui cesse silencieusement d’émettre pour les espaces archivés. Sans le balayage, tu l’apprends des mois plus tard, par un utilisateur.
Les suppressions sont le tueur silencieux. Quand une page disparaît ou que ses permissions se resserrent, rien dans le flux d’événements ne te le dit forcément, et le chunk orphelin continue d’être retrouvé et cité. Écris un tombstone à chaque suppression observée, et laisse le balayage de réconciliation purger tout ce qui, dans l’index, n’apparaît plus dans l’inventaire source. Traite « chunks sans document parent vivant » comme une métrique d’alerting, pas comme une corvée de ménage.
# Balayage de réconciliation — volontairement ennuyeux, volontairement planifié.
apiVersion: batch/v1
kind: CronJob
metadata:
name: rag-reconcile-confluence
namespace: rag-ingest
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid # un balayage lent ne doit pas s'empiler
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: Never
priorityClassName: batch-low
nodeSelector:
workload: gpu-batch # pas le pool d'inférence
containers:
- name: reconcile
image: registry.internal/rag/ingest:2026.06
args: ["reconcile", "--source=confluence", "--tombstone-orphans"]
Deux détails spécifiques à VKS comptent vraiment. D’abord, l’embedding batch et l’inférence en ligne se disputent le même silicium, et une ré-indexation dévorera joyeusement la latence de queue de ton endpoint de chat. Garde-les sur des node pools séparés, ou sur des slices distinctes si tu as suivi l’approche GPU pooling, et donne aux workers batch une priorityClassName plus basse pour que la préemption aille dans le bon sens quand la capacité se tend. Ensuite, changer de modèle d’embedding invalide l’index entier — des vecteurs issus de deux modèles ne sont pas comparables, même si les noms se ressemblent. Traite ça en blue/green : construis une seconde collection à côté de la vivante, écris dans les deux pendant la transition, bascule seulement quand la suite d’eval confirme que le nouvel index est au moins aussi bon, et garde l’ancienne collection tant que tu n’es pas certain.
Enfin, donne un chiffre à la fraîcheur. Le corpus lag — le temps écoulé entre la modification d’un document source et le moment où elle devient cherchable — est la métrique qui rend cette couche lisible pour des non-ingénieurs. Publie un p95 par système source, alerte quand il déborde, et tu arrêteras de débattre pour savoir si « l’assistant est à jour ».
Evals : golden queries et drift
On n’améliore pas ce qu’on ne mesure pas, et le RAG est particulièrement doué pour cacher ses régressions. Un changement qui améliore la réponse moyenne tout en cassant discrètement une classe de documents ressemble à un progrès — jusqu’à l’escalade de l’équipe propriétaire de cette classe.
Commence par un golden set de vraies questions. Deux à trois cents suffisent largement, et elles doivent venir du trafic réel, des transcripts de support ou d’entretiens — pas d’un brainstorming, parce que les questions inventées sont toujours mieux formulées que les vraies. Pour chaque entrée, note la réponse attendue et, plus important encore, les documents sources qu’un humain compétent citerait. C’est ce second champ qui rend l’évaluation bon marché possible.
Ensuite, évalue à deux niveaux, parce qu’ils attrapent des défaillances différentes et coûtent des ordres de grandeur différents.
| Couche | Ce qu’elle attrape | Comment on la note | Cadence |
|---|---|---|---|
| Retrieval | chunks manquants ou hors sujet, filtres cassés, dérive d’index | recall@k et MRR contre les sources attendues — déterministe, sans appel modèle | à chaque pull request |
| Génération | hallucination, affirmations non sourcées, nuances oubliées, ton | grille notée par un modèle juge, revue humaine échantillonnée | nocturne et avant release |
| Échantillon prod | drift des questions, requêtes sans réponse, nouvelles classes de documents | clustering des requêtes réelles, taux de faible confiance et de pouces bas | hebdomadaire |
L’évaluation du retrieval mérite l’essentiel de ton énergie au début. Elle tourne en quelques secondes, ne demande aucun GPU, produit un chiffre qui ne tremble pas, et attrape en pratique la majorité des régressions — parce que quand une réponse RAG est fausse, le bon passage n’était le plus souvent pas dans le contexte. Branche-la dans le pipeline qui garde déjà tes changements de plateforme ; si tu as amorcé ta livraison comme décrit dans l’article GitOps sur VKS, le job d’eval n’est qu’un check requis de plus avant qu’un changement de chunker, de prompt ou de paramètre de retrieval puisse être mergé.
L’évaluation de la génération, c’est là qu’il faut être honnête. Les modèles juges sont utiles et imparfaits : ils dérivent quand le juge est mis à jour, ils récompensent la fluidité, et ils sont indulgents avec les omissions. Épingle le modèle juge et la grille comme des artefacts versionnés, re-note un sous-ensemble de calibration figé dès que l’un des deux change, et garde une revue humaine permanente sur un petit échantillon aléatoire. Traite le score du juge comme une alarme de régression, pas comme une vérité sur la qualité.
Segmente le score, ne livre jamais la moyenne
Un chiffre d’eval agrégé cachera très volontiers l’effondrement d’un système source derrière l’amélioration d’un autre. Reporte par type de source, par intention de requête et par langue — les corpus bilingues, en particulier, régressent de façon asymétrique, et une moyenne unique ne le montrera pas.
Le drift referme la boucle. Les questions de production s’éloignent du golden set à mesure que les utilisateurs apprennent ce que l’assistant sait faire et que le métier évolue. Échantillonne les requêtes réelles chaque semaine, clusterise-les, et promeus celles qui reviennent sans être représentées dans le jeu actuel. Chaque pouce bas accompagné d’une trace est une entrée candidate — c’est la source de couverture d’eval la moins chère que tu trouveras.
Observabilité de bout en bout
« La réponse était fausse » n’est pas un rapport exploitable. Le rendre exploitable demande un trace ID unique qui suit la requête depuis l’entrée HTTP jusqu’à la sortie, en passant par la réécriture de requête, le filtrage, la recherche vectorielle, le reranking, l’assemblage du prompt et la génération — avec l’état intermédiaire attaché aux spans plutôt que perdu.
Les attributs qui valent le coup sont précis : requête d’origine et requête réécrite, filtres réellement appliqués, chunk IDs retournés avec leurs scores, nombre de chunks survivants après reranking, nombre de tokens du prompt, modèle et révision, time to first token, durée totale de génération, tokens de sortie, et statut du cache. Logue les chunk IDs plutôt que le texte des chunks — tu peux réhydrater le texte depuis le store au moment de l’investigation, et tu évites de dupliquer du contenu potentiellement sensible dans un pipeline de logs avec une autre rétention et une autre audience.
Côté plateforme, c’est du travail ordinaire que tu as probablement déjà fait. La stack Prometheus et Grafana sur VKS couvre les métriques de serving ; les ajouts propres au RAG, ce sont le corpus lag par source, le p95 de retrieval séparé du p95 de génération, les tokens de prompt par réponse, la profondeur de queue GPU, et les derniers scores d’eval exportés en gauges pour qu’un dashboard affiche qualité et latence sur le même écran. Ce dernier point change les conversations : la qualité cesse d’être une anecdote.
Le coût on-premises se comporte différemment du coût en cloud public, et la nuance se rate facilement. Il n’y a pas de facture au token, donc l’unité qui a du sens c’est la GPU-seconde par question répondue et, derrière, les tokens de prompt par réponse. Le gonflement du prompt est le poste de coût silencieux dominant : chaque chunk retrouvé en trop rallonge le prefill, gonfle la latence et consomme une capacité que tu aurais pu vendre à un autre tenant. C’est pour ça qu’un reranker réduit souvent le coût total malgré l’appel modèle supplémentaire : échanger un petit modèle pas cher contre un contexte plus court est en général un bon deal — mais à quel point, ça dépend entièrement de tes longueurs de contexte et de ton matériel, donc mesure-le sur ton propre cluster avant de t’engager.
Le cache rapporte beaucoup et se rate subtilement. Un cache exact sur requêtes normalisées est quasi gratuit et absorbe le nombre étonnant de questions dupliquées que posent les vrais utilisateurs. Un cache sémantique est plus puissant et plus dangereux, parce qu’un hit approximatif renvoie une réponse plausible à une question légèrement différente. Quel que soit ton choix, la clé de cache doit inclure le périmètre d’autorisation et la version du corpus — sinon le premier hit qui traverse une frontière de permission devient un incident de données, et une entrée périmée continue de servir le document d’hier après ré-indexation. Corréler tout ça avec les signaux d’infrastructure sous-jacents, c’est précisément le rôle de la couche fondations d’observabilité.
Conclusion
La vérité inconfortable du RAG en production, c’est que presque rien du travail difficile ne se passe dans les parties que les gens trouvent intéressantes. Le modèle est une dépendance qu’on met à jour. La base vectorielle est un datastore qu’on exploite. Ce qui décide si le système sera encore digne de confiance dans un an, c’est de savoir si l’identité des chunks survit à une migration amont, si un document supprimé disparaît vraiment de l’index, si un mauvais changement est attrapé par le golden set avant un utilisateur, et si une réponse fausse peut être remontée jusqu’à un chunk précis en moins de cinq minutes.
Le pipeline avant le modèle
Chunking structurel, identité de chunk stable et périmètre d’autorisation par chunk décident de la qualité de retrieval bien avant le tuning de prompt.
La fraîcheur est un SLO
Ingestion événementielle plus balayage de réconciliation, suppressions marquées par tombstone, ré-embedding en blue/green, corpus lag publié par source.
Mesurer, puis optimiser
Eval de retrieval en CI, eval de génération jugée chaque nuit, un trace ID de bout en bout, et la GPU-seconde par réponse comme vraie unité de coût.
Si tu démarres aujourd’hui, construis dans cet ordre : golden set d’abord, eval de retrieval ensuite, tracing en troisième, et seulement après le tuning du chunking et des prompts. Ça paraît à l’envers — tu passes les deux premières semaines à bâtir de l’instrumentation au lieu de fonctionnalités — mais c’est la seule séquence où chaque changement suivant est vérifiable. Les équipes qui inversent finissent par passer ces deux mêmes semaines plus tard, de bien plus mauvaise humeur, à chercher lequel de onze changements non documentés a dégradé l’assistant.
Reçois le prochain par email
Les nouveaux articles et séries, envoyés à leur publication. Aucun autre courrier.



