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.
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"
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.
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.
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.
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.
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.
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.
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.
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.
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.