<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Uig</title>
    <description>The latest articles on DEV Community by Uig (@uig).</description>
    <link>https://dev.to/uig</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F236854%2Fa4efca54-91b5-4fca-9e18-1319e77b0faf.png</url>
      <title>DEV Community: Uig</title>
      <link>https://dev.to/uig</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/uig"/>
    <language>en</language>
    <item>
      <title>1,5 mètre : le jour où mon robot a refusé de réparer la machine</title>
      <dc:creator>Uig</dc:creator>
      <pubDate>Wed, 28 Oct 2026 17:04:54 +0000</pubDate>
      <link>https://dev.to/uig/15-metre-le-jour-ou-mon-robot-a-refuse-de-reparer-la-machine-2l5</link>
      <guid>https://dev.to/uig/15-metre-le-jour-ou-mon-robot-a-refuse-de-reparer-la-machine-2l5</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp9gobf3wj1w0803zxxlh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp9gobf3wj1w0803zxxlh.gif" alt="mon robot répare la machine" width="684" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Je tire le levier, la machine casse. Mon robot s'approche, pose un diagnostic et la répare. Quatre lignes dans un terminal. Trois jours pour en arriver là.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1,5 mètre
&lt;/h3&gt;

&lt;p&gt;Mon robot était planté là, à 1,5 m pile de la machine. Il l'inspectait. Il avait son diagnostic. Et il ne la réparait pas. Jamais.&lt;/p&gt;

&lt;p&gt;Pas de crash. Pas d'exception. Pas une ligne d'erreur. Juste un robot en contemplation devant une machine en panne, pour l'éternité.&lt;/p&gt;

&lt;p&gt;La règle était simple : à 1,5 m ou moins, le robot peut s'occuper de la machine. Sauf que je l'avais écrite deux fois, dans deux états différents, et je me suis planté sur le signe. D'un côté &lt;code&gt;&amp;lt;=&lt;/code&gt;, de l'autre &lt;code&gt;&amp;gt;=&lt;/code&gt;. Deux versions d'une même règle, qui ont divergé pile là où personne ne regarde : sur la borne.&lt;/p&gt;

&lt;p&gt;Je l'ai repéré en relisant le code pendant une revue, puis reproduit avec un test à 1,5 m pile. La correction : la règle n'existe plus qu'à un seul endroit, et le test reste posé sur la borne, comme un vigile.&lt;/p&gt;

&lt;p&gt;Deux copies d'une même règle finissent toujours par se fâcher.&lt;/p&gt;

&lt;h3&gt;
  
  
  Un robot, une machine, un levier
&lt;/h3&gt;

&lt;p&gt;Ce robot est le premier habitant d'un monde que je construis. Un monde où des agents décident. À terme, ce monde sera mon laboratoire. Une capsule temporelle où ce que j'apprends et ce que je construis restent au même endroit. Quelque chose qui m'appartient, mais que je partage. Avec des habitants dedans.&lt;/p&gt;

&lt;p&gt;Pour l'instant, tout se passe dans le terminal, et rien n'est laissé au hasard. Le moteur est déterministe, et c'est voulu. Plus tard, un LLM proposera les actions du robot, et le moteur décidera de ce qui est permis. Le LLM propose, le moteur dispose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pourquoi je m'inflige ça
&lt;/h3&gt;

&lt;p&gt;J'ai un cimetière de side projects. Vous aussi, probablement. Et avec la GenAI, les idées germent vite et pourrissent tout aussi vite.&lt;/p&gt;

&lt;p&gt;Celui-ci, je le construis lentement. À contre-courant d'une industrie qui consomme toujours plus, toujours plus vite. Et en ralentissant, j'ai retrouvé un truc que je croyais perdu : le plaisir de coder. Chaque ligne. Chaque test.&lt;/p&gt;

