<?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>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>
