Sfoglia il codice sorgente

Version code works , problems with streamlit

Abdenbi IABBADENE 1 mese fa
parent
commit
cfc7d55d02
7 ha cambiato i file con 89 aggiunte e 82 eliminazioni
  1. 12 7
      Agents.py
  2. BIN
      __pycache__/Agents.cpython-313.pyc
  3. BIN
      __pycache__/tools.cpython-313.pyc
  4. BIN
      __pycache__/workflow_Agent.cpython-313.pyc
  5. 27 19
      app.py
  6. 27 3
      main.py
  7. 23 53
      tools.py

+ 12 - 7
Agents.py

@@ -7,7 +7,7 @@ from langgraph.graph.message import add_messages
 
 
 # Import de tes outils corrigés
-from tools import  excel_code_interpreter, convert_csv_to_excel  , generate_excel_chart , search_tool , inspect_data
+from tools import  excel_code_interpreter,   search_tool , inspect_data
 
 load_dotenv()
 
@@ -19,7 +19,7 @@ class AgentState(TypedDict):
 
 # 2. Configuration du Modèle et des Outils
 model_gpt = ChatOpenAI(model="o3-mini") 
-tools = [ search_tool  , excel_code_interpreter, convert_csv_to_excel , generate_excel_chart  , inspect_data ]
+tools = [ search_tool  , excel_code_interpreter,  inspect_data ]
 model_with_tools = model_gpt.bind_tools(tools)
 
 
@@ -30,10 +30,10 @@ 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. Si le fichier est en .csv, ordonne IMMÉDIATEMENT sa conversion via 'convert_csv_to_excel'. Ne demande pas la permission.\n"
-        "2. Définis un plan d'action claire \n"
-        "3. Ne pose JAMAIS de questions sur les données (prix, stock). L'Exécuteur doit les trouver lui-même via les outils.\n"
-        "4. Sois concis : Ton message doit être une directive pour l'Exécuteur, pas une conversation avec l'utilisateur."
+    
+        "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_gpt.invoke(msg)
@@ -54,9 +54,14 @@ def agent_executor(state: AgentState):
         "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 se terminer par 'result = ...'. C'est cette variable qui sera transmise au système.\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

BIN
__pycache__/Agents.cpython-313.pyc


BIN
__pycache__/tools.cpython-313.pyc


BIN
__pycache__/workflow_Agent.cpython-313.pyc


+ 27 - 19
app.py

@@ -33,44 +33,52 @@ for message in st.session_state.messages:
 
 # --- 3. Interaction avec les Agents ---
 if prompt := st.chat_input("Pose ta question sur tes données..."):
-    # 1. Affichage du message utilisateur
+    # 1. NETTOYAGE PRÉALABLE : On vide les anciens graphiques avant de commencer
+    # Cela garantit que list_of_plots ne contiendra que les nouveaux PNG
+    if not os.path.exists("outputs"):
+        os.makedirs("outputs")
+    for f in glob.glob("outputs/*.png"):
+        os.remove(f)
+
+    # 2. Affichage du message utilisateur
     st.session_state.messages.append({"role": "user", "content": prompt})
     with st.chat_message("user"):
         st.markdown(prompt)
 
-    # 2. Appel des agents
+    # 3. Appel des agents
     with st.chat_message("assistant"):
-        with st.status("Analyse en cours par les agents Dataltist...", expanded=True) as status:
+        # On utilise expanded=True pour voir le raisonnement des agents (Analyseur, Exécuteur...)
+        with st.status("Analyse Dataltist en cours...", expanded=True) as status:
             
-            # On définit les entrées pour ton graphe
             inputs = {
                 "messages": [("user", prompt)],
-                "current_df_path": file_path  # Le chemin vers le CSV chargé
+                "current_df_path": file_path
             }
             
-            # Exécution du workflow
+            # On lance le workflow (invoke est bloquant, donc l'analyse se termine avant la suite)
             final_state = app.invoke(inputs)
             
-            # RÉCUPÉRATION : On prend le message du DERNIER agent (le Reporter)
+            # Récupération du message final du Reporter
             response_text = final_state["messages"][-1].content
             st.markdown(response_text)
             
             # --- AFFICHAGE DES GRAPHIQUES ---
