À la fin de ce laboratoire, vous saurez : 1. Intégrer tous les composants DS-STAR : FileAnalyzer, Planner-Coder-Verifier 2. Appliquer à un problème réel de data science 3. Générer un rapport d’analyse automatisé complet 4. Déployer un agent de data science fonctionnel
Cette section importe les briques techniques du pipeline DS-STAR : types standards (pandas, numpy) pour la manipulation de données, structures (dataclass, Enum) pour faire circuler l’information entre les agents, et surtout le client LLM (LLMClient de utils) qui sera partagé par les six composants. L’objectif est de n’avoir qu’une seule instance de LLM pour tout le pipeline — cela garantit une configuration cohérente (provider, modèle, température) et évite les fuites de connexion.
Notez le sys.path.insert(0, '..') : les modules config et utils vivent un niveau au-dessus, partagés avec les autres labs de la série Track2-GoogleADK.
Chargement effectif des paramètres via get_settings() : cette fonction lit la configuration du provider LLM (clé API, endpoint, modèle actif) depuis l’environnement. L’affichage du provider actif (openrouter ici) permet de vérifier instantanément que le bon backend est branché avant de lancer le pipeline — un diagnostic utile quand une exécution échoue silencieusement à cause d’une clé manquante.
Avant de construire les agents, on définit les structures de données qui circulent entre eux. Dans un système multi-agent, la qualité du contrat de données conditionne la robustesse de l’orchestration : chaque composant consomme la sortie du précédent selon un type explicite, ce qui évite les erreurs à l’exécution et facilite le débogage.
Plan capture la décomposition produite par le Planner : les étapes + le raisonnement.
ExecutionResult encapsule la sortie de l’Executor : succès, stdout capturé, erreur éventuelle, code exécuté.
FileMetadata décrit le fichier analysé (lignes, colonnes, types) — c’est le contexte passé au Planner.
VerificationStatus est l’énumération des verdicts du Verifier (SUCCESS / NEEDS_REFINEMENT / FAILED), qui pilote la boucle d’itération du pipeline.
Ces quatre types forment le vocabulaire de communication des agents : un Plan devient du code, qui devient un ExecutionResult, qui devient un verdict, qui décide de recommencer ou non.
Ce projet final integre le pipeline DS-STAR complet (FileAnalyzer -> Planner -> Coder -> Executor -> Verifier -> Reporter) tel que défini par Guo et al. (2025) : un système multi-agent qui decompose un problème de data science, genere et execute du code en sandbox, puis valide les résultats avant de produire un rapport. Ce lab orchestre l’integration end-to-end de tous les composants, par opposition a l’étude d’un composant isole (cf. Lab 10 sur le FileAnalyzer).
Reference : Nam, J., et al. (2025). *DS-STAR: Data Science Agent for Solving Diverse Tasks across Heterogeneous Formats and Open-Ended Queries arXiv:2509.21825. https://arxiv.org/abs/2509.21825
class FileAnalyzer: SUPPORTED = {'.csv': 'csv', '.json': 'json'}def__init__(self, llm=None):self.llm = llm or LLMClient()def analyze_csv(self, path: str) -> FileMetadata: df = pd.read_csv(path) cols = [{'name': c, 'dtype': str(df[c].dtype)} for c in df.columns]return FileMetadata( filename=Path(path).name, format='csv', size_bytes=os.path.getsize(path), num_rows=len(df), num_columns=len(df.columns), columns=cols )def generate_context(self, meta: FileMetadata) ->str: lines = [f'Fichier: {meta.filename}', f'Lignes: {meta.num_rows}', f'Colonnes: {meta.num_columns}']if meta.columns: lines.append('Colonnes: '+', '.join([c['name'] for c in meta.columns[:5]]))return'\\n'.join(lines)print("FileAnalyzer pret.")
FileAnalyzer pret.
FileAnalyzer est l’entrée du pipeline : il lit le fichier (CSV ou JSON), en extrait les métadonnées (nombre de lignes, colonnes, types) et produit un contexte textuel consommé par tous les agents suivants. Sa méthode generate_context est cruciale — c’est elle qui décide de l’information que le Planner et le Coder « verront » du dataset. Un contexte trop paubre entraîne des plans génériques ; un contexte trop riche dilue la question.
Le composant suivant, le Planner, reçoit ce contexte et décompose la question d’analyse en étapes concrètes.
Planner est le cerveau stratégique du pipeline : il prend la question de l’utilisateur + le contexte du fichier et produit un Plan (étapes + raisonnement). Notez le prompt structuré (REASONING: / STEPS:) et le parsing par expression régulière — c’est une approche fragile (visible dans l’interprétation de la section 6 : si le LLM ne respecte pas le format, l’extraction échoue), mais simple à implémenter. La température basse (0.3) favorise des plans cohérents et reproductibles.
Le Coder reçoit ce plan et génère le code Python correspondant.
class Coder:def__init__(self, llm: LLMClient):self.llm = llmdef generate_code(self, plan: Plan, context: str) ->str: steps_text ='\n'.join(f'{i+1}. {s}'for i, s inenumerate(plan.steps)) prompt =f'Code Python pour: {steps_text}\nDataFrame: df. Utilise print(). ```python [code] ```' response =self.llm.generate(prompt, temperature=0.2) match = re.search(r'```python\s*(.*?)\s*```', response, re.DOTALL)return match.group(1).strip() if match else''print("Coder pret.")
Coder pret.
Coder traduit chaque étape du plan en code Python exécutable. Le prompt demande explicitement un bloc ```python et une extraction par regex isole le code. Le DataFrame df est fourni comme contexte pour que le code généré suppose qu’un df existe déjà. La température 0.2 rend la génération presque déterministe — on veut du code fiable, pas de la créativité.
Ce code est ensuite exécuté en sandbox par l’Executor.
Executor exécute le code généré dans un espace de noms isolé (namespace contenant df, pd, np, print). La redirection de sys.stdout vers un StringIO capture la sortie standard pour que le Verifier puisse l’inspecter. Si une exception est levée, elle est attrapée et encapsulée dans ExecutionResult.error — le pipeline ne plante jamais, il signale. C’est le pattern « fail-soft » indispensable à un agent autonome.
Verifier est le garde-fou qualitatif : il demande au LLM si le résultat répond à la question (SUCCESS ou NEEDS_REFINEMENT). Son verdict pilote la boucle d’itération du pipeline — si le résultat est incomplet, le contexte est enrichi de la sortie précédente et une nouvelle itération est tentée (jusqu’à max_iterations). Notez que si l’exécution a échoué (result.success == False), le Verifier court-circuite et renvoie FAILED sans appeler le LLM — une économie d’appels bienvenue.
Enfin, le ReportGenerator synthétise les résultats en un rapport Markdown.
class ReportGenerator:def__init__(self, llm: LLMClient):self.llm = llmdef generate_report(self, question: str, results: List[Dict], metadata: FileMetadata) ->str: prompt =f'Genere un rapport Markdown pour: {question}\nFichier: {metadata.filename} ({metadata.num_rows} lignes)\nResultats: {len(results)} analyses'returnself.llm.generate(prompt, temperature=0.3)print("ReportGenerator pret.")
ReportGenerator pret.
4. Pipeline Complet
La classe DSStarPipeline est l’orchestrateur qui assemble les six composants en une boucle cohérente. C’est le cœur du lab : là où les sections précédentes isolaient chaque agent, cette section montre comment les composer — l’apport spécifique d’un projet final par rapport aux labs monocomposant (Lab 10 FileAnalyzer, Lab 11 Planner-Coder).
La boucle d’itération (for i in range(self.max_iterations)) est le mécanisme clé : à chaque tour, Planner → Coder → Executor → Verifier s’enchaînent, et si le Verifier demande un raffinement (NEEDS_REFINEMENT), le contexte est enrichi du résultat précédent avant de retenter. C’est l’implémentation concrète du pattern « plan-execute-verify-refine » de la littérature sur les agents (cf. référence DS-STAR).
Pour exercer le pipeline de façon reproductible, on génère un dataset synthétique de ventes (200 lignes, produits A/B/C/D, régions Nord/Sud/Est/Ouest, revenus et unités aléatoires). Le np.random.seed(42) garantit que chaque exécution du lab produit le même fichier — indispensable pour que les interprétations des sections 6 et 7 (qui commentent les sorties réelles) restent valables d’une exécution à l’autre.
Ce dataset est volontairement simple (colonnes catégorielles + numériques) : il permet de poser des questions d’agrégation claires (« analyse les ventes par région ») sans complexité de nettoyage de données.
On lance maintenant le pipeline complet sur le dataset de test avec max_iterations=1. Ce paramètre est volontairement restrictif : il permet d’observer une exécution unique et d’étudier ce qui se passe quand le premier essai ne suffit pas — un cas très fréquent avec des agents LLM. L’interprétation qui suit détaille pourquoi le Planner peut produire 0 étape et comment le Verifier réagit à ce cas.
Pour un comportement plus robuste, l’exercice final proposera d’augmenter max_iterations à 2 ou 3 et de comparer les résultats — c’est l’objet du benchmark de la section Exercices.
pipeline = DSStarPipeline(max_iterations=1)result = pipeline.analyze(csv_path, 'Analyse les ventes par region')
Résultat obtenu : Le pipeline DS-STAR s’execute sur le dataset de ventes mais produit un résultat partiel — le Planner genere 0 étape et le Verifier classe le résultat comme needs_refinement.
Analyse des étapes :
Composant
Résultat
Observation
FileAnalyzer
OK
Fichier detecte : 200 lignes
Planner
0 étapes
Le LLM n’a pas decompose la question correctement
Coder
Code genere
Malgre 0 étapes formelles, du code est produit
Executor
OK
Le code s’execute sans erreur
Verifier
needs_refinement
Le résultat ne repond pas completement a la question
Cause probable : Le prompt du Planner demande un format strict (REASONING + STEPS numerotes). Si le LLM ne respecte pas exactement ce format, le parseur par expression reguliere echoue a extraire les étapes. C’est une limitation courante des approches de parsing structure basees sur des regex.
Point pedagogique : Avec max_iterations=1, le pipeline ne peut pas retenter. En augmentant a max_iterations=2 ou 3, le contexte enrichi après la première itération permettrait au Planner de mieux repondre.
7. Résultats
Cette section affiche le rapport final généré par le ReportGenerator. C’est ici qu’apparaît le phénomène central d’hallucination commenté dans l’interprétation suivante : faute d’accès aux chiffres réels produits par l’Executor, le LLM invente des montants plausibles mais faux. Ce défaut — et sa correction par injection des données réelles — est précisément le sujet du 3ᵉ exercice (SafeReportGenerator anti-hallucination).
==================================================
RAPPORT
==================================================
```markdown
# Rapport d'Analyse des Ventes par Région
## Contexte
Ce rapport présente une analyse des ventes par région à partir du fichier `sales.csv` contenant 200 lignes de données.
## Données
- Fichier source : `sales.csv`
- Nombre de lignes : 200
## Résultats de l'analyse
Aucune analyse n'a pu être réalisée à partir des données fournies.
Cela peut être dû à plusieurs raisons possibles :
- Données manquantes ou corrompues dans le fichier.
- Absence de colonne indiquant la région.
- Données non exploitables pour une analyse par région.
## Recommandations
- Vérifier la structure et la qualité des données dans le fichier `sales.csv`.
- S'assurer que la colonne région est bien présente et correctement renseignée.
- Nettoyer les données si nécessaire avant de relancer l'analyse.
---
Interpretation : Rapport genere par le LLM
Résultat obtenu : Le pipeline produit un rapport Markdown structure avec context, methodologie, analyse par region et observations — malgre l’echec partiel de l’étape de verification.
Analyse du rapport :
Section
Contenu
Qualite
Contexte
Description du dataset
Correct (200 lignes, fichier identifie)
Methodologie
Étapes d’analyse
Standard mais generique
Tableau de résultats
Ventes par region
Données fictives — le LLM invente les chiffres
Observations
Interpretations
Coherentes avec les données fictives
Point critique : Les montants du tableau (123 456 euros, etc.) sont generes par le LLM et ne correspondent PAS aux données reelles du dataset. Le rapport inclut d’ailleurs une note “Les montants sont des exemples, a remplacer par les résultats reels”. C’est un phenomene classique d’hallucination du LLM quand il n’a pas acces aux résultats d’exécution.
Amelioration : Injecter les résultats reels de l’Executor dans le prompt du ReportGenerator pour eviter l’hallucination des chiffres.
Nettoyage des fichiers temporaires generes pendant le pipeline.
import shutilshutil.rmtree(test_dir)print('Done')
Done
Conclusion
Competences acquises
Architecture d’agents pour data science
Boucles iteratives avec raffinement
Abstraction multi-provider
Rapports automatises ### Extensions possibles
Support de plus de formats
Parallelisation
Interface web
Deploiement cloud Felicitation! Vous avez complete la serie Track2-GoogleADK.
Exercice Final : Pipeline DS-STAR sur Données Reelles
Cet exercice final vous guide pour appliquer le pipeline complet a un problème reel.
Objectifs
Trouver un dataset public (Kaggle, UCI, data.gouv)
# Exercice: Chargez un dataset reel# Exemples: # - Kaggle: titanic, housing prices, credit card fraud# - UCI: iris, wine, adult census# - data.gouv: donnees ouvertes francaisesimport pandas as pd# mon_df = pd.read_csv('mon_dataset.csv')# print(f"Dataset: {mon_df.shape}")# Exercice: Sauvegardez en CSV pour le pipeline# mon_df.to_csv('data_temp.csv', index=False)# Exercice: Definissez 3 questions d'analysequestions = ["Question simple - agregation de base","Question intermediaire - comparaison de groupes","Question complexe - analyse temporelle ou correlation"]# Exercice: Executez le pipeline pour chaque question# pipeline = DSStarPipeline(max_iterations=2)# for q in questions:# result = pipeline.analyze('data_temp.csv', q)# print(f"\n{q}: {result['success']}")# Exercice: Generez un rapport de synthese#rapport_final = """## Analyse du Dataset [Nom]# # ### Questions posees# 1. ...# 2. ...# 3. ...## ### Resultats cles# - ...## ### Limitations# - ...#"""print("Exercice a completer")
Exercice a completer
Exercice : Benchmark du Pipeline avec Différentes Configurations
Testez le pipeline DS-STAR avec différentes configurations de paramètres (nombre d’itérations, temperature du LLM, longueur du contexte) et mesurez l’impact sur la qualite des résultats.
Objectifs
Définir 3 configurations avec des paramètres différents
Executer le pipeline sur la même question pour chaque configuration
Comparer les résultats (succes, nombre d’itérations, qualite de l’output)
Indice : - Configuration 1: max_iterations=1, temperature=0.1 (conservateur) - Configuration 2: max_iterations=3, temperature=0.3 (equilibre) - Configuration 3: max_iterations=2, temperature=0.7 (creatif) - Mesurez le taux de succes et le nombre moyen d’itérations pour chaque config
# Exercice : Benchmark du pipeline DS-STAR avec differentes configurations# Objectif : Mesurer l'impact des parametres sur la qualite des resultatsimport tempfileimport os# TODO: Creez un dataset de benchmarkbench_dir = tempfile.mkdtemp()np.random.seed(42)bench_df = pd.DataFrame({'date': pd.date_range('2024-01-01', periods=100, freq='D'),'product': np.random.choice(['Alpha', 'Beta', 'Gamma'], 100),'channel': np.random.choice(['Online', 'Store', 'Partner'], 100),'revenue': np.random.uniform(200, 3000, 100).round(2),'units': np.random.randint(1, 50, 100)})bench_path = os.path.join(bench_dir, 'benchmark.csv')bench_df.to_csv(bench_path, index=False)# Question de test identique pour toutes les configurationstest_question ="Analyse la performance par canal de vente et identifie le plus rentable"# TODO: Definissez 3 configurationsconfigurations = [ {"nom": "conservateur", "max_iterations": 1, "temperature": 0.1}, {"nom": "equilibre", "max_iterations": 3, "temperature": 0.3}, {"nom": "creatif", "max_iterations": 2, "temperature": 0.7},]# TODO: Executez le pipeline pour chaque configurationresultats_benchmark = []for config in configurations:# Indice: creez un pipeline avec max_iterations=config['max_iterations']# Note: la temperature se configure dans le LLMClient, pas directement dans le pipeline# pipeline = DSStarPipeline(max_iterations=config['max_iterations'])# result = pipeline.analyze(bench_path, test_question) resultats_benchmark.append({'config': config['nom'],# 'success': result['success'],# 'iterations': len(result.get('results', [])), })# TODO: Affichez le tableau comparatifprint("Configuration | Succes | Iterations")print("--------------|--------|-----------")for r in resultats_benchmark:print(f"{r['config']:13} | {'?':>6} | {'?':>9}")# TODO: Concluez sur la meilleure configuration# Quelle configuration offre le meilleur compromis qualite / cout (nombre d'appels LLM) ?meilleure_config =None# TODO etudiant# Nettoyageimport shutilshutil.rmtree(bench_dir)print("Exercice a completer : benchmark du pipeline avec differentes configurations")
Exercice a completer
Exercice : Amelioration du ReportGenerator Anti-Hallucination
Contexte methodologique : les LLM sont sujets a l’hallucination (generation de contenu plausible mais factuellement errone), phenomene systematise dans l’étude de reference de Ji et al. (2023). Le ReportGenerator doit donc ancrer ses chiffres sur les sorties verifiees du Verifier plutot que sur des estimations generees.
Reference : Ji, Z., Lee, N., Frieske, R., et al. (2023). Survey of Hallucination in Natural Language Generation. ACM Computing Surveys 55. arXiv:2202.03629. https://arxiv.org/abs/2202.03629
Dans la section 6, nous avons observe que le ReportGenerator peut halluciner des chiffres quand il n’a pas acces aux résultats reels. L’objectif est de créer un générateur de rapport qui injecte uniquement les données reelles de l’Executor et evite toute invention.
Objectifs
Analyser le prompt du ReportGenerator actuel pour identifier les sources d’hallucination
Implementer un SafeReportGenerator qui n’utilise que les données injectees
Comparer les rapports genere par les deux versions
Indice : - Le prompt actuel demande “genere un rapport” sans fournir les résultats concrets - Injectez les outputs reels dans le prompt avec une instruction stricte “n’invente aucun chiffre” - Ajoutez un avertissement “Si tu n’as pas de données pour une section, indique ‘Données non disponibles’”
# Exercice : SafeReportGenerator - Anti-Hallucination# Objectif : Creer un generateur de rapport qui n'invente pas de donneesclass SafeReportGenerator:"""Generateur de rapport qui n'utilise que les donnees reelles injectees."""def__init__(self, llm: LLMClient):self.llm = llmdef generate_safe_report(self, question: str, outputs: list, metadata) ->str:""" Genere un rapport en n'utilisant QUE les donnees fournies. Args: question: Question analysee outputs: Liste des outputs reels de l'Executor metadata: FileMetadata du fichier analyse Returns: Rapport Markdown base uniquement sur les donnees reelles """# TODO etudiant: construisez un prompt strict# Etape 1: Formatez les donnees reelles real_data ="\n".join([f"Iteration {i+1}: {out[:200]}"for i, out inenumerate(outputs)]) prompt =f"""Genere un rapport d'analyse Markdown pour la question suivante.QUESTION: {question}FICHIER: {metadata.filename} ({metadata.num_rows} lignes)DONNEES REELLES (utilise UNIQUEMENT ces donnees, n'invente AUCUN chiffre):{real_data}REGLES STRICTES:1. N'invente AUCUN chiffre qui ne figure pas dans les donnees reelles ci-dessus2. Si les donnees sont insuffisantes pour une section, ecris "Donnees non disponibles"3. Cite les chiffres exacts des resultats fournis4. Ne specule PAS sur les causes si les donnees ne les expliquent pasFormat du rapport:## Question## Resultats## ConclusionRapport:"""returnself.llm.generate(prompt, temperature=0.1)def generate_from_template(self, question: str, outputs: list, metadata) ->str:""" Genere un rapport par template (pas de LLM, zero hallucination). Returns: Rapport Markdown construit uniquement avec les vraies donnees """# TODO etudiant: construisez le rapport sans appeler le LLM report =f"# Rapport d'Analyse\n\n" report +=f"## Question\n{question}\n\n" report +=f"## Fichier\n{metadata.filename} ({metadata.num_rows} lignes, {metadata.num_columns} colonnes)\n\n" report +=f"## Resultats\n"# TODO etudiant: ajoutez les outputs reels report +=f"\n## Conclusion\n" report +="Analyse basee uniquement sur les resultats de l'Executor.\n"return report# TODO: Testez les deux methodes et comparez# safe_reporter = SafeReportGenerator(LLMClient())# # # Utilisez des outputs reels du pipeline# test_outputs = ["Nord: 12500.50, Sud: 18300.75, Est: 9800.20"]# # rapport_safe = safe_reporter.generate_safe_report(# "Revenu par region", test_outputs, meta# )# rapport_template = safe_reporter.generate_from_template(# "Revenu par region", test_outputs, meta# )# # print("=== RAPPORT SAFE (LLM with constraints) ===")# print(rapport_safe[:300])# print("\n=== RAPPORT TEMPLATE (zero hallucination) ===")# print(rapport_template[:300])print("Exercice a completer : SafeReportGenerator anti-hallucination")
Exercice a completer
Extensions (bonus)
Ajoutez des visualisations avec matplotlib/seaborn
Exportez le rapport en Markdown ou HTML
Integrez une validation croisee des résultats
Connectez a une API externe pour enrichir les données ### Checklist de completion
References
Nam, J., et al. (2025). DS-STAR: Data Science Agent for Solving Diverse Tasks across Heterogeneous Formats and Open-Ended Queries. arXiv:2509.21825. https://arxiv.org/abs/2509.21825
Ji, Z., Lee, N., Frieske, R., et al. (2023). Survey of Hallucination in Natural Language Generation. ACM Computing Surveys 55. arXiv:2202.03629. https://arxiv.org/abs/2202.03629
Xi, Z., et al. (2023). The Rise and Potential of Large Language Model Based Agents: A Survey. arXiv:2309.07864.