&lt;p&gt;Il réunit mes trois obsessions du moment : le creative coding (Three.js, shaders, animation), les agents IA (LLM, mémoire, tool calling) et l'IA de jeu (PNJ, simulation, comportements). Bref : des LLM-powered NPCs dans un monde 3D.&lt;/p&gt;

&lt;p&gt;Je viens de PHP. Puis Black October est arrivé : deux RCE critiques au cœur du langage (&lt;a href="https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/" rel="noopener noreferrer"&gt;CVE-2026-109618 et CVE-2026-109619&lt;/a&gt;), en plein raz-de-marée de rapports de sécurité assistés par IA. J'ai tourné ma veste et je suis passé à Python. J'y reviendrai dans un article dédié.&lt;/p&gt;

&lt;h3&gt;
  
  
  Une classe de plus, évidemment
&lt;/h3&gt;

&lt;p&gt;Revenons à nos moutons.&lt;/p&gt;

&lt;p&gt;Mon robot passe par quatre états : au repos, en déplacement, en inspection, en réparation. Pour coder ses transitions, deux options.&lt;/p&gt;

&lt;p&gt;La table de transitions : un dictionnaire &lt;code&gt;(état, événement) → handler&lt;/code&gt;. Plus proche de ma spec, plus simple à tracer. Raisonnable.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;État initial&lt;/th&gt;
&lt;th&gt;Événement&lt;/th&gt;
&lt;th&gt;Précondition&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;État final&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;idle&lt;/td&gt;
&lt;td&gt;machine.broke&lt;/td&gt;
&lt;td&gt;Robot à plus de 1,5 m de la machine&lt;/td&gt;
&lt;td&gt;move_to vers le POI de la machine&lt;/td&gt;
&lt;td&gt;moving&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Ma spec pour modéliser les états du robot&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Le state pattern : une classe par état.&lt;/p&gt;

&lt;p&gt;J'ai pris le state pattern. Évidemment. Presque dix ans de PHP, ça ne se soigne pas en une semaine : quand on me tend une classe de plus, je la prends.&lt;/p&gt;

&lt;p&gt;Ma justification officielle tient quand même la route : la logique de chaque état va grossir. D'autres états arriveront, comme &lt;code&gt;thinking&lt;/code&gt; quand le LLM réfléchira. Un nouvel état, une nouvelle classe, sans toucher aux autres.&lt;/p&gt;

&lt;p&gt;Une classe par état, donc. Et mon cerveau PHP a enchaîné tout seul : une classe par fichier. Des années de PSR-4, ça laisse des traces. En PHP, l'autoloader fait correspondre chaque classe à un fichier : &lt;code&gt;App\Robot\IdleState&lt;/code&gt; vit dans &lt;code&gt;src/Robot/IdleState.php&lt;/code&gt;, point. Alors quand je relis &lt;code&gt;robot_fsm.py&lt;/code&gt; avec ses huit classes en 152 lignes, j'ai des sueurs froides.&lt;/p&gt;

&lt;p&gt;Sauf qu'en Python, le fichier, c'est le module. Et un module regroupe ce qui va ensemble : aucune règle n'impose une classe par fichier, pas même la PEP 8. Huit classes qui parlent toutes de la même machine à états, c'est un module cohérent, pas un dépotoir. J'ai quand même sorti &lt;code&gt;Action&lt;/code&gt; de &lt;code&gt;events.py&lt;/code&gt;, parce qu'une action n'est pas un événement. Le reste attendra que ça fasse vraiment mal.&lt;/p&gt;

&lt;p&gt;Excusez la digression, mais le prix à payer pour le choix du state pattern : si chaque état décide dans son coin, la trace des décisions s'éparpille. Exactement ce que je veux éviter, puisque tout le projet repose sur des décisions qu'on peut relire. Alors chaque état renvoie sa décision, et un seul endroit, la machine à états, l'applique et la journalise. Les états proposent, la machine dispose. Ça ne vous rappelle rien ?&lt;/p&gt;

