Просмотр исходного кода

LLm local works ! testé pour analyser et generer des graphiques simple ! , reporter moins bon

Abdenbi 1 неделя назад
Родитель
Сommit
337a2bb8f9

+ 39 - 21
Agents.py

@@ -38,7 +38,7 @@ class AgentState(TypedDict):
 
 #model_gpt = ChatOpenAI(model="gpt-5.3-codex" ) 
 
-model_gpt_5 = ChatOpenAI(model="o4-mini" )
+#model_gpt_5 = ChatOpenAI(model="o4-mini" )
 tools = [ search_tool  , excel_code_interpreter,  inspect_data ]
 
 
@@ -49,16 +49,16 @@ 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(
+local_llm_chat = 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",
+local_llm_fonction = OllamaFunctions(
+    #model="deepseek-coder-v2:16b-lite-instruct-q4_K_M",
+    model="llama3.1:8b",
     temperature=0,
     format="json",
     num_ctx=4096
@@ -67,7 +67,7 @@ local_llm = OllamaFunctions(
 
 
 
-model_with_tools = local_llm.bind_tools(tools)
+model_with_tools = local_llm_fonction.bind_tools(tools)
 
 def agent_analyseur(state: AgentState):
     import os
@@ -128,7 +128,7 @@ def agent_analyseur(state: AgentState):
 
     # 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)
+    response = local_llm_chat.invoke(final_messages, config=config_analyseur)
 
     return {"messages": [response]}
 
@@ -144,28 +144,36 @@ def agent_executor(state: AgentState):
     # On construit le prompt en injectant le nom dynamiquement
     prompt = (
         "### SYSTEM ROLE ###\n"
-        "Tu es un moteur d'exécution Python. Réponds EXCLUSIVEMENT en JSON.\n\n"
+        "Tu es un moteur d'exécution Python. Réponds EXCLUSIVEMENT au format JSON.\n\n"
+        
+        "### FICHIER ACTUEL ###\n"
+        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"
+        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"
         
-        "### 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"
+        "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"
         
-        "### 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"
+        "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"
         
         "### 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"
+        "    \"code\": \"import pandas as pd\\nimport matplotlib.pyplot as plt\\n# ... code ...\\nplt.savefig('outputs/chart.png')\\nresult = 'Analyse terminée'\"\n"
         "  }\n"
         "}"
     )
-
-    # Nettoyage des messages pour Ollama (comme avant)
+        # Nettoyage des messages pour Ollama (comme avant)
     cleaned_messages = []
     for m in state["messages"]:
         if m.type == "tool":
@@ -210,7 +218,17 @@ def agent_reporter(state : AgentState) :
         "   '📥 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"
     )
-    messages = [SystemMessage(content=prompt_reporter)] + state["messages"]
 
-    response = local_llm.invoke(messages  ) 
-    return{"messages" : [response]}
+            # 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)
+
+    # On place le prompt dynamique en premier
+    final_messages = [HumanMessage(content=prompt_reporter)] + cleaned_messages
+
+    return {"messages": [local_llm_chat.invoke(final_messages)]}
+

BIN
__pycache__/Agents.cpython-313.pyc


BIN
__pycache__/tools.cpython-313.pyc


BIN
__pycache__/workflow_Agent.cpython-313.pyc


BIN
graph_workflow.png


+ 29 - 6
workflow_Agent.py

@@ -1,6 +1,7 @@
 from langgraph.graph import StateGraph , START , END 
 from langgraph.prebuilt import ToolNode, tools_condition
 from Agents import AgentState , agent_analyseur , agent_executor , tools , agent_reporter
+from langchain_core.messages import AIMessage , ToolMessage
 
 
 # 1. Initialisation du graphe avec l'état personnalisé
@@ -14,17 +15,31 @@ workflow.add_node("tools", ToolNode(tools))
 
 # 3. Définition des arêtes stables
 workflow.add_edge(START, "analyseur")
-workflow.add_edge("analyseur", "executor")
-workflow.add_edge("tools", "executor")  
+workflow.add_edge("analyseur", "executor") 
 workflow.add_edge("reporter", END)      
 
 # 4. Logique de routage personnalisée
 def router(state: AgentState):
-    last_message = state["messages"][-1]
-    # Si l'exécuteur demande un outil, on y va
-    if last_message.tool_calls:
+    messages = state["messages"]
+    last_message = messages[-1]
+
+    # CAS 1 : L'agent demande un outil
+    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
         return "tools"
-    # Sinon, cela signifie qu'il a fini son analyse technique -> on passe au rapport
+
+    # CAS 2 : Analyse du retour de l'outil 
+    if isinstance(last_message, ToolMessage):
+        content_upper = last_message.content.upper()
+        
+        # On détecte ton marqueur spécifique ou le mot "ERROR"
+        if "ERREUR PYTHON" in content_upper or "ERROR" in content_upper:
+            print("--- LOG : Erreur détectée, renvoi à l'exécuteur pour correction ---")
+            return "executor" 
+        
+        # Si pas d'erreur, on peut passer à la synthèse
+        return "reporter"
+
+    # CAS 3 : Par défaut
     return "reporter"
 
 # On applique la condition sur l'executor
@@ -34,6 +49,14 @@ workflow.add_conditional_edges(
     {"tools": "tools", "reporter": "reporter"}
 )
 
+workflow.add_conditional_edges(
+    "tools" , 
+    router , 
+    {"executor": "executor", "reporter": "reporter"}
+)
+
+
+
 # 5. Compilation
 app = workflow.compile()