Lundi, 9 h 10, ticket DATA-412 : « Le chiffre d'affaires 2025 dans le bot ne correspond pas au reporting financier ». L'enquête prend une demi-journée. Le bot, un LLM branché en direct sur BigQuery, avait additionné sale_price sur tous les statuts, articles annulés et retournés compris. Le SQL était valide, la réponse fluide, le chiffre faux de 30 %.
Ce n'est pas un problème de modèle. C'est un problème de définition : nulle part le système ne disait ce que « chiffre d'affaires » veut dire dans cette entreprise.
Pourquoi le text-to-SQL direct échoue en entreprise
Les bancs d'essai publics (Spider, BIRD) mesurent si le SQL généré renvoie le bon résultat sur un schéma propre, avec une question non ambiguë. En entreprise, trois choses changent :
| Difficulté | Exemple sur thelook_ecommerce | Conséquence avec un LLM en accès direct |
|---|---|---|
| Définitions implicites | « CA » exclut annulés et retournés | chiffre plausible et faux |
| Valeurs codées | le Brésil s'écrit Brasil, le rayon femme Women | filtre vide, résultat nul affiché comme un fait |
| Pièges de jointure | users.gender (client) et products.department (rayon) | deux réponses différentes à « les ventes femmes » |
| Données sensibles | users.email, street_address | une question naïve peut les exposer |
| Coût | events pèse 384 Mo, un SELECT * répété coûte | facture sans plafond |
La parade n'est pas un meilleur prompt. C'est de retirer au modèle les décisions qu'il ne doit pas prendre.
Le mécanisme : le modèle choisit des noms, le code écrit le SQL
sequenceDiagram participant U as Utilisateur participant A as Agent ADK participant C as Compilateur sémantique participant G as Garde-fous participant B as BigQuery U->>A: "Top 5 des marques par marge brute en 2025" A->>C: interroger(metriques=[marge_brute], dimensions=[marque], 2025, limite=5) C->>C: noms connus ? valeurs exactes ? jointures permises ? C->>G: SQL + paramètres G->>B: dry-run (gratuit) B-->>G: SELECT, 2 tables, 7 Mo G->>B: exécution, maximum_bytes_billed = 1 Go B-->>A: 5 lignes, 21 Mo facturés A-->>U: réponse + barres + limites + 3 questions de suite
Quatre décisions structurent le projet :
- Un catalogue fermé. Le modèle reçoit la liste des métriques et des dimensions avec leur définition. Il ne voit ni les tables ni les colonnes. Une colonne absente du catalogue (l'e-mail) n'est pas « interdite » : elle n'existe pas.
- Un compilateur déterministe. Même requête métier, même SQL, à l'octet près. Il se teste sans modèle.
- Une validation en profondeur. Le compilateur ne produit que des
SELECT; on vérifie quand même par un contrôle statique, puis par le dry-run de BigQuery, puis par un plafond appliqué par BigQuery lui-même. - Le graphique décidé par le code. Le modèle peut suggérer « ligne » ; si le résultat n'a pas d'axe temporel, le code refuse la suggestion.
La mise en œuvre : récupérer le dépôt et vérifier l'outillage
Le dépôt est dans files/. Son organisation suit le flux ci-dessus :
files/
semantique.yaml définitions métier : métriques, dimensions, jointures, valeurs
assistant/
semantique.py compilateur YAML -> SQL paramétré
garde_fous.py statique, dry-run, liste blanche, plafond, exécution
graphiques.py choix du graphique, spec Vega-Lite
agent.py agent ADK, outil interroger, sortie structurée
web.py API FastAPI + fichiers web/
web/ index.html, app.js, style.css
eval/ 20 questions + SQL de référence + juge
tests/ 57 tests sans réseau
infra/ deployer.sh, nettoyer.sh, statut.sh
ci/github-actions.yml à copier dans .github/workflows/Installez et lancez les tests. Ils n'ont besoin d'aucun identifiant : BigQuery et le modèle sont remplacés par des doublures.
cd files
make setup # crée .venv, installe requirements.txt, copie .env.example en .env
make testRésultat obtenu le 2026-10-15 sur un clone neuf :
Prêt. Renseignez .env puis : make test
......................................................... [100%]
57 passed in 1.96sRenseignez ensuite GOOGLE_CLOUD_PROJECT dans .env. Le projet sert à payer les requêtes BigQuery et les appels Gemini ; les données, elles, sont publiques et restent dans le projet bigquery-public-data.
Ce qui casse en production
| Symptôme | Cause | Parade |
|---|---|---|
make test échoue sur GOOGLE_CLOUD_PROJECT n'est pas défini | un test appelle BigQuery pour de vrai | la cible test retire la variable (env -u) : tout appel réseau échoue bruyamment au lieu de passer en silence sur votre poste |
| Les chiffres de l'assistant changent d'un jour à l'autre | thelook_ecommerce est régénéré quotidiennement | normal ici ; en production, figez une date d'arrêté (snapshot) dans la couche pour les chiffres publiés |
| Des ventes datées de demain | le jeu synthétique contient des horodatages futurs (jusqu'au 2026-10-18 lors de nos mesures) | filtre permanent oi.created_at < CURRENT_TIMESTAMP() dans le YAML |
| Deux équipes, deux chiffres | définitions dupliquées dans des tableaux de bord | une seule couche, versionnée, relue comme du code |
La règle de décision
- Couche sémantique + agent quand les questions portent sur des indicateurs connus et que l'exactitude engage quelqu'un (comité, client, finance).
- SQL libre généré seulement pour l'exploration par des analystes qui relisent le SQL avant de croire le chiffre, sur un jeu sans données sensibles.
- À surveiller dès le premier jour : la part de questions refusées faute de métrique. Elle dit quoi ajouter au catalogue.
Pratique
Un responsable demande « les ventes aux femmes en 2025 ». Le jeu contient users.gender (F/M) et products.department (Women/Men). Que doit faire l'assistant ?
Les deux lectures existent dans l'entreprise. Choisir en silence produit une réponse invérifiable (les deux premières options) ; refuser frustre l'utilisateur pour une ambiguïté qu'on peut lever en une phrase. L'instruction de l'agent impose de dire quelle dimension a été retenue.
Votre équipe hésite : donner au modèle le schéma complet des 7 tables pour qu'il écrive le SQL, ou un catalogue de 8 métriques et 15 dimensions. Quel argument tranche en faveur du catalogue ?
Gemini écrit du SQL BigQuery correct la plupart du temps ; ce n'est pas le point. Le catalogue fixe ce que « chiffre d'affaires » veut dire et ce qui est visible (pas d'e-mail). La réduction de prompt est réelle mais secondaire ; le coût BigQuery dépend des colonnes lues, pas de qui écrit la requête.
Exercice. Listez trois questions que votre direction commerciale pose chaque semaine. Pour chacune, écrivez la métrique, la dimension et le filtre qu'elle implique, puis la définition métier qui manque aujourd'hui dans vos outils.
Voir la solution
Exemple sur une boutique en ligne :
| Question | Métrique | Dimension | Filtre | Définition à écrire |
|---|---|---|---|---|
| « Où en est le CA du mois ? » | chiffre_affaires | jour | mois en cours | statuts inclus ; date de commande ou d'expédition ? |
| « Quels produits reviennent le plus ? » | taux_retour | categorie | 90 derniers jours | dénominateur : articles livrés, pas commandés |
| « Les nouveaux clients achètent-ils moins ? » | panier_moyen | (nouveau / existant) | trimestre | « nouveau » = première commande dans la période ? |
La troisième ligne montre une dimension absente du catalogue de ce projet (ancienneté du client) : c'est exactement le type de manque que l'assistant doit signaler au lieu de l'approximer.
- Je sais expliquer pourquoi un SQL valide peut donner un chiffre faux
- Je sais situer le compilateur, les garde-fous et le modèle dans le flux d'une question
- J'ai lancé
make setupetmake test(57 tests verts sans identifiants)