Procházet zdrojové kódy

first commit on dev

Abdenbi IABBADENE před 1 týdnem
rodič
revize
ea79b18b27

+ 13 - 0
.env_dev

@@ -0,0 +1,13 @@
+Project_final/.env_dev
+LANGFUSE_SECRET_KEY=
+LANGFUSE_PUBLIC_KEY=
+LANGFUSE_BASE_URL="https://cloud.langfuse.com"
+
+
+SERPER_API_KEY=
+GOOGLE_API_KEY=
+GROQ_API_KEY =
+OPENAI_API_KEY=
+MISTRAL_API_KEY=
+
+OPENROUTER_API_KEY=

+ 1 - 1
.gitignore

@@ -2,4 +2,4 @@ data
 outputs
 .env
 old_outputs
-.vscode
+.vscode

+ 120 - 66
Agents.py

@@ -2,13 +2,15 @@ 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
-from langgraph.graph.message import add_messages
-from langchain_google_genai import ChatGoogleGenerativeAI 
+from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage , AIMessage
+from langgraph.graph.message import add_messages 
 from langchain_groq import ChatGroq 
 from langfuse import get_client
 import pandas as pd
 from langfuse.langchain import CallbackHandler
+import json
+
+from langchain_community.chat_models import ChatOllama
  
 # Initialize Langfuse CallbackHandler for Langchain (tracing)
 langfuse_handler = CallbackHandler()
@@ -16,7 +18,7 @@ langfuse_handler = CallbackHandler()
 
 
 # Import de tes outils corrigés
-from tools import  excel_code_interpreter,   search_tool , inspect_data
+from tools import  excel_code_interpreter,   search_tool , inspect_data , call_tool_and_format_for_ollama , robust_json_parse
 
 langfuse = get_client()
 load_dotenv()
@@ -25,104 +27,156 @@ load_dotenv()
 class AgentState(TypedDict):
     messages: Annotated[Sequence[BaseMessage], add_messages]
     current_df_path: Optional[str]
-    generated_charts : List[str]
+    Data_colomns: Optional[str]
+    generated_charts : List[str]  
 
 # 2. Configuration du Modèle et des Outils
-model =ChatGroq(model="llama-3.3-70b-versatile")
+#model =ChatGroq(model="llama-3.3-70b-versatile")
 
 
 #model_llama=ChatGoogleGenerativeAI(model="gemini-2.5-flash")
 
-model_gpt = ChatOpenAI(model="gpt-5.3-codex" ) 
+#model_gpt = ChatOpenAI(model="gpt-5.3-codex" ) 
 
 model_gpt_5 = ChatOpenAI(model="o4-mini" )
 tools = [ search_tool  , excel_code_interpreter,  inspect_data ]
-model_with_tools = model_gpt.bind_tools(tools)
 
 
 
 
-# 3. Définition des Nœuds
+# Local LLM : 
+from langchain_experimental.llms.ollama_functions import OllamaFunctions
+from langchain_community.chat_models import ChatOllama
+
+# On crée une instance standard pour l'Analyseur
+analyseur_llm = ChatOllama(
+    model="deepseek-coder-v2:16b-lite-instruct-q4_K_M",
+    temperature=0,
+    format="json" # On garde le format JSON ici
+)
+
+
+local_llm = OllamaFunctions(
+    model="deepseek-coder-v2:16b-lite-instruct-q4_K_M",
+    #model="llama3.1:8b",
+    temperature=0,
+    format="json",
+    num_ctx=4096
+)
+
+
+
+
+model_with_tools = local_llm.bind_tools(tools)
+
 def agent_analyseur(state: AgentState):
-    # --- PHASE D'INSPECTION AUTOMATIQUE ---
-    # On récupère le chemin du fichier depuis le state
+    import os
+    import pandas as pd
+
+    # 1. PHASE D'INSPECTION (Identique)
     file_path = state.get("current_df_path")
-    inspection_info = ""
-    
+    inspection_info = "Aucun fichier détecté."
+    columns_list = []
+
     if file_path and os.path.exists(file_path):
         try:
-            # Lecture des 5 premières lignes pour comprendre la structure
             df_temp = pd.read_csv(file_path, nrows=5) if file_path.endswith('.csv') else pd.read_excel(file_path, nrows=5)
             columns_list = df_temp.columns.tolist()
             sample_data = df_temp.head(2).to_string()
