TL;DR : Un LLM est comme Don Quijote — tu ne peux pas le corriger, il est stochastique par nature. La solution n’est pas de guérir le fou, mais de placer un Sancho Panza déterministe à ses côtés. MDD se compose de deux couches : d’abord, tu étudies ses erreurs pour concevoir des outils qui les absorbent, puis tu le testes avec ces outils pour vérifier que tu n’as rien laissé passer. Conçois pour la folie, pas contre elle.
Cela faisait des semaines que je passais en revue des logs. 165 sessions d’un agent IA interagissant avec une CLI pour gérer des tâches. Plus de 500 erreurs. 370 tentatives de réessai. Des motifs qui se répétaient sans cesse : l’agent utilisait --status alors que l’option s’appelait --state. Il écrivait Todo alors que l’API attendait unstarted. Il passait urgent comme niveau de priorité, alors que le système n’accepte que des chiffres.
Ce qui était fascinant, c’est que chaque erreur avait du sens. Ce n’étaient pas des erreurs aléatoires ; c’étaient des erreurs plausibles. Exactement les erreurs que tu ferais si tu connaissais le domaine “grossièrement”, mais que tu n’avais jamais pris le temps de lire attentivement la documentation.
À un moment donné au milieu de cet audit, en regardant pour la énième fois un --status Done qui aurait dû être --state completed, je me suis rendu compte que je voyais un motif littéraire. Un motif vieux de 400 ans.
Don Quijote est un LLM
Réfléchis un instant. Don Quijote voit des moulins à vent et dit : “Ce sont des géants.” Ce n’est pas parce qu’il est idiot — loin de là, il est cultivé, il a beaucoup lu, il connaît les récits de chevalerie sur le bout des doigts. Le problème est que son modèle du monde est contaminé par des données d’apprentissage fictives. Il a lu tellement de romans de chevalerie que lorsqu’il voit quelque chose d’ambigu, il l’interprète en fonction de son training data. Moulins → géants. Troupeaux → armées. Auberges → châteaux.
Un LLM fait exactement la même chose. Il a vu des milliers d’APIs dans son apprentissage. Quand tu lui demandes d’en utiliser une qu’il ne connaît pas bien, il ne dit pas “je ne sais pas”. Il devine. Et il devine plutôt bien. Presque tout le temps. Juste assez bien pour te faire confiance. Mais lorsqu’il échoue, l’échec est plausible.
--status au lieu de --state. Parce que dans 60 % des CLIs qu’il a vues, l’option s’appelle --status.
Todo au lieu de unstarted. Parce que, sur l’interface graphique de l’outil, la colonne est étiquetée “Todo”. Le LLM a vu des captures d’écran dans la documentation. Il a lu des blogs. Il a déduit que si l’interface utilisateur indique “Todo”, alors l’API accepte “Todo”. Cela semble logique. C’est faux.
urgent au lieu de 1. Parce que dans la plupart des systèmes de gestion des priorités, urgent est une valeur valide. Qui conçoit une API où la priorité est un chiffre allant de 1 à 4 sans étiquettes textuelles ?
Chaque hallucination est une inférence raisonnable fondée sur des données incomplètes. Don Quijote n’est pas bête. Il est fou. Et tu ne peux pas guérir un fou.
Ce que Cervantes savait déjà
Cervantes ne tente pas de guérir Don Quijote. Ce qu’il fait, c’est de placer Sancho Panza à ses côtés.
Sancho n’est pas brillant. Il n’a pas lu les livres. Il n’a pas des visions grandioses. Mais il est déterministe. Quand Don Quijote dit “regarde ces géants”, Sancho lui répond : “Maître, ce sont des moulins.” Don Quijote ne l’écoute pas toujours, mais l’information est là. Le système est composé de deux couches : une couche stochastique qui génère des hypothèses (Don Quijote) et une couche déterministe qui les confronte à la réalité (Sancho).
C’est exactement l’architecture qu’il te faut lorsque tu travailles avec un LLM. Tu ne vas pas réussir à l’empêcher d’halluciner. C’est dans sa nature. Ce que tu peux faire, c’est ajouter des couches déterministes qui captent ses hallucinations avant qu’elles ne fassent des dégâts.
Et voici où intervient la méthodologie.
MDD : Madness Driven Design
MDD se compose de deux couches, et l’ordre est important.
Couche 1 : Archéologie a priori
Avant d’écrire une seule ligne de code, tu étudies la folie. Tu ne l’imagines pas — tu l’observes. Tu collectes des données réelles sur la manière dont le LLM interagit avec les outils existants et tu catalogues ses erreurs.
Dans mon cas, j’ai analysé 165 sessions d’un agent IA en interaction avec une CLI utilisée pour gérer les tâches d’une équipe de développement. Les chiffres :
| Catégorie d’erreur | Occurrences | Tentatives de réessai |
|---|---|---|
| Options inventées ou interdites | 275 | ~150 |
| Échappement JSON/GraphQL cassé | 25 | 80+ |
| Confusion de nomenclature | 40+ | 50+ |
| Opérations impossibles via la CLI | 60+ | 90+ |
| Output verbeux gaspillant des tokens | N/A | N/A |
Avec ces données, tu conçois l’outil afin qu’il absorbe les erreurs au lieu de les rejeter. Pour le dire simplement : le raisonnable s’adapte au fou, pas l’inverse.
Exemples concrets d’absorption :
Erreur du LLM → Conception de l’outil
───────────────────────────────────────────────
--status Done → --status est synonyme de --state
"Done" est normalisé à "completed"
--priority urgent → "urgent" est normalisé à 1
"high" → 2, "medium" → 3, "low" → 4
--no-pager → Flag ignoré silencieusement
(l’outil n’utilise jamais un pager)
Échappement cassé → Input via fichier ou stdin
dans les descriptions Jamais en ligne. Serde gère les erreurs.
Chaque ligne de ce tableau correspond à une décision de conception basée sur une erreur réelle observée. Pas sur une spéculation quant à “ce qui pourrait échouer”, mais sur un log concret indiquant “cela a échoué 40 fois en 165 sessions”.
La différence avec une conception classique est subtile mais importante. En conception conventionnelle, tu définis l’interface correcte et rejettes ce qui ne correspond pas. En MDD, tu définis l’interface correcte et toutes les interfaces incorrectes que ton utilisateur va essayer, puis tu les absorbes.
C’est comme concevoir une porte qui s’ouvre à la fois en poussant et en tirant. La porte “correcte” ne s’ouvre que dans une direction. La porte bien conçue s’ouvre dans les deux, car tu as observé que 40 % des gens poussent quand ils devraient tirer.
Couche 2 : Vérification a posteriori
Tu construis l’outil avec les défenses de la Couche 1 puis tu le testes. Tu donnes la nouvelle version de l’outil au LLM et observes quelles erreurs nouvelles il commet.
Si la Couche 1 est exhaustive, les erreurs nouvelles devraient être minimales. Si des erreurs apparaissent, c’est que ton design comporte des failles. Chaque nouvelle erreur devient un test de pénétration involontaire.
Lorsque j’ai appliqué cela à ma CLI, le LLM a inventé des choses que je n’avais pas observées lors de l’audit initial :
Un enum de tri inexistant. L’API permet de trier par
createdAtetupdatedAt. Le LLM a inventé une valeurprioritypour le même enum. Cela semble parfaitement logique — pourquoi ne pourrait-on pas trier par priorité ? Mais cette valeur n’existe pas dans le schema GraphQL.Un opérateur de filtre inexistant. Pour filtrer par état, l’API accepte
state.type.in. Le LLM a généréstate.id.or. Syntaxe cohérente, modèle plausible, complètement inventé.Une fonction de file locking issue d’un autre langage. Dans un projet en Rust, le LLM a suggéré
fcntl.flockpour verrouiller des fichiers. Or, cette fonction est du Python. En Rust, il faut utiliser le cratefs2.
Chacune de ces erreurs était plausible. Aucune n’était idiote. Et chacune révélait une faille : l’outil ne validait pas les valeurs de l’enum de tri, ne rejetait pas les opérateurs inventés, et sa documentation pour le crate de verrouillage de fichiers n’était pas incluse dans le contexte du modèle.
La Couche 2 boucle le processus. Tu ne te contentes pas de supposer que ton design est correct — tu le vérifies en mettant à l’épreuve l’agent le plus créatif (et le plus susceptible d’halluciner) que tu possèdes.
Le Sancho Panza Stack
La métaphore de Don Quijote et Sancho Panza n’est pas seulement une jolie analogie. C’est une architecture. En pratique, le “Sancho Panza” n’est pas une seule couche — c’est un stack composé de plusieurs couches déterministes, où chacune capture une catégorie de folie spécifique :
┌──────────────────────────────────────┐
│ LLM (Don Quijote) │ Génère des commandes plausibles
│ Stochastique, créatif │ mais potentiellement fausses
└──────────────┬───────────────────────┘
│ "--status Done --priority urgent"
┌──────────────▼───────────────────────┐
│ 1. Analyseur CLI (clap) │ Rejette les options inexistantes
│ Accepte les alias : --status→--state │ même pas en tant qu’alias
└──────────────┬───────────────────────┘
│ "--state Done --priority urgent"
┌──────────────▼───────────────────────┐
│ 2. Normalisation │ "Done"→"completed"
│ Équivalences d’état, priorités │ "urgent"→1
└──────────────┬───────────────────────┘
│ "--state completed --priority 1"
┌──────────────▼───────────────────────┐
│ 3. Validation │ "completed" est-il un état
│ Contre enums connus │ valide ? 1 est-il dans le bon range ?
└──────────────┬───────────────────────┘
│ state=completed, priority=1
┌──────────────▼───────────────────────┐
│ 4. Sérialisation (serde) │ Échappement correct par
│ Variables GraphQL, pas de chaînes│ construction. Pas de chaînes cassées
│ interpolées │ ni d’injections possibles
└──────────────┬───────────────────────┘
│ {"state":"completed","priority":1}
┌──────────────▼───────────────────────┐
│ 5. API + gestion des erreurs │ Si l’API rejette quelque chose,
│ Retry avec backoff, messages │ l’erreur est lisible et
│ explicites │ exploitable
└──────────────────────────────────────┘
Cinq couches. Chacune est déterministe. Chacune capture une catégorie d’erreur que le LLM risque de commettre. Le LLM n’a pas besoin d’être exact — il suffit qu’il soit approximativement correct ; ensuite, le stack fait le reste.
C’est comme un filtre de purification. En haut, tu introduis de l’eau sale (input stochastique du LLM) et en bas, tu obtiens de l’eau potable (requête GraphQL valide). Chaque couche élimine un type d’impureté. Aucune couche seule n’est suffisante. Ensemble, elles le sont.
MDD contre fuzz testing : une différence fondamentale
Si tu connais le fuzz testing, tu pourrais penser que “c’est la même chose”. Ça ne l’est pas.
| Fuzz testing | MDD | |
|---|---|---|
| Input | Aléatoire, mal formé | Plausible, cohérent, bien écrit |
| Objectif | Trouver des crashes | Trouver des erreurs sémantiques |
| L’input semble valide | Non | Oui — c’est précisément le problème |
| Exemple | \x00\xff\xfe comme nom | --priority urgent comme option |
Un fuzzer génère des données inutilisables et teste si ton programme crashe. MDD compose des inputs qui semblent valides mais sont en réalité factuellement faux. --priority urgent n’est pas un contenu aléatoire — c’est exactement ce qu’écrirait un humain sachant un peu mais pas tout sur le domaine. Un fuzzer ne générerait jamais cela ; c’est trop cohérent.
Même chose pour le mutation testing ou le chaos engineering. Ces méthodes modifient ton code ou perturbent ton infrastructure pour vérifier si les tests les détectent. MDD ne casse rien : il génère des contenus valides selon un modèle du monde différent. C’est la différence entre une attaque brute et une attaque par ingénierie sociale : l’un essaie toutes les combinaisons, l’autre te convainc d’ouvrir la porte.
Une idée actionnable
Tu n’as pas besoin de construire une CLI en Rust pour appliquer MDD. Ce modèle fonctionne avec n’importe quelle interface que peut utiliser un LLM :
Étape 1 : Observe la folie. Avant de concevoir (ou de reconcevoir) un outil, fais tester la version actuelle par le LLM et enregistre chaque erreur. Pas 5 sessions — fais-en 50. Les motifs apparaissent avec le volume.
Étape 2 : Classifie les erreurs. Sont-elles liées à la terminologie ? Au format ? À la sémantique ? Chaque catégorie nécessite une contre-mesure spécifique.
Étape 3 : Conçois pour absorber. Ne rejette pas --status avec une erreur obscure. Accepte --status comme synonyme de --state. Ne rejette pas urgent comme priorité. Normalise-le à 1. Ton utilisateur principal sera souvent un agent qui comprend le domaine à 80 %. Conçois pour cet 80 %.
Étape 4 : Teste et vérifie. Donne la nouvelle version de l’outil au LLM sans instructions spéciales. Chaque erreur nouvelle révèle une faille dans ta Couche 1. Répare et répète.
Si ton outil sera utilisé par des humains et des LLMs, les protections de MDD améliorent l’expérience pour les deux. Parce que les humains font les mêmes erreurs que les LLMs — seulement moins souvent et avec plus d’embarras.
Celui qui conçoit le Sancho est l’architecte
Un point de confusion fréquent mérite clarification. Ce n’est pas le LLM qui conçoit le Sancho Panza Stack. Le LLM est Don Quijote. C’est toi qui es Cervantes.
C’est toi qui observes les motifs de folie. C’est toi qui choisis quoi normaliser et quoi rejeter. C’est toi qui construis les couches déterministes. Le LLM peut t’aider à écrire du code — il est doué en développement — mais les décisions de conception te reviennent.
C’est la différence entre “j’ai demandé à mon IA de corriger ses propres erreurs” (cela ne fonctionne pas — elle continuera de les commettre) et “j’ai observé les erreurs de mon IA pour construire un système capable de les absorber” (cela marche — le système est déterministe).
Absolument pas question de laisser le LLM s’auto-corriger. Sa nature stochastique garantit qu’il répétera les mêmes erreurs en des variantes infinies. Ce dont tu as besoin, ce n’est pas un meilleur LLM — c’est un meilleur Sancho.
Ce qui compte vraiment
MDD n’est pas une méthodologie de test. C’est une méthodologie de conception d’outils. La question n’est pas “comment détecter que l’IA se trompe ?” mais “comment concevoir pour que les erreurs n’aient pas de conséquences ?”.
C’est la même philosophie que les guardrails sur les routes sinueuses. Tu n’empêches pas les gens de tourner mal — tu mets des barrières pour que tourner mal ne les tue pas. Tu ne corriges pas le conducteur — tu protèges la route.
Cervantes l’avait compris il y a quatre siècles. Il n’a pas essayé de guérir Don Quijote. Il lui a donné un Sancho Panza et a laissé l’histoire s’écrire.
Ta CLI, ton API, ton SDK — quoi que ce soit que ton LLM va manipuler — a besoin de son propre Sancho. Déterministe, obstiné, incapable d’hallucinations. Pas brillant. Pas créatif. Juste correct.
Conçois pour la folie. Le raisonnable s’adapte au fou.