Просмотр исходного кода

Version code works , problems with streamlit

Abdenbi IABBADENE 1 месяц назад
Родитель
Сommit
cfc7d55d02
7 измененных файлов с 89 добавлено и 82 удалено
  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
 # 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()
 load_dotenv()
 
 
@@ -19,7 +19,7 @@ class AgentState(TypedDict):
 
 
 # 2. Configuration du Modèle et des Outils
 # 2. Configuration du Modèle et des Outils
 model_gpt = ChatOpenAI(model="o3-mini") 
 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)
 model_with_tools = model_gpt.bind_tools(tools)
 
 
 
 
@@ -30,10 +30,10 @@ def agent_analyseur(state: AgentState):
     prompt = (
     prompt = (
         "Tu es l'Analyseur Stratégique. Ton rôle est de décomposer la demande utilisateur en étapes techniques claires.\n"
         "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"
         "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"]
     msg = [SystemMessage(content=prompt)] + state["messages"]
     response = model_gpt.invoke(msg)
     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"
         "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"
         "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"
         "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"
         "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."
         "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
     # 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 ---
 # --- 3. Interaction avec les Agents ---
 if prompt := st.chat_input("Pose ta question sur tes données..."):
 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})
     st.session_state.messages.append({"role": "user", "content": prompt})
     with st.chat_message("user"):
     with st.chat_message("user"):
         st.markdown(prompt)
         st.markdown(prompt)
 
 
-    # 2. Appel des agents
+    # 3. Appel des agents
     with st.chat_message("assistant"):
     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 = {
             inputs = {
                 "messages": [("user", prompt)],
                 "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)
             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
             response_text = final_state["messages"][-1].content
             st.markdown(response_text)
             st.markdown(response_text)
             
             
             # --- AFFICHAGE DES GRAPHIQUES ---
             # --- 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")
             list_of_plots = glob.glob("outputs/*.png")
             
             
             if list_of_plots:
             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):
                 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")
             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})
     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 langchain_core.messages import HumanMessage
 from workflow_Agent import app
 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__":
 if __name__ == "__main__":
     inputs = {
     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. ")],
         #"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"
         "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):
     for event in app.stream(inputs):
+        final_events.append(event) # On garde une trace du dernier état
         for node, value in event.items():
         for node, value in event.items():
             print(f"\n[Nœud : {node}]")
             print(f"\n[Nœud : {node}]")
             if "messages" in value:
             if "messages" in value:
                 last_msg = value["messages"][-1]
                 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}"
                 content = last_msg.content if last_msg.content else f"Appel d'outil: {last_msg.tool_calls}"
                 print(content)
                 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
 import matplotlib
 matplotlib.use('Agg')  # À placer AVANT d'importer pyplot
 matplotlib.use('Agg')  # À placer AVANT d'importer pyplot

+ 23 - 53
tools.py

@@ -71,76 +71,46 @@ def inspect_data(file_name: str):
 @tool
 @tool
 def excel_code_interpreter(file_path: str, code: str):
 def excel_code_interpreter(file_path: str, code: str):
     """Exécute du code Python sur le fichier (CSV ou Excel) chargé dans 'df'."""
     """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)
     data_folder_path = os.path.join(BASE_DIR, "data", file_name)
     root_path = os.path.join(BASE_DIR, file_name)
     root_path = os.path.join(BASE_DIR, file_name)
     
     
-    # Choix du chemin
     if os.path.exists(data_folder_path):
     if os.path.exists(data_folder_path):
         full_path = data_folder_path
         full_path = data_folder_path
     elif os.path.exists(root_path):
     elif os.path.exists(root_path):
         full_path = root_path
         full_path = root_path
     else:
     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:
     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:
     except Exception as e:
         return f"ERREUR PYTHON : {str(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()
 ddg = DuckDuckGoSearchRun()
 @tool 
 @tool