Sfoglia il codice sorgente

Version all works , streamlit works

Abdenbi IABBADENE 1 mese fa
parent
commit
fbd82ffb76
7 ha cambiato i file con 155 aggiunte e 56 eliminazioni
  1. 35 9
      Agents.py
  2. 14 0
      Modeles_GPT.py
  3. BIN
      Rapport_Analytique.xlsx
  4. BIN
      __pycache__/Agents.cpython-313.pyc
  5. 96 42
      app.py
  6. 8 4
      main.py
  7. 2 1
      requirements.txt

+ 35 - 9
Agents.py

@@ -4,11 +4,16 @@ 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
@@ -18,9 +23,14 @@ class AgentState(TypedDict):
     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_gpt.bind_tools(tools)
+model_with_tools = model.bind_tools(tools)
 
 
 
@@ -36,7 +46,7 @@ def agent_analyseur(state: AgentState):
         "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)
+    response = model.invoke(msg)
     return {"messages": [response]}
 
 
@@ -77,14 +87,30 @@ def agent_executor(state: AgentState):
 def agent_reporter(state : AgentState) : 
 
     prompt_reporter = (
-        "Tu es l'Agent de Reporting. Ton rôle est de traduire les résultats techniques en une synthèse claire.\n"
-        "CONSIGNES :\n"
-        "1. FIDÉLITÉ : Ne parle que des données réellement trouvées et calculées par l'Exécuteur.\n"
-        "2. STRUCTURE : Présente les faits marquants, puis les chiffres clés, puis les livrables (graphiques/fichiers).\n"
-        "3. ADAPTATION : Si l'analyse porte sur des ventes, utilise un vocabulaire commercial. Si c'est de l'actuariat, utilise un vocabulaire technique.\n"
-        "4. NEUTRALITÉ : Ne propose pas de texte de remplissage si les données sont manquantes. Sois factuel."
+        "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_gpt.invoke(messages) 
+    response = model.invoke(messages) 
     return{"messages" : [response]}

+ 14 - 0
Modeles_GPT.py

@@ -0,0 +1,14 @@
+import os
+from openai import OpenAI
+from dotenv import load_dotenv
+
+load_dotenv()
+
+client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
+
+# Récupération de la liste des modèles
+models = client.models.list()
+
+print("Modèles disponibles pour ma clé :")
+for model in models.data:
+    print(f"- {model.id}")

BIN
Rapport_Analytique.xlsx


BIN
__pycache__/Agents.cpython-313.pyc


+ 96 - 42
app.py

@@ -1,84 +1,138 @@
 import streamlit as st
 import os
-from workflow_Agent import app # Ton graphe compilé
 import glob
+import shutil
+from workflow_Agent import app  # Ton graphe compilé
+from langchain_core.messages import HumanMessage
 
+# --- CONFIGURATION ET NETTOYAGE AU DÉMARRAGE ---
 st.set_page_config(page_title="Dataltist AI Assistant", layout="wide")
 st.title("📊 Dataltist BI Chat")
 
-# --- 1. Gestion du Fichier ---
+from langfuse.langchain import CallbackHandler
+ 
+# Initialize Langfuse CallbackHandler for Langchain (tracing)
+langfuse_handler = CallbackHandler()
+
+
+# Initialisation du dossier de travail
+if "app_initialized" not in st.session_state:
+    if os.path.exists("outputs"):
+        shutil.rmtree("outputs")
+    os.makedirs("outputs", exist_ok=True)
+    if not os.path.exists("data"):
+        os.makedirs("data", exist_ok=True)
+    st.session_state.app_initialized = True
+    st.session_state.query_count = 0
+    st.session_state.messages = []
+
+# --- 1. SIDEBAR : GESTION DES DONNÉES ---
+file_path = None
 with st.sidebar:
-    st.header("Données")
+    st.header("📁 Données")
     uploaded_file = st.file_uploader("Charge ton fichier (CSV/Excel)", type=["csv", "xlsx"])
     
     if uploaded_file:
-        # On sauvegarde le fichier dans le dossier 'data'
         file_path = os.path.join("data", uploaded_file.name)
         with open(file_path, "wb") as f:
             f.write(uploaded_file.getbuffer())
-        st.success(f"Fichier {uploaded_file.name} chargé !")
+        st.success(f"Fichier '{uploaded_file.name}' prêt !")
 
-# --- 2. Interface de Chat ---
-if "messages" not in st.session_state:
-    st.session_state.messages = []
+    st.write("---")
+    if st.button("🗑️ Effacer la discussion & fichiers", use_container_width=True):
+        st.session_state.messages = []
+        st.session_state.query_count = 0
+        if os.path.exists("outputs"):
+            shutil.rmtree("outputs")
+        os.makedirs("outputs", exist_ok=True)
+        st.rerun()
 
-if st.sidebar.button("🗑️ Effacer la discussion"):
-    st.session_state.messages = []
-    st.rerun()
-
-# Affichage de l'historique
+# --- 2. AFFICHAGE DE L'HISTORIQUE ---
 for message in st.session_state.messages:
     with st.chat_message(message["role"]):
         st.markdown(message["content"])
+        # Réaffichage des graphiques archivés pour ce message
+        if "charts" in message and message["charts"]:
+            cols = st.columns(min(len(message["charts"]), 2))
+            for idx, path in enumerate(message["charts"]):
+                if os.path.exists(path):
+                    cols[idx % 2].image(path, use_container_width=True)
 
-# --- 3. Interaction avec les Agents ---
+# --- 3. INTERACTION AVEC LES AGENTS ---
 if prompt := st.chat_input("Pose ta question sur tes données..."):
-    # 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)
+    if not file_path:
+        st.error("Veuillez d'abord charger un fichier dans la barre latérale.")
+        st.stop()
+
+    # Archivage : Création du dossier spécifique pour cette requête
+    current_query_id = st.session_state.query_count
+    query_folder = os.path.join("outputs", f"query_{current_query_id}")
+    os.makedirs(query_folder, exist_ok=True)
 
-    # 2. Affichage du message utilisateur
+    # Affichage message utilisateur
     st.session_state.messages.append({"role": "user", "content": prompt})
     with st.chat_message("user"):
         st.markdown(prompt)
 
-    # 3. Appel des agents
+    # Appel des agents
     with st.chat_message("assistant"):
-        # On utilise expanded=True pour voir le raisonnement des agents (Analyseur, Exécuteur...)
         with st.status("Analyse Dataltist en cours...", expanded=True) as status:
             
             inputs = {
-                "messages": [("user", prompt)],
+                "messages": [HumanMessage(content=prompt)],
                 "current_df_path": file_path
             }
             
-            # On lance le workflow (invoke est bloquant, donc l'analyse se termine avant la suite)
-            final_state = app.invoke(inputs)
+            # Exécution du graphe
+            final_state = app.invoke(
+                inputs, 
+                config={"callbacks": [langfuse_handler], "run_name": f"Query_{st.session_state.query_count}"}
+            )
             
-            # Récupération du message final du Reporter
+            # Récupération de la réponse finale (Reporter)
             response_text = final_state["messages"][-1].content
             st.markdown(response_text)
             
-            # --- AFFICHAGE DES GRAPHIQUES ---
-            # On cherche les fichiers APRES l'exécution
-            list_of_plots = glob.glob("outputs/*.png")
+            # --- GESTION DES FICHIERS GÉNÉRÉS ---
+            new_files_paths = []
+            # On scanne la racine de 'outputs/' pour trouver ce que l'Exécuteur a créé
+            raw_files = [f for f in glob.glob("outputs/*") if os.path.isfile(f)]
             
-            if list_of_plots:
-                st.write("---") # Séparateur visuel
-                st.subheader("📊 Visualisations générées")
+            if raw_files:
+                st.write("---")
+                st.subheader("📊 Résultats de l'analyse")
+                
+                # On déplace les fichiers vers le dossier de la query pour l'historique
+                for f in raw_files:
+                    dest_path = os.path.join(query_folder, os.path.basename(f))
+                    shutil.move(f, dest_path)
+                    new_files_paths.append(dest_path)
+
+                # Affichage des images et fichiers Excel
+                images = [p for p in new_files_paths if p.lower().endswith(('.png', '.jpg'))]
+                docs = [p for p in new_files_paths if p.lower().endswith(('.xlsx', '.csv'))]
+
+                if images:
+                    cols = st.columns(min(len(images), 2))
+                    for idx, img_path in enumerate(images):
+                        cols[idx % 2].image(img_path, use_container_width=True, caption=f"Visuel {idx+1}")
                 
-                # 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):
-                    col_idx = idx % 2
-                    with cols[col_idx]:
-                        st.image(plot_path, use_container_width=True, caption=f"Analyse {idx+1}")
+                if docs:
+                    for doc_path in docs:
+                        with open(doc_path, "rb") as f:
+                            st.download_button(
+                                label=f"📥 Télécharger {os.path.basename(doc_path)}",
+                                data=f,
+                                file_name=os.path.basename(doc_path),
+                                mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+                            )
             
             status.update(label="Analyse terminée !", state="complete")
 
-    # 4. Sauvegarde dans l'historique pour le chat
-    st.session_state.messages.append({"role": "assistant", "content": response_text})
+    # Mise à jour de la session et du compteur
+    st.session_state.messages.append({
+        "role": "assistant", 
+        "content": response_text, 
+        "charts": new_files_paths
+    })
+    st.session_state.query_count += 1

+ 8 - 4
main.py

@@ -10,18 +10,22 @@ 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")):
+for f in glob.glob(os.path.join(output_dir, "*")):
     os.remove(f)
 
 if __name__ == "__main__":
     inputs = {
-        "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("Je veux un fichier excel qui stock les equiquipe restant acteuellemet en champions league (huitiemme final 2026) , avec leur dernier resultats et le prochain adversaire et des infos supplimentaire en relation avec se sujet ")], 
+        #"messages" : [HumanMessage("Je veux un fichier excel ou y'a un tableau qui croiss les ventes et les pays , a ces coté ( a l'interieur de meme fichier ) un graphique pour visualier ca ")], 
+        #"messages" : [HumanMessage(content="Génère un fichier Excel 'outputs/Rapport_Analytique.xlsx' via xlsxwriter. Pleusieures feuille sur tt les teste statistiques possible , voicie  4 feuilles premiere : 'Synthèse Pays', 'Performance Vendeurs', 'Analyse Produits' et 'Croisement Global'. Pour chaque feuille, insère un graphique natif Excel (add_chart) illustrant les données (Barres pour pays/vendeurs, Pie pour produits). en total je veux 10 feulles , le dernier graphique de la dernier feuille doit etre un camambert ")] ,
+        #"messages": [HumanMessage(content="je veux voir par graphiques les produits les plus vendus en Belgique et par quel fournisseur ")] , 
+        "messages" : [HumanMessage(content = " je veux un fichier excel pour faire les diffirentes stats sur les sales , et 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"
     }
 
+
+
 # On stocke les événements pour pouvoir récupérer les graphiques à la fin
     final_events = []
     

+ 2 - 1
requirements.txt

@@ -6,4 +6,5 @@ openpyxl
 matplotlib
 seaborn
 python-dotenv
-duckduckgo-search
+duckduckgo-search
+xlsxwriter