À la fin de ce laboratoire, vous saurez : 1. Implémenter le module File Analyzer de DS-STAR 2. Analyser un fichier CSV : schéma typé, statistiques par colonne et taux de manquants 3. Extraire des métadonnées structurées automatiquement 4. Générer des résumés intelligents avec le LLM
Prérequis
Python 3.10+
Configuration multi-provider active
Connaissance de Pandas (Lab 4)
Durée estimée : 30-40 minutes
Repère bibliographique. Ce laboratoire implémente le module File Analyzer de l’agent DS-STAR (Data Science - Structured Thought and Action). DS-STAR est un agent data-science à l’état de l’art décrit par Google Research (Nam et al., DS-STAR: Data Science Agent for Solving Diverse Tasks across Heterogeneous Formats and Open-Ended Queries, arXiv:2509.21825, 2025) — un harnais multi-module déterministe (7 modules : analyse de fichier, planification structurée, génération et exécution de code, vérification itérative) conçu pour traiter des données de formats hétérogènes. Le File Analyzer en est le composant d’ingestion et de profilage automatique.
1. Configuration
import asyncioimport sysfrom pathlib import Path# Add parent directory (Track2-GoogleADK) to path for config/utils importssys.path.insert(0, str(Path().resolve().parent))import osimport jsonimport pandas as pdimport numpy as npfrom dataclasses import dataclass, asdictfrom config import get_settingsfrom utils import LLMClientfrom utils.adk_runtime import run_data_agentprint("Imports OK : asyncio, Google ADK runtime, pandas, numpy, ""dataclasses, config, utils")
Imports OK : asyncio, Google ADK runtime, pandas, numpy, dataclasses, config, utils
Chargement des paramètres de configuration. DS-STAR étant multi-provider, la configuration n’est pas câblée : get_settings() lit le fournisseur actif affiché par la cellule suivante, de sorte que le même FileAnalyzer fonctionne derrière différents LLM compatibles. C’est ce qui permet au composant d’être testé avec un provider réel puis basculé vers un autre modèle sans toucher au code métier.
Lecture. Le provider affiché ci-dessus pilote les deux chemins LLM du lab. LLMClient conserve la comparaison from-scratch des résumés, tandis que run_data_agent construit le vrai runtime Google ADK. Dans les deux cas, changer de modèle reste un changement de configuration ; la différence pédagogique porte sur l’orchestration ADK — session, runner, événements et outils — désormais observable dans la section 5.
2. FileMetadata DataClass
Le composant File Analyzer doit produire une description structurée du fichier qu’il examine, utilisable à la fois par un humain (affichage) et par le module aval (le Planner, qui décide des analyses à lancer). Une dataclass est le conteneur naturel : champs typés, comparaison et sérialisation gratuites (asdict pour le prompt LLM).
Classe FileMetadata definie : filename, format, size_bytes, num_rows, num_columns, columns, statistics, sample_data
Lecture.FileMetadata porte exactement ce que le Planner et le LLM ont besoin de connaître sans relire le fichier : format (pour choisir la stratégie de lecture), num_rows/num_columns (pour estimer le coût des analyses), columns (schéma + dtype + % manquant par colonne, plus min/max/mean par colonne numérique dans columns[i]['stats']), statistics (champ déclaré dans la dataclass mais qu’aucun chemin de ce lab n’alimente), et sample_data (un échantillon pour donner au LLM un aperçu concret du contenu). C’est l’interface contractuelle entre File Analyzer et le reste de DS-STAR : tout ce qui suit consomme cette structure, pas le fichier brut.
3. FileAnalyzer Class
Cœur du composant. L’analyseur dispatche par extension (detect_format), lit via Pandas pour les formats tabulaires, calcule par colonne le dtype, le taux de manquants et — pour les numériques — les statistiques descriptives. Deux méthodes de résumé cohabitent : une textuelle déterministe (generate_summary) et une LLM contextualisée (generate_llm_summary).
Classe FileAnalyzer definie : detection format, analyse CSV, generation resume textuel et LLM
Lecture. Le dictionnaire SUPPORTEDdéclare quatre formats (.csv/.json/.md/.txt), mais analyze() n’en implémente qu’un : il dispatche vers analyze_csv et lève ValueError pour tout autre format détecté. JSON, Markdown et texte sont donc annoncés, pas traités — les exercices de fin de lab les ajoutent, et il faut alors étendre analyze() autant que SUPPORTED pour que la déclaration produise un effet. analyze_csv enrichit chaque colonne d’un dict {name, dtype, missing_pct, stats} — c’est cette granularité qui rend les métadonnées actionnables : le Planner peut repérer qu’une colonne numérique a 6 % de manquants et décider d’une imputation, sans relire le fichier. La séparation generate_summary (déterministe, pour les logs) vs generate_llm_summary (contextuelle, pour l’utilisateur) reflète les deux publics de DS-STAR.
Test de l’analyseur sur un fichier réaliste : un catalogue de 100 produits, 5 colonnes, avec 6 % de valeurs manquantes injectées sur price (lignes 10 à 15). Ce défaut n’est pas cosmétique — il teste que l’analyseur détecte et quantifie les manquants, ce qui est précisément l’information dont le Planner a besoin pour décider d’un traitement.
# Test analyseuranalyzer = FileAnalyzer()meta = analyzer.analyze(csv_path)print(analyzer.generate_summary(meta))
Lecture. Le résumé textuel confirme que l’analyseur a bien extrait la structure : 100 lignes, 5 colonnes, et — point critique — price (float64): 6.0% manquant. Les quatre autres colonnes sont complètes. Cette sortie déterministe est la source de vérité que le LLM ne réinvente pas : il la reçoit en contexte et la reformule, il ne la calcule pas.
Détail des colonnes détectées, avec statistiques descriptives sur les variables numériques. C’est le niveau de granularité qui sépare un « descripteur de fichier » trivial d’un composant DS-STAR : non seulement on connaît le schéma, mais on dispose des bornes et de la moyenne, prêtes à alimenter une décision d’analyse.
# Colonnes detailfor c in meta.columns:print(f"{c['name']}: {c['dtype']}, {c.get('missing_pct', 0)}% manquant")if'stats'in c:print(f" -> min={c['stats']['min']:.1f}, max={c['stats']['max']:.1f}, mean={c['stats']['mean']:.1f}")
Lecture. Les statistiques par colonne révèlent la forme des données : id est une clé primaire séquentielle, tandis que price et qty couvrent des plages suffisamment larges pour motiver une segmentation. Les bornes et moyennes affichées juste au-dessus sont ce qui permettrait au Planner de proposer un seuillage ou un binning pertinent — un descripteur sans statistiques ne le permettrait pas.
5. Profilage par un vrai agent Google ADK
Le profil déterministe reste la source de vérité. Cette cellule transmet uniquement les dimensions mesurées à un agent Google ADK réel : Agent et Runner sont construits par le runtime partagé, une session en mémoire est créée, puis le LLM doit appeler l’outil Python dataset_profile. Les événements ci-dessous prouvent l’appel et la réponse de l’outil ; une simple réponse textuelle ne suffirait pas.
# Exécution réelle Google ADK : Agent + session + Runner + tool call + LLMimport warningsadk_prompt = (f"Le fichier {meta.filename} contient {meta.num_rows} lignes et "f"{meta.num_columns} colonnes. Appelle dataset_profile, puis interprète ""brièvement le nombre de cellules et le ratio lignes par colonne.")with warnings.catch_warnings(): warnings.filterwarnings("ignore", message=(r"\[EXPERIMENTAL\] feature "r"FeatureName\.JSON_SCHEMA_FOR_FUNC_DECL is enabled\." ), category=UserWarning, module=r"google\.adk\.models\.llm_request", ) adk_result =await run_data_agent(adk_prompt)print(f"ADK events: {adk_result.event_count}")print(f"ADK tool calls: {', '.join(adk_result.tool_calls)}")print(f"ADK tool responses: {', '.join(adk_result.tool_responses)}")print(f"ADK tool round-trip: {adk_result.tool_was_invoked}")print(f"ADK LLM response: {adk_result.response_text}")
ADK events: 3
ADK tool calls: dataset_profile
ADK tool responses: dataset_profile
ADK tool round-trip: True
ADK LLM response: Le fichier `products.csv` contient 100 lignes et 5 colonnes, ce qui donne un total de 500 cellules (100 lignes x 5 colonnes = 500 cellules). Le ratio lignes par colonne est de 20, ce qui signifie qu'il y a en moyenne 20 lignes pour chaque colonne. Cette structure permet une distribution des données que l'on peut considérer comme équilibrée, facilitant ainsi l'analyse des informations contenues dans le jeu de données.
Lecture. La preuve ne repose pas sur le titre du notebook : le flux d’événements ADK montre un appel dataset_profile, sa réponse, puis une réponse finale du LLM. Le round-trip d’outil est donc exécuté par le vrai Runner, dans une session ADK, et non simulé par une classe maison. Les dimensions viennent de FileMetadata ; les valeurs dérivées (cells, rows_per_column) sont calculées par l’outil déterministe dataset_profile, dont le contrat se limite à la forme du tableau. L’agent les restitue correctement, mais il conclut sa réponse sur une propriété que rien ne mesure : il qualifie la distribution des données d’« équilibrée » alors qu’un ratio lignes par colonne ne dit rien de la répartition des valeurs. La preuve runtime est acquise ; la conclusion interprétative reste une affirmation de LLM, pas une mesure.
# Resume guideq ="Quelles analyses puis-je faire sur ces donnees?"print(f"Question: {q}\n")result = analyzer.generate_llm_summary(meta, q)print(result)
Question: Quelles analyses puis-je faire sur ces donnees?
Sur les données contenues dans le fichier `products.csv`, plusieurs analyses peuvent être effectuées pour obtenir des informations utiles sur les produits. Voici quelques suggestions :
1. **Analyse des prix** :
- Calculer le prix moyen, médian et écart-type des produits.
- Identifier les produits les plus chers et les moins chers.
- Analyser la distribution des prix (histogramme, boxplot).
2. **Analyse des quantités** :
- Calculer la quantité totale de produits en stock.
- Identifier les produits avec la plus grande et la plus petite quantité en stock.
- Analyser la distribution des quantités (histogramme).
3. **Analyse par catégorie** :
- Compter le nombre de produits par catégorie.
- Calculer le prix moyen par catégorie.
- Identifier la catégorie avec le plus grand chiffre d'affaires (prix * quantité).
4. **Analyse des produits manquants** :
- Évaluer l'impact des valeurs manquantes dans la colonne `price` (6% manquant).
- Décider d'une stratégie pour gérer ces valeurs manquantes (imputation, suppression, etc.).
5. **Analyse de la corrélation** :
- Analyser la corrélation entre le prix et la quantité pour voir s'il existe une relation entre ces deux variables.
6. **Visualisations** :
- Créer des graphiques pour visualiser les tendances, comme des diagrammes en barres pour les catégories, des nuages de points pour la relation entre le prix et la quantité, etc.
7. **Segmentation des produits** :
- Segmenter les produits en fonction de leur prix et de leur quantité pour identifier des groupes de produits similaires.
8. **Analyse de la rentabilité** :
- Si des données sur les coûts sont disponibles, calculer la rentabilité de chaque produit.
Ces analyses peuvent fournir des insights précieux pour la gestion des stocks, la tarification, et les décisions stratégiques concernant les produits.
Lecture. Cette seconde sortie garde volontairement le chemin LLMClient from-scratch pour comparer un appel LLM direct à l’orchestration ADK précédente. Le résumé guidé propose des analyses concrètes — segmentation, relation prix/quantité, traitement des manquants — mais il ne devient pas pour autant un « agent ADK ». Seule la cellule précédente apporte la session, le runner et le round-trip d’outil qui justifient ce qualificatif.
6. Résumé du Lab
Points clés
FileAnalyzer : analyse automatique des fichiers CSV — schéma typé, statistiques par colonne et taux de manquants. Les autres formats déclarés dans SUPPORTED (JSON, Markdown, texte) ne sont pas implémentés par analyze() : les exercices de fin de lab les ajoutent.
FileMetadata : structure unique consommée à la fois par l’affichage déterministe et par les chemins LLM — contrat entre File Analyzer et le Planner.
Google ADK réel : run_data_agent crée l’agent, la session et le runner ; les événements prouvent l’appel et la réponse de dataset_profile avant la réponse LLM finale.
Comparaison honnête : LLMClient illustre un appel direct from-scratch ; il n’est plus présenté comme un agent ADK.
Prochaine étape
Lab 11 : la boucle Planner-Coder-Verifier, qui consomme précisément les FileMetadata produites ici pour décider, générer et vérifier le code d’analyse.
À vous d’étendre le FileAnalyzer pour gérer un nouveau format de fichier !
# Exercice : Etendez le FileAnalyzer pour gerer les fichiers JSON# 1. Ajoutez une methode analyze_json# 2. Testez-la sur un fichier JSON de testimport jsonimport tempfile# Creation d'un fichier JSON de testexercise_dir = tempfile.mkdtemp()test_json_path = os.path.join(exercise_dir, 'test_data.json')test_data = {'products': [ {'id': 1, 'name': 'Laptop', 'price': 999.99, 'stock': 15}, {'id': 2, 'name': 'Mouse', 'price': 19.99, 'stock': 50}, {'id': 3, 'name': 'Keyboard', 'price': 49.99, 'stock': 30} ],'metadata': {'source': 'inventory_system','last_updated': '2026-03-13' }}withopen(test_json_path, 'w') as f: json.dump(test_data, f)# Exercice: Implementez la classe ExtendedFileAnalyzer# Elle doit heriter de FileAnalyzer et ajouter une methode analyze_jsonclass ExtendedFileAnalyzer(FileAnalyzer):def analyze_json(self, path):"""Analyse un fichier JSON et extrait les metadonnees."""# Exercice: Ouvrez et chargez le fichier JSON# Indice: with open(path, 'r') as f: data = json.load(f) data =None# Remplacez None# Exercice: Comptez les cles au niveau racine root_keys =None# Indice: list(data.keys())# Exercice: Trouvez la plus grande liste dans les valeurs# Indice: parcourez data.items(), testez isinstance(value, list) max_list =0 list_name =None# ... votre code ici# Exercice: Retournez un FileMetadata avec les informations extraitesreturnNone# Remplacez par FileMetadata(...)# Exercice: Testez l'analyseur etendu# extended_analyzer = ExtendedFileAnalyzer()# json_meta = extended_analyzer.analyze_json(test_json_path)# print(f"Fichier: {json_meta.filename}")# print(f"Cles racine: {json_meta.columns}")# Nettoyage# os.remove(test_json_path)print("Exercice a completer")
Exercice a completer
Exercice : Resume LLM avec Analyse Guidee
Utilisez le FileAnalyzer pour generer un resume LLM guide par une question metier. L’objectif est de comprendre comment la question influence la qualite et la pertinence du resume genere.
Objectifs
Créer un nouveau fichier CSV avec des données de type différent (ex: scores d’etudiants)
Analyser le fichier avec le FileAnalyzer
Comparer les resumes LLM avec et sans question guidee
Indice : - analyzer.generate_llm_summary(meta) pour un resume general - analyzer.generate_llm_summary(meta, question="Votre question") pour un resume guide - Observez comment la question oriente l’analyse du LLM
# Exercice : Resume LLM avec analyse guidee# Objectif : Comparer resumes generaux vs. resumes guides par une questionimport tempfile# TODO: Creez un dataset de scores d'etudiantsexercise_dir2 = tempfile.mkdtemp()scores_df = pd.DataFrame({'etudiant': [f'E{i}'for i inrange(1, 51)],'maths': np.random.randint(8, 20, 50),'physique': np.random.randint(6, 20, 50),'francais': np.random.randint(10, 20, 50),'section': np.random.choice(['S', 'L', 'ES'], 50)})scores_path = os.path.join(exercise_dir2, 'scores.csv')scores_df.to_csv(scores_path, index=False)# TODO: Analysez le fichier avec FileAnalyzer# analyzer = FileAnalyzer()# meta = analyzer.generate_summary(analyzer.analyze(scores_path))# TODO: Generez un resume general (sans question)# resume_general = analyzer.generate_llm_summary(analyzer.analyze(scores_path))# print("=== RESUME GENERAL ===")# print(resume_general[:300])# TODO: Generez un resume guide par une question metier# question = "Quels etudiants sont en difficulte et dans quelles matieres ?"# resume_guide = analyzer.generate_llm_summary(analyzer.analyze(scores_path), question)# print("\n=== RESUME GUIDE ===")# print(resume_guide[:300])# TODO: Comparez les deux approches# Quelles informations supplementaires le resume guide fournit-il ?print("Exercice a completer : resume LLM general vs guide par question")
Exercice a completer : resume LLM general vs guide par question
Exercice : Extension Multi-Format du FileAnalyzer
Etendez le FileAnalyzer pour supporter un format supplementaire : les fichiers texte (.txt). L’objectif est d’extraire des statistiques basiques (nombre de lignes, de mots, de caractères) et de generer un resume structure.
Objectifs
Constater que .txt est déjà déclaré dans SUPPORTED par la classe de base — la déclaration ne suffit pas : analyze() ne dispatche que vers .csv
Implementer analyze_txt(self, path) dans une classe heritee
Tester sur un fichier texte de test
Indice : - Ouvrez le fichier avec open(path, 'r', encoding='utf-8') - Comptez les mots avec len(text.split()) et les lignes avec text.count('\n') - Retournez un FileMetadata avec les statistiques extraites
# Exercice : Extension du FileAnalyzer pour les fichiers texte# Objectif : Ajouter le support du format .txt avec extraction de statistiques# TODO: Creez une classe TextFileAnalyzer qui herite de FileAnalyzerclass TextFileAnalyzer(FileAnalyzer):"""Analyseur etendu avec support des fichiers texte."""def__init__(self, llm_client=None):super().__init__(llm_client)# Etape 1: Ajoutez .txt au dictionnaire SUPPORTEDself.SUPPORTED['.txt'] ='text'def analyze_txt(self, path):""" Analyse un fichier texte et extrait les metadonnees. Args: path: chemin vers le fichier .txt Returns: FileMetadata avec statistiques textuelles """# Etape 2: Lisez le contenu du fichier# Indice: with open(path, 'r', encoding='utf-8') as f: content = f.read() content =None# TODO etudiant : remplacez# Etape 3: Calculez les statistiques# num_lines = content.count('\n') + 1# num_words = len(content.split())# num_chars = len(content)# Etape 4: Retournez un FileMetadatareturnNone# TODO etudiant : remplacez par FileMetadata(...)# TODO: Creez un fichier texte de testexercise_dir3 = tempfile.mkdtemp()txt_path = os.path.join(exercise_dir3, 'notes.txt')test_text ="""Notes de reunion - Projet Data ScienceDate: 2026-03-15Participants: Alice, Bob, CharliePoints discutes:1. Le dataset contient 10000 lignes et 45 colonnes2. La variable cible est bien equilibree (52% / 48%)3. Les features categorielles necessitent un encoding4. Le modele baseline obtient 72% d'accuracy5. Objectif: atteindre 80% avec le feature engineeringProchaines etapes:- Nettoyer les valeurs manquantes (12% sur la colonne age)- Tester un RandomForest et un XGBoost- Generer les submissions pour Kaggle"""withopen(txt_path, 'w', encoding='utf-8') as f: f.write(test_text)# TODO: Testez l'analyseur texte# txt_analyzer = TextFileAnalyzer()# txt_meta = txt_analyzer.analyze_txt(txt_path)# print(f"Fichier: {txt_meta.filename}")# print(f"Format: {txt_meta.format}")# print(f"Taille: {txt_meta.size_bytes} octets")print("Exercice a completer : extension multi-format du FileAnalyzer")
Exercice a completer : extension multi-format du FileAnalyzer
Conclusion
Ce lab a implémenté le composant File Analyzer de l’architecture DS-STAR : à partir d’un fichier CSV, il produit une FileMetadata structurée (format, dimensions, schéma typé, statistiques par colonne, taux de manquants), puis la transmet à un vrai agent Google ADK. Les autres formats déclarés dans SUPPORTED restent à implémenter — c’est l’objet des exercices de fin de lab.
Les points clés à retenir :
La séparation déterministe / agentique : generate_summary calcule la source de vérité ; l’agent ADK l’interprète et doit appeler un outil, sans devenir la source des chiffres — il reste toutefois libre de conclure au-delà de ce que l’outil mesure (cf. la lecture de la section « Profilage par un vrai agent Google ADK »).
La preuve runtime : session, runner, événements, appel d’outil, réponse d’outil et réponse LLM sont visibles dans les outputs committés.
La comparaison from-scratch : LLMClient reste utile pour montrer un appel LLM direct, mais il n’est pas confondu avec Google ADK.
Le rôle de pivot : File Analyzer est l’entrée du pipeline DS-STAR. Tout ce qui suit (Lab 11, planification) consomme sa sortie structurée, jamais le fichier brut.
Pour aller plus loin : le lab suivant (Lab 11) montre comment le Planner consomme ces métadonnées pour orchestrer la génération et la vérification de code d’analyse.
Références
Nam et al., DS-STAR: Data Science Agent for Solving Diverse Tasks across Heterogeneous Formats and Open-Ended Queries, arXiv:2509.21825, 2025. Article fondateur de l’agent DS-STAR (architecture multi-module : File Analyzer, planification structurée, génération/exécution de code, vérification).
Google Research, DS-STAR: A State-of-the-Art Versatile Data Science Agent, research.google/blog/ds-star-a-state-of-the-art-versatile-data-science-agent/, 2025. Billet de présentation (benchmark DABStep).
Z. Xi et al., The Rise and Potential of Large Language Model Based Agents: A Survey, arXiv:2309.07864, 2023. Cadre conceptuel des agents LLM — suite des Labs 8-9.