-            inspection_info = f"\n\n### DONNÉES RÉELLES DU FICHIER :\n- Colonnes détectées : {columns_list}\n- Aperçu des données :\n{sample_data}"
+            inspection_info = (
+                f"COLONNES EXACTES : {columns_list}\n"
+                f"APERÇU DES DONNÉES :\n{sample_data}"
+            )
         except Exception as e:
-            inspection_info = f"\n\n⚠️ Erreur lors de l'inspection du fichier : {e}"
+            inspection_info = f"⚠️ Erreur lors de l'inspection : {e}"
+
+    # 2. RÉCUPÉRATION DE LA QUERY
+    user_query = state["messages"][-1].content if state["messages"] else "Pas de question."
 
-    # --- CONSTRUCTION DU PROMPT AVEC LES DONNÉES RÉELLES ---
+    # 3. CONSTRUCTION DU PROMPT DYNAMIQUE (Comme l'Exécuteur)
+    # On définit les règles métier ici
     prompt = (
-            "Tu es l'Analyseur Stratégique de Dataltist. Ton rôle est de définir le 'QUOI' faire, pas le 'COMMENT'.\n\n"
-            "### RÈGLES D'OR :\n"
-            "1. PAS DE CODE : Ne génère JAMAIS de blocs de code Python (```python). C'est le rôle de l'Exécuteur.\n"
-            "2. PLAN D'ACTION : Liste les étapes logiques en utilisant les noms de colonnes exacts.\n"
-            "3. INSPECTION : Base-toi uniquement sur les colonnes détectées ci-dessous.\n"
-            "4. CONCISION : Sois une tour de contrôle, donne des ordres clairs et précis.\n"
-            f"{inspection_info}"
-        )
+        "### SYSTEM ROLE ###\n"
+        "Tu es l'Analyseur de Dataltist. Ton rôle est de créer un plan d'action logique.\n"
+        "Tu dois répondre EXCLUSIVEMENT au format JSON.\n\n"
+        
+        "### DONNÉES DISPONIBLES ###\n"
+        f"{inspection_info}\n\n"
+        
+        "### RÈGLES CRITIQUES ###\n"
+        "1. PAS DE CODE : Ne génère jamais de Python.\n"
+        f"2. COLONNES : Utilise uniquement {columns_list}.\n"
+        "3. PLAN : Détaille les étapes (Somme, Moyenne, Regroupement, etc.).\n\n"
+        
+        "### STRUCTURE JSON ATTENDUE ###\n"
+        "{\n"
+        "  \"plan\": [\"étape 1\", \"étape 2\"],\n"
+        "  \"colonnes_utilisees\": [\"col1\", \"col2\"]\n"
+        "}\n"
+    )
+
+    # 4. PRÉPARATION DU MESSAGE UNIQUE (La méthode qui marche pour l'Exécuteur)
+    # On fusionne le prompt et la question utilisateur dans un seul HumanMessage
+    full_input = f"{prompt}\n\n### QUESTION UTILISATEUR ###\n{user_query}"
     
+    final_messages = [HumanMessage(content=full_input)]
+
     config_analyseur = {
         "callbacks": [langfuse_handler],
-        "metadata": {"agent_name": "Analyseur"},
-        "tags": ["Dataltist", "Planning"]
+        "metadata": {"agent_name": "Analyseur"}
     }
 
-    msg = [SystemMessage(content=prompt)] + state["messages"]
-    response = model.invoke(msg , config=config_analyseur)
-    
-    # On retourne la réponse de l'IA qui contient maintenant le plan basé sur les vraies colonnes
+    # 5. APPEL DU MODÈLE
+    # Note : On utilise local_llm (ou analyseur_llm si tu as séparé)
+    response = local_llm.invoke(final_messages, config=config_analyseur)
+
     return {"messages": [response]}
 
 
 
-def agent_executor(state: AgentState):
-    # 1. On récupère le chemin actuel depuis le State
-    # Si current_df_path est None, on met data.xlsx par défaut
-    file_path = state.get("current_df_path") or "data.xlsx"
-    clean_path = file_path.replace("\\", "/")
 
