Преглед изворни кода

correction sortie de reporter

Abdenbi пре 4 дана
родитељ
комит
4d5f5fc1e1
2 измењених фајлова са 104 додато и 58 уклоњено
  1. 73 49
      Agents.py
  2. 31 9
      tools.py

+ 73 - 49
Agents.py

@@ -2,7 +2,7 @@ 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 , AIMessage
+from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage , AIMessage , ToolMessage
 from langgraph.graph.message import add_messages 
 from langchain_groq import ChatGroq 
 from langfuse import get_client
@@ -50,21 +50,28 @@ from langchain_community.chat_models import ChatOllama
 
 # On crée une instance standard pour l'Analyseur
 local_llm_chat = ChatOllama(
-    model="deepseek-coder-v2:16b-lite-instruct-q4_K_M",
+    model="llama3.1:8b",
     temperature=0,
     format="json" # On garde le format JSON ici
 )
 
 
 local_llm_fonction = OllamaFunctions(
-    #model="deepseek-coder-v2:16b-lite-instruct-q4_K_M",
-    model="llama3.1:8b",
+    model="deepseek-coder-v2:16b-lite-instruct-q4_K_M",
+    #model="llama3.1:8b",
     temperature=0,
     format="json",
     num_ctx=4096
 )
 
 
+local_llm_reporter = ChatOllama(
+    model="llama3.1:8b",
+    temperature=0,
+    
+)
+
+
 
 
 model_with_tools = local_llm_fonction.bind_tools(tools)
@@ -150,28 +157,33 @@ def agent_executor(state: AgentState):
         f"Fichier : '{file_name}' | Chemin : 'data/{file_name}'\n\n"
         
         "### INSTRUCTIONS DE CODE & SÉCURITÉ ###\n"
-        "1. Imports : Commence par les imports (pandas, matplotlib.pyplot, etc.).\n"
+        "1. Imports : pandas, matplotlib.pyplot.\n"
         f"2. Chargement : df = pd.read_csv('data/{file_name}')\n"
         
-        "3. SYNTAXE (Apostrophes) : Utilise TOUJOURS des doubles guillemets (\"\") pour les labels et titres.\n"
-        "   INTERDIT : plt.title('L'analyse') | VALIDE : plt.title(\"L'analyse\")\n\n"
+        "3. SYNTAXE (Apostrophes) : Utilise TOUJOURS des doubles guillemets (\"\") pour les labels.\n\n"
         
-        "4. GRAPHIQUES (SAUVEGARDE OBLIGATOIRE) :\n"
-        "   - Ne JAMAIS utiliser plt.show().\n"
-        "   - Utilise TOUJOURS : plt.savefig('outputs/nom_du_graphe.png')\n"
-        "   - Assure-toi que le dossier 'outputs/' est utilisé pour la sauvegarde.\n\n"
+        "4. GRAPHIQUES (SAUVEGARDE) :\n"
+        "   - INTERDIT : plt.show()\n"
+        "   - OBLIGATOIRE : plt.savefig('outputs/nom_du_graphe.png')\n\n"
         
-        "5. SORTIE : Termine ton code par une variable 'result' contenant le résumé texte des données "
-        "ou la confirmation du fichier généré (ex: result = \"Graphique sauvegardé : ventes.png\").\n\n"
+        "5. DATA REPORTING (CRUCIAL POUR LE REPORTER) :\n"
+        "   - AVANT de générer le graphique, calcule les stats clés (Somme, Moyenne, Top 3).\n"
+        "   - Utilise print() pour afficher ces chiffres afin que le Reporter les reçoive.\n\n"
         