-            st.subheader("📊 Visualisations générées")
-            
-            # On récupère tous les fichiers .png dans le dossier outputs
+            # On cherche les fichiers APRES l'exécution
             list_of_plots = glob.glob("outputs/*.png")
             
             if list_of_plots:
-                # On crée des colonnes si on a plusieurs graphiques
-                cols = st.columns(len(list_of_plots))
+                st.write("---") # Séparateur visuel
+                st.subheader("📊 Visualisations générées")
+                
+                # Organisation en colonnes pour éviter de longs défilements verticaux
+                # On limite à 2 colonnes par ligne pour une meilleure lisibilité
+                cols = st.columns(2) 
                 for idx, plot_path in enumerate(list_of_plots):
-                    with cols[idx]:
-                        st.image(plot_path, use_container_width=True, caption=os.path.basename(plot_path))
-            else:
-                st.info("Aucun graphique n'a été généré pour cette analyse.")
-
+                    col_idx = idx % 2
+                    with cols[col_idx]:
+                        st.image(plot_path, use_container_width=True, caption=f"Analyse {idx+1}")
+            
             status.update(label="Analyse terminée !", state="complete")
-        
-    # 3. Sauvegarde dans l'historique de session
+
+    # 4. Sauvegarde dans l'historique pour le chat
     st.session_state.messages.append({"role": "assistant", "content": response_text})

+ 27 - 3
main.py

@@ -1,23 +1,47 @@
 from langchain_core.messages import HumanMessage
 from workflow_Agent import app
 
+import os
+import glob
+
+# --- Préparation de l'environnement ---
+output_dir = "outputs"
+if not os.path.exists(output_dir):
+    os.makedirs(output_dir)
+
+# Nettoyage des anciens graphiques pour ne pas mélanger les analyses
+for f in glob.glob(os.path.join(output_dir, "*.png")):
+    os.remove(f)
+
 if __name__ == "__main__":
     inputs = {
-        "messages" : [HumanMessage(content = " Je veux savoir qui les meilleurs sales personne dans ma campany , egalement les produit et categorie les mieux vendu , tu me fais des graphs  beau avec seaborn egalement pour me montrer ca , et un graph ou tu les crois pour voir le produit le plus vendu par categorie par les top vendeurs  ")] , 
+        "messages" : [HumanMessage(content="Je veux un tableu excel qui resume : le nombre d'achat par chaque pays , nombre de vendre par chaque sales personne  , et par produit , essai de les croiser pour bien analyser mon dataset ")] ,
+        #messages": [HumanMessage(content="je veux voir par graphiques les produits les plus vendus en Belgique et par quel fournisseur ")] , 
+        #"messages" : [HumanMessage(content = " Je veux savoir qui les meilleurs sales personne dans ma campany , egalement les produit et categorie les mieux vendu , tu me fais des graphs  beau avec seaborn egalement pour me montrer ca , et un graph ou tu les crois pour voir le produit le plus vendu par categorie par les top vendeurs  ")] , 
         #"messages": [HumanMessage(content="Cherche la tandance de croissance du marché des Ecrans gaming ( ecran dans le dataset ) pour 2026 , puis calucle une Demande_Prevue dans mon dataset (Formule : Stock actuel * (1 + %croissance_trouvé)) , Identifie si le stock actuel d'Ecrans est suffisant ou s'il y a un risque de rupture , Génère un graphique de type 'Gauge' ou un 'Bar chart' comparant le Stock Actuel vs Demande Prévue. ")],
         "current_df_path": "ventes_materiel_informatique.csv"
     }
 
-    print("--- Début de l'exécution ---")
+# On stocke les événements pour pouvoir récupérer les graphiques à la fin
+    final_events = []
+    
     for event in app.stream(inputs):
+        final_events.append(event) # On garde une trace du dernier état
         for node, value in event.items():
             print(f"\n[Nœud : {node}]")
             if "messages" in value:
                 last_msg = value["messages"][-1]
-                # Affiche le texte ou l'appel d'outil
                 content = last_msg.content if last_msg.content else f"Appel d'outil: {last_msg.tool_calls}"
                 print(content)
 
