Challenge 01 - Vérification d'un PoC RAG

Comprendre le système de fond en comble

Le PoC RAG du Challenge 01, expliqué pour partir de zéro et finir capable d'en parler à un expert

1Le scénario business : qui veut quoi

Un grossiste en fruits et légumes possède un assortiment : des produits, rangés en catégories, fournis par des fournisseurs, avec des noms traduits en trois langues (français, allemand, anglais). Ses équipes aimeraient poser des questions en langage courant ("montre-moi tous les produits de la catégorie baies") au lieu de passer par l'informatique.

Pour tester l'idée, l'entreprise a fait construire un PoC (proof of concept, un prototype de démonstration : assez pour juger si l'idée tient, pas assez pour être utilisé en vrai). L'épreuve l'appelle "PoC RAG" (retrieval-augmented generation : une IA censée aller chercher les vraies données avant de répondre, au lieu de répondre de mémoire). Précision qui te donnera un point de crédibilité : au sens strict, ce que fait ce PoC s'appelle du text-to-SQL avec du function-calling - le LLM traduit ta phrase en requête (ou en choix de requête) vers la base, la base exécute, et le résultat repart tel quel. Le LLM ne voit jamais les données elles-mêmes, il écrit seulement la requête. La section 4 le montre en détail.

Ton rôle de candidat AIBS dans l'épreuve : vérifier ce PoC. C'est-à-dire l'essayer, mesurer ce qui marche, comprendre pourquoi ça rate quand ça rate, et savoir l'expliquer - exactement ce que ce cours te prépare à faire.

Le dire à un expert

"Le PoC expose l'assortiment via une API REST et ajoute deux endpoints LLM pour les requêtes en langage naturel. Ma mission était d'en vérifier la fiabilité avant toute décision d'industrialisation."
"Le dossier parle de PoC RAG, mais stricto sensu le flux est du text-to-SQL et du function-calling : le modèle traduit la question en requête, la base l'exécute, et le résultat est renvoyé brut. Le LLM n'accède jamais aux données, il n'y a pas d'étape de génération fondée sur les données récupérées."

Traduction des termes : une API REST est une porte d'entrée standardisée par laquelle des programmes se parlent via le web ; un endpoint est un guichet précis de cette porte (une URL qui répond à une question donnée) ; un LLM (large language model) est un modèle d'IA qui comprend et produit du texte ; industrialiser, c'est passer du prototype au vrai système de production.

2La base de données : comment elle est construite

2.1 - L'idée de départ : des classeurs reliés entre eux

Une base de données relationnelle, c'est un ensemble de tableaux (appelés tables) qui se renvoient les uns aux autres. Chaque table a des colonnes (les champs : id, prix, nom...) et des lignes (les enregistrements : une ligne = un produit, un fournisseur...).

Image simple : un classeur Excel avec 6 feuilles. Au lieu de répéter le nom du fournisseur sur chaque ligne de produit, la feuille "produits" note juste "fournisseur n° 2", et la feuille "fournisseurs" garde une seule fois la fiche complète du n° 2. Les feuilles se citent par numéro.

Ces numéros ont des noms précis. La clé primaire (primary key, PK) est le numéro d'identité unique d'une ligne dans sa table : la colonne id. La clé étrangère (foreign key, FK) est une colonne qui contient l'id d'une ligne d'une AUTRE table, pour la citer : supplier_id dans la table des produits pointe vers l'id de la table des fournisseurs.

2.2 - Le schéma réel : les 6 tables, telles qu'elles existent dans le conteneur

Voici le plan exact de la base (relevé directement dedans, rien d'inventé). Le badge PK marque la clé primaire, FK une clé étrangère avec la table qu'elle cite. Retiens bien : il y a SIX tables, et suppliers n'a PAS de table de traduction.

languages 3 lignes
  • id PK
  • code UNIQUE
  • name
categories 8 lignes
  • id PK
categories_i18n 24 lignes
  • id PK
  • category_id FK → categories
  • language_id FK → languages
  • name
  • UNIQUE (category_id, language_id)
products 29 lignes
  • id PK
  • price
  • supplier_id FK → suppliers
  • category_id FK → categories
products_i18n 87 lignes
  • id PK
  • product_id FK → products
  • language_id FK → languages
  • name
  • description
  • UNIQUE (product_id, language_id)
suppliers 4 lignes
  • id PK
  • name
  • country
suppliers_i18n -- cette table N'EXISTE PAS. Les noms de fournisseurs ne sont pas traduits. Retiens ce vide : c'est exactement la table que le guichet v2 a inventée le jour J (question Q6).

Surprise du schéma : la table categories ne contient QUE des numéros, aucun nom. Et la table products ne contient ni nom ni description, juste un prix et deux renvois. Où sont les noms ? Dans les tables de traduction. Note aussi le nom de la colonne : dans la maquette publiée ici, la clé de langue s'appelle language_id ; la base de l'examen utilisait lang (même idée, colonne légèrement différente - utile à savoir pour lire la preuve de Q6).

2.3 - Visite guidée avec de vraies lignes : suivons les Fraises

Le mieux pour comprendre est de suivre une vraie donnée à travers les tables. Prenons le produit n° 1. Voici les lignes réelles de la base (les couleurs montrent qui cite qui).

products - le produit n° 1
idpricesupplier_idcategory_id
14.911

Cette ligne dit seulement : "le produit n° 1 coûte 4.90, vient du fournisseur n° 1 et appartient à la catégorie n° 1". Pour savoir comment il s'appelle, direction la table de traduction des produits :

products_i18n - les 3 noms du produit n° 1
idproduct_idlanguage_idnamedescription
111FraisesFraises fraîches de saison, calibre 25-35mm
212ErdbeerenFraises fraîches de saison, calibre 25-35mm
313StrawberriesFresh seasonal strawberries, 25-35mm grade

Oui, la description de la ligne allemande est restée en français : c'est un défaut réel des données du PoC, pas une coquille du cours - exactement le genre de détail qu'un vérificateur de PoC consigne dans son rapport.

Trois lignes pour un seul produit : une par langue. Le language_id cite la table des langues :

languages - les 3 langues
idcodename
1frFrançais
2deDeutsch
3enEnglish

Même mécanique pour la catégorie n° 1, qui n'a un nom que dans sa table de traduction :

categories_i18n - les 3 noms de la catégorie n° 1
idcategory_idlanguage_idname
111Baies
212Beeren
313Berries

Garde cette table en tête : la catégorie n° 1 s'appelle Baies en français et Berries en anglais. C'est précisément ce mot "Berries" qui va piéger le LLM en Q4.

Et le fournisseur n° 1, lui, vit dans une table toute simple, sans aucune traduction :

suppliers - le fournisseur n° 1
idnamecountry
1Marché Primeur SAFrance

Bilan de la visite : "Fraises, 4.90, catégorie Baies, fournisseur Marché Primeur SA" n'existe nulle part en une seule ligne. Cette phrase est reconstituée en suivant les renvois à travers 5 tables. Cette opération de reconstitution s'appelle une jointure (join) - retiens ce mot, il revient partout.

2.4 - Pourquoi découper ainsi ? La normalisation

Ce découpage qui paraît compliqué a une logique : chaque information n'est écrite qu'une seule fois. Si "Marché Primeur SA" change de nom, on corrige une ligne, pas 29. Si on ajoute l'italien, on ajoute des lignes de traduction sans toucher aux produits. C'est ce qu'on appelle la normalisation : organiser les tables pour éliminer les doublons et les risques d'incohérence.

Le suffixe _i18n est une convention du métier : abréviation d'internationalization (i + 18 lettres + n). Une table x_i18n contient les textes traduits de la table x. La contrainte UNIQUE verrouille le bon sens, et elle protège les deux tables de traduction : une catégorie (ou un produit) ne peut pas avoir deux noms différents dans la même langue.

Le détail qui devient piège d'examen : products et categories ont leur table de traduction, suppliers n'en a PAS (les noms de sociétés ne se traduisent pas). Un humain qui lit le schéma le voit. À l'épreuve, le LLM du guichet v2 a généralisé le motif _i18n par imitation et a inventé suppliers_i18n - c'est la question Q6. En t'entraînant, tu la verras parfois réussir, parfois inventer autre chose : c'est son caractère probabiliste.

Le dire à un expert

"Le schéma est normalisé : six tables, avec des tables de traduction i18n pour les produits et les catégories, reliées par clés étrangères à une table de langues. Les libellés ne vivent que dans les tables i18n, sous contrainte d'unicité par langue. Les fournisseurs, eux, n'ont pas de table i18n."
"Toute requête qui veut un libellé doit donc faire une jointure sur la table i18n en fixant la langue - c'est précisément là que le PoC se fragilise quand le prompt ne précise pas la langue, ou quand le modèle invente une table i18n qui n'existe pas."

3Le code du serveur : le voyage d'une requête

3.1 - Les étapes du voyage

Quand tu lances GET /categories, voici le trajet complet, en cinq étapes :

  1. Ton navigateur envoie une requête HTTP (le langage du web) à l'adresse /categories?lang=fr (tout ce qui suit le ? est une option glissée dans l'adresse : ici l'option lang avec la valeur fr).
  2. Le serveur FastAPI (un cadre de travail Python pour construire des API, un "framework") reçoit la requête et la confie à la fonction prévue pour ce guichet.
  3. La fonction interroge la base via SQLAlchemy (un traducteur, appelé ORM, qui transforme du code Python en langage SQL, la langue des bases de données).
  4. Le moteur de la base exécute la requête et renvoie les lignes.
  5. FastAPI emballe le résultat en JSON (texte structuré en paires "nom": valeur) et le renvoie à ton navigateur.

Point capital, valable pour les six tables : le LLM n'apparaît nulle part dans ce voyage. Les guichets de données sont du pur code, sans aucune IA. C'est ce qui les rend fiables.

3.2 - Le vrai code de GET /categories, annoté bloc par bloc

Ce qui suit est le code réel du guichet des catégories, découpé en quatre blocs. Tu n'as pas besoin de savoir le réécrire : l'objectif est de pouvoir le LIRE.

@router.get("/categories", response_model=list[CategoryOut], summary="Lister toutes les catégories")
def list_categories(lang: str = Query(default="fr", ...), db: Session = Depends(get_db)):
Le panneau du guichet. @router.get("/categories") dit à FastAPI : "quand quelqu'un appelle GET /categories, exécute la fonction ci-dessous". Le paramètre lang a "fr" comme valeur par défaut : si tu ne précises rien, la réponse sera en français. db est la connexion à la base, fournie automatiquement.
    language = db.query(Language).filter(Language.code == lang).first()
    lang_id = language.id if language else 1
Traduction du code langue en numéro. On cherche dans la table languages la ligne dont le code vaut "fr" pour récupérer son id (1). Détail révélateur : si la langue demandée n'existe pas, le code retombe en silence sur 1 (le français) au lieu de signaler l'erreur - le genre de choix discutable qu'un vérificateur de PoC note dans son rapport.
    rows = (
        db.query(Category.id, CategoryI18n.name, func.count(Product.id).label("product_count"))
        .join(CategoryI18n, (CategoryI18n.category_id == Category.id) & (CategoryI18n.language_id == lang_id))
        .outerjoin(Product, Product.category_id == Category.id)
        .group_by(Category.id, CategoryI18n.name)
        .order_by(Category.id)
        .all()
    )
Le coeur : la requête. En lisant ligne par ligne : "donne-moi l'id de chaque catégorie, son nom, et le nombre de ses produits" ; .join(CategoryI18n, ...) = "va chercher le nom dans la table de traduction, dans la langue demandée" (la jointure de la section 2.3) ; .outerjoin(Product, ...) = "rattache les produits de chaque catégorie, et garde aussi les catégories sans produit" (c'est le sens de outer) ; .group_by(...) = "regroupe par catégorie pour que le comptage se fasse catégorie par catégorie".
    return [CategoryOut(id=r.id, name=r.name, product_count=r.product_count) for r in rows]
L'emballage du résultat : chaque ligne devient un objet à trois champs (id, name, product_count), que FastAPI convertit en JSON - exactement ce que tu vois dans le cockpit quand tu lances la question Q1.

3.3 - La même chose en SQL, mot par mot

Derrière le Python, SQLAlchemy produit du SQL. Voici la requête équivalente, à un détail près : le code de 3.2 ajoute un tri par id (ORDER BY c.id) que cette variante omet. C'est exactement le SQL que le guichet IA v1 - que tu découvriras en section 4 - renvoie dans son champ sql_used. Sa traduction française terme à terme suit :

SELECT c.id, ci.name, COUNT(p.id)
FROM categories c
JOIN categories_i18n ci ON ci.category_id = c.id AND ci.language_id = 1
LEFT JOIN products p ON p.category_id = c.id
GROUP BY c.id, ci.name

SELECT = "donne-moi ces colonnes" ; FROM categories = "en partant de la table des catégories" ; JOIN ... ON ... = "relie chaque catégorie à sa ligne de traduction, à condition que la langue soit la n° 1" ; LEFT JOIN products = "relie aussi les produits, en gardant les catégories qui n'en ont pas" ; GROUP BY = "regroupe les lignes par catégorie" (pour que COUNT compte par groupe). Les lettres c, ci, p sont des surnoms (alias) donnés aux tables pour raccourcir.

Le dire à un expert

"Les endpoints de données sont des handlers FastAPI qui passent par l'ORM SQLAlchemy sur la base. Les libellés sont résolus par jointure sur les tables i18n avec la langue en paramètre, défaut français. Aucun LLM sur ce chemin."
"Ces endpoints sont déterministes : même question, même réponse. La base calcule les agrégats par catégorie (le product_count, via COUNT et GROUP BY) ; le dénombrement final ou le tri, je le fais moi-même sur le JSON reçu. Dans les deux cas, aucun calcul n'est délégué au LLM - c'est la bonne méthode pour Q1 à Q3."

4Les deux moteurs IA : v1 prudent, v2 audacieux

Les guichets /llm/v1 et /llm/v2 acceptent tous les deux une phrase libre ("prompt") et utilisent le même modèle d'IA local (Ollama, gratuit, aucune clé API). Toute la différence tient à la mission qu'on lui confie - et c'est le coeur des questions Q4 à Q6. Dans les deux cas, le LLM ne voit jamais les données : il produit seulement une requête, ou un choix de requête.

v1 - function-calling artisanal

  1. Le serveur donne au LLM un menu fermé de 4 requêtes toutes faites, avec leur description.
  2. Le LLM répond en JSON : quel plat du menu, et avec quels paramètres ("query_id" + "parameters").
  3. Le serveur exécute alors une requête SQL écrite à la main par le développeur : du SQL toujours valide.

C'est du function-calling fait à la main : le modèle choisit une fonction dans un catalogue fermé et remplit les blancs. Zone de risque : le CHOIX et le REMPLISSAGE. Le LLM peut choisir le mauvais plat, remplir le mauvais paramètre, ou - pire - renvoyer une réponse fluide et fausse. C'est la mécanique des questions Q4 et Q5.

v2 - génération SQL dynamique

  1. Le serveur donne au LLM le plan de la base (la description des 6 tables ci-dessous).
  2. Le LLM écrit librement une requête SQL complète.
  3. Le serveur exécute ce SQL tel quel, sans le vérifier.

Zone de risque : TOUT. Table inventée (suppliers_i18n...), faute de syntaxe, logique fausse mais résultat plausible. C'est la mécanique de la question Q6.

4.1 - Q4-Q5 : la réponse fausse silencieuse (le piège le plus dangereux)

Prompt de la donnée : Show me all the products in the berries category. Voici ce que le guichet v1 a vraiment répondu le jour J (preuve complète sur la page Preuves du jour J) :

"answer": "We have two products in the berries category: Mandarins and
Oranges. ... Mandarins (Sku ZF-003) ... Oranges (Sku ZF-001) ...",
"endpoint": "GET /categories/{category_id}/products"
Le piège : cette réponse ne lève AUCUNE erreur. Elle est fluide, assurée, détaillée - et factuellement FAUSSE. Mandarines et Oranges sont des Agrumes, pas des Baies. Le LLM a résolu "berries" vers la mauvaise catégorie et a présenté des agrumes comme des baies. Rien ne le signale : c'est ce qu'on appelle une réponse fausse silencieuse, le mode d'échec le plus dangereux d'un système d'IA.

Pourquoi ce dérapage ? À cause d'une erreur de résolution d'entité multilingue. "Berries" est un mot anglais. Le LLM doit décider à quelle catégorie réelle il correspond (rappel de la section 2.3 : la catégorie n° 1 s'appelle "Baies" en français, "Berries" en anglais). Faute de langue précisée et faute de contrainte stricte, il fait une correspondance approximative et tombe à côté - tout en gardant un ton parfaitement sûr de lui. Le ton assuré et l'endpoint annoncé masquent l'erreur.

Réponse Q4 : le défaut n'est pas une panne technique, c'est que le LLM a renvoyé les mauvais produits (des agrumes au lieu de baies) en ayant l'air sûr de lui. Une erreur de résolution d'entité, présentée comme une vérité.

Réponse Q5 (ce qui manque au prompt) : il manque un identifiant de catégorie sans ambiguïté et la langue. Correction : fournir la catégorie exacte (par id, ou par nom localisé exact + lang), ou - mieux - contraindre le système à résoudre l'entité contre le vrai catalogue AVANT de répondre. Principe à retenir : ne jamais laisser la couche générative inventer la correspondance d'entité ; résoudre les entités de façon déterministe d'abord.

En t'entraînant en live sur la maquette, le même prompt sera instable : parfois une réponse fausse comme ci-dessus, parfois un nom de catégorie inventé tiré d'un produit (ex. "fraises") qui donne un 422 Category 'fraises' not found in language 'fr', parfois du JSON invalide (un commentaire # glissé dans le JSON) qui donne un 500 LLM returned invalid JSON, parfois une réussite. Cette instabilité EST la leçon : un composant non déterministe sur le chemin critique d'une requête de données.

4.2 - Q6 : l'hallucination de schéma

Prompt de la donnée : Show me the details of bananas. Le guichet v2 a généré du SQL qui joint une table hallucinée. Erreur d'exécution dure renvoyée le jour J (preuve complète sur la page Preuves du jour J) :

Table 'examdb.suppliers_i18n' doesn't exist  (erreur 1146)

La table inventée est suppliers_i18n. 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. Sauf que les fournisseurs n'ont pas de table de traduction (rappel : l'encart rouge de la section 2.2). La base refuse, code 1146 "table inconnue". C'est une hallucination de schéma : inventer une table qui colle au motif mais n'existe pas.

Détail à connaître pour lire la preuve : la base de l'examen utilisait une colonne lang dans les tables i18n, la maquette publiée ici utilise language_id. Même idée, colonne légèrement différente.

Réponse Q6 : la table inventée est suppliers_i18n, par fausse analogie avec le motif _i18n. Les 6 vraies tables sont celles de la section 2.2.

En live, "Show me the details of bananas." est lui aussi instable : parfois du SQL valide mais 0 résultat (filtre banane absent ou faux), parfois une erreur de syntaxe SQL, parfois une autre table hallucinée (ex. product_i18n au singulier). La méthode : repérer dans le generated_sql toute table qui n'appartient pas aux 6 vraies, et la traiter comme une alerte rouge.

4.3 - Les vraies instructions données à l'IA

Rien de magique : le serveur envoie au LLM un texte d'instructions. Le menu de v1 (les 4 requêtes prédéfinies parmi lesquelles le modèle choisit) :

GET_CATEGORIES      # lister les catégories avec compte de produits (param : lang)
GET_PRODUCTS        # lister les produits, filtre possible par nom de catégorie (lang, category_name)
GET_PRODUCT_DETAIL  # détail d'un produit par son nom (lang, product_name requis)
GET_MOST_EXPENSIVE  # trouver le produit le plus cher (lang)

Consigne textuelle de v1 (traduite de l'anglais) : "Tu es un sélectionneur de requêtes de base de données pour un grossiste en fruits et légumes. Choisis la requête prédéfinie la plus adaptée au prompt de l'utilisateur et extrais les paramètres. Réponds UNIQUEMENT en JSON." Voilà le function-calling artisanal : un catalogue fermé, un choix, des paramètres.

Pour v2, le serveur transmet le plan de la base tel quel (c'est tout ce que l'IA sait du schéma) :

Database schema:
- languages(id PK, code, name)
- categories(id PK)
- categories_i18n(id PK, category_id FK, language_id FK, name)
- products(id PK, price, supplier_id FK, category_id FK)
- products_i18n(id PK, product_id FK, language_id FK, name, description)
- suppliers(id PK, name, country)

avec la consigne : "Tu es un expert SQL. Génère uniquement une requête SELECT, sans explication ni mise en forme." Relis ce plan : suppliers n'y a pas de table i18n. L'information était sous les yeux de l'IA et, à l'épreuve, elle a quand même inventé suppliers_i18n par imitation du motif. Voilà pourquoi on parle d'hallucination : produire avec assurance quelque chose qui n'existe pas.

4.4 - Le tableau à retenir

Guichets données (GET)v1 - function-calling artisanalv2 - génération SQL dynamique
Qui écrit le SQLLe développeurLe développeur (le LLM choisit et remplit)Le LLM, librement
ComportementDéterministe : même question, même réponseProbabiliste sur le choix, déterministe sur l'exécutionProbabiliste de bout en bout
Erreurs possiblesQuasi aucuneMauvais choix, mauvais paramètre, réponse fausse silencieuseTables hallucinées, syntaxe fausse, logique fausse plausible
Dans l'épreuveQ1, Q2, Q3Q4, Q5Q6

Le dire à un expert

"v1 reproduit à la main le pattern function-calling : le modèle choisit une fonction dans un catalogue fermé et en extrait les arguments en JSON. L'échec phare de Q4 n'est pas une panne : c'est une réponse fluide et fausse - des agrumes présentés comme des baies - causée par une erreur de résolution d'entité multilingue. Le pire des cas, car rien ne la signale."
"v2 fait de la génération SQL dynamique : le modèle peut halluciner des éléments de schéma, comme la table suppliers_i18n inexistante observée à l'épreuve. La généralisation du motif _i18n est une hallucination de schéma typique du text-to-SQL sans validation."
"Pour industrialiser, je recommanderais des garde-fous : résolution d'entité déterministe avant la génération, validation du SQL contre le schéma réel avant exécution, droits en lecture seule, liste blanche de tables, et journalisation des requêtes générées."

5Les 3 modes d'échec et la méthode de vérification

Le label "PoC RAG" masque que le mécanisme réel est du text-to-SQL avec function-calling : le LLM ne voit jamais les données, il écrit seulement une requête contre un schéma. Cette couche est non déterministe et échoue de trois façons, classées ici par gravité décroissante.

Le plus dangereux

1. Réponse fausse silencieuse

A l'air juste, est faux. Aucune erreur levée. C'est le cas Q4 : des agrumes présentés comme des baies, sur un ton parfaitement assuré. Le pire, car rien ne le signale - seule une vérification contre la base le révèle.

Au moins visible

2. Erreur d'exécution dure

Table ou colonne hallucinée, la base refuse. C'est le cas Q6 : suppliers_i18n n'existe pas, erreur 1146. Désagréable, mais honnête : l'échec est immédiatement visible.

Sournois

3. Instabilité

Le même prompt donne des résultats différents d'une exécution à l'autre : tantôt juste, tantôt 422, tantôt JSON invalide, tantôt SQL faux. Un composant non déterministe sur le chemin critique.

5.1 - La méthode de vérification

La règle d'or du vérificateur de PoC : pour toute question factuelle, lire la base directement (un GET) et compter soi-même. La couche LLM se traite comme une suggestion à vérifier, jamais comme une vérité.

A retenir : le prix du produit le plus cher (Cerises, 9.80) n'a ni unité ni devise affichée. Ce genre de limite de PoC se consigne aussi dans le rapport de vérification - ce n'est pas une réponse à donner, c'est un défaut à signaler.

6Parler à un expert : les phrases qui font mouche

Pour chaque question de l'épreuve, la formulation que tu peux assumer devant un jury ou un spécialiste - chaque terme a été expliqué plus haut.

Q1-Q3"Je réponds par les endpoints REST déterministes : la base calcule déjà les agrégats (product_count via COUNT et GROUP BY), et le dénombrement final ou le tri, je le fais moi-même sur le JSON reçu. Je ne délègue jamais un calcul à un LLM : il prédit une valeur plausible, il ne calcule pas."
Q4"L'échec n'est pas une erreur technique : v1 a renvoyé une réponse fluide et fausse, présentant des agrumes (Mandarines, Oranges) comme des baies. C'est une erreur de résolution d'entité multilingue - 'berries' mal rattaché à la catégorie réelle - et c'est le mode d'échec le plus dangereux : une réponse crédible mais fausse, qu'aucune erreur ne signale."
Q5"Il manque au prompt un identifiant de catégorie sans ambiguïté et la langue. Correction : résoudre l'entité de façon déterministe contre le vrai catalogue avant de répondre, plutôt que de laisser la couche générative inventer la correspondance."
Q6"Le modèle a généralisé le motif _i18n par fausse analogie et référencé une table suppliers_i18n absente du schéma : hallucination de schéma typique du text-to-SQL sans validation. Les fournisseurs n'ont pas de table de traduction."

Glossaire récapitulatif

PoC
proof of concept : un prototype de démonstration, assez pour juger si l'idée tient, pas assez pour la production
RAG
retrieval-augmented generation : une IA qui récupère les vraies données puis rédige sa réponse dessus. Ici le dossier emploie le terme au sens large : le flux réel du PoC est du text-to-SQL, le LLM ne lit jamais les données
text-to-SQL
une IA qui traduit une phrase en langage naturel en requête SQL, exécutée ensuite par la base. Le moteur réel de ce PoC
function-calling
le mécanisme par lequel un LLM choisit une fonction dans un catalogue prédéfini et en remplit les paramètres, au lieu de répondre en texte libre. C'est ce que v1 reproduit à la main (function-calling artisanal)
résolution d'entité
décider à quelle entité réelle de la base correspond un mot du prompt (ici : à quelle catégorie correspond "berries"). L'étape qui rate en Q4, surtout en multilingue
hallucination de schéma
quand le LLM invente un élément de structure de la base (table, colonne) qui n'existe pas, par imitation d'un motif. Le cas de suppliers_i18n en Q6
i18n
abréviation d'internationalization (i + 18 lettres + n) : les tables _i18n portent les textes traduits de la table associée
table / ligne / colonne
le tableau, l'enregistrement, le champ - les briques de toute base relationnelle
clé primaire (PK) / clé étrangère (FK)
le numéro d'identité unique d'une ligne / une colonne qui cite la clé primaire d'une autre table pour relier les deux
jointure (join)
l'opération qui reconstitue une information complète en suivant les renvois entre tables
normalisation
organiser les tables pour que chaque information ne soit écrite qu'une seule fois
SQL
la langue des bases de données (SELECT, FROM, JOIN, GROUP BY...)
API REST / endpoint
la porte d'entrée standardisée d'un système, et chacun de ses guichets
JSON
le format texte structuré en "nom": valeur que les API renvoient
FastAPI / SQLAlchemy
le framework web Python qui sert l'API, et le traducteur Python-vers-SQL (ORM) qui parle à la base
déterministe / probabiliste
même entrée, même sortie garantie / sortie qui peut varier - LA distinction de l'épreuve
LLM, prompt, hallucination
le modèle d'IA, la consigne qu'on lui donne, et sa capacité à affirmer avec aplomb des choses inexistantes

Mettre tout ça en pratique dans le cockpit guidé