&lt;p&gt;Verdict quand &lt;code&gt;thinking&lt;/code&gt; arrivera. Si je me suis trompé, vous le lirez ici.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quatre claques Python
&lt;/h3&gt;

&lt;p&gt;Python n'a pas de mot-clé &lt;code&gt;interface&lt;/code&gt;. J'ai cherché. Pour donner un contrat à mes états, j'ai atterri sur &lt;code&gt;ABC&lt;/code&gt; et &lt;code&gt;@abstractmethod&lt;/code&gt; : une classe de base abstraite, &lt;code&gt;RobotStateHandler&lt;/code&gt;, et une méthode &lt;code&gt;handle&lt;/code&gt; que chaque état doit implémenter. Sauf que le contrat ne se vérifie qu'à l'instanciation. Oubliez &lt;code&gt;handle&lt;/code&gt; dans une sous-classe, et Python vous laisse la déclarer tranquillement ; il ne râle qu'au moment où tu essaies de créer l'objet. PHP, lui, refuse la classe dès sa déclaration.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;default_factory&lt;/code&gt; attend une fonction. Avec &lt;code&gt;default_factory=list&lt;/code&gt;, mon robot est devenu une liste vide. Pas un robot cassé : une liste. Vide. Et Python n'a rien dit, parce qu'il ne vérifie pas les annotations de type à l'exécution.&lt;/p&gt;

&lt;p&gt;Deux de mes tests portaient le même nom. Le second écrasait le premier, en silence. Un test qui passe parce qu'il n'existe plus. C'est ruff qui a cafté.&lt;/p&gt;

&lt;p&gt;Les bugs vivent sur les bornes, alors mes tests y vivent aussi : 1,5 m pile pour la distance, et 0, 69, 70, 89, 90 et 100 pour les seuils d'usure du diagnostic.&lt;/p&gt;

&lt;p&gt;J'ai noté une vingtaine d'autres notions. Je les garde pour une série « Un dev PHP apprend Python ».&lt;/p&gt;

&lt;h3&gt;
  
  
  La suite
&lt;/h3&gt;

&lt;p&gt;Prochaine étape : tirer le levier depuis une page web et voir le robot réagir en direct.&lt;/p&gt;

&lt;p&gt;Le code est sur GitHub : &lt;a href="https://github.com/Arawnn/the-little-workshop" rel="noopener noreferrer"&gt;github.com/Arawnn/the-little-workshop&lt;/a&gt;. Et vous, vous faites quoi en ce moment ?&lt;/p&gt;

</description>
      <category>french</category>
      <category>python</category>
      <category>gamedev</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>L'illusion de l'auto-citation</title>
      <dc:creator>Uig</dc:creator>
      <pubDate>Mon, 19 Oct 2026 20:18:16 +0000</pubDate>
      <link>https://dev.to/uig/lillusion-de-lauto-citation-5cb9</link>
      <guid>https://dev.to/uig/lillusion-de-lauto-citation-5cb9</guid>
      <description>&lt;p&gt;&lt;strong&gt;4 citations sur 4 vérifiées. 6 valeurs justes sur 24. Les valeurs existent bien, mais pas à la bonne place !&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexte
&lt;/h2&gt;

