|
@@ -0,0 +1,171 @@
|
|
|
|
|
+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
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+import matplotlib
|
|
|
|
|
+matplotlib.use('Agg') # Force Matplotlib à ne pas ouvrir de fenêtre graphique
|
|
|
|
|
+
|
|
|
|
|
+@tool
|
|
|
|
|
+def convert_csv_to_excel(csv_path: str):
|
|
|
|
|
+ """Convertit un fichier CSV en Excel (.xlsx)."""
|
|
|
|
|
+ if not os.path.exists(csv_path):
|
|
|
|
|
+ return f"Désolé, je ne trouve pas le fichier '{csv_path}'. Vérifiez qu'il est bien présent dans le dossier du projet."
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ df = pd.read_csv(csv_path)
|
|
|
|
|
+ new_path = csv_path.replace(".csv", ".xlsx")
|
|
|
|
|
+ df.to_excel(new_path, index=False)
|
|
|
|
|
+ return f"Succès : Le fichier a été converti en {new_path}"
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ return f"Erreur lors de la lecture du CSV : {str(e)}"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@tool
|
|
|
|
|
+def inspect_data(file_name: str):
|
|
|
|
|
+ """
|
|
|
|
|
+ Explore le fichier situé dans le dossier 'data/' pour retourner les colonnes et un aperçu.
|
|
|
|
|
+ Passer uniquement le nom du fichier (ex: 'data.csv').
|
|
|
|
|
+ """
|
|
|
|
|
+ # 1. On nettoie le nom du fichier au cas où l'IA ajoute des guillemets
|
|
|
|
|
+ clean_name = file_name.strip().replace("'", "").replace('"', "")
|
|
|
|
|
+
|
|
|
|
|
+ # 2. On construit le chemin ABSOLU vers le dossier data
|
|
|
|
|
+ # On suppose que ton script est à la racine du projet
|
|
|
|
|
+ data_path = os.path.join(os.getcwd(), "data", clean_name)
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ # 3. Vérification de l'existence du fichier
|
|
|
|
|
+ if not os.path.exists(data_path):
|
|
|
|
|
+ return f"Erreur : Le fichier '{clean_name}' est introuvable dans le dossier data/."
|
|
|
|
|
+
|
|
|
|
|
+ # 4. Lecture selon l'extension (Note : correction de 'endiwith' en 'endswith')
|
|
|
|
|
+ if data_path.lower().endswith(".csv"):
|
|
|
|
|
+ df = pd.read_csv(data_path) # Utilisation de la variable data_path, PAS de la chaîne "file_path"
|
|
|
|
|
+ elif data_path.lower().endswith((".xlsx", ".xls")):
|
|
|
|
|
+ df = pd.read_excel(data_path)
|
|
|
|
|
+ else:
|
|
|
|
|
+ return "Format de fichier non supporté. Utilisez .csv ou .xlsx"
|
|
|
|
|
+
|
|
|
|
|
+ # 5. Extraction des infos
|
|
|
|
|
+ columns_names = df.columns.tolist()
|
|
|
|
|
+ preview = df.head(3).to_string()
|
|
|
|
|
+
|
|
|
|
|
+ return f"Colonnes trouvées : {columns_names}\n\nAperçu des données :\n{preview}"
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ return f"Erreur lors de l'inspection : {str(e)}"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@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('"', ""))
|
|
|
|
|
+
|
|
|
|
|
+ # 1. Définition de la priorité : Dossier 'data' d'abord
|
|
|
|
|
+ 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}"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ 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__" :{}}
|
|
|
|
|
+
|
|
|
|
|
+ # Injection des bibliothèques pour l'IA
|
|
|
|
|
+ local_vars = {"df": df, "pd": pd, "plt": plt, "result": None}
|
|
|
|
|
+ exec(code, safe_globals , local_vars)
|
|
|
|
|
+
|
|
|
|
|
+ return str(local_vars.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
|
|
|
|
|
+def search_tool(query : str) :
|
|
|
|
|
+ """Recherche sur le web. Limité pour économiser les tokens."""
|
|
|
|
|
+ try:
|
|
|
|
|
+ results = ddg.run(query)
|
|
|
|
|
+
|
|
|
|
|
+ if not results:
|
|
|
|
|
+ return "Aucun résultat trouvé."
|
|
|
|
|
+
|
|
|
|
|
+ # 1. On nettoie les espaces superflus pour gagner des tokens
|
|
|
|
|
+ clean_results = " ".join(results.split())
|
|
|
|
|
+
|
|
|
|
|
+ # 2. On limite intelligemment (ex: 1200 chars pour plus de contexte)
|
|
|
|
|
+ # Mais on s'assure de ne pas couper un mot au milieu
|
|
|
|
|
+ limit = 1200
|
|
|
|
|
+ if len(clean_results) <= limit:
|
|
|
|
|
+ return clean_results
|
|
|
|
|
+
|
|
|
|
|
+ return clean_results[:limit] + "... [Résultat tronqué pour économie]"
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ return f"Erreur lors de la recherche : {str(e)}"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|