-    # 2. On construit un prompt qui contient le VRAI chemin
+
+def agent_executor(state: AgentState):
+    # On récupère le nom exact du fichier depuis le state
+    full_path = state.get("current_df_path") or "data/default.csv"
+    file_name = os.path.basename(full_path) # Récupère 'mon_fichier.csv'
+    
+    # On construit le prompt en injectant le nom dynamiquement
     prompt = (
-        f"Tu es un Data Scientist expert spécialisé en automatisation. Le fichier cible est disponible ici : '{file_path}'.\n\n"
-        "PROTOCOLE D'ACTION IMPÉRATIF :\n"
-        "1. CONFIANCE STRATÉGIQUE : Utilise les noms de colonnes fournis par l'Analyseur ( Obligatoire , change pas ).\n"
-        "2. RECHERCHE EXTERNE : Utilise 'search_tool' si des données manquent.\n"
-        "3. ZÉRO SIMULATION : Ne jamais inventer de données.\n"
-        "4. LOGIQUE PANDAS : Utilise 'df' pour filtrer les données exactes.\n"
-        "5. CODE AUTONOME : Ton script Python doit être complet et prêt à l'exécution. \n"
-        f"   - Tu DOIS impérativement définir la variable `file_path = '{clean_path}'` au début de ton code.\n"
-        "   - Inclus tous les imports (pandas as pd, matplotlib.pyplot as plt, etc.).\n"
-        "   - Termine impérativement par 'result = ...'.\n"
-        "6. PERSISTANCE : Utilise 'plt.savefig('outputs/nom_du_graphe.png')' et ferme la figure avec 'plt.close()'.\n\n"
-        "Tu es un moteur d'exécution froid et précis. Pas de discours.\n"
-        "STRUCTURE DE RÉPONSE :\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"
+        "### SYSTEM ROLE ###\n"
+        "Tu es un moteur d'exécution Python. Réponds EXCLUSIVEMENT en JSON.\n\n"
+        
+        "### FICHIER ACTUEL (IMPORTANT) ###\n"
+        f"Tu dois travailler UNIQUEMENT sur le fichier : '{file_name}'\n"
+        f"Le chemin à utiliser dans ton code est : 'data/{file_name}'\n\n"
+        
+        "### INSTRUCTIONS DE CODE ###\n"
+        "1. Commence ton code par l'import des librairies.\n"
+        f"2. Charge le fichier avec : df = pd.read_csv('data/{file_name}') (ou read_excel).\n"
+        "3. Termine TOUJOURS par une variable 'result' contenant ton analyse.\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\\n# Ton code ici...\"\n"
+        "  }\n"
+        "}"
     )
 
-    # 3. Préparation des messages
-    messages = [SystemMessage(content=prompt)] + state["messages"]
+    # Nettoyage des messages pour Ollama (comme avant)
+    cleaned_messages = []
+    for m in state["messages"]:
+        if m.type == "tool":
+            cleaned_messages.append(HumanMessage(content=f"Résultat : {m.content}"))
+        else:
+            cleaned_messages.append(m)
 
-    config_executor= {
-        "callbacks": [langfuse_handler],
-        "metadata": {"agent_name": "executor"},
-        "tags": ["Dataltist", "executor"]
-    }
-    
-    # 4. Appel du modèle avec les outils
-    response = model_with_tools.invoke(messages , config= config_executor )
-    
-    return {"messages": [response]}
+    # On place le prompt dynamique en premier
+    final_messages = [HumanMessage(content=prompt)] + cleaned_messages
+
+    return {"messages": [model_with_tools.invoke(final_messages)]}
 
 
 
@@ -158,5 +212,5 @@ def agent_reporter(state : AgentState) :
     )
     messages = [SystemMessage(content=prompt_reporter)] + state["messages"]
 
-    response = model.invoke(messages  ) 
+    response = local_llm.invoke(messages  ) 
     return{"messages" : [response]}

+ 30 - 0
Notes/ollama_commands.txt

@@ -0,0 +1,30 @@
+1. Afficher ollama : 
+ollama 
+
+2.Télécharger un modèle :*
+ollama pull model_name
+
+3. Lancer un modèle :
+ollama run model_name 
+
+4. Voir les modèles installé
+ollama list 
+
+5. Supprimer un modèle
+ollama rm llama3 
+
+6. Lancer un serveur API (optionnel)
+ollama serve 
+
+
+*************************** 
+1 - Arrêter le serveur : 
+taskkill /PID 15736 /F       ->  /PID 15736 → c’est le numéro du processus que tu as trouvé
+
+
+2 - Vérifier que le port est libre : 
+netstat -ano | findstr 11434 
+
+
+3 - voir les processus Ollama :
+tasklist | findstr ollama