&lt;p&gt;Je suis en train de construire un petit package PHP pour analyser mes factures de climatisation. Les prix augmentent, mais difficile de savoir de « combien » précisément. Exemple, avec les chiffres d'une de mes vraies factures :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// (prix actuel - prix contrat) / prix contrat
((0,07405888 - 0,06178300) / 0,06178300) × 100 = 19,87 %  -&amp;gt; +20 % sur le kWh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voici le flux du script de démo :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo59sz9qth60ry3e62aaw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo59sz9qth60ry3e62aaw.png" alt=" " width="800" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;L'idée : fournir au LLM, ici un modèle local (gemma4:e2b) qui tourne avec Ollama sur mon Mac, trois ingrédients :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;un prompt ;&lt;/li&gt;
&lt;li&gt;l'extraction linéarisée d'une facture PDF ;&lt;/li&gt;
&lt;li&gt;le schéma JSON, passé via le paramètre &lt;code&gt;format&lt;/code&gt;, pour obtenir une réponse toujours structurée de la même façon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Je demande aussi au modèle, pour chaque ligne, une &lt;strong&gt;citation&lt;/strong&gt; : la ligne de la facture d'où il a tiré ses valeurs, copiée mot pour mot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ACS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"base"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"18,34"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"citation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ACS 2.475,00 0,00740931 €/L 21,00 18,34 € 3,85 € 22,19 €"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le code vérifie ensuite que la citation existe dans le texte d'origine de la facture, puis que chaque valeur figure dans la citation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tout est vert… puis plus rien
&lt;/h2&gt;

&lt;p&gt;Avec une première version du prompt, les valeurs sont presque toutes justes, mais les citations ne servent à rien : le modèle n'y met que le libellé (« ACS ») au lieu de la ligne complète.&lt;/p&gt;

&lt;p&gt;Je modifie donc mon prompt pour préciser ce qu'est une citation :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;La citation est la ligne complète du tableau, telle qu'imprimée, et l'unité est celle du prix (€/L), pas celle de la quantité.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et hourra : 24/24 pour les valeurs (6 champs × 4 lignes) et 4/4 pour les citations. C'est plié, terminé, bonsoir !&lt;/p&gt;

&lt;p&gt;Mais ensuite, je me plante dans la normalisation des espaces : j'envoie au LLM le texte extrait &lt;em&gt;normalisé&lt;/em&gt;. Et là, c'est la catastrophe : mon score de valeurs tombe à 6/24, alors que les citations sont toujours correctes, 4/4.&lt;/p&gt;

&lt;p&gt;Toutes les valeurs sont présentes, mais pas dans les bons champs. Exemple sur la ligne ACS :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Champ&lt;/th&gt;
&lt;th&gt;Valeur attendue&lt;/th&gt;
&lt;th&gt;Valeur extraite&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;quantity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2.475,00&lt;/td&gt;
&lt;td&gt;2.475,00 ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;price&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0,00740931&lt;/td&gt;
&lt;td&gt;0,00740931 ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vat_percent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;21,00&lt;/td&gt;
&lt;td&gt;0.00000000 ✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;18,34&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;21,00&lt;/strong&gt; ✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;taxes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,85&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;18,34&lt;/strong&gt; ✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subtotal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;22,19&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;3,85&lt;/strong&gt; ✗&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Chaque montant a glissé d'une colonne.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce que la citation ne voit pas
&lt;/h2&gt;

&lt;p&gt;Le schéma garantit une réponse structurée. La citation garantit que les valeurs extraites existent bien dans la source. &lt;strong&gt;Ni le schéma, ni la citation ne garantissent que le LLM range chaque valeur dans la bonne case.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pourquoi les colonnes ont glissé
&lt;/h2&gt;

&lt;p&gt;La normalisation a supprimé les tabulations qui permettaient au LLM d'identifier les colonnes de la facture.&lt;/p&gt;

&lt;p&gt;Ce que pdfparser produisait (texte brut) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Concepto → Cant. → Tarifa → %IVA → Base → Impuestos → Subtotal
ACS → 2.475,00 → 0,00740931 €/L → 21,00 → 18,34 € → 3,85 € → 22,19 €
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Les flèches représentent une tabulation. 7 colonnes d'en-tête, 7 cellules de données. Une tabulation marque toujours le passage à la cellule suivante. Les espaces, eux, n'apparaissent qu'à l'intérieur d'une cellule (&lt;code&gt;0,00740931 €/L&lt;/code&gt;, &lt;code&gt;18,34 €&lt;/code&gt;). Le modèle n'a qu'à compter les tabulations.&lt;/p&gt;

