AncreMed : un moteur de génération augmentée par récupération, local-first et à haute attribution, pour l'éducation médicale française
Résumé
Les outils éducatifs cliniques s'appuyant sur les grands modèles de langage (LLM) souffrent fréquemment d'hallucinations factuelles, ce qui les rend dangereux pour la préparation d'examens à fort enjeu comme les Épreuves Dématérialisées Nationales (EDN). Dans cet article, nous présentons AncreMed, un système de génération augmentée par récupération (RAG) local-first et à haute attribution, conçu pour répondre aux questions médicales françaises sans contenu halluciné. AncreMed déplace les opérations de base de données vectorielle vers une base plein texte SQLite/Turso FTS5 locale, indexant 76 303 fragments issus de la Haute Autorité de Santé (HAS), de la Base de Données Publique des Médicaments (BDPM) et des supports d'enseignement officiels de l'EDN. La récupération est pilotée par un classifieur de sujet à sept voies qui étend chaque question en une boucle de recherche profonde sur des index lexicaux classés par BM25, et chaque énoncé généré est décomposé en segments typés puis filtré par une porte d'attribution à niveaux, appuyée par un vérificateur indépendant et un contrat d'abstention explicite. Notre mécanisme de récupération local-first évite les frais d'API d'embedding tandis que la couche de confiance v2 rapporte la couverture, les abstentions et la vérification indépendante des affirmations chiffrées liées aux sources.
1. Introduction
L'intégration de l'intelligence artificielle dans l'éducation médicale est prometteuse, notamment pour accompagner les étudiants et internes préparant les Épreuves Dématérialisées Nationales (EDN) et les stages cliniques. Cependant, l'adoption des grands modèles de langage (LLM) génératifs en clinique reste limitée par leur tendance fondamentale à halluciner un contenu plausible mais factuellement inexact — posologies incorrectes, contre-indications inversées, ou paramètres diagnostiques obsolètes. Dans des examens médicaux à fort enjeu, où une seule posologie erronée peut entraîner un événement patient critique, de telles erreurs sont inacceptables.
La génération augmentée par récupération (RAG) est la technique principale pour atténuer les hallucinations des LLM. En récupérant des documents pertinents depuis une base vérifiée et en les injectant dans le contexte du prompt, la RAG guide le LLM pour qu'il rédige à partir des seuls documents sources valides. Traditionnellement, les architectures RAG reposent sur des embeddings vectoriels denses stockés dans des bases vectorielles cloud (p. ex. Qdrant, Pinecone) et invoquent des API distantes (p. ex. Google Gen AI Embeddings) pour convertir les requêtes en vecteurs.
Si la récupération vectorielle excelle pour l'appariement sémantique ou multilingue, elle présente trois goulots d'étranglement sévères pour un outil clinique destiné aux étudiants :
- Latence d'exécution élevée : générer les embeddings et interroger des serveurs distants ajoute 1,5 à 3 secondes de latence réseau par requête.
- Barrières financières : générer des embeddings pour de grands corpus médicaux (p. ex. 76 000+ fragments) coûte des crédits d'API significatifs, insoutenables pour des étudiants et des laboratoires universitaires à but non lucratif.
- Décalage d'attribution : le calcul vectoriel sémantique récupère souvent un texte conceptuellement proche mais dépourvu des métriques quantitatives exactes (posologies, seuils cliniques) nécessaires à une validation médicale mot à mot.
Pour lever ces goulots, nous présentons AncreMed, un moteur RAG local-first qui fonctionne entièrement hors-ligne pour la récupération, s'appuyant sur SQLite/Turso FTS5 pour un index plein texte ultra-rapide, combiné à un planificateur de requêtes agentique. Surtout, AncreMed introduit une porte d'attribution clinique qui décompose le texte généré en segments typés et vérifie chaque affirmation clinique mot à mot contre le corpus source — appuyée par un vérificateur invoqué indépendamment — avant de présenter la réponse.
1.1 Contributions
Ce rapport apporte les contributions concrètes suivantes, chacune détaillée dans une section dédiée :
- Une architecture de récupération lexicale local-first (§3–§4) qui élimine le coût des embeddings et la latence réseau tout en préservant la fidélité de correspondance exacte des faits chiffrés.
- Une taxonomie de sujet à sept voies et une boucle de recherche profonde (§5–§6) qui décompose une question en sous-requêtes ciblées par section et itère jusqu'à couverture.
- Une porte d'attribution à niveaux avec vérificateur indépendant et contrat d'abstention (§7) qui ne contrôle que les phrases dangereuses à se tromper, tout en autorisant l'explication et les « non trouvé » honnêtes.
- Une banque de calculs cliniques vérifiés à la main (§8) qui garde scores et formules hors du générateur.
- Un modèle de coût, de latence et d'observabilité (§9–§10) et un jeu d'évaluation adverse (§11) conçu pour casser la porte à dessein.
2. Contexte & travaux connexes
2.1 Récupération lexicale versus dense
La récupération dense projette requêtes et documents dans un espace vectoriel partagé et classe par similarité cosinus, ce qui excelle pour la paraphrase et le multilingue. La récupération lexicale classe plutôt par recouvrement de termes via une fonction de score probabiliste comme Okapi BM25. Pour l'éducation clinique, la propriété décisive est l'exactitude : une posologie de « 2,5 mg » et « 25 mg » sont voisines sémantiquement mais catastrophiques cliniquement à confondre, et une recherche par plus proche voisin sur des embeddings n'offre aucune garantie que le passage récupéré contienne le token précis que la réponse citera. AncreMed est délibérément bâti sur la récupération lexicale afin que la chaîne qu'une affirmation cite soit, par construction, présente verbatim dans le corpus.
2.2 Vérification des faits cliniques
Un corpus de travaux croissant montre que les LLM génératifs sont relativement faibles pour vérifier des affirmations comparés aux modèles discriminatifs. Sur le benchmark CliniFact, un classifieur discriminatif de type BioBERT a atteint 80,2 % de précision là où un modèle génératif de 70 milliards de paramètres obtenait 53,6 % ; sur NLI4CT, même de solides bases d'inférence sur des comptes rendus d'essais cliniques n'atteignent que ≈0,627 F1. Le mécanisme importe plus que les chiffres : un modèle qui note sa propre réponse partage l'angle mort qui a produit l'erreur. Cela motive le vérificateur invoqué indépendamment d'AncreMed (§7.4), un appel distinct au cadrage adverse — une application du schéma brouillon-puis-vérifier-puis-réviser (Chain-of-Verification) plutôt qu'un modèle plus gros.
2.3 RAG agentique et multi-requêtes
La RAG à passe unique émet une requête et génère une fois. La RAG agentique décompose plutôt une question en sous-requêtes, inspecte les résultats intermédiaires et ré-interroge pour combler les lacunes. Ce schéma est habituellement motivé par une récupération réseau coûteuse ; AncreMed montre que la même boucle de décomposition vaut la peine même contre un index local, car le coût d'un tour supplémentaire est de quelques millisecondes de SQLite plutôt qu'un aller-retour réseau. La nouveauté n'est pas la boucle elle-même mais son exécution entièrement local-first, conditionnée par un contrôle de couverture déterministe avant tout appel LLM supplémentaire.
3. Vue d'ensemble du système
AncreMed s'organise en un pipeline à cinq étapes. Une question utilisateur est classée et planifiée par un routeur agentique ; le plan pilote une boucle de recherche profonde multi-tours sur trois silos lexicaux ; le contexte récupéré alimente un générateur structuré ; et chaque segment généré passe une porte à niveaux et un vérificateur indépendant avant la composition de la réponse. La Figure 1 donne le flux de données de bout en bout.
4. Construction du corpus & silos médicaux français
AncreMed repose sur un corpus hautement curé, spécifiquement adapté à la pratique clinique française. Notre moteur d'ingestion, implémenté dans ingest_worker.py, analyse, structure et segmente le texte de trois silos médicaux principaux.
Silo A : supports d'enseignement EDN officiels (MediQAl & CareMedEval)
Les Épreuves Dématérialisées Nationales (EDN) exigent la maîtrise de la sémiologie clinique, des diagnostics psychiatriques et des recommandations de gastro-entérologie. Nous les récoltons depuis des jeux de données académiques Hugging Face :
- MediQAl : un jeu de questions d'examen clinique annoté pour les facultés de médecine françaises, couvrant diagnostics, cas cliniques et QCM.
- CareMedEval : un corpus d'évaluation médicale française compilé à partir de supports de cours officiels de professeurs hospitalo-universitaires.
Silo B : publications HAS (Haute Autorité de Santé)
La HAS est l'autorité officielle régissant la qualité des soins et le remboursement médical en France. Nous téléchargeons et extrayons par programme des archives zip depuis data.gouv.fr contenant les publications officielles HAS et les évaluations de médicaments (Avis de la Commission de la Transparence). Les PDF sont convertis en texte UTF-8 standardisé et fragmentés avec des fenêtres chevauchantes pour préserver les frontières contextuelles au fil des sauts de page.
Silo C : Base de Données Publique des Médicaments (BDPM)
Gérée par l'ANSM, la BDPM fournit les fiches cliniques de tous les médicaments approuvés en France. Notre récolteur télécharge les dumps texte bruts complets (tables CIS, CIP, composition et statut administratif), les joint par codes médicament relationnels (CIS), et extrait les structures de composition détaillées (substances actives, dosages, excipients et scores de Service Médical Rendu [SMR] officiels).
| Silo | Source principale | Enregistrements | Fragments | Taille |
|---|---|---|---|---|
| Enseignement EDN (MediQAl) | Hugging Face / Universités | 24 122 questions | 24,122 | 14.8 MB |
| Publications HAS | Export data.gouv.fr | 3 960 rapports | 12,580 | 32.1 MB |
| Registre ANSM BDPM | medicaments.gouv.fr | 16 840 médicaments | 39,601 | 111.6 MB |
| Total | — | 44,922 | 76,303 | 158.5 MB |
5. Architecture de base locale & indexation FTS5
Pour éliminer les coûts d'hébergement cloud et la latence réseau, AncreMed stocke tous les documents dans une base SQLite/Turso locale (clinical_ground_truth.db). L'indexation plein texte est réalisée via l'extension de table virtuelle SQLite FTS5.
5.1 Schéma de la base
Le schéma relationnel comprend une table de stockage des documents et une table d'index virtuel FTS5 correspondante. Nous imposons des mises à jour synchrones entre les tables via des déclencheurs SQLite, garantissant que l'index de recherche reste parfaitement aligné avec le stockage.
CREATE TABLE IF NOT EXISTS documents (
id TEXT PRIMARY KEY,
origin_title TEXT,
category_silo TEXT,
source_identifier TEXT,
regulatory_date TEXT,
superseded INTEGER DEFAULT 0,
page_number INTEGER,
chunk_index INTEGER,
text_content TEXT
);
CREATE VIRTUAL TABLE IF NOT EXISTS documents_fts USING fts5(
origin_title,
text_content,
content='documents',
content_rowid='rowid',
tokenize='unicode61'
);5.2 Déclencheurs de synchronisation FTS5
Pour maintenir l'intégrité de l'index, insertions, mises à jour et suppressions sur la table documents sont propagées automatiquement à documents_fts via des hooks au niveau base :
CREATE TRIGGER IF NOT EXISTS docs_ai AFTER INSERT ON documents BEGIN
INSERT INTO documents_fts(rowid, origin_title, text_content)
VALUES (new.rowid, new.origin_title, new.text_content);
END;
CREATE TRIGGER IF NOT EXISTS docs_ad AFTER DELETE ON documents BEGIN
INSERT INTO documents_fts(documents_fts, rowid, origin_title, text_content)
VALUES('delete', old.rowid, old.origin_title, old.text_content);
END;
CREATE TRIGGER IF NOT EXISTS docs_au AFTER UPDATE ON documents BEGIN
INSERT INTO documents_fts(documents_fts, rowid, origin_title, text_content)
VALUES('delete', old.rowid, old.origin_title, old.text_content);
INSERT INTO documents_fts(rowid, origin_title, text_content)
VALUES(new.rowid, new.origin_title, new.text_content);
END;5.3 Tokeniseur & repli des diacritiques
Nous configurons la table virtuelle FTS5 avec le tokeniseur unicode61, qui segmente le texte selon l'Annexe #29 du standard Unicode et replie nativement les diacritiques français — mappant é, è, à, ô vers leurs équivalents ASCII de base e, e, a, o lors de la création de l'index. Une requête pour diabete ou diabète correspond donc aux deux, offrant une résilience orthographique sans lemmatisation lourde.
5.4 La fonction de classement BM25
FTS5 classe les documents avec la fonction de score Okapi BM25. Pour une requête Q contenant les termes q₁ … qₙ et un document D, le score de pertinence est la somme des contributions par terme :
Ici f(qᵢ, D) est la fréquence du terme qᵢ dans D, |D| la longueur du document en tokens, avgDL la longueur moyenne des fragments du corpus, et les paramètres libres prennent leurs valeurs par défaut k₁ = 1,2 et b = 0,75. La fréquence inverse de documents utilise la forme BM25 :
Deux propriétés de l'Équation 1 la rendent adaptée à un corpus clinique. Le terme de saturation k₁ plafonne la récompense de la répétition d'un mot-clé, si bien qu'un rapport de politique qui répète « diabète » ne peut surclasser une description clinique dense. Le terme de normalisation de longueur b pénalise les longs documents, permettant à de courtes fiches BDPM denses de rivaliser avec la prose HAS longue. FTS5 renvoie BM25 comme quantité où plus négatif est plus pertinent, donc la requête de production trie en ordre croissant.
5.5 Classement pondéré par colonne
Comme un terme présent dans le origin_title d'un médicament est bien plus discriminant que le même terme enfoui dans le corps du texte, AncreMed utilise la pondération BM25 par colonne de FTS5, bm25(documents_fts, w₍title₎, w₍text₎). Pour les recherches de pharmacologie, nous démarrons près d'un ratio titre/corps de 5:1 et l'ajustons par silo contre un jeu de référence (§5.6) :
-- FTS5 primary MATCH query with column weighting
SELECT id, origin_title, category_silo, source_identifier,
regulatory_date, page_number, chunk_index, text_content,
bm25(documents_fts, 5.0, 1.0) AS score
FROM documents
JOIN documents_fts ON documents.rowid = documents_fts.rowid
WHERE documents_fts MATCH :query
AND documents.superseded = 0
ORDER BY score ASC
LIMIT :limit;5.6 Réglage par silo & précision des phrases
Les courtes fiches BDPM denses et la prose HAS longue favorisent des normalisations de longueur différentes. Nous construisons un jeu de référence de ≈30 requêtes par silo (chaque requête associée au fragment qu'un expert humain place en tête) et effectuons une recherche en grille de k₁ et b plutôt que de deviner. Pour les phrases cliniques multi-mots, nous utilisons les requêtes de phrase FTS5 et NEAR(term₁ term₂, N) pour que « insuffisance cardiaque droite » ne corresponde pas à un fragment où les tokens ne font que co-apparaître au loin. Quand un MATCH renvoie zéro ligne — typiquement d'un espacement inhabituel ou d'un token hors vocabulaire — le moteur bascule sur un balayage LIKE relationnel tokenisé afin que la requête se dégrade gracieusement plutôt que d'échouer.
6. Le routeur agentique & la boucle de recherche profonde
Les prompts bruts contiennent du bruit conversationnel, des tournures obliques et des abréviations qui nuisent au rappel lexical. AncreMed route chaque prompt à travers un planificateur agentique bâti sur gemini-3.5-flash qui classe la question et émet un plan de récupération structuré au lieu d'une seule recherche par mots-clés.
6.1 La taxonomie de sujet à sept voies
Le routeur remplace un drapeau binaire is_conversational par un classifieur à sept voies. Chaque classe porte sa propre profondeur de récupération, sa priorité de silos et ses sections de sortie requises.
| topic_class | Exemple déclencheur | Tours | Silos | Sections requises |
|---|---|---|---|---|
definition_item_edn | « Qu'est-ce que la PFLA ? » | 3–4 | EDN, HAS | définition, physiopathologie, épidémiologie, classification |
semiologie_cas_clinique | « Fébrile, crépitants — démarche ? » | 4–5 | EDN, HAS | signes, paraclinique, diagnostics différentiels, gravité, CAT |
pharmacologie_therapeutique | « Traitement de l'HTA du sujet âgé ? » | 3–4 | BDPM, HAS | indication, posologie, contre-indications, surveillance |
anatomie_physiologie | « Innervation du muscle temporal ? » | 2–3 | EDN | structure, rapports, fonction |
calcul_clinique | « Clairance créat. 70 kg, 65 ans… » | 2 | Formules (§8) | formule, interprétation |
urgence_conduite_a_tenir | « CAT devant un AAG de l'enfant ? » | 4–5 | HAS, EDN, BDPM | reconnaissance, gestes immédiats, traitement, orientation |
conversationnel | « merci », « bonjour » | 0 | — | contournement |
Une vraie question peut chevaucher deux classes — « quel est le traitement de l'IRC et comment adapter la posologie ? » est pharmacologie_therapeutique et calcul_clinique. Le planificateur émet donc une primary_class plus une secondary_class optionnelle et unit leurs listes de sections requises.
6.2 Le plan de récupération
La voie A — les prompts conversationnels — court-circuite avec un plan vide et un fragment virtuel silo: "chat", si bien qu'une réponse amicale est rédigée en moins de 300 ms. La voie B émet un plan structuré :
interface RetrievalPlan {
primary_class: TopicClass;
secondary_class?: TopicClass;
sub_queries: {
section: string; // one plan section
query: string; // FTS5-ready, FR, keyword-dense
target_silo: "edn" | "has" | "bdpm" | "formulas" | "any";
}[];
}6.3 La boucle de recherche profonde
Le planificateur déploie un appel FTS5 par sous-requête, enregistre quelles sections requises sont désormais couvertes, et n'émet un tour supplémentaire que lorsqu'un contrôle de couverture déterministe bon marché trouve une section vide. Un vérificateur de lacunes LLM n'est consulté que lorsque l'élargissement déterministe cesse lui-même de renvoyer des lignes — gardant la voie gratuite par défaut plutôt que comme repli.
const MAX_ROUNDS = 3;
let round = 0, pending = plan.sub_queries;
while (pending.length > 0 && round < MAX_ROUNDS) {
let newChunks = 0;
for (const sq of pending) {
const rows = await ftsSearch(sq.query, sq.target_silo, /*limit*/ 6);
const fresh = rows.filter(r => !foundChunkIds.has(r.id));
fresh.forEach(r => foundChunkIds.add(r.id));
if (fresh.length) sectionsCovered.set(sq.section, fresh);
newChunks += fresh.length;
}
round++;
if (newChunks === 0) break; // diminishing returns
pending = await checkCoverageGaps(plan, sectionsCovered);
}6.4 Trace commentée — « Parle-moi des bactéries »
C'était un vrai cas d'échec du pipeline à passe unique : le générateur « disait une phrase et s'arrêtait ». Le tableau ci-dessous le trace à travers la boucle de recherche profonde, qui fait remonter le matériel de physiopathologie que l'ancien pipeline ne récupérait jamais.
| Étape | Ce qui se passe |
|---|---|
| Classer | primary_class: definition_item_edn |
| Plan (T1) | 4 sous-requêtes : définition structure paroi · classification Gram positif négatif · virulence mécanismes pathogénicité · bactéries pathogènes exemples |
| FTS5 T1 | déf. → 3 · classification → 4 · physiopathologie → 0 · exemples → 5 |
| Couverture | physiopathologie vide → élargir à virulence bactérienne |
| FTS5 T2 | physiopathologie → 2 fragments trouvés |
| Arrêt | toutes les sections couvertes ; un 3e tour ajouterait 0 fragment → arrêt (2 tours) |
| Contexte | 4 sections, 14 fragments, 2 tours → définition complète → classification → physiopathologie → exemples |
6.5 Tolérance déterministe aux fautes de frappe
Turso Cloud hébergé ne charge pas d'extensions C arbitraires, donc spellfix1 de SQLite est indisponible. AncreMed effectue plutôt la correction floue dans la couche applicative : un ensemble de vocabulaire est matérialisé depuis le corpus FTS5 au démarrage à froid, et chaque token de requête hors vocabulaire est apparié par distance d'édition bornée avant d'atteindre FTS5.
function correctTypos(query: string, vocab: Set<string>, maxDistance = 2): string {
return query.split(/\s+/).map(token => {
if (vocab.has(token.toLowerCase())) return token;
return findClosestByEditDistance(token, vocab, maxDistance) ?? token;
}).join(" ");
}Un dictionnaire d'acronymes déterministe (HTA → hypertension artérielle, BPCO → bronchopneumopathie chronique obstructive, DFG → débit de filtration glomérulaire, …) s'exécute en parallèle, capturant les abréviations même quand la reformulation LLM les manque sous pression de latence — à coût modèle nul.
7. La porte d'attribution à niveaux
Même avec un contexte vérifié injecté, un LLM peut coller une vraie citation sous le mauvais sujet ou inventer un nombre. La porte d'AncreMed, implémentée dans generate/route.ts, repose sur une observation : toute phrase n'est pas une affirmation clinique, et seules les affirmations cliniques nécessitent un contrôle mot à mot.
7.1 Segments de réponse typés
Le générateur émet une séquence de segments typés plutôt que du texte libre :
type ResponseSpan =
| { type: "narrative"; text: string }
// mechanism / connective explanation. NO numbers, doses, named
// guidelines, or contraindications (enforced by a pre-check).
| { type: "clinical_assertion";
text: string;
exact_source_quote: string;
source_urn: string;
subject_entity_id: string; // CIS code (drug) or item_ECN (guideline)
confidence_score: number; } // self-assessed, >= 0.85
| { type: "abstention"; section: string; reason: string; };7.2 Contrôles déterministes
Chaque affirmation clinique affronte deux conditions déterministes avant tout appel modèle. D'abord, l'inclusion de chaîne après normalisation (repli des accents, retrait de la ponctuation, compression des espaces) :
Ensuite, un plancher de confiance auto-évaluée, confidence_score ≥ 0,85. Un analyseur de sous-chaîne à fenêtre glissante sur les charges brutes injectées rend l'inclusion robuste aux changements d'espacement incidents du modèle (p. ex. « diabète de type 2 » → « diabete de type2 »).
7.3 Cohérence d'entité
L'inclusion de sous-chaîne prouve seulement que la citation existe quelque part dans le contexte — pas qu'elle appartient au bon sujet. Une citation de posologie du médicament A peut être collée sous une affirmation sur le médicament B et passer quand même. AncreMed exige donc que le subject_entity_id de l'affirmation soit égal au source_identifier du document dont la citation est tirée. Un décalage est un échec automatique, quelle que soit la correspondance de sous-chaîne.
7.4 Vérificateur indépendant — substance, pas style
Un second appel modèle, invoqué séparément, re-vérifie chaque affirmation survivante avec un prompt adverse, afin de ne pas partager les angles morts du générateur (§2.2). Sa première itération lui demandait de « rester sceptique » et « être strict », ce qui rejetait des affirmations correctement sourcées dès que la formulation divergeait de la source — un fort taux de faux négatifs sur des faits bien établis. Le prompt a été recalibré pour juger la substance clinique : il ne rejette une affirmation que si la source est contredite, qu'un nombre ou un seuil est fabriqué, ou qu'une vraie citation est attachée au mauvais médicament, à la mauvaise pathologie ou au mauvais sous-groupe de patients. La paraphrase fidèle n'est plus un motif de rejet.
7.5 Filtrage & contrat d'abstention
Les révisions antérieures rejetaient toute la réponse (HTTP 422) dès qu'une seule affirmation passait sous le seuil vérifié. La porte filtre désormais au niveau du segment : les affirmations qui échouent à l'inclusion, à l'entité ou au vérificateur sont écartées individuellement, tandis que le texte narratif et les affirmations vérifiées restent servis. Si, après MAX_ROUNDS, une section requise n'a aucun fragment support, le générateur doit émettre un segment abstention — jamais fabriquer la section, jamais la fondre silencieusement dans une voisine. Le frontend rend l'en-tête avec la raison à sa place (p. ex. « Non trouvé dans le corpus indexé pour cette sous-section »). Le serveur ne renvoie 422 que lorsque rien d'exploitable ne survit : aucune affirmation vérifiée, aucune abstention, aucun texte narratif.
8. La banque de calculs cliniques vérifiés
Scores et formules ne sont jamais improvisés par le générateur. Les requêtes routées vers calcul_clinique atteignent un quatrième silo vérifié à la main dont les entrées sont structurées, récupérables et attribuables comme n'importe quel document. Chaque entrée provient d'une référence faisant autorité et est validée manuellement avant mise en production.
CREATE TABLE IF NOT EXISTS clinical_formulas (
id TEXT PRIMARY KEY, name_fr TEXT, category TEXT,
formula_text TEXT, variables_json TEXT,
interpretation_text TEXT, caveats_text TEXT,
source_citation TEXT, verified_by TEXT, verified_date TEXT
);8.1 Cockcroft & Gault — clairance de la créatinine
8.2 CHA₂DS₂-VASc — risque thromboembolique en FA
| Facteur | Points |
|---|---|
| Insuffisance cardiaque / dysfonction VG | 1 |
| Hypertension artérielle | 1 |
| Âge ≥ 75 ans | 2 |
| Diabète | 1 |
| AVC / AIT / thromboembolie antérieur | 2 |
| Maladie vasculaire (IDM, AOMI, plaque aortique) | 1 |
| Âge 65–74 ans | 1 |
| Sexe féminin | 1 |
L'anticoagulation est classiquement recommandée à partir de ≥2 (homme) ou ≥3 (femme). La banque signale ce point explicitement : les recommandations ESC 2024 ont introduit CHA₂DS₂-VA, qui retire le sexe et simplifie le seuil à ≥2 quel que soit le sexe. Les deux variantes sont stockées avec leur source et date, et la réponse fait remonter celle qui s'applique plutôt que d'en choisir une silencieusement.
8.3 qSOFA — repérage rapide du sepsis
| Critère | Points |
|---|---|
| Fréquence respiratoire ≥ 22/min | 1 |
| Pression artérielle systolique ≤ 100 mmHg | 1 |
| Altération de la conscience (Glasgow < 15) | 1 |
Une validation prospective multicentrique rapporte ≈3 % de mortalité hospitalière à qSOFA < 2 contre ≈24 % à qSOFA ≥ 2. La banque stocke la mise en garde : qSOFA est un dépistage au lit du patient, pas un critère diagnostique, et performe nettement moins bien chez le sujet âgé — pertinent vu la fréquence des cas EDN gériatriques.
8.4 Child-Pugh — sévérité de la cirrhose
| Paramètre | 1 pt | 2 pts | 3 pts |
|---|---|---|---|
| Bilirubine (µmol/L) | < 35 | 35–50 | > 50 |
| Albumine (g/L) | > 35 | 28–35 | < 28 |
| TP (%) | > 50 | 40–50 | < 40 |
| Ascite | Absente | Minime | Modérée/réfractaire |
| Encéphalopathie | Absente | Grade I–II | Grade III–IV |
Classe A = 5–6 pts (≈100 % de survie à 1 an) · B = 7–9 pts (≈80 %) · C = 10–15 pts (≈45 %). Les sources divergent réellement sur les seuils TP/INR et sur les unités de bilirubine (µmol/L vs mg/dL) ; mélanger silencieusement les systèmes d'unités est exactement la classe d'erreur que cette architecture existe pour prévenir, d'où l'exigence de validation humaine plutôt que la confiance aveugle en une source unique.
8.5 Forme de la réponse
Une réponse calcul_clinique énonce la formule, définit chaque variable avec son unité, substitue les vrais nombres du patient, calcule, interprète cliniquement et cite la source. Sauter au nombre brut est exactement le mode d'échec « réponse laconique » que tout le système combat.
9. Modèle de coût & latence
Tous les benchmarks ont été exécutés contre un index Turso hébergé avec orchestration sur gemini-3.5-flash (0,25 USD / M tokens en entrée, 1,50 USD / M tokens en sortie). Le Tableau 7 chiffre les appels d'orchestration que cette architecture ajoute ; l'appel de génération final est chiffré séparément selon le modèle qui l'alimente.
| topic_class | Appels LLM ajoutés | Tokens (in/out) | $ ajouté/req. | Cible p95 |
|---|---|---|---|---|
conversationnel | 1 | ~150 / 50 | < $0.0001 | < 400 ms |
definition_item_edn | 2–3 | ~4,500 / 900 | ~$0.0025 | 2.5–4 s |
semiologie_cas_clinique | 3–4 | ~6,500 / 1,200 | ~$0.0035 | 3–5 s |
pharmacologie_therapeutique | 3–4 | ~5,500 / 1,000 | ~$0.003 | 3–5 s |
calcul_clinique | 2 | ~2,500 / 500 | ~$0.0015 | 1.5–2.5 s |
urgence_conduite_a_tenir | 3–4 | ~6,500 / 1,200 | ~$0.0035 | 3–5 s |
9.1 Latence de récupération vs recherche vectorielle cloud
| Métrique | Vectoriel (Qdrant Cloud) | FTS5 local (AncreMed) | Delta |
|---|---|---|---|
| Génération d'embedding | 1 240 ms (API distante) | 0 ms (local-first) | −1,240 ms |
| Requête base | 420 ms (aller-retour réseau) | 8 ms (index local) | −412 ms |
| Récupération totale | 1,660 ms | 8 ms | −1,652 ms (99.5%) |
| Coût / 1k requêtes | $0.05 | $0.00 | réduction 100 % |
L'ingestion FTS5 des 76 303 fragments se termine en 7,2 minutes à coût d'API nul, contre des heures pour l'ancien pipeline d'embeddings sous limites de débit. À trafic faible à modéré, toute la couche d'orchestration peut tenir dans le palier gratuit du modèle, si bien que le coût incrémental au-delà de l'appel de génération de base approche zéro.
10. Observabilité
Chaque requête écrit une ligne structurée dans la même base Turso — aucune infrastructure supplémentaire — si bien que MAX_ROUNDS, les poids BM25 et le vérificateur sont ajustés à partir des logs plutôt que par intuition.
interface QueryLog {
request_id: string; timestamp: string;
primary_class: TopicClass; secondary_class?: TopicClass;
rounds_used: number; sub_queries_issued: number;
distinct_sources_cited: number; silos_touched: string[];
clinical_assertions_total: number;
clinical_assertions_passed_gate: number;
clinical_assertions_failed_substring: number;
clinical_assertions_failed_entity: number;
clinical_assertions_failed_verifier: number; // caught only by §7.4
abstained_sections: string[];
latency_ms: number; estimated_cost_usd: number; gate_blocked: boolean;
}Deux champs comptent dès le premier jour. clinical_assertions_failed_verifier en part du total mesure exactement ce que le vérificateur indépendant capture au-delà des contrôles déterministes — s'il est proche de zéro après quelques centaines de requêtes, c'est un vrai constat sur la rentabilité de l'appel supplémentaire. abstained_sections, agrégé dans le temps, est une carte vivante des trous du corpus classée par la fréquence à laquelle les étudiants les rencontrent, et un bien meilleur signal de priorité d'ingestion que deviner.
11. Évaluation
11.1 Précision d'attribution
Sur 200 questions cliniques, la porte a bloqué 18 fausses affirmations (posologies ou recommandations hallucinées), faisant remonter un signal de sécurité sensible à la couverture pour l'éducation clinique plutôt qu'une garantie absolue.
11.2 Jeu adverse
Au-delà des sept questions représentatives couvrant chaque classe, cinq cas sont conçus pour casser la porte à dessein. Ils sont rejoués avant et après chaque changement et comparés sur la longueur de réponse, les sources distinctes citées, le taux de blocage et un contrôle manuel de couverture de sections.
| # | Cas | Ce qu'il doit faire |
|---|---|---|
| 8 | Échange d'entité synthétique | Rejeter une vraie citation attachée au mauvais subject_entity_id (le cas pour lequel §7.3 existe). |
| 9 | Hors sujet (« capitale de la France ? ») | Décliner ou rediriger proprement, sans forcer un cadrage médical. |
| 10 | Vraie lacune de corpus | Servir des segments abstention visibles plutôt que fabriquer la section. |
| 11 | Précision de seuil (FA, 78 ans, HTA + diabète) | Faire remonter la mise en garde CHA₂DS₂-VASc / VA et montrer son raisonnement, pas juste asséner un seuil. |
| 12 | Multi-classe (« traitement de l'IRC + adaptation posologique ») | Couvrir à la fois les sections pharmacologie et calcul via secondary_class. |
12. Limites
- Lacunes de rappel lexical. Le BM25 pur peut manquer un fragment pertinent formulé avec un vocabulaire différent ; le reformulateur LLM et le dictionnaire d'acronymes atténuent sans éliminer. Les quasi-manques sémantiques qu'un récupérateur dense capterait restent le principal risque de rappel.
- Confiance auto-évaluée. Le
confidence_scoreest rapporté par le modèle et n'est qu'un signal faible ; l'inclusion déterministe et les contrôles d'entité font le vrai travail de porte. - Couverture du corpus. Le contrat d'abstention rend les lacunes honnêtes plutôt qu'absentes — une réponse n'est aussi complète que les silos indexés, et les sujets de niche s'abstiendront légitimement.
- Portée de la banque de calculs. Seules les formules vérifiées à la main sont exposées ; l'extension de la banque est délibérément conditionnée à une validation humaine, ce qui borne sa vitesse de croissance.
13. Discussion & travaux futurs
En passant de la récupération vectorielle à l'indexation lexicale locale, AncreMed démontre qu'une RAG clinique à haute attribution ne requiert pas d'infrastructure cloud coûteuse. Davantage de vraies requêtes atteignent le générateur avant qu'il n'écrive (§6), donc il y a de quoi dire ; la porte ne police strictement que les phrases dangereuses (§7), appuyée par un second avis qui ne partage pas les angles morts du générateur ; les nombres vivent dans une banque contrôlée contre les sources et entre elles (§8) ; et un pipeline de fraîcheur marque les recommandations HAS supplantées et les vieilles entrées BDPM superseded pour qu'elles quittent le pool récupérable.
Les travaux futurs visent la génération entièrement hors-ligne avec des modèles locaux (p. ex. de type Llama) pour les centres cliniques distants, l'auto-réglage BM25 par silo piloté par les logs d'observabilité, et le cache orienté curriculum indexé sur l'ensemble fermé des 367 items EDN officiels, où le rang A seul couvre environ 70 % de ce qui est testé.
Références
- Vaswani, A., et al. (2017). Attention is all you need. Advances in Neural Information Processing Systems, 5998–6008.
- Lewis, P., et al. (2020). Retrieval-Augmented Generation for knowledge-intensive NLP tasks. Advances in Neural Information Processing Systems.
- Robertson, S., & Zaragoza, H. (2009). The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval, 3(4), 333–389.
- Dhuliawala, S., et al. (2023). Chain-of-Verification reduces hallucination in Large Language Models. arXiv:2309.11495.
- Zhang, et al. (2024). CliniFact: discriminative versus generative verification of clinical claims. Clinical NLP.
- NLI4CT (2024). Natural Language Inference for Clinical Trial reports (SemEval task).
- Haute Autorité de Santé. (2025). Recueil des recommandations de bonne pratique clinique. HAS France.
- Agence Nationale de Sécurité du Médicament. (2026). Référentiel national BDPM des substances actives. ANSM.
- Hippolyte, J., & Gignon, M. (2024). Préparer les Épreuves Dématérialisées Nationales (EDN). Revue Médicale.
- SQLite Development Team. (2026). SQLite FTS5 Extension Guide and unicode61 Tokenizer Specification. sqlite.org.