Pièces à conviction

Les preuves du jour J

Voilà ce que l'IA a réellement produit le jour de l'examen Série 0. Deux fichiers bruts, récupérés tels quels, sans retouche.

Ces deux fichiers sont les réponses brutes du LLM (le modèle de langage) le jour de l'examen. Ils diffèrent de la maquette de démonstration : l'examen avait des SKU (références produit), du stock disponible et une colonne lang dans la base. Mais ils enseignent exactement les mêmes modes d'échec. La méthode pour vérifier est identique ; seuls les chiffres changent.

À retenir avant de lire : aucune de ces deux réponses n'est correcte. L'une est fausse sans le dire, l'autre plante à l'exécution. Ce sont deux des trois modes d'échec du Challenge 01.

POST /llm/v1 Réponse fluide et FAUSSE

Q4 - "Show me all the products in the berries category."

Fichier brut : data/q4-response.json

"answer": "We have two products in the berries category: Mandarins and Oranges. Here are their details for your convenience - you can check availability as well! Mandarins (Sku ZF-003): These mandarins are sweet, organic specimens that come with easy peeling features in mind. They're priced at $5.2 per unit of KGM and we currently have 320 units available for purchase! Oranges (Sku ZF-001): A juicy treat from our organic produce, these oranges are designed to be ideal for juicing purposes specifically. Priced at $3.4 per unit of KGM and we have 400 units available on hand!",
"endpoint": "GET /categories/{category_id}/products"
Lecture : le ton est assuré, le détail est riche (prix, stock, qualité bio), et un endpoint est même annoncé. Tout respire la confiance. Et pourtant, c'est faux.

L'analyse corrigée

C'est une erreur sémantique silencieuse, le mode d'échec le plus dangereux. Aucune erreur n'est levée, rien ne signale la faute : la réponse paraît parfaitement crédible alors qu'elle est factuellement fausse.

1 Mandarines (ZF-003) et Oranges (ZF-001) sont des AGRUMES, pas des baies. La catégorie demandée était berries (baies). Le LLM a résolu "berries" vers la mauvaise catégorie et présente des agrumes comme des baies.
2 Aucune erreur n'est levée. Pas de code 422, pas de 500, pas de message d'alerte. Le système répond "200 OK" avec un contenu faux. Rien dans la réponse n'indique au lecteur qu'il regarde une erreur.
3 Le ton masque la faute. Le détail inventé (specimens bio, faciles à peler, 320 et 400 unités en stock) rend la réponse d'autant plus convaincante. Plus une réponse fausse est fluide, plus elle est dangereuse.

Le vrai défaut : une erreur de résolution d'entité. Le LLM a deviné quelle catégorie correspond à "berries" au lieu de la résoudre de façon déterministe contre le vrai catalogue. La correction de fond : ne jamais laisser la couche générative inventer la correspondance d'entité ; résoudre les entités d'abord (par identifiant, ou nom localisé exact + langue), répondre ensuite.

POST /llm/v2 Table hallucinée - erreur 1146

Q6 - "Show me the details of bananas."

Fichier brut : data/q6-response.json

"detail": "Generated SQL could not be executed: (pymysql.err.ProgrammingError) (1146, \"Table 'examdb.suppliers_i18n' doesn't exist\")
[SQL: SELECT p.*, pi18n.name AS product_name, ci18n.name AS category_name, si.name AS supplier_name
      FROM products p
      JOIN categories c ON p.category_id = c.id
      JOIN ( SELECT * FROM categories_i18n WHERE lang = 'en' AND name LIKE '%%banana%%' ) ci18n ON c.id = ci18n.category_id
      JOIN products_i18n pi18n ON p.id = pi18n.product_id AND pi18n.lang = 'en'
      JOIN suppliers s ON p.supplier_id = s.id
      JOIN ( SELECT * FROM suppliers_i18n WHERE lang = 'en' AND name LIKE '%%banana%%' ) si ON s.id = si.supplier_id; ]"

Le double %% dans '%%banana%%' est le texte brut du fichier : c'est l'echappement du caractere % par la couche SQLAlchemy/MySQL. On l'affiche tel quel, sans retouche.

Lecture : cette fois, la base refuse net. Le SQL est syntaxiquement correct, mais il interroge une table qui n'existe pas. C'est visible, c'est honnête - donc moins dangereux que Q4.

L'analyse corrigée

C'est une hallucination de schéma : le LLM a inventé la table suppliers_i18n pour traduire les noms de fournisseurs. La base renvoie l'erreur MySQL 1146 (table inconnue) et rien ne s'exécute.

1 La table inventée est suppliers_i18n. Elle n'existe pas. Les 6 vraies tables sont : languages, categories, categories_i18n, products, products_i18n, suppliers.
2 Le mécanisme : généralisation du motif _i18n par imitation. Le modèle a vu categories → categories_i18n et products → products_i18n, donc il a supposé suppliers → suppliers_i18n. Logique en apparence, mais les noms de fournisseurs ne sont pas traduits : cette table n'a jamais existé.
3 Détail de l'examen : la colonne lang. Le SQL du jour J filtre avec WHERE lang = 'en'. La maquette publiée de cet atelier utilise la colonne language_id à la place - même idée, nom de colonne légèrement différent. Cela ne change rien à la leçon : l'erreur vient de la table inventée, pas de la colonne.

Le label "PoC RAG" masque le mécanisme réel : du text-to-SQL. Le LLM ne voit jamais les données, il écrit seulement une requête contre un schéma qu'il croit connaître. Quand le schéma réel ne colle pas à son intuition, il invente.


La leçon

Trois modes d'échec, par gravité croissante

Sous le label "PoC RAG", le mécanisme réel est du text-to-SQL : une couche non déterministe qui échoue de trois façons. Les deux preuves ci-dessus en illustrent deux.

1

Réponse fausse silencieuse

A l'air juste, est faux - le pire des trois, car rien ne le signale. C'est Q4 : des agrumes présentés comme des baies, sans la moindre alerte.

2

Erreur d'exécution dure

Table ou colonne hallucinée, la base refuse - au moins c'est visible. C'est Q6 : la table suppliers_i18n et l'erreur 1146.

3

Instabilité

Le même prompt donne des résultats différents d'une exécution à l'autre. Un composant non déterministe sur le chemin critique d'une requête de données.

Pourquoi on vérifie en lisant la base directement : pour une question factuelle, on lit la base par un appel GET et on compte soi-même. La couche LLM est traitée comme une suggestion à vérifier, jamais comme une vérité. Les guichets bleus (GET) donnent la vérité ; les guichets verts (POST /llm) donnent une hypothèse.

Reproduire ces échecs en direct

Ces preuves sont figées. Pour voir le LLM se tromper en temps réel - et constater l'instabilité d'une exécution à l'autre - lance les mêmes questions toi-même.