À la fin de ce laboratoire, vous saurez : 1. Implémenter la boucle itérative Planner-Coder-Verifier de DS-STAR 2. Créer un système multi-agents pour l’analyse de données 3. Gérer les échecs et raffinements automatiques 4. Orchestrer plusieurs composants LLM en pipeline
Prérequis
Lab 10 (File Analyzer) complété
Connaissance des patterns d’agents
Configuration multi-provider active
Durée estimée : 45-60 minutes
1. Architecture Planner-Coder-Verifier
Repères bibliographiques. Cette boucle itérative Planner → Coder → Executor → Verifier est un cas particulier du paradigme ReAct (Reasoning + Acting) de S. Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models, arXiv:2210.03629, ICLR 2023, où l’agent alterne raisonnement et actions observables. L’étape de Verifier qui renvoie vers le Coder en cas d’échec (raffinement automatique) suit le principe d’auto-réflexion formalisé par N. Shinn et al., Reflexion: Language Agents with Verbal Reinforcement Learning, arXiv:2303.11366, NeurIPS 2023. Le découpage en rôles spécialisés (Planner / Coder / Verifier) est l’architecture multi-agent déterministe retenue par DS-STAR (Nam et al., arXiv:2509.21825, 2025).
Question --> [PLANNER] --> Plan
|
v
[CODER] --> Code
|
v
[EXECUTOR] --> Result
|
v
[VERIFIER] --> Success/Retry
Schema : la boucle Planner-Coder-Verifier
Chaque étape transforme l’entree de la précédente ; le Verifier conclut sur un succes ou declenche un nouvel essai (retry).
flowchart TD
Q["Question"] --> P["PLANNER (genere le Plan)"]
P --> C["CODER (genere le Code)"]
C --> E["EXECUTOR (produit le Result)"]
E --> V["VERIFIER"]
V --> SR["Success / Retry"]
2. Configuration
Même socle multi-provider qu’au Lab 10 : get_settings() expose un fournisseur actif unique, et les quatre agents (Planner, Coder, Executor, Verifier) reçoivent un LLMClient qui sait parler à n’importe quel backend compatible. L’orchestrateur DSStarAgent instancie ces quatre composants — changer de modèle est un changement de configuration, pas de code.
import sysimport warningsfrom pathlib import Pathsys.path.insert(0, str(Path().resolve().parent))import jsonimport reimport pandas as pdimport numpy as npfrom typing import Optional, Dict, List, Tuplefrom dataclasses import dataclassfrom enum import Enumfrom config import get_settingsfrom utils import LLMClientfrom utils.adk_runtime import build_agent, run_agent_turnprint("Imports OK : pandas, numpy, LLMClient et runtime Google ADK réel")
Imports OK : pandas, numpy, LLMClient et runtime Google ADK réel
Lecture. L’output précédent donne le provider réellement actif pour cette exécution. Les étapes LLM historiques de la première boucle passent par LLMClient; la section Google ADK plus bas réutilise la même configuration pour construire de vrais Agent, sessions et Runner. Un provider unique simplifie le laboratoire, même si une architecture de production pourrait affecter un modèle différent à chaque rôle.
3. Data Classes
La boucle fait circuler des données structurées entre ses étapes, pas du texte libre. Trois dataclasses matérialisent ce contrat : Plan (les étapes + le raisonnement qui les justifie), ExecutionResult (succès/échec + stdout + erreur éventuelle), et VerificationStatus (un enum à trois valeurs : SUCCESS, NEEDS_REFINEMENT, FAILED). C’est ce typage qui rend le raffinement automatique possible — le Verifier ne renvoie pas une chaîne ambiguë, mais un verdict actionnable.
Lecture. Trois structures définies : Plan porte la décomposition en étapes et le raisonnement du Planner ; ExecutionResult capture le stdout, l’erreur et le succès de l’exécution ; VerificationStatus est l’enum qui pilote la boucle. Le point décisif est NEEDS_REFINEMENT : c’est ce verdict qui renvoie le Coder au travail avec le contexte de l’échec, au lieu d’un simple échec terminal. Sans cette troisième valeur, la boucle ne pourrait pas corriger — elle ne ferait que constater.
4. Planner Agent
Première étape de la boucle : le Planner reçoit la question et les métadonnées du dataset (le FileMetadata du Lab 10), et produit un Plan — une liste ordonnée d’étapes d’analyse, accompagnée du raisonnement qui les justifie. C’est l’étape où le LLM « pense » à haute voix avant de coder : décomposer le problème avant de le résoudre.
class Planner:def__init__(self, llm: LLMClient):self.llm = llmdef create_plan(self, question: str, context: str) -> Plan: prompt =f"""Tu es un planificateur d'analyse de donnees.CONTEXTE DES FICHIERS:{context}QUESTION: {question}Genere un plan d'analyse en 3-5 etapes. Pour chaque etape, decris:1. L'objectif2. La methode3. Le resultat attenduFormat de sortie:REASONING: [ton raisonnement]STEPS:1. [etape 1]2. [etape 2]...Plan:""" response =self.llm.generate(prompt, temperature=0.3)# Parse response steps = [] reasoning ="" lines = response.split('\n') in_steps =Falsefor line in lines:if line.startswith('REASONING:'): reasoning = line.replace('REASONING:', '').strip()elif line.startswith('STEPS:'): in_steps =Trueelif in_steps and re.match(r'^\d+\.', line.strip()): steps.append(re.sub(r'^\d+\.\s*', '', line.strip()))return Plan(steps=steps, reasoning=reasoning)print("Classe Planner definie : decomposition de questions en plans d'analyse (3-5 etapes)")
Classe Planner definie : decomposition de questions en plans d'analyse (3-5 etapes)
Lecture. Le Planner est un wrapper autour du LLM qui force la sortie en un Plan structuré : il extrait les étapes via un parseur (expressions régulières sur un format attendu). C’est précisément ce parseur qui est fragile — l’Exercice 2 du lab vous demande d’ailleurs d’améliorer sa robustesse, car un Planner qui extrait 0 étapes fait échouer toute la boucle en aval.
5. Coder Agent
Deuxième étape : le Coder reçoit le Plan et génère du code Python qui l’exécute. Il produit une chaîne de code (pas un fichier), destinée à être passée à l’Executor. C’est l’étape la plus exposée aux échecs : un LLM peut générer du code qui appelle des colonnes inexistantes, oublie un import, ou se trompe de nom de variable — d’où la nécessité du Verifier en aval.
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"""Tu es un programmeur Python expert. Genere du code pour executer ce plan.CONTEXTE:{context}PLAN:{steps_text}Genere UNIQUEMENT du code Python executable entre balises ```python ... ```Le DataFrame est disponible dans la variable 'df'.Utilise print() pour afficher les resultats.Code:""" response =self.llm.generate(prompt, temperature=0.2)# Extract code match = re.search(r'```python\s*(.*?)\s*```', response, re.DOTALL)if match:return match.group(1).strip()return responseprint("Classe Coder definie : generation de code Python a partir d'un plan d'analyse")
Classe Coder definie : generation de code Python a partir d'un plan d'analyse
Lecture. Le Coder transforme un Plan abstrait en code concret exécutable. Sa fragilité est intrinsèque : il infère les noms de colonnes et la API Pandas depuis le contexte, et se trompe parfois (le test plus bas le montrera avec un ['revenu'] au lieu de 'revenue'). C’est exactement la défaillance que la boucle Planner-Coder-Verifier est conçue pour rattraper — à condition que max_iterations laisse assez de tentatives.
6. Executor
Troisième étape : l’Executor prend le code généré et l’exécute dans un namespace contrôlé (df, pd, np exposés), en capturant stdout et exceptions. Il ne raisonne pas — il exécute et rapporte. La capture de l’erreur (traceback Python) est ce qui permettra au Verifier de diagnostiquer, puis au Coder de corriger.
Classe Executor definie : execution securisee de code Python avec capture stdout
Lecture. L’Executor sandboxe l’exécution : un namespace restreint avec df/pd/np, et la capture systématique du stdout et des exceptions. Le point critique est qu’il ne valide rien — un code qui plante renvoie une ExecutionResult avec success=False et le message d’erreur. Ce message remonte au Verifier, qui décide s’il mérite un retry (erreur corrigeable) ou un échec définitif.
7. Verifier Agent
Quatrième étape : le Verifier examine le résultat de l’exécution à la lumière de la question initiale. Trois verdicts possibles : SUCCESS (la question est répondue), NEEDS_REFINEMENT (le résultat est partiel ou faux, mais l’erreur est corrigeable — on relance le Coder avec le contexte de l’échec), FAILED (erreur non récupérable). C’est la décision qui pilote la boucle.
class Verifier:def__init__(self, llm: LLMClient):self.llm = llmdef verify(self, question: str, result: ExecutionResult) -> Tuple[VerificationStatus, str]:ifnot result.success:return VerificationStatus.FAILED, f"Erreur d'execution: {result.error}" prompt =f"""Verifie si ce resultat repond a la question.QUESTION: {question}RESULTAT:{result.output[:1000]}Reponds par:- SUCCESS si le resultat est complet et correct- NEEDS_REFINEMENT si le resultat est partiel mais prometteur- FAILED si le resultat ne repond pas du toutVerdict:""" response =self.llm.generate(prompt, temperature=0.1).upper()if'SUCCESS'in response:return VerificationStatus.SUCCESS, "Resultat valide"elif'REFINEMENT'in response:return VerificationStatus.NEEDS_REFINEMENT, "Necessite des ajustements"return VerificationStatus.FAILED, "Resultat insuffisant"print("Classe Verifier definie : validation des resultats (SUCCESS / NEEDS_REFINEMENT / FAILED)")
Classe Verifier definie : validation des resultats (SUCCESS / NEEDS_REFINEMENT / FAILED)
Lecture. Le Verifier est le juge de la boucle : il compare le résultat produit à la question posée, et émet un VerificationStatus. La distinction NEEDS_REFINEMENT vs FAILED est subtile et importante — une KeyError sur un nom de colonne est raffinable (le Coder peut corriger), une erreur de logique métier peut ne pas l’être. C’est ce verdict qui détermine si l’itération suivante a lieu.
8. DS-STAR Orchestrator
L’orchestrateur assemble les quatre composants en une boucle : Plan → Code → Execute → Verify, répétée jusqu’à SUCCESS ou max_iterations. À chaque itération, le contexte s’enrichit — le Coder reçoit le code précédent et l’erreur observée, de sorte que la tentative suivante est informée de l’échec précédent. C’est ce mécanisme qui distingue DS-STAR d’un simple pipeline linéaire.
Classe DSStarAgent definie : orchestrateur Planner-Coder-Executor-Verifier avec boucle iterative
Lecture.DSStarAgent est la colle : il instancie les quatre agents et pilote la boucle. Le paramètre max_iterations borne le nombre de tentatives — un compromis entre coût (chaque itération appelle le LLM plusieurs fois) et qualité (plus d’itérations = plus de chances de rattraper un échec). À max_iterations=1, aucune retry n’est possible : un seul échec du Coder condamne la question, comme le montrera le test.
9. Test avec un Dataset
Dataset synthétique : 200 lignes de ventes (date, produit, région, revenu, unités). La question posée à l’agent (« Quelle est la région la plus rentable ? ») demande une agrégation par région — une tâche typique que DS-STAR doit pouvoir résoudre en une boucle Plan → Code → Execute → Verify.
La cellule suivante exécute la boucle historique LLMClient avec une seule itération. Sa trace permet d’observer le plan produit, le code exécuté et le verdict réellement obtenu, sans présupposer un échec particulier du modèle.
# Test de l'agent DS-STAR avec 1 iteration pour rapiditeagent = DSStarAgent(df, max_iterations=1)question ="Quelle est la region avec le plus grand revenu moyen?"result = agent.analyze(question)print("\n"+"="*50)print("RESULTAT FINAL:")print("="*50)if result['success']:print(result['output'])else:print(f"Erreur: {result.get('error', 'Inconnu')}")
=== ITERATION 1/1 ===
[PLANNER] Creation du plan...
Etapes: 5
[CODER] Generation du code...
[EXECUTOR] Execution...
[ERROR] ['revenu']
==================================================
RESULTAT FINAL:
==================================================
Erreur: Max iterations atteint
Lecture du résultat historique. La trace précédente montre le comportement réellement obtenu par LLMClient sur ce dataset seedé : elle indique le nombre d’étapes, l’output de l’Executor et le verdict du Verifier. Cette observation sert de point de comparaison à la jambe ADK, sans généraliser un succès ou un échec stochastique à toutes les exécutions.
10. La même boucle sur le vrai runtime Google ADK
La première implémentation rend les contrats Planner-Coder-Executor-Verifier explicites, mais ses rôles LLM sont des wrappers maison autour de LLMClient. Cette seconde jambe conserve exactement la boucle pédagogique tout en remplaçant chaque rôle LLM par un vrai google.adk.agents.Agent exécuté dans une session par un Runner.
L’Executor reste volontairement local et déterministe : ADK orchestre le raisonnement et les événements, tandis que le code Pandas généré reste visible, exécutable et inspectable. Le Planner reçoit un outil Python réel pour lire le schéma au lieu de l’inventer.
def get_dataset_schema() -> Dict:"""Retourne au Planner ADK le schéma réellement disponible."""return {"rows": int(len(df)),"columns": {name: str(dtype) for name, dtype in df.dtypes.items()}, }print("Outil ADK prêt : get_dataset_schema expose "f"{len(df)} lignes et {len(df.columns)} colonnes")
Le schéma est désormais obtenu par un outil déterministe. La cellule suivante construit les trois rôles ADK et garde explicitement la boucle d’orchestration dans le notebook.
asyncdef adk_analyze(question: str, max_iterations: int=2) -> Dict:"""Exécute la boucle visible Planner → Coder → Executor → Verifier.""" schema = get_dataset_schema() context = ("DataFrame df disponible. Schéma exact: "f"{json.dumps(schema, ensure_ascii=False)}. ""Le Planner doit appeler get_dataset_schema." ) executor = Executor(df) evidence = {} last_result = ExecutionResult(False, "", "Aucune exécution") last_verdict ="FAILED" planner = build_agent("lab11_planner","Planifie une analyse de données à partir du schéma réel.", ("Appelle obligatoirement get_dataset_schema. Réponds ensuite avec ""REASONING: sur une ligne, puis STEPS: et 3 étapes numérotées." ), tools=(get_dataset_schema,), ) coder = build_agent("lab11_coder","Transforme un plan d'analyse en code Pandas exécutable.", ("Réponds uniquement avec un bloc ```python```. Le DataFrame s'appelle ""df. Respecte exactement le schéma fourni et la métrique demandée: ""revenue désigne le revenu, units le nombre d'unités. Utilise print()." ), ) verifier = build_agent("lab11_verifier","Vérifie qu'une sortie répond à la question initiale.", ("Commence impérativement par SUCCESS, NEEDS_REFINEMENT ou FAILED, ""puis justifie en une phrase. Vérifie le nom exact de la métrique." ), )for iteration inrange(max_iterations): planner_session =f"lab11-planner-{iteration}" plan_turn =await run_agent_turn( planner,f"QUESTION: {question}\nCONTEXTE: {context}", session_id=planner_session, ) evidence[f"planner-{iteration}"] = {"session_id": planner_session,"events": plan_turn.event_count,"tool_calls": ", ".join(plan_turn.tool_calls),"tool_responses": ", ".join(plan_turn.tool_responses), } coder_session =f"lab11-coder-{iteration}" code_turn =await run_agent_turn( coder,f"QUESTION: {question}\nPLAN:\n{plan_turn.response_text}\nCONTEXTE: {context}", session_id=coder_session, ) evidence[f"coder-{iteration}"] = {"session_id": coder_session,"events": code_turn.event_count,"tool_calls": ", ".join(code_turn.tool_calls),"tool_responses": ", ".join(code_turn.tool_responses), } match = re.search(r"```(?:python)?\s*(.*?)\s*```", code_turn.response_text, re.DOTALL, ) code = match.group(1).strip() if match else code_turn.response_text.strip() last_result = executor.execute(code) verifier_session =f"lab11-verifier-{iteration}" verify_turn =await run_agent_turn( verifier, (f"QUESTION: {question}\nSCHÉMA: {json.dumps(schema, ensure_ascii=False)}\n"f"SUCCÈS EXÉCUTION: {last_result.success}\n"f"ERREUR: {last_result.error}\nSORTIE:\n{last_result.output[:1200]}" ), session_id=verifier_session, ) evidence[f"verifier-{iteration}"] = {"session_id": verifier_session,"events": verify_turn.event_count,"tool_calls": ", ".join(verify_turn.tool_calls),"tool_responses": ", ".join(verify_turn.tool_responses), } last_verdict = verify_turn.response_text.strip() verdict_match = re.match(r"^(SUCCESS|NEEDS_REFINEMENT|FAILED)\b", last_verdict.upper(), ) verdict_token = verdict_match.group(1) if verdict_match else"FAILED"if last_result.success and verdict_token =="SUCCESS":return {"success": True,"output": last_result.output,"code": code,"iterations": iteration +1,"verdict": last_verdict,"planner_tool_round_trip": plan_turn.tool_was_invoked,"evidence": evidence, } context += (f"\nTentative {iteration +1}: erreur={last_result.error}; "f"verdict={last_verdict}; corrige le code." )return {"success": False,"output": last_result.output,"error": last_result.error or"Max iterations atteint","iterations": max_iterations,"verdict": last_verdict,"planner_tool_round_trip": any( item["tool_calls"] and item["tool_responses"]for role, item in evidence.items()if role.startswith("planner-") ),"evidence": evidence, }print("Boucle ADK définie : Planner, Coder et Verifier utilisent de vrais Agent/Runner")
Boucle ADK définie : Planner, Coder et Verifier utilisent de vrais Agent/Runner
La fonction est définie sans encore appeler le provider. L’exécution suivante lance réellement les trois rôles et imprime les preuves issues de leurs flux d’événements.
# Exécution réelle : trois Agent ADK, trois sessions, trois Runner et un outilwith 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 adk_analyze(question)expected_region = df.groupby("region")["revenue"].mean().idxmax()assert adk_result["planner_tool_round_trip"], "Le Planner ADK n'a pas appelé l'outil"assert adk_result["success"], f"La boucle ADK a échoué: {adk_result['verdict']}"assert expected_region in adk_result["output"], ("La sortie LLM/Executor ne nomme pas la région calculée avec revenue")print("\n"+"="*50)print("PREUVE GOOGLE ADK RÉELLE")print("="*50)for role, evidence in adk_result["evidence"].items():print(f"{role}: session={evidence['session_id']}, "f"events={evidence['events']}, "f"tool_calls={evidence['tool_calls'] or'aucun'}, "f"tool_responses={evidence['tool_responses'] or'aucune'}" )print(f"Planner tool round-trip: {adk_result['planner_tool_round_trip']}")print(f"Succès: {adk_result['success']}")print(f"Itérations: {adk_result['iterations']}")print(f"Région attendue (calcul déterministe): {expected_region}")print(f"Verdict LLM: {adk_result['verdict']}")print(f"Sortie Executor:\n{adk_result['output']}")
==================================================
PREUVE GOOGLE ADK RÉELLE
==================================================
planner-0: session=lab11-planner-0, events=3, tool_calls=get_dataset_schema, tool_responses=get_dataset_schema
coder-0: session=lab11-coder-0, events=1, tool_calls=aucun, tool_responses=aucune
verifier-0: session=lab11-verifier-0, events=1, tool_calls=aucun, tool_responses=aucune
Planner tool round-trip: True
Succès: True
Itérations: 1
Région attendue (calcul déterministe): Ouest
Verdict LLM: SUCCESS, la sortie répond correctement à la question initiale en identifiant la région avec le plus grand revenu moyen.
Sortie Executor:
La région avec le plus grand revenu moyen est Ouest avec un revenu moyen de 2800.6191304347826.
Lecture du résultat ADK. La trace précédente est la preuve d’exécution : chaque rôle LLM possède une session nommée et émet des événements via un Runner. Le Planner réalise en plus un aller-retour d’outil (get_dataset_schema) avant que le Coder ne produise le code, que l’Executor local ne l’exécute et que le Verifier ne rende son verdict. Cette jambe n’est donc ni une classe maison appelée « ADK », ni une réponse simulée.
11. Résumé du Lab
Ce que nous avons implémenté
Planner : décompose la question en étapes d’analyse, avec raisonnement.
Coder : génère le code Python qui exécute le plan, à partir du contexte.
Executor : exécute le code dans un namespace contrôlé et capture stdout + erreurs.
Verifier : valide le résultat (SUCCESS), demande un raffinement (NEEDS_REFINEMENT) ou déclare l’échec (FAILED).
Google ADK réel : instancie les rôles LLM comme de vrais Agent, crée une session et un Runner par rôle, puis observe les événements et le round-trip d’outil du Planner.
Points clés
La boucle itérative est la valeur ajoutée de DS-STAR : une erreur observée peut être réinjectée dans le contexte de l’itération suivante au lieu de devenir un échec silencieux.
Le Verifier est le point de décision : son verdict pilote le succès, le raffinement ou l’arrêt.
max_iterations est un compromis coût/qualité : il borne explicitement le nombre d’appels LLM.
ADK orchestre, l’Executor reste déterministe : le runtime agentique porte les rôles LLM et leurs événements; l’exécution Pandas reste visible et contrôlée dans le notebook.
Prochaine étape
Lab 12 : workshop DS-STAR complet sur fichiers réels, avec davantage d’itérations et de cas d’échec à raffiner.
Exercice : Analysez vos propres données
En utilisant l’architecture DS-STAR que vous venez d’apprendre, créez un agent capable d’analyser un dataset de votre choix.
Objectifs
Créer un dataset personnalise (ou utiliser un dataset publique)
=== ITERATION 1/2 ===
[PLANNER] Creation du plan...
Etapes: 5
[CODER] Generation du code...
[EXECUTOR] Execution...
[ERROR] Cannot describe a DataFrame without columns
=== ITERATION 2/2 ===
[PLANNER] Creation du plan...
Etapes: 5
[CODER] Generation du code...
[EXECUTOR] Execution...
[OUTPUT] Le DataFrame est vide.
Aperçu du DataFrame après nettoyage :
Empty DataFrame
Columns: []
Index: []
...
[VERIFIER] Verification...
[STATUS] failed: Resultat insuffisant
Reussite: False, Iterations: 2
Exercice : Amelioration du Prompt du Planner
Le parseur du Planner utilise des expressions regulieres pour extraire les étapes. L’objectif est d’ameliorer le prompt et/ou le parseur pour augmenter le taux de succes de l’extraction.
Objectifs
Analyser pourquoi le Planner extrait parfois 0 étapes
Proposer un prompt plus robuste avec des instructions plus strictes
Implementer un parseur de fallback (ex: detecter les puces - ou *)
Indice : - Le format actuel exige REASONING: et STEPS: avec des numéros 1. - Un fallback pourrait chercher des lignes commencant par - ou * - Testez votre version amelioree sur les mêmes questions que le Planner original
# Exercice : Amelioration du prompt et du parseur du Planner# Objectif : Rendre l'extraction des etapes plus robuste# TODO: Analysez le prompt actuel du Planner (cell-8) et identifiez les faiblesses# Le prompt demande "REASONING:" et "STEPS:" avec des numeros# Que se passe-t-il si le LLM repond differemment ?# TODO: Creez une classe RobustPlanner qui herite de Plannerclass RobustPlanner(Planner):"""Planner avec prompt ameliore et parseur de fallback."""def create_plan(self, question: str, context: str) -> Plan:# Etape 1: Modifiez le prompt pour etre plus explicite prompt =f"""Tu es un planificateur d'analyse de donnees.CONTEXTE: {context[:600]}QUESTION: {question}IMPORTANT: Reponds OBLIGATOIREMENT dans ce format exact:REASONING: [ton raisonnement en une phrase]STEPS:1. [premiere etape]2. [deuxieme etape]3. [troisieme etape]""" response =self.llm.generate(prompt, temperature=0.3)# Etape 2: Parse avec le regex standard steps = [] reasoning ="" in_steps =Falsefor line in response.split('\n'):if line.startswith('REASONING:'): reasoning = line.replace('REASONING:', '').strip()elif line.startswith('STEPS:'): in_steps =Trueelif in_steps and re.match(r'^\d+\.', line.strip()): steps.append(re.sub(r'^\d+\.\s*', '', line.strip()))# Etape 3: Fallback si aucune etape trouvee# Indice: cherchez des lignes commencant par "- " ou "* "ifnot steps:pass# TODO etudiant : implementez le fallbackreturn Plan(steps=steps, reasoning=reasoning)# TODO: Testez et comparez avec le Planner original# robust_planner = RobustPlanner(LLMClient())# test_question = "Quelle region genere le plus de revenus?"# test_context = "DataFrame avec colonnes: date, product, region, revenue, units"# plan = robust_planner.create_plan(test_question, test_context)# print(f"Etapes trouvees: {len(plan.steps)}")# for i, step in enumerate(plan.steps):# print(f" {i+1}. {step}")print("Exercice a completer : amelioration du prompt et parseur du Planner")
Exercice a completer : amelioration du prompt et parseur du Planner
Exercice : Analyseur d’Erreurs pour l’Executor
L’Executor retourne des messages d’erreur Python (KeyError, NameError, etc.). L’objectif est de créer un composant ErrorAnalyzer qui classifie les erreurs et genere un contexte de correction automatique pour la boucle iterative.
Objectifs
Classifier les erreurs les plus frequentes du code genere par le LLM
Créer un dictionnaire de correspondance erreur -> correction
Integrer l’ErrorAnalyzer dans la boucle du DSStarAgent
Indice : - Erreurs frequentes : KeyError (mauvais nom de colonne), NameError (variable non définie), AttributeError (méthode inexistante) - Pour chaque type, proposez une correction automatique (ex: suggerer les noms de colonnes proches) - Utilisez difflib.get_close_matches() pour suggerer des noms de colonnes similaires
# Exercice : Analyseur d'erreurs automatique pour l'Executor# Objectif : Classifier et corriger automatiquement les erreurs du code genereimport difflibclass ErrorAnalyzer:"""Analyse les erreurs d'execution et suggere des corrections."""def__init__(self, available_columns: list=None):self.available_columns = available_columns or []def classify_error(self, error_message: str) ->str:""" Classifie le type d'erreur. Args: error_message: message d'erreur Python Returns: Type d'erreur: 'key_error', 'name_error', 'attribute_error', 'type_error', 'other' """# TODO: Implementez la classification# Indice: utilisez des mots-cles comme "KeyError", "NameError", etc. error_lower = error_message.lower()if'keyerror'in error_lower:return'key_error'# TODO etudiant : ajoutez les autres types d'erreursreturn'other'def suggest_correction(self, error_message: str, error_type: str) ->str:""" Suggere une correction basee sur le type d'erreur. Args: error_message: message d'erreur original error_type: type classifie Returns: Message de correction pour enrichir le contexte """if error_type =='key_error':# Etape 1: Extrayez le nom de colonne errone# Indice: cherchez entre guillemets dans le message d'erreur wrong_col =None# TODO etudiant : extrayez avec un regex# Etape 2: Suggerez des colonnes proches# Indice: difflib.get_close_matches(wrong_col, self.available_columns, n=3) suggestions = []returnf"La colonne '{wrong_col}' n'existe pas. Colonnes disponibles: {self.available_columns}. Suggestions proches: {suggestions}"# TODO etudiant : ajoutez des corrections pour name_error, attribute_error, etc.returnf"Erreur de type {error_type}: {error_message}"# TODO: Testez l'ErrorAnalyzer# analyzer = ErrorAnalyzer(available_columns=['date', 'product', 'region', 'revenue', 'units'])# # # Test avec une erreur KeyError typique# error = "KeyError: 'revenu'"# error_type = analyzer.classify_error(error)# correction = analyzer.suggest_correction(error, error_type)# print(f"Type: {error_type}")# print(f"Correction: {correction}")print("Exercice a completer : analyseur d'erreurs automatique pour l'Executor")
Exercice a completer : analyseur d'erreurs automatique pour l'Executor
Points de reflexion
Combien d’itérations sont necessaires pour chaque type de question ?
Quelles erreurs l’agent rencontre-t-il et comment les corrige-t-il ?
Comment pourriez-vous ameliorer le verifier ?
Références
S. Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models, arXiv:2210.03629, ICLR 2023. Paradigme Reasoning+Acting : l’agent alterne raisonnement et actions observables — fondement de la boucle Planner-Coder-Verifier.
N. Shinn et al., Reflexion: Language Agents with Verbal Reinforcement Learning, arXiv:2303.11366, NeurIPS 2023. Auto-réflexion verbale et raffinement itératif après échec — principe de l’étape Verifier → Coder.
Nam et al., DS-STAR: Data Science Agent for Solving Diverse Tasks across Heterogeneous Formats and Open-Ended Queries, arXiv:2509.21825, 2025. Agent DS-STAR dont ce lab implémente la boucle cœur (suite Lab 10).
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 Labs 8-10).