binární
Rapport_Analytique.xlsx


binární
__pycache__/Agents.cpython-313.pyc


binární
__pycache__/Agents_wooooork.cpython-313.pyc


binární
__pycache__/tools.cpython-313.pyc


binární
__pycache__/workflow_Agent.cpython-313.pyc


+ 46 - 0
ancien version.py

@@ -0,0 +1,46 @@
+"""# 3. Définition des Nœuds
+def agent_analyseur(state: AgentState):
+    # --- PHASE D'INSPECTION AUTOMATIQUE ---
+    # On récupère le chemin du fichier depuis le state
+    file_path = state.get("current_df_path")
+    inspection_info = ""
+    
+    if file_path and os.path.exists(file_path):
+        try:
+            # Lecture des 5 premières lignes pour comprendre la structure
+            df_temp = pd.read_csv(file_path, nrows=5) if file_path.endswith('.csv') else pd.read_excel(file_path, nrows=5)
+            columns_list = df_temp.columns.tolist()
+            sample_data = df_temp.head(2).to_string()
+            inspection_info = f"\n\n### DONNÉES RÉELLES DU FICHIER :\n- Colonnes détectées : {columns_list}\n- Aperçu des données :\n{sample_data}"
+        except Exception as e:
+            inspection_info = f"\n\n⚠️ Erreur lors de l'inspection du fichier : {e}"
+
+    # --- CONSTRUCTION DU PROMPT AVEC LES DONNÉES RÉELLES ---
+    prompt = (
+            "Tu es l'Analyseur de Dataltist. Réponds exclusivement au format JSON.\n"
+            "Définis les étapes logiques de l'analyse sans écrire de code.\n"
+            "### RÈGLES D'OR :\n"
+            "1. PAS DE CODE : Ne génère JAMAIS de blocs de code Python (```python). C'est le rôle de l'Exécuteur.\n"
+            "2. PLAN D'ACTION : Liste les étapes logiques en utilisant les noms de colonnes exacts.\n"
+            "3. INSPECTION : Base-toi uniquement sur les colonnes détectées ci-dessous.\n"
+            "4. CONCISION : Sois une tour de contrôle, donne des ordres clairs et précis.\n"
+            f"{inspection_info}"
+        )
+    
+    config_analyseur = {
+        "callbacks": [langfuse_handler],
+        "metadata": {"agent_name": "Analyseur"},
+        "tags": ["Dataltist", "Planning"]
+    }
+
+    msg = [SystemMessage(content=prompt)] + state["messages"]
+    response = local_llm.invoke(msg , config=config_analyseur)
+    
+    # On retourne la réponse de l'IA qui contient maintenant le plan basé sur les vraies colonnes
+    return {"messages": [response]}
+
+        ⚠️ IMPORTANT :
+    - Ta réponse doit commencer par { et finir par }
+    - Aucun texte avant ou après
+    - Aucun markdown (```json interdit)
+"""

+ 21 - 1
app.py

@@ -4,6 +4,7 @@ import glob
 import shutil
 from workflow_Agent import app  # Ton graphe compilé
 from langchain_core.messages import HumanMessage
+import pandas as pd
 
 # --- CONFIGURATION ET NETTOYAGE AU DÉMARRAGE ---
 st.set_page_config(page_title="Dataltist AI Assistant", layout="wide")
@@ -28,15 +29,34 @@ if "app_initialized" not in st.session_state:
 
 # --- 1. SIDEBAR : GESTION DES DONNÉES ---
 file_path = None
+
 with st.sidebar:
     st.header("📁 Données")
     uploaded_file = st.file_uploader("Charge ton fichier (CSV/Excel)", type=["csv", "xlsx"])
     
     if uploaded_file:
+        # 1. Définition et sauvegarde du fichier
         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}' prêt !")