-        "### EXEMPLE DE FORMAT ATTENDU ###\n"
-        "{\n"
-        "  \"tool\": \"excel_code_interpreter\",\n"
-        "  \"tool_input\": {\n"
-        f"    \"file_path\": \"data/{file_name}\",\n"
-        "    \"code\": \"import pandas as pd\\nimport matplotlib.pyplot as plt\\n# ... code ...\\nplt.savefig('outputs/chart.png')\\nresult = 'Analyse terminée'\"\n"
-        "  }\n"
-        "}"
+        "6. VARIABLE 'result' :\n"
+        "   - Termine ton code par une variable 'result' qui récapitule :\n"
+        "     1. Les fichiers générés (ex: 'outputs/ventes.png')\n"
+        "     2. Un résumé textuel des chiffres affichés par les graphiques.\n\n"
+        
+        "### EXEMPLE DE CODE INTERNE ATTENDU ###\n"
+        "import pandas as pd\n"
+        "import matplotlib.pyplot as plt\n"
+        "df = pd.read_csv('...')\n"
+        "stats = df.groupby('Pays')['Ventes'].sum()\n"
+        "print(stats) # <--- Ceci permet au Reporter de voir les chiffres !\n"
+        "plt.bar(stats.index, stats.values)\n"
+        "plt.savefig('outputs/ventes_pays.png')\n"
+        "result = f\"Graphique 'ventes_pays.png' généré. Chiffres clés : {stats.to_dict()}\"\n"
     )
         # Nettoyage des messages pour Ollama (comme avant)
     cleaned_messages = []
@@ -191,37 +203,49 @@ def agent_executor(state: AgentState):
 def agent_reporter(state : AgentState) : 
 
     prompt_reporter = (
-        "Tu es l'Agent de Reporting de Dataltist.\n"
-        "Ton rôle : Convertir les logs de l'Exécuteur en une synthèse factuelle pour l'utilisateur.\n\n"
-
-        "RÈGLES CRITIQUES (ANTI-SILENCE) :\n"
-        "- DISPONIBILITÉ : Tu dois TOUJOURS générer une réponse, même pour confirmer une erreur ou une absence de données.\n"
-        "- STRUCTURE : Utilise des tirets pour lister les points clés.\n"
-        "- SYNTHÈSE : Si l'Exécuteur donne 'result = 42', écris 'Résultat : 42'.\n"
-        "- FICHIERS : Liste uniquement le nom des fichiers (ex: graphe_incendie.png), JAMAIS le chemin 'outputs/'.\n\n"
-
-        "FORMAT IMPÉRATIF :\n"
-        "1. Résumé des données traitées.\n"
-        "2. Valeurs calculées (moyennes, totaux, etc.).\n"
-        "3. Liste des visuels générés (si présents).\n\n"
-        "4. L'affichage doit etre claire , tu laisse les espaces .\n\n"
-
-        "INTERDICTIONS :\n"
-        "- Pas de politesse inutile ('Voici le rapport...', 'J'espère que...').\n"
-        "- Pas de répétition des étapes techniques de l'agent.\n"
-        "- Pas de phrases vides si l'exécution a échoué : explique brièvement l'échec."
-
-        "RÈGLES D'AFFICHAGE DES FICHIERS :\n"
-        "1. CITATION : Si l'Exécuteur a généré des fichiers (images ou données), cite-les obligatoirement.\n"
-        "2. FORMAT : Utilise TOUJOURS ce format exact pour introduire un fichier : \n"
-        "   '📊 Visualisation générée : [nom_du_fichier.png]'\n"
-        "   '📥 Fichier disponible : [nom_du_fichier.csv/xlsx]'\n"
-        "3. SANS CHEMIN : Ne mentionne JAMAIS le dossier 'outputs/' ou 'data/'. Utilise uniquement le nom final du fichier.\n\n"
+        ### RÔLE ###
+"""            Tu es un Expert Consultant en Data Visualisation et Analyse Statistique. 
+            Ton objectif est de transformer des données techniques brutes en un rapport stratégique clair, élégant et actionnable.
+
+            ### DIRECTIVES DE RÉDACTION ###
+            1. INTERPRÉTATION : Ne te contente pas de citer les chiffres. Explique ce qu'ils signifient Utilise correctement les chiffres retournés par les outils : assure-toi de bien les comprendre et de les avoir triés.
+            2. STRUCTURE : Utilise des titres (##), du gras (**), et des listes à puces pour la clarté.
+            3. VISUELS : Mentionne les graphiques générés avec l'émoji 📊.
+            4. TON : Professionnel, dynamique et rassurant. 
+            5. PAS DE JSON : Ta réponse doit être uniquement du texte formaté en Markdown.
+
+            ### STRUCTURE DU RAPPORT ###
+            ## 📝 Synthèse de l'Analyse
+            (Un paragraphe fluide qui résume la situation globale).
+
+            ## 💡 Points Clés & Insights
+            - **[Point 1]** : Explication...
+            - **[Point 2]** : Explication...
+
+            ## 📊 Visualisations Disponibles
+            - [Nom du fichier.png] : Brève description de ce que le graphique démontre.
+
+            ## 🚀 Recommandations si tu n'as
+            (Une ou deux phrases sur la prochaine étape suggérée)."""
     )
 
