
Lorsque les utilisateurs de RAG posent des questions vagues : clarifiez une fois, apprenez la valeur par défaut
dans Enterprise Document Intelligence, une série qui construit un système RAG d’entreprise à partir de quatre briques : analyse, analyse de questions, récupération et génération.
Il étend l’article 6 (analyse des questions) au cas où la question n’est pas assez précise : demandez une clarification ciblée, apprenez la valeur par défaut de la réponse, restez silencieux la prochaine fois.

La brique d’analyse des questions transforme le texte de l’utilisateur en une ParsedQuestion tapée. Ce compagnon reprend le mode de défaillance que la brique nomme en une seule puce et le développe comme son propre modèle. Il manque à la question une information dont le système a besoin (quel document ? quelle page ? quel type de clause ?). La solution la moins chère consiste à demander. La bonne solution consiste à demander, puis à apprendre la valeur par défaut pour que le cas suivant soit silencieux. Deux schémas Pydantic et une boucle courte comblent l’écart.
La brique d’analyse des questions esquisse le pipeline : l’utilisateur tape du texte libre, l’analyse des questions produit une ParsedQuestion typée, le répartiteur achemine les champs saisis, la récupération étend le corpus. La puce à l’intérieur de ce croquis que ce compagnon développe : lorsque ParsedQuestion a un champ manquant ou de faible confiance, le système peut soit (a) déduire silencieusement une valeur par défaut, (b) refuser et demander à l’utilisateur, (c) faire les deux avec une politique apprise. La troisième option est le modèle de production. Ce compagnon expédie le contrat et un exemple de courtier travaillé.
1. Le mode de défaillance que l’article principal ne mentionne que
La question d’analyse de la brique couvre le chemin heureux. L’utilisateur tape « quelle est la franchise sur Acme Premier ? »l’analyseur de questions identifie l’entité (Acme Premier), l’intention (recherche de franchise), le champ de schéma à remplir (deductible_amount) et les itinéraires du répartiteur. La plupart du trafic de production ne suit pas la bonne voie.
Les échecs courants, sur un seul contrat téléchargé, par fréquence sur le trafic réel du courtier :
- Type de champ ambigu: « Quelle est la limite ? ». La plupart des contrats en comportent plusieurs : limite de couverture par événement, limite globale, sous-limite par risque, franchise sinistre. Le système doit deviner lequel.
- Portée de la page manquante: « Qu’est-ce que ça dit? » sur un document de 200 pages. Où dans le document, le résumé ? les exclusions ? le planning ? Le système peut répondre s’il sait où chercher.
- Portée de date ambiguë: « quelle est la franchise sur la couverture contenu de la maison ? » sur un contrat avec un ancien horaire et un avenant de renouvellement. Quel horaire s’applique ?
- Intention ambiguë: « la section garantie ». Le lire pour citer une clause, la résumer ou en extraire les conditions ? Chaque chemin utilise des briques différentes en aval.
- Entité implicite: « le preneur d’assurance » sur un contrat qui répertorie une entreprise assurée, un bénéficiaire et un assuré supplémentaire désigné. De quel rôle l’utilisateur parle-t-il ?
Chacune de ces questions concerne UN document que l’utilisateur a déjà épinglé. La version corpus du même échec (quel document ? quelle politique dans un portefeuille ?) se situe à une couche supérieure et est abordée à la fin de la section 6.
La ParsedQuestion typée contient les champs. Ce qui manque, c’est le boucle qui les remplit lorsque l’utilisateur ne l’a pas fait.
2. Le contrat à deux schémas Pydantic
Deux objets structurés font le travail. Le premier est un ClarificationRequest le système émet lorsqu’un champ est allumé ParsedQuestion est en dessous du seuil de confiance. La seconde est une ClarificationDefault le système stocke après chaque demande, de sorte que la question équivalente suivante reçoit une réponse sans la poser.
from datetime import datetime
from pydantic import BaseModel, Field
class ClarificationRequest(BaseModel):
"""Emitted when a ParsedQuestion field is below confidence threshold."""
target_field: str # field on ParsedQuestion to fill
question_to_user: str # plain-English question to show
candidate_values: list[str] # values the system can propose
proposed_default: str | None = None # the value the system would pick
proposed_default_reason: str | None = None # one-sentence why
audit: dict = Field(default_factory=dict) # request_id, model, prompt_version
class ClarificationDefault(BaseModel):
"""The learned answer, refreshed across requests."""
target_field: str # which ParsedQuestion field
doctype: str # broker_contract, invoice, ...
sub_conditions: dict = Field(default_factory=dict) # stratifying keys
candidate_votes: dict[str, float] # value -> weighted vote count
confidence: float # 0..1, drives ask/apply decision
sample_size: int
last_refreshed: datetime
Le premier objet est le demande à l’utilisateur. La seconde est ce que le système apprend de nombreuses demandesdonc il arrête de demander les plus faciles.
3. L’exemple du courtier travaillé
La boucle de clarification se déclenche une fois par demandepas une fois par tour de conversation. Chaque demande ci-dessous est un événement distinct au fil du temps : l’utilisateur télécharge un contrat, pose une question, le système demande des éclaircissements ou applique une valeur par défaut apprise, la réponse est expédiée. La prochaine demande peut avoir lieu quelques jours plus tard. C’est pas une conversation à plusieurs tours (V2 Bonus B04 couvre ce modèle séparément).
L’utilisateur est un expert en sinistre junior chez le courtier. Elle télécharge un nouveau contrat et tape « qui est l’assureur? » (qui est l’assureur ?). Le système traite la demande :
Cas 1 (première fois que le système voit cet utilisateur / ce type de contrat). Questions analysées target_field analyse comme insurer_name. Le système n’a aucun défaut appris pour où regarder. Il ouvre une ClarificationRequest :
Je vais regarder à la page 1, puisque c’est là que l’assureur est habituellement nommé sur le contrat d’un courtier. Est-ce le bon point de départ ?
L’utilisateur clique Oui. Le système lit la page 1, trouve l’assureur, répond. Un ClarificationDefault est écrit: pour target_field = insurer_name sur doctype = broker_contractla valeur par défaut source_page = 1 obtient un vote +1.
Cas 2 (une semaine plus tard, un contrat différent). Même forme de question : « Qui est l’assureur ? ». Le système lit ses valeurs par défaut apprises. source_page = 1 est la valeur par défaut recommandée avec un niveau de confiance de 0,78 sur 12 cas antérieurs. Le système applique la valeur par défaut en silence et répond. Aucune clarification n’a été tirée.
Cas 12 (un contrat où la page 1 est une page de garde, pas le corps). La page 1 n’a pas de nom d’assureur. Le système lit source_page = 1 à partir des défauts appris, échoue, détecte la panne (le champ de schéma revient nul), revient à demander :
La page 1 n’a pas nommé d’assureur sur ce contrat. Dois-je essayer la table des matières pour trouver où elle est nommée, ou souhaitez-vous me diriger vers une page ?
L’utilisateur dit essayez la table des matières. Le système lit la table des matières, trouve la section d’informations sur l’assureur, récupère et répond. Le défaut appris est désormais stratifié: source_page = 1 pour les contrats de courtier avec page_1_kind = body, source_page = TOC pour les contrats de courtier avec page_1_kind = coversheet. Le classificateur pour page_1_kind est une petite colonne savante.
4. Le mécanisme d’apprentissage de la valeur par défaut
La valeur par défaut apprise est un petit tableau, une ligne par (target_field, doctype, sous-conditions facultatives). Chaque ligne suit les valeurs candidates que le système a essayées, les votes de l’utilisateur (oui/non explicites ou implicites lorsque l’utilisateur accepte la réponse sans correction) et une bande de confiance.
Les règles de mise à jour :
- Accord d’utilisation explicite: l’utilisateur clique sur Oui sur une valeur par défaut proposée. Le nombre de votes par défaut augmente. La confiance augmente.
- Acceptation implicite: le système applique une valeur par défaut en silence, la réponse est correcte (signal d’évaluation en aval de la couche d’évaluation par mode de défaillance), aucune correction dans la conversation. Compté comme un soft +1.
- Désaccord explicite: l’utilisateur dit Non ou corrige. Le nombre de votes par défaut pour la valeur proposée diminue, le candidat nommé par l’utilisateur gagne.
- Détection des pannes: la valeur candidate par défaut renvoie la valeur null du schéma. Compté comme un signal de stratification et non comme une baisse de voix, car la valeur peut être correcte pour certains contrats et erronée pour d’autres.
La confiance détermine si le système demande ou s’applique simplement. En dessous de 0,6, demandez toujours. Au-dessus de 0,85, appliquez toujours silencieusement. Entre les deux, demandez de temps à autre de rafraîchir le signal.
from typing import Literal
from datetime import datetime
Signal = Literal["explicit_yes", "explicit_no", "implicit_ok", "failure"]
def update(default: ClarificationDefault, value: str, signal: Signal) -> ClarificationDefault:
"""One vote on a ClarificationDefault row, returns a new row."""
votes = dict(default.candidate_votes)
if signal == "explicit_yes": votes[value] = votes.get(value, 0) + 1.0
elif signal == "explicit_no": votes[value] = votes.get(value, 0) - 1.0
elif signal == "implicit_ok": votes[value] = votes.get(value, 0) + 0.5
# "failure": no vote change, only a stratification candidate
n_new = default.sample_size + 1
top = max(votes.values()) if votes else 0.0
confidence_new = max(0.0, top) / n_new
return default.model_copy(update={
"candidate_votes": votes,
"confidence": confidence_new,
"sample_size": n_new,
"last_refreshed": datetime.now(),
})
def gate(default: ClarificationDefault) -> Literal["apply", "ask_occasionally", "ask"]:
"""Per-row gate: confidence < 0.6 always asks; > 0.85 applies; in between, refresh."""
if default.confidence > 0.85: return "apply"
if default.confidence < 0.60: return "ask"
return "ask_occasionally"
La discipline qui compte : chaque clarification demandée et chaque défaut appliqué atterrit sur la surface d’audit. La clarification se déclenche sous la forme d’une ligne sur la couche de stockage query_log (à côté de la question de l’utilisateur, de la version du modèle, de la décision d’expédition). L’application par défaut enregistre à la fois la valeur par défaut utilisée et le ClarificationDefault ID de ligne du tableau à l’horodatage de la demande, donc la réponse d’audit à « Comment le système est-il arrivé à la réponse selon laquelle la page 1 était le bon endroit où chercher ? » est une jointure SQL. L’évaluation par mode de défaillance lit les mêmes lignes pour calculer l’exactitude de l’application par défaut par type de document.
5. La frontière avec les motifs adjacents
Le modèle de ce compagnon est pas un dialogue multi-tours de chatbot. Il s’agit d’une clarification ciblée, demandée une fois, puis le système répond ou apprend. La conversation ne porte pas la clarification à travers les tours ; le défaut appris le transporte à travers les demandes.
La limite :
- La question d’analyse de la brique: produit ParsedQuestion. Ce compagnon gère le cas où ParsedQuestion a des champs de faible confiance.
- La couche ontologie du corpus: les valeurs par défaut apprises se trouvent à côté des tables d’ontologie. Nouveau type de ligne :
clarification_defaults_dfaux côtésconcept_keywords_dfet amis. L’ontologie est le foyer canonique : les entrées sélectionnées par des experts sont pré-ensemencées ; les entrées apprises grandissent à leurs côtés et sont révisées. - La couche de stockage: les lignes de clarification et les lignes d’application par défaut sont jointes à
query_logparquestion_id. Aucune nouvelle infrastructure d’audit n’est nécessaire. - L’évaluation par mode de défaillance: l’ensemble d’évaluation comprend le taux de tir de clarification et l’exactitude de l’application par défaut par champ et par type de document.
6. Ce que cet article ne couvre pas encore
Trois préoccupations différées :
- Clarifications multi-champs. L’exemple ici concerne un champ manquant. Les cas réels en ont souvent deux (entité ET portée, intention ET champ). Le schéma évolue mais l’UX de poser trois questions d’affilée est mauvaise ; la bonne approche consiste à regrouper et à présenter un petit formulaire. Hors de portée pour la v1 de cet article.
- Utilisateurs contradictoires. Un utilisateur qui répond Oui à tout entraîne de mauvais défauts. Les valeurs par défaut nécessitent un signal de réputation par utilisateur ou un examen périodique de l’équipe. Le volume 4 (RAG agentique avec audit) contient l’analogue pour les écritures de mémoire agentiques ; la même forme s’adapte ici.
- Partage par défaut entre locataires. Si le courtier A et le courtier B expédient tous deux sur la plateforme, leurs valeurs par défaut apprises sont-elles partagées ? Le modèle d’isolement des locataires dit non par défaut. Une future extension pourrait permettre le partage facultatif des valeurs par défaut au niveau du type de document qui ne dépendent pas des données spécifiques au locataire.
7. Conclusion
La brique d’analyse de questions produit ParsedQuestion. Ce compagnon expédie la boucle qui remplit ses champs manquants : un Pydantic ClarificationRequest pour demander à l’utilisateur, un Pydantic ClarificationDefault pour apprendre de la réponse, une courte boucle qui décide par champ s’il faut demander ou postuler en silence. Le coût est de deux schémas et une colonne de table. L’avantage est que le système arrête de poser les questions faciles et ne pose que les questions ambiguës.
La boucle de clarification interagit également avec la récupération : une valeur par défaut sûre restreint la portée de la récupération avant l’exécution de la recherche, la réduisant souvent d’une recherche à l’échelle du corpus à une recherche sur une seule page. Le système combiné est ce qui rend le « Qui est l’assureur ? » la question atterrit en une seule étape sur un contrat que le système a vu à plusieurs reprises.
Sources et lectures complémentaires
Aligné sur la position de cet article. Anthropique modèles de conception d’agents le poste couvre le demander avant de deviner modèle comme l’une des primitives agents canoniques. La documentation de l’API OpenAI Assistants couvre le modèle de clarification structurée sous le appel de fonction titre.
Angle différent : La plupart des frameworks de chatbot 2026 utilisent par défaut l’inférence silencieuse lorsqu’un champ est manquant, sous prétexte que demander dégrade l’expérience utilisateur. La position prise par ce compagnon : l’inférence silencieuse est très bien seulement lorsqu’un défaut appris a la confiance nécessaire pour le justifier. Sans la table des valeurs par défaut apprises, l’inférence silencieuse n’est qu’une simple supposition. Plus tôt dans la série :
- Document Intelligence : introduction de la série. Ce que la série construit, brique par brique, et dans quel ordre.
- Baseline Enterprise RAG, du PDF à la réponse en surbrillance. Le pipeline à quatre briques de bout en bout : entrée PDF, réponse en surbrillance.
- Les intégrations ne sont pas magiques : les modes de défaillance prévisibles de la récupération RAG. Où l’intégration de la similarité gagne (synonymes, fautes de frappe, paraphrase), où elle échoue de manière prévisible (termes inconnus, négation, pertinence terme par rapport à la réponse) et comment l’utiliser de toute façon.
- Les rerankers ne sont pas magiques non plus : quand la couche Cross-Encoder en vaut le coût. Ce qu’un encodeur croisé ajoute par rapport aux intégrations de bi-encodeurs, mesuré et quand cela vaut la latence.
- RAG n’est pas un apprentissage automatique et la boîte à outils ML résout le mauvais problème. Pourquoi les balayages et les réglages de taille de bloc optimisent la mauvaise chose ; itinéraire par type de question à la place.
- Des regex aux modèles de vision : quelle technique RAG correspond à quel problème. Deux axes, complexité du document et contrôle des questions, qui choisissent la technique pour chaque cas.
- 10 erreurs RAG courantes que nous constatons constamment en production. Dix erreurs de production, organisées brique par brique, avec la solution pour chacune.
- Au-delà de extract_text : les deux couches d’un PDF qui déterminent la qualité RAG. La première moitié de la brique d’analyse : la nature, les signaux et le résumé du document.
- Arrêtez de renvoyer du texte plat à partir d’un PDF : la forme relationnelle dont RAG a besoin. La seconde moitié de la brique d’analyse : les tables relationnelles lues par chaque brique en aval.