+        st.success(f"Fichier '{uploaded_file.name}' prêt !") 
+
+        df = pd.read_csv(file_path) if file_path.endswith(".csv") else pd.read_excel(file_path)
+        columns_list = df.columns.tolist()
+
+        # 2. Initialisation ou Mise à jour du Session State
+        # On utilise le nom exact de ta classe : AgentState
+        if "AgentState" not in st.session_state:
+            st.session_state.AgentState = {
+                "messages": [], # Liste vide pour l'historique
+                "current_df_path": file_path, # Le chemin vers le fichier uploadé
+                "Data_colomns" : columns_list, 
+                "generated_charts": [] # Liste vide pour les futurs PNG
+            }
+        else:
+            # Si l'utilisateur change de fichier, on met à jour le chemin
+            st.session_state.AgentState["current_df_path"] = file_path
+
 
     st.write("---")
     if st.button("🗑️ Effacer la discussion & fichiers", use_container_width=True):

+ 2 - 5
main.py

@@ -18,8 +18,8 @@ if __name__ == "__main__":
         #"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="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"
     }
@@ -47,6 +47,3 @@ if __name__ == "__main__":
     else:
         print("Aucun graphique n'a été généré.")
 
-import matplotlib
-matplotlib.use('Agg')  # À placer AVANT d'importer pyplot
-import matplotlib.pyplot as plt

+ 0 - 55
pricing.py

@@ -1,55 +0,0 @@
-import matplotlib.pyplot as plt
-
-# ---------------------------
-# Données complètes pour agents IA avec prix
-# ---------------------------
-agents_detailed = [
-    # Planificateur / Brain
-    ["Planificateur", "GPT-5.2", "Très puissant", "Élevé", "Décomposition tâches complexes", "Idéal grandes entreprises", 1.75, 14.00],
-    ["Planificateur", "Claude Opus 4.6", "Puissant", "Moyen", "Raisonnement long, multi-étapes", "Bon pour workflows longs", 5.00, 25.00],
-    ["Planificateur", "Gemini 3.1 Pro", "Très puissant", "Élevé", "Multimodal, long contexte", "Excellente intégration multimédia", 2.00, 12.00],
-    ["Planificateur", "GPT-5.1 Codex", "Puissant", "Moyen", "Planification + code workflow", "Idéal pour automatisation", 1.75, 14.00],
-    ["Planificateur", "GPT-4.1", "Correct", "Bas", "Raisonnement basique, rapide", "Projets limités / budget réduit", 3.00, 12.00],
-
-    # Exécutant / Tool Agent
-    ["Exécutant", "GPT-5.1 Codex Max", "Très puissant", "Élevé", "Génération de code multi-fichiers", "Idéal pour Excel, graphiques", 1.75, 14.00],
-    ["Exécutant", "Claude Sonnet 4.6", "Puissant", "Moyen", "Manipulation fichiers et outils", "Bon pour automatisation des tâches", 3.00, 15.00],
-    ["Exécutant", "Gemini 3.1 Pro", "Très puissant", "Élevé", "Exécution multimodale, scripts complexes", "Gestion de workflows lourds", 2.00, 12.00],
-    ["Exécutant", "GPT-4.1", "Correct", "Bas", "Scripts simples et rapides", "Projets simples, faible budget", 3.00, 12.00],
-    ["Exécutant", "GPT-5 mini", "Bas", "Très bas", "Scripts simples, rapide", "Prototypage rapide", 0.25, 2.00],
-
-    # Rapporteur / Résumeur
-    ["Rapporteur", "GPT-5.2", "Très puissant", "Élevé", "Synthèse logique et claire", "Rapports clients complexes", 1.75, 14.00],
-    ["Rapporteur", "GPT-4.1", "Puissant", "Moyen", "Rapports structurés", "Rapports quotidiens / semi-complexes", 3.00, 12.00],
-    ["Rapporteur", "Claude Sonnet 4.6", "Puissant", "Moyen", "Rapports détaillés", "Bon pour multi-étapes", 3.00, 15.00],
-    ["Rapporteur", "Gemini 3.1 Pro", "Très puissant", "Élevé", "Synthèse multimodale", "Rapports complexes multimédia", 2.00, 12.00],
-    ["Rapporteur", "GPT-5 mini", "Correct", "Bas", "Rapports courts rapides", "Projets simples", 0.25, 2.00],
-]
-
-# ---------------------------
-# Création du tableau
-# ---------------------------
-fig, ax = plt.subplots(figsize=(20, 15))
-ax.axis('off')
-
-# Colonnes du tableau
-columns = ["Rôle Agent", "Modèle", "Puissance", "Coût", "Points forts", "Contexte d'usage", "Input ($/1M tokens)", "Output ($/1M tokens)"]
-
-# Création du tableau matplotlib
-table = ax.table(
-    cellText=agents_detailed,
-    colLabels=columns,
-    loc='center',
-    cellLoc='center',
-    colColours=["#FFD700", "#ADD8E6", "#90EE90", "#FFB6C1", "#E0FFFF", "#FFFACD", "#FFE4B5", "#FFDAB9"]
-)
-
-# Style et formatage
-table.auto_set_font_size(False)
-table.set_fontsize(9)
-table.auto_set_column_width(list(range(len(columns))))
-ax.set_title("🤖 Tableau complet des modèles pour agents IA avec prix Input/Output", fontsize=16, fontweight='bold')
-
-# Affichage
-plt.tight_layout()
-plt.show()