+    # --- Récupération des graphiques après le stream ---
+    print("\n--- Analyse terminée : Récupération des visuels ---")
+    current_plots = glob.glob(os.path.join(output_dir, "*.png"))
+    
+    if current_plots:
+        print(f"Graphiques générés pour cette requête : {current_plots}")
+    else:
+        print("Aucun graphique n'a été généré.")
 
 import matplotlib
 matplotlib.use('Agg')  # À placer AVANT d'importer pyplot

+ 23 - 53
tools.py

@@ -71,76 +71,46 @@ def inspect_data(file_name: str):
 @tool
 def excel_code_interpreter(file_path: str, code: str):
     """Exécute du code Python sur le fichier (CSV ou Excel) chargé dans 'df'."""
-    # Nettoyage du nom de fichier
-    file_name = os.path.basename(file_path.strip().replace("'", "").replace('"', ""))
+
+
+    import warnings
+    warnings.filterwarnings("ignore")
     
-    # 1. Définition de la priorité : Dossier 'data' d'abord
+    # Nettoyage et construction du chemin (Ton code existant est bon ici)
+    file_name = os.path.basename(file_path.strip().replace("'", "").replace('"', ""))
     data_folder_path = os.path.join(BASE_DIR, "data", file_name)
     root_path = os.path.join(BASE_DIR, file_name)
     
-    # Choix du chemin
     if os.path.exists(data_folder_path):
         full_path = data_folder_path
     elif os.path.exists(root_path):
         full_path = root_path
     else:
-        # Si rien n'est trouvé, on aide l'agent avec un message clair
-        files_in_data = os.listdir(os.path.join(BASE_DIR, "data")) if os.path.exists(os.path.join(BASE_DIR, "data")) else "Dossier data absent"
-        return f"ERREUR : Fichier '{file_name}' introuvable. Contenu de 'data/': {files_in_data}"
-
+        return f"ERREUR : Fichier '{file_name}' introuvable."
 
-    
     try:
-        # Lecture selon l'extension
-        if file_name.endswith('.csv'):
-            df = pd.read_csv(full_path)
-        else:
-            df = pd.read_excel(full_path)
-
-        safe_globals={"__builtins__" :{}}
+        # Lecture du fichier
+        df = pd.read_csv(full_path) if file_name.endswith('.csv') else pd.read_excel(full_path)
+
+        # SOLUTION : On utilise un seul dictionnaire pour simplifier
+        # On passe les builtins réels du système pour autoriser les imports
+        context = {
+            "df": df,
+            "pd": pd,
+            "plt": plt,
+            "os": os,
+            "result": None,
+            "__builtins__": __builtins__ # On donne accès aux fonctions natives
+        }
 
-        # Injection des bibliothèques pour l'IA
-        local_vars = {"df": df, "pd": pd, "plt": plt, "result": None}
-        exec(code, safe_globals , local_vars)
+        # On exécute dans le même dictionnaire pour globals et locals
+        exec(code, context)
         
-        return str(local_vars.get("result", "Exécution terminée."))
+        return str(context.get("result", "Exécution terminée."))
     except Exception as e:
         return f"ERREUR PYTHON : {str(e)}"
     
 
-@tool
-def generate_excel_chart(file_path: str, code: str, output_image: str = "chart.png"):
-    """
-    Exécute du code Python pour générer un graphique Matplotlib/Seaborn à partir du fichier Excel.
-    Le DataFrame est chargé dans 'df'.
-    IMPORTANT : Le code doit finir par plt.savefig(output_image).
-    """
-    # Force le mode sans interface graphique (important pour les scripts automatisés)
-    plt.switch_backend('Agg')
-    plt.clf() # Nettoie les anciens graphiques en mémoire
-    
-    full_path = os.path.join(BASE_DIR, file_path)
-    try:
-        df = pd.read_excel(full_path)
-        
-        # Environnement d'exécution pour l'IA
-        local_vars = {
-            "df": df, 
-            "plt": plt, 
-            "sns": sns, 
-            "output_image": output_image
-        }
-        
-        exec(code, {}, local_vars)
-        
-        if os.path.exists(output_image):
-            return f"Succès : Graphique généré et enregistré sous '{output_image}'."
-        else:
-            return "Erreur : Le code a été exécuté mais aucun fichier image n'a été créé."
-            
-    except Exception as e:
-        return f"Erreur lors de la génération du graphique : {str(e)}"
-
 
 ddg = DuckDuckGoSearchRun()
 @tool