
J'ai construit une démo pour reproduire une erreur célèbre de l'IA. Ma baseline a refusé de la commettre.
L'erreur que je n'arrivais pas à faire se produire
J'ai commencé ce build en voulant recréer une défaillance précise et bien documentée. L'IA d'appariement de patients lit une note clinique comme du texte, et confond donc des mots qui se ressemblent mais signifient des choses différentes en médecine. L'exemple canonique est limpide : un essai de phase III sur un anticoagulant exclut les patients ayant un antécédent de cathétérisme cardiaque, la note d'un patient mentionne une pose de cathéter veineux central, un outil d'appariement par similarité voit deux procédures de cathéter cardiovasculaire, les note proches, et exclut un patient qui était en réalité éligible. Des évaluations publiées confirment que de vrais modèles commettent exactement cette erreur (Fierce Biotech, 2025). Je voulais que ma démo la montre en train de se produire, puis que mon moteur l'attrape.
J'ai donc écrit une baseline équitable pour jouer le méchant. Similarité cosinus TF-IDF au niveau des entités, n-grammes de mots et de caractères de 3 à 5, une vraie méthode de similarité vectorielle. Je lui ai même offert une configuration généreuse et j'ai fixé son seuil de décision par validation croisée en sa faveur (ROC stratifié en 3 plis, J de Youden, graine 13, plafonné à t = 0.6932), parce qu'un homme de paille ne prouve rien. Puis j'ai lancé le cas du cathétérisme cardiaque et j'ai attendu l'exclusion injustifiée.
Elle n'est pas venue. La baseline a noté les deux expressions relatives au cathéter largement en dessous de son propre seuil issu de la validation croisée et a renvoyé éligible. L'erreur autour de laquelle j'avais construit toute la démo refusait simplement de se reproduire.
Je m'étais mis en tête de mettre en scène une défaillance célèbre et j'ai découvert que mon méchant honnête était trop faible pour la commettre.
La raison s'est révélée instructive, et je veux être précis à son sujet parce qu'il est facile d'en faire trop. Une baseline lexicale creuse ne produit pas cette fausse exclusion particulière. Il faut des plongements sémantiques denses pour rapprocher assez ces deux expressions pour déclencher le seuil. Ajouter un modèle de plongement lourd aurait fait exploser la démo en quelque chose qu'on ne peut pas lancer hors ligne en une seule commande, alors j'ai tranché : garder la baseline honnête et creuse, et arrêter de prétendre qu'elle commet un crime qu'elle ne peut pas commettre. Cette décision a réorganisé tout le morceau que je construis. Si vous voulez le lancer vous-même, il est sur veriprajna.com/fr/demos/moteur-de-raisonnement-d-eligibilite-aux-essais-cliniques.
Qu'est-ce qu'une voie centrale, au juste ?
J'ai tout de même conservé le cas du cathétérisme cardiaque, parce qu'il s'est révélé prouver quelque chose de meilleur qu'une erreur attrapée. Il prouve pourquoi la réponse de mon moteur est digne de confiance. Les deux concepts ici ont de vrais identifiants SNOMED-CT vérifiables. Le cathétérisme veineux central est 392230005. Le cathétérisme cardiaque est 41976001. Vous pouvez coller l'un ou l'autre dans n'importe quel navigateur SNOMED public et confirmer qu'ils siègent sur des branches différentes de la hiérarchie. Il n'y a aucun chemin is-a de l'un vers l'autre. Une voie centrale n'est pas un cathétérisme cardiaque, et seule une hiérarchie le sait.
C'est toute la thèse dans une seule arête de graphe. Un score de similarité ne peut pas représenter « is-a ». Il ne peut représenter que « ces chaînes se ressemblent », et se ressembler n'est pas la même chose que signifier la même chose. Quand mon moteur évalue l'exclusion « pas de cathétérisme cardiaque antérieur », il ne note rien. Il pose une question structurelle : le fait vérifié du patient est-il subsumé par le concept interdit ? Il parcourt l'ontologie, ne trouve aucun chemin de subsumption, et renvoie éligible avec une trace en trois étapes nommant les deux ID de concepts et l'arête de graphe qu'il a vérifiée.

Quand j'ai vu cette trace s'afficher pour la première fois, ce qui m'a frappé n'était pas le verdict. C'était le reçu en dessous. La case de la baseline sur le même écran montre un chiffre de similarité sans provenance et sans rien de reproductible. La case de mon moteur nomme les deux SCTID et la question is-a exacte qu'il a posée. Un régulateur peut déposer l'un de ces deux. L'autre est un chiffre avec un haussement d'épaules attaché. Ce contraste, et non une erreur attrapée, est ce que le cas du cathétérisme cardiaque rapporte réellement.
Le patient que l'outil d'appariement a réellement écarté
J'avais encore besoin d'un vrai patient perdu, alors je suis allé chercher où ma baseline honnête échoue vraiment, et je l'ai trouvé dans un seul mot : pas. Le dossier héros synthétique P-074 porte la ligne de note « Aucune preuve de diabète. » L'une des exclusions du protocole d'oncologie est « pas de diagnostic de diabète sucré. » La baseline vectorielle voit le jeton « diabète » assis juste à côté du « diabète » du critère et les fait correspondre à similarité 1.0. Un score parfait. Elle n'a aucun modèle de la négation, alors elle lit une phrase qui déclare le diabète exclu comme si elle le déclarait inclus, et elle exclut un patient qui était éligible.
C'est le patient que l'outil d'appariement écarte, et c'est le temps fort que j'attendais à l'origine du cas du cathétérisme cardiaque. La négation est l'endroit où une baseline creuse casse honnêtement, à son propre meilleur seuil, sans trucage.