&lt;p&gt;Après ma normalisation (tout devient espace) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Concepto Cant. Tarifa %IVA Base Impuestos Subtotal
ACS 2.475,00 0,00740931 €/L 21,00 18,34 € 3,85 € 22,19 €
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La ligne de données passe de 7 à 11 éléments : le modèle n'a plus de moyen fiable de deviner où finit une cellule, et il a probablement mal compté.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4 citations sur 4 vérifiées. 6 valeurs justes sur 24. Les valeurs existent bien, mais pas à la bonne place !&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Et alors ?
&lt;/h2&gt;

&lt;p&gt;Je l'ai détecté en local parce que je confronte les valeurs extraites aux valeurs attendues, avec un fichier que je connais à l'avance. En production, ce fichier n'existe pas.&lt;/p&gt;

&lt;p&gt;Et en production, la même chose arrive sans erreur de notre part : quand un fournisseur change la mise en page de ses factures, quand on passe par un OCR, ou quand on change d'extracteur PDF. L'entrée change, et la structure disparaît silencieusement. Si je m'appuyais uniquement sur les citations, la facture serait passée.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un contrôle qui, lui, l'aurait vu
&lt;/h2&gt;

&lt;p&gt;Une solution possible, dans mon cas, est d'ajouter un point de contrôle déterministe : un calcul croisé (&lt;em&gt;cross-checking&lt;/em&gt;) qui vérifie que les calculs sont corrects.&lt;/p&gt;

&lt;p&gt;En reprenant la ligne ACS :&lt;/p&gt;

&lt;p&gt;quantité × prix = 2 475 × 0,00740931 = &lt;strong&gt;18,34&lt;/strong&gt;, alors que le modèle a mis &lt;strong&gt;21,00&lt;/strong&gt; dans &lt;code&gt;base&lt;/code&gt;. Le calcul échoue, et l'extraction est rejetée.&lt;/p&gt;

&lt;p&gt;Chaque contrôle voit une chose, et en laisse passer une autre :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contrôle&lt;/th&gt;
&lt;th&gt;Ce qu'il prouve&lt;/th&gt;
&lt;th&gt;Ce qu'il laisse passer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Schéma JSON&lt;/td&gt;
&lt;td&gt;Les champs existent, au bon type&lt;/td&gt;
&lt;td&gt;Une valeur dans le mauvais champ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Citation&lt;/td&gt;
&lt;td&gt;La valeur existe dans le document, sur la bonne ligne&lt;/td&gt;
&lt;td&gt;Une valeur dans la mauvaise colonne&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calcul croisé&lt;/td&gt;
&lt;td&gt;Les valeurs sont cohérentes entre elles&lt;/td&gt;
&lt;td&gt;Une erreur qui garde les calculs justes (rare, mais possible)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Ce que je change dans mon pipeline
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Je ne touche plus à l'entrée du modèle.&lt;/strong&gt; Le texte envoyé au LLM reste brut, tabulations comprises. La normalisation sert uniquement à comparer, et elle s'applique à des copies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Je ne considère plus une citation comme une preuve suffisante.&lt;/strong&gt; Elle prouve qu'une valeur existe dans le document. Pour savoir si elle est dans la bonne case, j'ajoute un contrôle qui relie les valeurs entre elles : quantité × prix = base, et somme des lignes = total.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Je mesure avant de croire.&lt;/strong&gt; Chaque changement (prompt, modèle, extracteur PDF) repasse sur des factures dont je connais les vraies valeurs. Sans ce score, j'aurais vu 4/4 citations vérifiées, et je serais passé à la suite.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Et vous, comment vérifiez-vous que vos extractions LLM rangent les bonnes valeurs dans les bonnes cases ? &lt;/p&gt;

</description>
      <category>french</category>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
