|
|
@@ -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)]}
|
|
|
|