+ 0 - 42
test__.py

@@ -1,42 +0,0 @@
-import os
-from langfuse import Langfuse
-
-# 1. On définit les clés manuellement pour être SÛR à 100%
-# Remplace les pointillés par tes vraies clés
-PUBLIC_KEY = "pk-lf-f07364b3-d0a6-4252-8d65-447500b27cb7"
-SECRET_KEY = "sk-lf-da8a0214-de29-43e0-9c53-f85f8371a37f"
-HOST = "https://cloud.langfuse.com"
-
-print("--- Démarrage du test d'authentification Langfuse ---")
-
-try:
-    # 2. Initialisation explicite
-    langfuse = Langfuse(
-        public_key=PUBLIC_KEY,
-        secret_key=SECRET_KEY,
-        host=HOST,
-        debug=True # Active le mode debug pour voir ce qui se passe
-    )
-
-    # 3. Test d'envoi d'une trace simple
-    print("Tentative d'envoi d'une trace de test...")
-    trace = langfuse.trace(name="Test Authentification Dataltist")
-    
-    # 4. Envoi d'un score de test
-    print("Tentative d'envoi d'un score...")
-    langfuse.score(
-        trace_id=trace.id,
-        name="test_score",
-        value=1
-    )
-
-    # 5. Forcer l'envoi vers le serveur
-    langfuse.flush()
-    
-    print("\n✅ SUCCÈS : Aucune erreur d'authentification détectée.")
-    print(f"Vérifie ton dashboard Langfuse, tu devrais voir une trace nommée 'Test Authentification Dataltist'.")
-    print(f"ID de la trace envoyée : {trace.id}")
-
-except Exception as e:
-    print(f"\n❌ ÉCHEC : Une erreur est survenue.")
-    print(f"Détails de l'erreur : {str(e)}")

+ 44 - 2
tools.py

@@ -1,10 +1,10 @@
 import os
 import pandas as pd
-from langchain_core.tools import tool
+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 matplotlib
 matplotlib.use('Agg')  # Force Matplotlib à ne pas ouvrir de fenêtre graphique
@@ -139,3 +139,45 @@ def search_tool(query : str) :
 
 
 
+def call_tool_and_format_for_ollama(tool_output):
+    """
+    Cette fonction transforme la sortie brute de l'outil 
+    en un format que ton Llama 3.1 8B local peut comprendre.
+    """
+    # Au lieu de renvoyer un ToolMessage (qui fait planter Ollama)
+    # On crée un message "Observation"
+    observation_message = HumanMessage(
+        content=f"OBSERVATION DE L'OUTIL :\n{tool_output}\n\nUtilise ces données pour continuer ton analyse."
+    )
+    return observation_message
+
+
+import re
+import json
+
+def robust_json_parse(text):
+    try:
+        cleaned = text.strip()
+
+        # Extraction JSON
+        match = re.search(r"\{.*\}", cleaned, re.DOTALL)
+        if not match:
+            raise ValueError("Aucun JSON détecté")
+
+        json_str = match.group()
+
+        # Parsing direct
+        try:
+            return json.loads(json_str)
+        except json.JSONDecodeError:
+            pass
+
+        # Auto-fix
+        json_str = json_str.replace("\n", " ")
+        json_str = re.sub(r",\s*}", "}", json_str)
+        json_str = re.sub(r",\s*]", "]", json_str)
+
+        return json.loads(json_str)
+
+    except Exception as e:
+        raise ValueError(f"Parsing JSON échoué : {e}\nRAW:\n{text}")

binární
~$Rapport_Analytique.xlsx