Je n'arrête pas de penser à quel point cette défaillance est silencieuse. Pas de message d'erreur, pas de drapeau de faible confiance, aucun signal que quelque chose a mal tourné. Le score est 1.0, le plus haut possible, le plus confiant que le système puisse jamais être. La baseline n'est jamais plus certaine qu'au moment exact où elle a le plus tort. Un coordinateur qui passe en revue une file de ces cas n'a aucun moyen de savoir que cette correspondance parfaite particulière est un patient qui aurait dû être enrôlé. Multipliez cela sur un protocole et vous comprenez pourquoi 80 % des essais manquent leurs délais d'enrôlement (consensus de l'industrie, 2025), et pourquoi chaque échec de sélection coûte environ 1 200 $ en moyenne (Antidote.me, 2025).
La baseline n'a jamais été plus confiante qu'au moment exact où elle avait le plus tort. Ce n'est pas un bug qu'on peut faire disparaître en ajustant des paramètres. C'est une erreur de catégorie.
Pourquoi le verdict vit-il en dehors du modèle ?
J'ai pris tôt une décision d'architecture dont je pense maintenant que c'était la seule qui comptait : tenir le modèle de langage entièrement à l'écart du verdict. Il y a exactement une étape probabiliste dans tout le pipeline. Un modèle interchangeable par fournisseur, purement consultatif, lit la prose désordonnée et propose des faits candidats, chacun portant l'extrait verbatim d'où il a lu le fait et un ID de concept candidat tiré d'un petit vocabulaire fermé. C'est la seule chose qu'un modèle sache vraiment bien faire : lire. Il n'a pas de voix au chapitre sur qui est éligible.
Tout ce qui suit est du code déterministe que je peux auditer. Avant qu'un fait proposé n'atteigne une décision, un vérificateur adversarial le confronte à la note littérale avec trois contrôles : l'extrait est-il réellement présent, est-il nié, et le sujet est-il le patient plutôt qu'un membre de la famille. Le fait « Aucune preuve de diabète » échoue au contrôle de négation et n'atteint jamais le moteur. Sur le même dossier, « Antécédents familiaux de cancer du sein » échoue au contrôle de sujet, parce que cet antécédent appartient à un membre de la famille et non au patient, et il est signalé comme rejeté avec le contrôle échoué nommé.

Sur l'ensemble du gold set, ce vérificateur a rejeté 7 instances de faits, 3 mauvais faits distincts (une mention niée de diabète, une attribution d'antécédents familiaux de cancer du sein, et un médicament halluciné planté sans extrait à l'appui), répartis sur 4 des 13 exécutions de cas évaluées, tous avant qu'ils ne puissent toucher un verdict. Quand on me demande « comment faire confiance à ce que l'agent a tiré de mes notes », ce panneau est toute la réponse. Je ne vous demande pas de lui faire confiance. Je vous montre ce qu'il a proposé, ce qui a été écarté et pourquoi.
Ensuite, le verdict lui-même est du Python simple situé en dehors du cadre agent : un moteur de logique déontique qui évalue les interdictions, les exceptions temporelles et les exigences sur l'ontologie et un peu de calcul de dates. Un modèle ne peut pas outrepasser cette barrière, parce que le modèle n'est pas dans la pièce quand la barrière s'exécute. C'est aussi ce qui rend le moteur reproductible. Quand la logique est du code déterministe sur une ontologie fixe, relancer le même dossier produit la même réponse, octet pour octet, à chaque fois.
Le modèle lit. Il ne vote pas. Cette seule frontière est ce qui rend une relance octet-identique.
Le seul chiffre qui intéressait mon lecteur des opérations cliniques
J'ai passé des semaines à optimiser des métriques qui, j'ai fini par me l'admettre, ne font pas perdre le sommeil à l'acheteur. La précision de décision est un chiffre de classement. La personne responsable de la faisabilité chez un promoteur ou un CRO ne compare pas des scores de classement. Elle regarde un calendrier d'enrôlement glisser, et chaque jour de glissement coûte cher. Le rapport d'impact Tufts CSDD (2024) situe le coût d'un retard d'enrôlement à environ 800 000 $ par jour en ventes de prescriptions perdues, et plus élevé dans les aires thérapeutiques que touche cette démo : environ 840 000 $ par jour en oncologie et 1,4 M$ par jour en cardiovasculaire. La complexité des protocoles a grimpé de 139 % dans les procédures d'essai depuis 2005 (IQVIA, 2026), ce qui signifie plus de critères, plus de clauses, et plus d'endroits où un outil d'appariement textuel peut se tromper sur l'un d'eux.
Alors j'ai cessé de mener avec la précision et j'ai commencé à mener avec le chiffre qui correspond réellement à cette douleur : les patients éligibles que vous n'avez pas perdus. Sur un gold set étiqueté fixe de 13 cas tirés de 7 patients synthétiques sur 2 protocoles synthétiques, mon moteur perd 0 patient éligible. La baseline équitable en perd 3. Même jeu, même seuil fixé par validation croisée à l'avantage propre de la baseline.

Je veux être exact sur ce que ces chiffres sont et ne sont pas. Ce sont la sortie propre du harnais sur ce seul jeu fixe de 13 cas, pas une promesse en monde ouvert. Le 100 % est « 100 % sur ce gold set », jamais « toujours juste ». Je ne vais pas vous dire que TrialProof ne se trompe jamais, parce que je n'ai pas les données pour le dire et je ne croirais personne qui le dirait. Ce que je peux dire est plus étroit et, je crois, plus utile : sur ce jeu le moteur perd zéro patient éligible, chaque décision porte une trace reproductible, deux décisions se sont abstenues en sécurité avec NEEDS-REVIEW quand un examen de laboratoire ou un signe vital requis manquait au lieu de deviner, et relancer l'ensemble du jeu était octet-identique, 13 sur 13. Tous les patients, notes et protocoles sont des données synthétiques figées, aucun dossier réel nulle part. Vous pouvez regarder chacune de ces exécutions sur veriprajna.com/fr/demos/moteur-de-raisonnement-d-eligibilite-aux-essais-cliniques.
Le chiffre qui m'importe n'est pas la précision. Ce sont les patients éligibles que je n'ai pas écartés. Sur ce jeu, c'est zéro perdu contre les trois de la baseline.
Il y a aussi une forme réglementaire à cela, et je vais la nommer avec soin. Le guide FDA de janvier 2026 sur le Clinical Decision Support est le cadre pertinent pour une aide à l'appariement avec humain dans la boucle comme celle-ci. Chaque décision émise par le moteur peut s'exporter sous forme d'enregistrement CDISC SDTM IE, une ligne par patient et critère, portant le verdict, la trace de raisonnement, les ID de concepts et l'opération déontique. Ce n'est pas une autorisation et je n'en revendique aucune. C'est un alignement et une direction. Mais cela signifie que la trace n'est pas une commodité de débogage. C'est un artefact déposable, et elle existe par construction sur chaque décision plutôt qu'après coup.
Ce à quoi je reviens sans cesse
Je reviens sans cesse au moment où mon méchant a refusé de jouer son rôle, parce que cela a changé la question que je posais. Pendant trois ans le domaine a demandé comment rendre le modèle meilleur pour décider qui est éligible. De meilleurs prompts, un plus grand contexte, plus de retrieval, tout visant à rendre un système probabiliste assez digne de confiance pour trancher sur l'enrôlement d'un patient. J'ai passé le premier tronçon de ce build à l'intérieur de ce cadre aussi, essayant d'attraper un modèle dans une erreur pour pouvoir corriger le modèle.
Ce qui a enfin cliqué, c'est que c'était la mauvaise couche. Un score de similarité ne peut pas représenter « is-a », ne peut pas représenter « pas », et ne peut pas représenter « sauf si la thérapie a été achevée plus de douze mois avant la randomisation ». Aucune quantité de prompting n'ajoute cela, parce que ce ne sont pas des problèmes de langage. Ce sont des problèmes de logique. Alors le geste mature n'est pas de rendre le modèle digne de confiance. C'est de rendre la confiance inutile. Laisser le modèle faire la seule chose qu'il sache bien faire : lire la prose et proposer des faits avec l'extrait d'où il les a lus. Puis faire jeter par un vérificateur ce que la note ne soutient pas, et faire calculer le verdict par du code simple et auditable, sur une ontologie médicale.
L'éligibilité doit être calculée, pas prédite. Ce à quoi je ne m'attendais pas, en entrant, c'est que le bénéfice ne ressemblerait pas du tout à un benchmark. Elle ressemble à un reçu. Le même dossier donne la même réponse à chaque fois, la réponse nomme l'ID de concept et l'arête de graphe qui l'a décidée, et le chiffre qui fait réellement perdre le sommeil à un responsable de faisabilité tombe à zéro patient éligible écarté.
Et si vous préférez le regarder plutôt que de lire ma description, voici le tout qui tourne de bout en bout.
Voici donc la question que je n'ai pas cessé de retourner, et j'aimerais vraiment savoir comment vous y répondez. Quand l'enjeu est la chance réelle d'une personne d'entrer dans un essai, où voulez-vous que votre confiance vive : dans un modèle qu'il faut croire, ou dans du code que vous pouvez lire ?


