Un analyste qui ne fait pas exploser la facture
Le cas d'usage
Une équipe e-commerce pose des questions du type « quel chiffre d'affaires par catégorie en 2025 ? », « combien de commandes annulées ? ». Les données sont dans BigQuery. L'agent doit :
- comprendre la question et les tables ;
- écrire une requête GoogleSQL (le dialecte SQL de BigQuery) ;
- l'exécuter sans risque : ni écriture, ni table interdite, ni facture surprise ;
- répondre avec le résultat, la requête et une explication.
Comment BigQuery facture une requête
En tarification à la demande (on-demand), BigQuery facture les octets lus, pas le temps ni le nombre de lignes renvoyées. Et il stocke les données par colonne : il ne lit que les colonnes citées dans la requête.
| Requête | Ce qui est lu | Conséquence |
|---|---|---|
SELECT * FROM t LIMIT 5 | toutes les colonnes, toute la table | LIMIT ne réduit pas les octets facturés |
SELECT id, status FROM t | 2 colonnes, toute la table | beaucoup moins cher |
SELECT COUNT(*) FROM t | métadonnées seulement | presque gratuit |
| requête sur une table partitionnée, filtrée sur la partition | seulement les partitions utiles | le bon réflexe sur les grosses tables |
Les tables de thelook_ecommerce sont petites (mesuré le 2026-10-15 : orders 6,8 Mo, order_items 13,6 Mo, products 4,3 Mo, events 385 Mo), ce qui en fait un terrain d'entraînement sans danger.
Un agent exécute SELECT * FROM `projet.ventes.evenements` LIMIT 10 sur une table de 500 Go. Combien d'octets sont facturés à la demande ?
Le LIMIT s'applique après la lecture. BigQuery facture les colonnes lues sur toute la table (sauf partitions ou clustering exploités par un filtre). Sélectionner moins de colonnes est le premier levier.
Deux protections natives de BigQuery
| Mécanisme | Ce qu'il fait | Coût |
|---|---|---|
Dry-run (dry_run=True) | BigQuery valide la requête et renvoie : type d'instruction (SELECT, DELETE…), tables lues, octets qui seraient traités | gratuit, rien n'est exécuté |
maximum_bytes_billed | si la requête dépasse ce plafond, BigQuery la refuse avant de la lancer | aucun : la requête échoue sans être facturée |
Le dry-run sert à décider (et à expliquer au modèle pourquoi on refuse) ; maximum_bytes_billed est le filet appliqué par BigQuery lui-même si tout le reste a échoué.
sequenceDiagram participant M as "Gemini" participant C as "controle_cout" participant B as "BigQuery" M->>C: "execute_sql(SELECT * FROM order_items LIMIT 5)" C->>B: "dry-run" B-->>C: "SELECT, order_items, 13,6 Mo" C-->>M: "refus : trop coûteux, sélectionne moins de colonnes" M->>C: "execute_sql(SELECT status, COUNT(*) ...)" C->>B: "dry-run" B-->>C: "SELECT, order_items, 1,9 Mo" C->>B: "exécution, maximum_bytes_billed = 1 Go" B-->>M: "lignes"
L'architecture du lab
| Brique | Rôle | Fourni par |
|---|---|---|
BigQueryToolset | outils get_table_info et execute_sql, lecture seule, plafond, labels | ADK |
controle_cout | dry-run, liste blanche de tables, plafond expliqué au modèle | votre code (before_tool_callback) |
Gemini sur Vertex AI | comprendre la question, écrire le SQL, expliquer | Vertex AI, europe-west1 |
| ADC | identifiants de votre compte, trouvés automatiquement | gcloud auth application-default login |
Pourquoi garder maximum_bytes_billed alors que le callback refuse déjà les requêtes trop chères au dry-run ?
Le callback est votre code : il peut avoir un bug, être retiré par erreur, ou ne pas couvrir un nouvel outil. maximum_bytes_billed est vérifié par BigQuery pour chaque requête exécutée avec cette configuration.
Exercice : estimer avant de lancer
Avec la console BigQuery ou la commande bq, estimez sans rien payer le volume lu par ces deux requêtes, et expliquez la différence.
SELECT * FROM `bigquery-public-data.thelook_ecommerce.order_items` LIMIT 5;
SELECT status, COUNT(*) FROM `bigquery-public-data.thelook_ecommerce.order_items` GROUP BY status;Voir la solution
bq query --project_id=VOTRE_PROJET --use_legacy_sql=false --dry_run \
'SELECT * FROM `bigquery-public-data.thelook_ecommerce.order_items` LIMIT 5'Le dry-run de la première annonce environ 13,6 Mo (toutes les colonnes, toute la table) ; la seconde, environ 1,9 Mo (une seule colonne, status). Le LIMIT 5 n'a rien changé. Notez qu'en facturation à la demande, BigQuery arrondit au Mo supérieur avec un minimum de 10 Mo par table lue : deux petites requêtes peuvent coûter autant.
Résultat obtenu
Dry-runs réels du 2026-10-15 lancés par la fonction estimer() du lab (rien n'est facturé) :
{'type': 'SELECT', 'octets': 1000032, 'tables': ['bigquery-public-data.thelook_ecommerce.orders']}
{'type': 'SELECT', 'octets': 384573827, 'tables': ['bigquery-public-data.thelook_ecommerce.events']}
{'type': 'SELECT', 'octets': 2610516, 'tables': ['bigquery-public-data.thelook_ecommerce.users']}La première compte les commandes de 2025 (1 Mo lu : seule la colonne created_at) ; la deuxième lit toute la table events (385 Mo) ; la troisième lit les e-mails de users, une table que notre liste blanche refusera.
Je sais faire
- Expliquer ce que BigQuery facture à la demande, et pourquoi
LIMITne réduit pas la facture - Lancer un dry-run et lire les octets estimés
- Distinguer le rôle du dry-run et celui de
maximum_bytes_billed - Décrire l'architecture agent + toolset + callback de contrôle