import os from typing import Annotated, Sequence, TypedDict, Optional , List from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage from langgraph.graph.message import add_messages from langchain_google_genai import ChatGoogleGenerativeAI from langchain_groq import ChatGroq from langfuse import get_client # Import de tes outils corrigés from tools import excel_code_interpreter, search_tool , inspect_data langfuse = get_client() load_dotenv() # 1. Définition du State class AgentState(TypedDict): messages: Annotated[Sequence[BaseMessage], add_messages] current_df_path: Optional[str] generated_charts : List[str] # 2. Configuration du Modèle et des Outils model =ChatGroq(model="llama-3.3-70b-versatile") #model_llama=ChatGoogleGenerativeAI(model="gemini-2.5-flash") model_gpt = ChatOpenAI(model="o3-mini") tools = [ search_tool , excel_code_interpreter, inspect_data ] model_with_tools = model.bind_tools(tools) # 3. Définition des Nœuds def agent_analyseur(state: AgentState): prompt = ( "Tu es l'Analyseur Stratégique. Ton rôle est de décomposer la demande utilisateur en étapes techniques claires.\n" "RÈGLES CRITIQUES :\n" "1. Définis un plan d'action claire \n" "2. Ne pose JAMAIS de questions sur les données (prix, stock). L'Exécuteur doit les trouver lui-même via les outils.\n" "3. Sois concis : Ton message doit être une directive pour l'Exécuteur, pas une conversation avec l'utilisateur." ) msg = [SystemMessage(content=prompt)] + state["messages"] response = model.invoke(msg) return {"messages": [response]} def agent_executor(state: AgentState): # 1. On récupère le chemin actuel depuis le State # Si current_df_path est None, on met data.xlsx par défaut file_path = state.get("current_df_path") or "data.xlsx" # 2. On construit un prompt qui contient le VRAI chemin prompt = ( f"Tu es un Data Scientist expert spécialisé en automatisation. Le fichier cible se trouve dans le dossier 'data/' : '{file_path}'.\n" "PROTOCOLE D'ACTION IMPÉRATIF :\n" "1. INSPECTION PRÉALABLE : Avant de générer le moindre code Python, appelle TOUJOURS 'inspect_data'.\n Tu dois connaître les colonnes réelles avant de coder.\n" "2. ACTION D'ABORD : Si tu n'as pas les données externes, appelle 'search_tool' immédiatement.\n" "3. ZÉRO SIMULATION : Il est strictement interdit d'inventer des prix ou des stocks. Si l'outil 'search_tool' donne une plage de prix (ex: 30-40€), calcule la moyenne (35€).\n" "4. LOGIQUE PANDAS : Ne fais aucun calcul manuel. Utilise 'df' pour filtrer le produit exact (ex: df[df['Produit'] == 'Souris']).\n" "5. VARIABLE RESULT : Ton script Python DOIT etre complis avec tout les imports et se terminer par 'result = ...'. C'est cette variable qui sera transmise au système.\n" "6. PERSISTANCE : Si un graphique est demandé, utilise 'plt.savefig('outputs/nom_du_graphe.png')'.\n" "Tu es un moteur d'exécution froid et précis." "STRUCTURE DE TA RÉPONSE POST-OUTIL :\n" "- 'Statut : Code exécuté avec succès.'\n" "- 'Fichiers : [liste des .png générés]'\n" "- 'Validation : Les données de [Colonnes] ont été traitées.'\n" "PAS DE DISCOURS, PAS D'ÉTAPES." ) # 3. Préparation des messages messages = [SystemMessage(content=prompt)] + state["messages"] # 4. Appel du modèle avec les outils response = model_with_tools.invoke(messages) return {"messages": [response]} def agent_reporter(state : AgentState) : prompt_reporter = ( "Tu es l'Agent de Reporting.\n" "Ton rôle est de transformer les résultats techniques fournis par l'Exécuteur en un compte rendu court, clair et factuel.\n\n" "RÈGLES STRICTES :\n" "- N'invente aucune information.\n" "- Ne répète pas les mêmes informations.\n" "- Évite les phrases génériques ou marketing.\n" "- Sois concis et direct.\n" "- Utilise uniquement les données réellement calculées.\n\n" "FORMAT DE SORTIE OBLIGATOIRE :\n" "1) Résultats clés\n" "- Liste courte des résultats principaux (produits les plus vendus, top commerciaux, etc.)\n\n" "2) Observations\n" "- Interprétation courte basée uniquement sur les résultats.\n\n" "3) Livrables générés\n" "- Liste des graphiques et fichiers créés avec leurs chemins exacts.\n\n" "IMPORTANT :\n" "- Maximum 6 à 8 lignes au total.\n" "- Pas de conclusion inutile.\n" "- Pas de texte explicatif long." ) messages = [SystemMessage(content=prompt_reporter)] + state["messages"] response = model.invoke(messages) return{"messages" : [response]}