+
+    all_messages = state["messages"]
+    
+    # On ne garde que :
+    # 1. Le premier message (User) pour le contexte
+    # 2. Le DERNIER message de l'IA (le code qui a marché)
+    # 3. Le DERNIER message de l'outil (les données réelles)
+    
+    important_messages = [
+        all_messages[0], # La question de l'utilisateur
+        [m for m in all_messages if isinstance(m, AIMessage)][-1], # Le dernier code
+        [m for m in all_messages if isinstance(m, ToolMessage)][-1] # Le dernier résultat
+    ]
+
             # Nettoyage des messages pour Ollama (comme avant)
     cleaned_messages = []
-    for m in state["messages"]:
+    for m in important_messages :
         if m.type == "tool":
             cleaned_messages.append(HumanMessage(content=f"Résultat : {m.content}"))
         else:
@@ -230,5 +254,5 @@ def agent_reporter(state : AgentState) :
     # On place le prompt dynamique en premier
     final_messages = [HumanMessage(content=prompt_reporter)] + cleaned_messages
 
-    return {"messages": [local_llm_chat.invoke(final_messages)]}
+    return {"messages": [local_llm_reporter.invoke(final_messages)]}
 

+ 31 - 9
tools.py

@@ -1,10 +1,12 @@
-import os
+import os 
 import pandas as pd
 from langchain_core.tools import tool 
 from langchain_community.tools import DuckDuckGoSearchRun
 import matplotlib.pyplot as plt
 import seaborn as sns
 from langchain_core.messages import HumanMessage, AIMessage
+import sys
+import io
 
 import matplotlib
 matplotlib.use('Agg')  # Force Matplotlib à ne pas ouvrir de fenêtre graphique
@@ -71,13 +73,13 @@ 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'."""
-
-
+    
     import warnings
     warnings.filterwarnings("ignore")
     
-    # Nettoyage et construction du chemin (Ton code existant est bon ici)
+    # 1. Nettoyage et construction du chemin
     file_name = os.path.basename(file_path.strip().replace("'", "").replace('"', ""))
+    # Assure-toi que BASE_DIR est défini globalement ou remplace-le par os.getcwd()
     data_folder_path = os.path.join(BASE_DIR, "data", file_name)
     root_path = os.path.join(BASE_DIR, file_name)
     
@@ -88,26 +90,46 @@ def excel_code_interpreter(file_path: str, code: str):
     else:
         return f"ERREUR : Fichier '{file_name}' introuvable."
 
+    # 2. CAPTURE DE LA CONSOLE (stdout)
+    # On crée un tampon pour intercepter les print()
+    buffer = io.StringIO()
+    old_stdout = sys.stdout # On sauvegarde la sortie d'origine (ton terminal)
+    sys.stdout = buffer     # On redirige vers notre variable
+
     try:
         # 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
+        # 3. Préparation du contexte d'exécution
         context = {
             "df": df,
             "pd": pd,
             "plt": plt,
             "os": os,
             "result": None,
-            "__builtins__": __builtins__ # On donne accès aux fonctions natives
+            "__builtins__": __builtins__ 
         }
 
-        # On exécute dans le même dictionnaire pour globals et locals
+        # 4. Exécution du code généré par l'IA
         exec(code, context)
+
+        # 5. Récupération des données capturées
+        output_console = buffer.getvalue()
+        final_result_variable = context.get("result", "")
+
+        # On remet le système à la normale
+        sys.stdout = old_stdout
+
+        # 6. ON FUSIONNE TOUT POUR LE REPORTER
+        # C'est ce bloc de texte que le Reporter va recevoir dans Langfuse
+        full_report = f"--- RÉSULTATS DE LA CONSOLE ---\n{output_console}\n"
+        full_report += f"--- RÉSUMÉ FINAL ---\n{final_result_variable}"
         
-        return str(context.get("result", "Exécution terminée."))
+        return full_report
+
     except Exception as e:
+        # En cas d'erreur, on n'oublie pas de remettre le stdout à la normale
+        sys.stdout = old_stdout
         return f"ERREUR PYTHON : {str(e)}"