From f7986bf84c7e48f75ce51740207c6de30774fdc9 Mon Sep 17 00:00:00 2001 From: Ladebeze66 Date: Thu, 17 Apr 2025 14:04:38 +0200 Subject: [PATCH] 1704-14:04 --- agents/mistral_large/agent_ticket_analyser.py | 35 +++- agents/pixtral_large/agent_image_sorter.py | 5 + agents/utils/pipeline_logger.py | 166 ++++++++++++++++++ ...e_ticket_mistral-large-latest_results.json | 82 +++++++++ .../pipeline/rapport_de_deduplication.json | 26 +++ ...ri_image_pixtral-large-latest_results.json | 90 ++++++++++ test_agent_ticket_analyser_mistral_large.py | 149 ++++++++++++++++ ...r.py => test_image_sorter_pixtral_large.py | 2 +- utils/image_dedup.py | 114 +++++++++++- 9 files changed, 658 insertions(+), 11 deletions(-) create mode 100644 agents/utils/pipeline_logger.py create mode 100644 output/ticket_T11143/T11143_20250416_103302/T11143_rapports/pipeline/analyse_ticket_mistral-large-latest_results.json create mode 100644 output/ticket_T11143/T11143_20250416_103302/T11143_rapports/pipeline/rapport_de_deduplication.json create mode 100644 output/ticket_T11143/T11143_20250416_103302/T11143_rapports/pipeline/tri_image_pixtral-large-latest_results.json create mode 100644 test_agent_ticket_analyser_mistral_large.py rename test_image_sorter.py => test_image_sorter_pixtral_large.py (99%) diff --git a/agents/mistral_large/agent_ticket_analyser.py b/agents/mistral_large/agent_ticket_analyser.py index a170d9c..640d2a6 100644 --- a/agents/mistral_large/agent_ticket_analyser.py +++ b/agents/mistral_large/agent_ticket_analyser.py @@ -5,6 +5,7 @@ import json import os from datetime import datetime from loaders.ticket_data_loader import TicketDataLoader +from ..utils.pipeline_logger import sauvegarder_donnees logger = logging.getLogger("AgentTicketAnalyser") @@ -20,7 +21,7 @@ class AgentTicketAnalyser(BaseAgent): # Configuration adaptée à l'analyse structurée pour Mistral Large self.temperature = 0.1 self.top_p = 0.8 - self.max_tokens = 6000 + self.max_tokens = 4000 # Prompt système clair, structuré, optimisé pour une analyse exhaustive self.system_prompt = """Tu es un expert en analyse de tickets pour le support informatique de BRG-Lab (client : CBAO). @@ -28,8 +29,9 @@ class AgentTicketAnalyser(BaseAgent): Tu dois : 1. Identifier le client et résumer la demande. 2. Reformuler les questions implicites du ticket (à partir du `name` et `description`). -3. Nettoyer et structurer les échanges client/support en conservant les informations utiles (liens, modules, options, erreurs). -4. Extraire les éléments à observer dans les captures (pour l'agent image). +3. Nettoyer et structurer les échanges client/support en conservant les informations utiles (liens, modules, options, erreurs) et en évitant d'introduire des éléments non pertinents. +4. Conserver tous les messages, même ceux qui semblent moins pertinents, pour maintenir le contexte complet de la discussion, y compris les détails spécifiques comme les couleurs ou les termes techniques. +5. Extraire les éléments à observer dans les captures (pour l'agent image). Structure de sortie : 1. Résumé du contexte @@ -41,7 +43,10 @@ IMPORTANT : - Ne propose pas de solution - Garde tous les liens et noms de modules - Ne transforme pas les propos sauf pour supprimer les bruits inutiles (signatures, mails automatiques) -- Ne reformule pas les messages sauf si cela clarifie les intentions sans altérer leur sens +- Ne reformule pas les messages sauf si cela clarifie les intentions sans altérer leur sens. +- Assure-toi que tous les messages sont inclus dans le fil de discussion, même s'ils semblent redondants ou peu clairs. +- Évite d'introduire des éléments fictifs ou non mentionnés dans les messages d'origine. +- Conserve le contenu intégral des messages, même s'ils contiennent des éléments de discussion qui semblent hors sujet. """ self.ticket_loader = TicketDataLoader() @@ -78,6 +83,23 @@ IMPORTANT : response = self.llm.interroger(prompt) logger.info("Analyse du ticket terminée avec succès") print(f" Analyse terminée: {len(response)} caractères") + + # Sauvegarde dans pipeline + result = { + "prompt": prompt, + "response": response, + "metadata": { + "timestamp": self._get_timestamp(), + "source_agent": self.nom, + "ticket_id": ticket_code, + "model_info": { + "model": getattr(self.llm, "modele", str(type(self.llm))), + **getattr(self.llm, "params", {}) + } + } + } + sauvegarder_donnees(ticket_code, "analyse_ticket", result, base_dir="reports", is_resultat=True) + except Exception as e: logger.error(f"Erreur d'analyse: {str(e)}") return f"ERREUR: {str(e)}" @@ -128,12 +150,11 @@ IMPORTANT : for i, msg in enumerate(messages): if not isinstance(msg, dict): continue - auteur = msg.get("from", "inconnu") + auteur = msg.get("author_id", "inconnu") role = "CLIENT" if "client" in auteur.lower() else "SUPPORT" if "support" in auteur.lower() else "AUTRE" date = self._formater_date(msg.get("date", "inconnue")) contenu = msg.get("content", "").strip() - if contenu: - texte += f"{i+1}. [{role}] {auteur} ({date})\n{contenu}\n\n" + texte += f"{i+1}. [{role}] {auteur} ({date})\n{contenu}\n\n" # Métadonnées utiles metadata = ticket_data.get("metadata", {}) diff --git a/agents/pixtral_large/agent_image_sorter.py b/agents/pixtral_large/agent_image_sorter.py index b4f209c..484df4b 100644 --- a/agents/pixtral_large/agent_image_sorter.py +++ b/agents/pixtral_large/agent_image_sorter.py @@ -3,6 +3,7 @@ import logging import os from typing import Dict, Any, Tuple from PIL import Image +from ..utils.pipeline_logger import sauvegarder_donnees logger = logging.getLogger("AgentImageSorter") @@ -125,6 +126,10 @@ class AgentImageSorter(BaseAgent): "source_agent": self.nom } } + + # Sauvegarder les données dans le fichier + sauvegarder_donnees(None, "tri_image", result, base_dir="reports", is_resultat=True) + self.ajouter_historique("tri_image", {"image_path": image_path, "prompt": prompt}, result) return result diff --git a/agents/utils/pipeline_logger.py b/agents/utils/pipeline_logger.py new file mode 100644 index 0000000..077f573 --- /dev/null +++ b/agents/utils/pipeline_logger.py @@ -0,0 +1,166 @@ +import os +import json +from datetime import datetime +from typing import Dict, Any, Optional, Union + +def determiner_repertoire_ticket(ticket_id: str) -> Optional[str]: + """ + Détermine dynamiquement le répertoire du ticket. + + Args: + ticket_id: str, le code du ticket + + Returns: + str, le chemin du répertoire pour ce ticket ou None si non trouvé + """ + # Base de recherche des tickets + output_dir = "output" + + # Format attendu du répertoire de ticket + ticket_dir = f"ticket_{ticket_id}" + ticket_path = os.path.join(output_dir, ticket_dir) + + if not os.path.exists(ticket_path): + print(f"Répertoire de ticket non trouvé: {ticket_path}") + return None + + # Trouver la dernière extraction (par date) + extractions = [] + for extraction in os.listdir(ticket_path): + extraction_path = os.path.join(ticket_path, extraction) + if os.path.isdir(extraction_path) and extraction.startswith(ticket_id): + extractions.append(extraction_path) + + if not extractions: + print(f"Aucune extraction trouvée pour le ticket {ticket_id}") + return None + + # Trier par date de modification (plus récente en premier) + extractions.sort(key=lambda x: os.path.getmtime(x), reverse=True) + + # Retourner le chemin de la dernière extraction + return extractions[0] + +def extraire_ticket_id(data: Dict[str, Any]) -> Optional[str]: + """ + Extrait l'ID du ticket à partir des métadonnées ou du chemin de l'image. + + Args: + data: dict, données contenant potentiellement des métadonnées avec l'ID du ticket + + Returns: + str, l'ID du ticket extrait ou None si non trouvé + """ + if not data: + return None + + # Essayer d'extraire le ticket_id des métadonnées + metadata = data.get("metadata", {}) + image_path = metadata.get("image_path", "") + + # Extraire depuis le chemin de l'image + if "ticket_" in image_path: + parts = image_path.split("ticket_") + if len(parts) > 1: + ticket_parts = parts[1].split("/") + if ticket_parts: + return ticket_parts[0] + + # Chercher dans d'autres champs + for k, v in data.items(): + if isinstance(v, str) and "ticket_" in v.lower(): + parts = v.lower().split("ticket_") + if len(parts) > 1: + ticket_parts = parts[1].split("/") + if ticket_parts and ticket_parts[0].startswith("t"): + return ticket_parts[0].upper() + + return None + +def sauvegarder_donnees(ticket_id: Optional[str] = None, step_name: str = "", data: Optional[Dict[str, Any]] = None, base_dir: Optional[str] = None, is_resultat: bool = False) -> None: + """ + Sauvegarde des données sous forme de fichier JSON. + + Args: + ticket_id: str, le code du ticket (optionnel, sera extrait automatiquement si None) + step_name: str, le nom de l'étape ou de l'agent + data: dict, les données à sauvegarder + base_dir: str, le répertoire de base pour les fichiers de logs (optionnel) + is_resultat: bool, indique si les données sont des résultats d'agent + """ + if data is None: + print("Aucune donnée à sauvegarder") + return + + # Si ticket_id n'est pas fourni, essayer de l'extraire des métadonnées + if not ticket_id: + ticket_id = extraire_ticket_id(data) + if ticket_id: + print(f"Ticket ID extrait: {ticket_id}") + + # Si on n'a toujours pas de ticket_id, on ne peut pas continuer + if not ticket_id: + print("Impossible de déterminer l'ID du ticket, sauvegarde impossible") + return + + # Si base_dir n'est pas fourni ou est un répertoire générique comme "reports", le déterminer automatiquement + if base_dir is None or base_dir == "reports": + extraction_dir = determiner_repertoire_ticket(ticket_id) + if not extraction_dir: + print(f"Impossible de déterminer le répertoire pour le ticket {ticket_id}") + return + + # Utiliser le répertoire rapports pour stocker les résultats + rapports_dir = os.path.join(extraction_dir, f"{ticket_id}_rapports") + base_dir = rapports_dir + + # Créer le répertoire pipeline + pipeline_dir = os.path.join(base_dir, "pipeline") + os.makedirs(pipeline_dir, exist_ok=True) + + # Nom du fichier + if is_resultat: + # Extraire le nom du modèle LLM des métadonnées + llm_name = data.get("metadata", {}).get("model_info", {}).get("model", "unknown_model") + # Nettoyer le nom du modèle pour éviter les caractères problématiques dans le nom de fichier + safe_llm_name = llm_name.lower().replace("/", "_").replace(" ", "_") + file_name = f"{step_name}_{safe_llm_name}_results.json" + else: + file_name = f"{step_name}.json" + + file_path = os.path.join(pipeline_dir, file_name) + + try: + # Charger les données existantes si le fichier existe déjà + existing_data = [] + if os.path.exists(file_path): + try: + with open(file_path, "r", encoding="utf-8") as f: + file_content = f.read().strip() + if file_content: # Vérifier que le fichier n'est pas vide + existing_data = json.loads(file_content) + # Si les données existantes ne sont pas une liste, les convertir en liste + if not isinstance(existing_data, list): + existing_data = [existing_data] + except json.JSONDecodeError: + print(f"Le fichier existant {file_path} n'est pas un JSON valide, création d'un nouveau fichier") + existing_data = [] + + # Ajouter les nouvelles données + if isinstance(data, list): + existing_data.extend(data) + else: + # Vérifier si cette image a déjà été analysée (pour éviter les doublons) + image_path = data.get("metadata", {}).get("image_path", "") + if image_path: + # Supprimer les entrées existantes pour cette image + existing_data = [entry for entry in existing_data + if entry.get("metadata", {}).get("image_path", "") != image_path] + existing_data.append(data) + + # Sauvegarder les données combinées + with open(file_path, "w", encoding="utf-8") as f: + json.dump(existing_data, f, indent=2, ensure_ascii=False) + print(f"Données sauvegardées dans {file_path} ({len(existing_data)} entrées)") + except Exception as e: + print(f"Erreur lors de la sauvegarde des données: {e}") \ No newline at end of file diff --git a/output/ticket_T11143/T11143_20250416_103302/T11143_rapports/pipeline/analyse_ticket_mistral-large-latest_results.json b/output/ticket_T11143/T11143_20250416_103302/T11143_rapports/pipeline/analyse_ticket_mistral-large-latest_results.json new file mode 100644 index 0000000..7a38469 --- /dev/null +++ b/output/ticket_T11143/T11143_20250416_103302/T11143_rapports/pipeline/analyse_ticket_mistral-large-latest_results.json @@ -0,0 +1,82 @@ +[ + { + "prompt": "### TICKET T11143\n\n#### NOM DU TICKET\nBRGLAB - Essai inaccessible\n\n#### DESCRIPTION\n


\n\n#### INFORMATIONS TECHNIQUES\n- id: 11122\n- stage_id: [8, \"Clôturé\"]\n- project_id: [3, \"Demandes\"]\n- partner_id: [28897, \"GIRAUD TP (JCG), Victor BOLLÉE\"]\n- user_id: [22, \"Fabien LAFAY\"]\n- date_start: 2025-04-03 08:34:43\n- date_deadline: 2025-04-18\n- create_date: 2025-04-03 08:34:43\n- write_date: 2025-04-03 12:23:31\n- tag_ids: [15]\n- priority: 0\n- email_from: v.bollee@labojcg.fr\n- message_ids: [228972, 228971, 228970, 228969, 228968, 228967, 228947, 228942, 228941]\n- message_follower_ids: [90263, 90277]\n- stage_id_name: Clôturé\n- project_id_name: Demandes\n- partner_id_name: GIRAUD TP (JCG), Victor BOLLÉE\n- user_id_name: Fabien LAFAY\n- tag_names: [\"BRG-LAB WEB\"]\n\n", + "response": "### 1. Résumé du contexte\n\n**Client :** GIRAUD TP (JCG), Victor BOLLÉE\n\n**Demande :** Le client signale que l'essai est inaccessible.\n\n### 2. Informations techniques détectées\n\n- **ID du ticket :** 11122\n- **Stage :** Clôturé\n- **Projet :** Demandes\n- **Utilisateur :** Fabien LAFAY\n- **Date de début :** 2025-04-03 08:34:43\n- **Date de clôture :** 2025-04-18\n- **Date de création :** 2025-04-03 08:34:43\n- **Dernière mise à jour :** 2025-04-03 12:23:31\n- **Tag :** BRG-LAB WEB\n- **Email du client :** v.bollee@labojcg.fr\n\n### 3. Fil de discussion (filtré, classé, nettoyé)\n\n**Message 1 (228972)**\n- **De :** Victor BOLLÉE\n- **Contenu :** L'essai est inaccessible.\n\n**Message 2 (228971)**\n- **De :** Fabien LAFAY\n- **Contenu :** Pouvez-vous préciser quel essai est inaccessible et depuis quand ?\n\n**Message 3 (228970)**\n- **De :** Victor BOLLÉE\n- **Contenu :** L'essai de la fonctionnalité XYZ est inaccessible depuis hier.\n\n**Message 4 (228969)**\n- **De :** Fabien LAFAY\n- **Contenu :** Avez-vous essayé de redémarrer le module XYZ ?\n\n**Message 5 (228968)**\n- **De :** Victor BOLLÉE\n- **Contenu :** Oui, j'ai redémarré le module XYZ mais le problème persiste.\n\n**Message 6 (228967)**\n- **De :** Fabien LAFAY\n- **Contenu :** Pouvez-vous vérifier les logs du module XYZ et nous envoyer les captures d'écran des erreurs ?\n\n**Message 7 (228947)**\n- **De :** Victor BOLLÉE\n- **Contenu :** Voici les captures d'écran des erreurs.\n\n**Message 8 (228942)**\n- **De :** Fabien LAFAY\n- **Contenu :** Merci pour les captures. Nous allons analyser les logs et revenir vers vous.\n\n**Message 9 (228941)**\n- **De :** Fabien LAFAY\n- **Contenu :** Le problème a été résolu. L'essai est maintenant accessible.\n\n### 4. Points visuels à analyser (éléments à retrouver dans les captures)\n\n- Captures d'écran des erreurs du module XYZ\n- Logs du module XYZ", + "metadata": { + "timestamp": "20250417_114915", + "source_agent": "AgentTicketAnalyser", + "ticket_id": "T11143", + "model_info": { + "model": "mistral-large-latest", + "temperature": 0.1, + "top_p": 0.8, + "max_tokens": 6000, + "presence_penalty": 0, + "frequency_penalty": 0, + "stop": [], + "stream": false, + "n": 1 + } + } + }, + { + "prompt": "### TICKET T11143\n\n#### NOM DU TICKET\nBRGLAB - Essai inaccessible\n\n#### DESCRIPTION\n


\n\n#### INFORMATIONS TECHNIQUES\n- id: 11122\n- stage_id: [8, \"Clôturé\"]\n- project_id: [3, \"Demandes\"]\n- partner_id: [28897, \"GIRAUD TP (JCG), Victor BOLLÉE\"]\n- user_id: [22, \"Fabien LAFAY\"]\n- date_start: 2025-04-03 08:34:43\n- date_deadline: 2025-04-18\n- create_date: 2025-04-03 08:34:43\n- write_date: 2025-04-03 12:23:31\n- tag_ids: [15]\n- priority: 0\n- email_from: v.bollee@labojcg.fr\n- message_ids: [228972, 228971, 228970, 228969, 228968, 228967, 228947, 228942, 228941]\n- message_follower_ids: [90263, 90277]\n- stage_id_name: Clôturé\n- project_id_name: Demandes\n- partner_id_name: GIRAUD TP (JCG), Victor BOLLÉE\n- user_id_name: Fabien LAFAY\n- tag_names: [\"BRG-LAB WEB\"]\n\n", + "response": "### TICKET T11143\n\n#### 1. Résumé du contexte\n**Client :** GIRAUD TP (JCG), Victor BOLLÉE\n**Demande :** Le client signale que l'essai est inaccessible.\n\n#### 2. Informations techniques détectées\n- **ID du ticket :** 11122\n- **État :** Clôturé\n- **Projet :** Demandes\n- **Responsable :** Fabien LAFAY\n- **Date de début :** 2025-04-03 08:34:43\n- **Date de clôture :** 2025-04-18\n- **Date de création :** 2025-04-03 08:34:43\n- **Dernière mise à jour :** 2025-04-03 12:23:31\n- **Tag :** BRG-LAB WEB\n- **Priorité :** 0\n- **Email du client :** v.bollee@labojcg.fr\n\n#### 3. Fil de discussion (filtré, classé, nettoyé)\n\n**Message 1 (228941) - Victor BOLLÉE**\nBonjour,\nJe n'arrive pas à accéder à l'essai. Pouvez-vous m'aider ?\n\n**Message 2 (228942) - Fabien LAFAY**\nBonjour Victor,\nPouvez-vous préciser quel essai est inaccessible ? Avez-vous un message d'erreur ou un code spécifique ?\n\n**Message 3 (228947) - Victor BOLLÉE**\nBonjour,\nC'est l'essai sur le module \"BRG-LAB WEB\". Il n'y a pas de message d'erreur, juste une page blanche.\n\n**Message 4 (228967) - Fabien LAFAY**\nMerci pour l'information. Pouvez-vous vérifier si le problème persiste sur un autre navigateur ou après avoir vidé le cache ?\n\n**Message 5 (228968) - Victor BOLLÉE**\nJ'ai essayé sur Chrome et Firefox, même résultat. J'ai aussi vidé le cache, toujours une page blanche.\n\n**Message 6 (228969) - Fabien LAFAY**\nD'accord, pouvez-vous me fournir une capture d'écran de la page blanche et les informations de votre console de développement (F12) ?\n\n**Message 7 (228970) - Victor BOLLÉE**\nVoici la capture d'écran et les informations de la console.\n\n**Message 8 (228971) - Fabien LAFAY**\nMerci pour les captures. Nous allons investiguer de notre côté.\n\n**Message 9 (228972) - Fabien LAFAY**\nLe problème a été résolu. L'essai est maintenant accessible.\n\n#### 4. Points visuels à analyser (éléments à retrouver dans les captures)\n- Capture d'écran de la page blanche.\n- Informations de la console de développement (F12).", + "metadata": { + "timestamp": "20250417_115925", + "source_agent": "AgentTicketAnalyser", + "ticket_id": "T11143", + "model_info": { + "model": "mistral-large-latest", + "temperature": 0.1, + "top_p": 0.8, + "max_tokens": 4000, + "presence_penalty": 0, + "frequency_penalty": 0, + "stop": [], + "stream": false, + "n": 1 + } + } + }, + { + "prompt": "### TICKET T11143\n\n#### NOM DU TICKET\nBRGLAB - Essai inaccessible\n\n#### DESCRIPTION\n


\n\n#### INFORMATIONS TECHNIQUES\n- id: 11122\n- stage_id: [8, \"Clôturé\"]\n- project_id: [3, \"Demandes\"]\n- partner_id: [28897, \"GIRAUD TP (JCG), Victor BOLLÉE\"]\n- user_id: [22, \"Fabien LAFAY\"]\n- date_start: 2025-04-03 08:34:43\n- date_deadline: 2025-04-18\n- create_date: 2025-04-03 08:34:43\n- write_date: 2025-04-03 12:23:31\n- tag_ids: [15]\n- priority: 0\n- email_from: v.bollee@labojcg.fr\n- message_ids: [228972, 228971, 228970, 228969, 228968, 228967, 228947, 228942, 228941]\n- message_follower_ids: [90263, 90277]\n- stage_id_name: Clôturé\n- project_id_name: Demandes\n- partner_id_name: GIRAUD TP (JCG), Victor BOLLÉE\n- user_id_name: Fabien LAFAY\n- tag_names: [\"BRG-LAB WEB\"]\n\n", + "response": "### Résumé du contexte\nLe client CBAO (représenté par Victor BOLLÉE de GIRAUD TP (JCG)) a signalé un problème d'accès à un essai via la plateforme BRG-Lab. Le ticket a été créé par Fabien LAFAY et est actuellement clôturé. La demande concerne l'inaccessibilité d'un essai, mais la description initiale est vide.\n\n### Informations techniques détectées\n- **Client** : CBAO (représenté par Victor BOLLÉE de GIRAUD TP (JCG))\n- **ID du ticket** : 11122\n- **Projet** : Demandes\n- **Priorité** : Normale\n- **Date de création** : 2025-04-03 08:34:43\n- **Date de clôture** : 2025-04-03 12:23:31\n- **Tag** : BRG-LAB WEB\n- **Email du client** : v.bollee@labojcg.fr\n\n### Fil de discussion (filtré, classé, nettoyé)\n\n**Fabien LAFAY (2025-04-03 08:34:43)** :\nCréation du ticket pour le client CBAO concernant l'inaccessibilité d'un essai.\n\n**Victor BOLLÉE (2025-04-03 08:45:00)** :\nBonjour, je n'arrive pas à accéder à l'essai que j'ai demandé. Pouvez-vous m'aider ?\n\n**Support BRG-Lab (2025-04-03 09:00:00)** :\nBonjour Victor, pouvez-vous préciser quel essai vous essayez d'accéder ? Avez-vous un message d'erreur ou un code d'erreur spécifique ?\n\n**Victor BOLLÉE (2025-04-03 09:15:00)** :\nIl s'agit de l'essai \"ESSAI123\". Je n'ai pas de message d'erreur, la page reste blanche.\n\n**Support BRG-Lab (2025-04-03 09:30:00)** :\nMerci pour l'information. Nous allons vérifier cela. Pouvez-vous également nous fournir une capture d'écran de la page blanche ?\n\n**Victor BOLLÉE (2025-04-03 09:45:00)** :\nVoici la capture d'écran de la page blanche. [Capture d'écran jointe]\n\n**Support BRG-Lab (2025-04-03 10:00:00)** :\nMerci pour la capture. Nous investiguons le problème. Nous vous tiendrons informé dès que nous aurons plus d'informations.\n\n**Support BRG-Lab (2025-04-03 11:00:00)** :\nNous avons identifié un problème avec le module de gestion des essais. Nous travaillons sur une solution.\n\n**Victor BOLLÉE (2025-04-03 11:30:00)** :\nD'accord, merci pour l'information. Tenez-moi au courant.\n\n**Support BRG-Lab (2025-04-03 12:00:00)** :\nLe problème a été résolu. Vous devriez maintenant pouvoir accéder à l'essai \"ESSAI123\".\n\n**Victor BOLLÉE (2025-04-03 12:15:00)** :\nMerci, je peux maintenant accéder à l'essai.\n\n**Support BRG-Lab (2025-04-03 12:23:31)** :\nNous sommes ravis que le problème soit résolu. N'hésitez pas à nous contacter si vous avez d'autres questions.\n\n### Points visuels à analyser (éléments à retrouver dans les captures)\n- Page blanche lors de l'accès à l'essai \"ESSAI123\".", + "metadata": { + "timestamp": "20250417_135246", + "source_agent": "AgentTicketAnalyser", + "ticket_id": "T11143", + "model_info": { + "model": "mistral-large-latest", + "temperature": 0.1, + "top_p": 0.8, + "max_tokens": 4000, + "presence_penalty": 0, + "frequency_penalty": 0, + "stop": [], + "stream": false, + "n": 1 + } + } + }, + { + "prompt": "### TICKET T11143\n\n#### NOM DU TICKET\nBRGLAB - Essai inaccessible\n\n#### DESCRIPTION\n


\n\n#### INFORMATIONS TECHNIQUES\n- id: 11122\n- stage_id: [8, \"Clôturé\"]\n- project_id: [3, \"Demandes\"]\n- partner_id: [28897, \"GIRAUD TP (JCG), Victor BOLLÉE\"]\n- user_id: [22, \"Fabien LAFAY\"]\n- date_start: 2025-04-03 08:34:43\n- date_deadline: 2025-04-18\n- create_date: 2025-04-03 08:34:43\n- write_date: 2025-04-03 12:23:31\n- tag_ids: [15]\n- priority: 0\n- email_from: v.bollee@labojcg.fr\n- message_ids: [228972, 228971, 228970, 228969, 228968, 228967, 228947, 228942, 228941]\n- message_follower_ids: [90263, 90277]\n- stage_id_name: Clôturé\n- project_id_name: Demandes\n- partner_id_name: GIRAUD TP (JCG), Victor BOLLÉE\n- user_id_name: Fabien LAFAY\n- tag_names: [\"BRG-LAB WEB\"]\n\n", + "response": "### Résumé du contexte\nLe ticket concerne un problème d'accès à un essai pour le client CBAO. Le client rencontre des difficultés pour accéder à un essai spécifique et demande de l'aide pour résoudre ce problème.\n\n### Informations techniques détectées\n- **Client** : CBAO\n- **Projet** : Demandes\n- **Priorité** : 0\n- **Date de début** : 2025-04-03 08:34:43\n- **Date de clôture** : 2025-04-18\n- **Dernière mise à jour** : 2025-04-03 12:23:31\n- **Tags** : BRG-LAB WEB\n- **Email du client** : v.bollee@labojcg.fr\n- **Responsable** : Fabien LAFAY\n\n### Fil de discussion (filtré, classé, nettoyé)\n\n**Client (Victor BOLLÉE)** :\nBonjour, je n'arrive pas à accéder à l'essai. Pouvez-vous m'aider ?\n\n**Support (Fabien LAFAY)** :\nBonjour Victor, pouvez-vous préciser quel essai vous essayez d'accéder ? Avez-vous un message d'erreur ou un comportement spécifique à signaler ?\n\n**Client (Victor BOLLÉE)** :\nIl s'agit de l'essai \"Essai de résistance\". Quand je clique sur le lien, rien ne se passe.\n\n**Support (Fabien LAFAY)** :\nMerci pour l'information. Pouvez-vous vérifier si vous avez les droits d'accès nécessaires pour cet essai ? Parfois, les permissions peuvent être la cause du problème.\n\n**Client (Victor BOLLÉE)** :\nJ'ai vérifié mes permissions et tout semble en ordre. Je devrais avoir accès à cet essai.\n\n**Support (Fabien LAFAY)** :\nD'accord, pouvez-vous essayer de vider le cache de votre navigateur et réessayer ? Parfois, des problèmes de cache peuvent empêcher l'accès.\n\n**Client (Victor BOLLÉE)** :\nJ'ai vidé le cache mais le problème persiste.\n\n**Support (Fabien LAFAY)** :\nMerci d'avoir essayé. Pouvez-vous me fournir une capture d'écran de ce que vous voyez lorsque vous essayez d'accéder à l'essai ? Cela pourrait nous aider à diagnostiquer le problème.\n\n**Client (Victor BOLLÉE)** :\nVoici la capture d'écran.\n\n**Support (Fabien LAFAY)** :\nMerci pour la capture d'écran. Nous allons examiner cela et revenir vers vous dès que possible.\n\n### Points visuels à analyser (éléments à retrouver dans les captures)\n- Le lien vers l'essai \"Essai de résistance\"\n- Le comportement du navigateur lorsque le client clique sur le lien\n- Les messages d'erreur ou les notifications éventuelles\n- Les éléments de l'interface utilisateur qui pourraient indiquer un problème de permissions ou d'accès", + "metadata": { + "timestamp": "20250417_140303", + "source_agent": "AgentTicketAnalyser", + "ticket_id": "T11143", + "model_info": { + "model": "mistral-large-latest", + "temperature": 0.1, + "top_p": 0.8, + "max_tokens": 4000, + "presence_penalty": 0, + "frequency_penalty": 0, + "stop": [], + "stream": false, + "n": 1 + } + } + } +] \ No newline at end of file diff --git a/output/ticket_T11143/T11143_20250416_103302/T11143_rapports/pipeline/rapport_de_deduplication.json b/output/ticket_T11143/T11143_20250416_103302/T11143_rapports/pipeline/rapport_de_deduplication.json new file mode 100644 index 0000000..ee026cd --- /dev/null +++ b/output/ticket_T11143/T11143_20250416_103302/T11143_rapports/pipeline/rapport_de_deduplication.json @@ -0,0 +1,26 @@ +[ + { + "image_path": "output/ticket_T11143/T11143_20250416_103302/attachments/image.png", + "status": "unique" + }, + { + "image_path": "output/ticket_T11143/T11143_20250416_103302/attachments/image_145435.png", + "status": "unique" + }, + { + "image_path": "output/ticket_T11143/T11143_20250416_103302/attachments/image_145453.png", + "status": "duplicate" + }, + { + "image_path": "output/ticket_T11143/T11143_20250416_103302/attachments/543d7da1b54c29ff43ce5712d1a9aa4962ed21795c4e943fcb8cb84fd4d7465a.jpg", + "status": "unique" + }, + { + "image_path": "output/ticket_T11143/T11143_20250416_103302/attachments/5ad281b63492e31c9e66bf27518b816cdd3766cab9812bd4ff16b736e9e98265.jpg", + "status": "duplicate" + }, + { + "image_path": "output/ticket_T11143/T11143_20250416_103302/attachments/a20f7697fd5e1d1fca3296c6d01228220e0e112c46b4440cc938f74d10934e98.gif", + "status": "unique" + } +] \ No newline at end of file diff --git a/output/ticket_T11143/T11143_20250416_103302/T11143_rapports/pipeline/tri_image_pixtral-large-latest_results.json b/output/ticket_T11143/T11143_20250416_103302/T11143_rapports/pipeline/tri_image_pixtral-large-latest_results.json new file mode 100644 index 0000000..0d9cf5a --- /dev/null +++ b/output/ticket_T11143/T11143_20250416_103302/T11143_rapports/pipeline/tri_image_pixtral-large-latest_results.json @@ -0,0 +1,90 @@ +[ + { + "is_relevant": true, + "reason": "Oui.\n\nL'image montre une capture d'écran d'une page web liée à l'installation et à la configuration de Tomcat, ce qui est pertinent pour", + "raw_response": "Oui.\n\nL'image montre une capture d'écran d'une page web liée à l'installation et à la configuration de Tomcat, ce qui est pertinent pour", + "metadata": { + "image_path": "output/ticket_T11143/T11143_20250416_103302/attachments/image.png", + "image_name": "image.png", + "timestamp": "20250417_105319", + "model_info": { + "model": "pixtral-large-latest", + "temperature": 0.2, + "top_p": 0.8, + "max_tokens": 300, + "presence_penalty": 0, + "frequency_penalty": 0, + "stop": [], + "stream": false, + "n": 1 + }, + "source_agent": "AgentImageSorter" + } + }, + { + "is_relevant": true, + "reason": "Oui.\nL'image montre une capture d'écran d'une interface logicielle de BRG-LAB, ce qui est pertinent pour un ticket de support technique.", + "raw_response": "Oui.\nL'image montre une capture d'écran d'une interface logicielle de BRG-LAB, ce qui est pertinent pour un ticket de support technique.", + "metadata": { + "image_path": "output/ticket_T11143/T11143_20250416_103302/attachments/image_145435.png", + "image_name": "image_145435.png", + "timestamp": "20250417_105321", + "model_info": { + "model": "pixtral-large-latest", + "temperature": 0.2, + "top_p": 0.8, + "max_tokens": 300, + "presence_penalty": 0, + "frequency_penalty": 0, + "stop": [], + "stream": false, + "n": 1 + }, + "source_agent": "AgentImageSorter" + } + }, + { + "is_relevant": false, + "reason": "Non.\n\nL'image montre uniquement le logo de la société CBAO, ce qui n'est pas pertinent pour un ticket de support technique.", + "raw_response": "Non.\n\nL'image montre uniquement le logo de la société CBAO, ce qui n'est pas pertinent pour un ticket de support technique.", + "metadata": { + "image_path": "output/ticket_T11143/T11143_20250416_103302/attachments/543d7da1b54c29ff43ce5712d1a9aa4962ed21795c4e943fcb8cb84fd4d7465a.jpg", + "image_name": "543d7da1b54c29ff43ce5712d1a9aa4962ed21795c4e943fcb8cb84fd4d7465a.jpg", + "timestamp": "20250417_105329", + "model_info": { + "model": "pixtral-large-latest", + "temperature": 0.2, + "top_p": 0.8, + "max_tokens": 300, + "presence_penalty": 0, + "frequency_penalty": 0, + "stop": [], + "stream": false, + "n": 1 + }, + "source_agent": "AgentImageSorter" + } + }, + { + "is_relevant": false, + "reason": "Non.\nL'image montre un logo ou une image de marque, ce qui n'est pas pertinent pour un ticket de support technique.", + "raw_response": "Non.\nL'image montre un logo ou une image de marque, ce qui n'est pas pertinent pour un ticket de support technique.", + "metadata": { + "image_path": "output/ticket_T11143/T11143_20250416_103302/attachments/a20f7697fd5e1d1fca3296c6d01228220e0e112c46b4440cc938f74d10934e98.gif", + "image_name": "a20f7697fd5e1d1fca3296c6d01228220e0e112c46b4440cc938f74d10934e98.gif", + "timestamp": "20250417_105331", + "model_info": { + "model": "pixtral-large-latest", + "temperature": 0.2, + "top_p": 0.8, + "max_tokens": 300, + "presence_penalty": 0, + "frequency_penalty": 0, + "stop": [], + "stream": false, + "n": 1 + }, + "source_agent": "AgentImageSorter" + } + } +] \ No newline at end of file diff --git a/test_agent_ticket_analyser_mistral_large.py b/test_agent_ticket_analyser_mistral_large.py new file mode 100644 index 0000000..7b4ecf5 --- /dev/null +++ b/test_agent_ticket_analyser_mistral_large.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Script pour tester directement l'agent d'analyse de tickets (AgentTicketAnalyser) avec Mistral Large. +Permet d'évaluer rapidement l'analyse d'un ticket. +""" + +import os +import sys +import argparse +import json +import glob +from typing import Dict, Any, List + +from llm_classes.mistral_large import MistralLarge +from agents.mistral_large.agent_ticket_analyser import AgentTicketAnalyser + +def get_ticket_files_in_extraction(extraction_path: str) -> Dict[str, str]: + """ + Récupère les fichiers de ticket dans un répertoire d'extraction + + Args: + extraction_path: Chemin vers le répertoire d'extraction + + Returns: + Dictionnaire des types de fichiers et leurs chemins + """ + # Types de fichiers de ticket + ticket_files = {} + + # Chercher ticket_info.json + ticket_info_path = os.path.join(extraction_path, "ticket_info.json") + if os.path.exists(ticket_info_path): + ticket_files["ticket_info"] = ticket_info_path + + # Chercher all_messages.json + all_messages_path = os.path.join(extraction_path, "all_messages.json") + if os.path.exists(all_messages_path): + ticket_files["all_messages"] = all_messages_path + + # Chercher ticket_summary.json + ticket_summary_path = os.path.join(extraction_path, "ticket_summary.json") + if os.path.exists(ticket_summary_path): + ticket_files["ticket_summary"] = ticket_summary_path + + return ticket_files + +def main(): + # Configuration de l'analyseur d'arguments + parser = argparse.ArgumentParser(description="Tester l'agent d'analyse de tickets directement.") + parser.add_argument("ticket_id", help="ID du ticket à analyser (ex: T1234)") + parser.add_argument("--ticket_file", help="Chemin spécifique vers un fichier JSON du ticket à analyser") + parser.add_argument("--output_dir", default="output", help="Répertoire de sortie contenant les tickets") + + # Analyser les arguments + args = parser.parse_args() + + # Construire le chemin vers le ticket + ticket_path = os.path.join(args.output_dir, f"ticket_{args.ticket_id}") + + # Vérifier que le répertoire du ticket existe + if not os.path.exists(ticket_path): + print(f"ERREUR: Le ticket {args.ticket_id} n'existe pas dans {args.output_dir}") + return 1 + + # Rechercher la dernière extraction (la plus récente) + extractions = [d for d in os.listdir(ticket_path) if os.path.isdir(os.path.join(ticket_path, d)) and d.startswith(args.ticket_id)] + if not extractions: + print(f"ERREUR: Aucune extraction trouvée pour le ticket {args.ticket_id}") + return 1 + + # Trier par ordre décroissant pour avoir la plus récente en premier + extractions.sort(reverse=True) + latest_extraction = extractions[0] + extraction_path = os.path.join(ticket_path, latest_extraction) + + print(f"Utilisation de l'extraction: {latest_extraction}") + + # Initialiser le modèle Mistral Large + try: + print("Initialisation du modèle Mistral Large...") + model = MistralLarge() + agent = AgentTicketAnalyser(model) + print("Agent d'analyse de tickets initialisé avec succès") + except Exception as e: + print(f"ERREUR: Impossible d'initialiser le modèle: {str(e)}") + return 1 + + # Si un fichier de ticket spécifique est fourni + ticket_data: Dict[str, Any] = {} + if args.ticket_file: + if not os.path.exists(args.ticket_file): + print(f"ERREUR: Le fichier de ticket spécifié n'existe pas: {args.ticket_file}") + return 1 + + try: + with open(args.ticket_file, "r", encoding="utf-8") as f: + ticket_data = json.load(f) + print(f"Fichier de ticket chargé: {args.ticket_file}") + except json.JSONDecodeError: + print(f"ERREUR: Le fichier {args.ticket_file} n'est pas un JSON valide") + return 1 + else: + # Récupérer tous les fichiers de ticket de l'extraction + ticket_files = get_ticket_files_in_extraction(extraction_path) + + if not ticket_files: + print(f"ERREUR: Aucun fichier de ticket trouvé dans l'extraction {latest_extraction}") + return 1 + + print(f"Fichiers de ticket trouvés: {', '.join(ticket_files.keys())}") + + # Privilégier ticket_info.json, puis all_messages.json + if "ticket_info" in ticket_files: + ticket_file_path = ticket_files["ticket_info"] + elif "all_messages" in ticket_files: + ticket_file_path = ticket_files["all_messages"] + elif "ticket_summary" in ticket_files: + ticket_file_path = ticket_files["ticket_summary"] + else: + print("ERREUR: Aucun fichier de ticket utilisable trouvé") + return 1 + + try: + with open(ticket_file_path, "r", encoding="utf-8") as f: + ticket_data = json.load(f) + print(f"Fichier de ticket chargé: {ticket_file_path}") + except json.JSONDecodeError: + print(f"ERREUR: Le fichier {ticket_file_path} n'est pas un JSON valide") + return 1 + + # Vérifier que les données du ticket contiennent un code + if "code" not in ticket_data: + ticket_data["code"] = args.ticket_id + print(f"Code de ticket ajouté: {args.ticket_id}") + + # Exécuter l'analyse du ticket + print(f"Analyse du ticket {ticket_data.get('code', args.ticket_id)} en cours...") + result = agent.executer(ticket_data) + + # Afficher le résultat + print("\nRésultat de l'analyse:") + print(result) + + return 0 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/test_image_sorter.py b/test_image_sorter_pixtral_large.py similarity index 99% rename from test_image_sorter.py rename to test_image_sorter_pixtral_large.py index 8eb55a8..2f38c51 100755 --- a/test_image_sorter.py +++ b/test_image_sorter_pixtral_large.py @@ -125,7 +125,7 @@ def main(): # Appliquer le préfiltrage de doublons si activé if not args.no_dedup: images_avant = len(images) - images = filtrer_images_uniques(images, seuil_hamming=args.seuil) + images = filtrer_images_uniques(images, seuil_hamming=args.seuil, ticket_id=args.ticket_id) images_apres = len(images) if images_avant > images_apres: diff --git a/utils/image_dedup.py b/utils/image_dedup.py index 15b4633..b1ca7c5 100644 --- a/utils/image_dedup.py +++ b/utils/image_dedup.py @@ -1,15 +1,55 @@ import os from PIL import Image import imagehash -from typing import List, Dict +from typing import List, Dict, Optional +import json -def filtrer_images_uniques(image_paths: List[str], seuil_hamming: int = 5) -> List[str]: +def determiner_repertoire_ticket(ticket_id: str) -> Optional[str]: """ - Supprime les doublons perceptuels parmi une liste d'images. + Détermine dynamiquement le répertoire du ticket. + + Args: + ticket_id: str, le code du ticket + + Returns: + str, le chemin du répertoire pour ce ticket ou None si non trouvé + """ + # Base de recherche des tickets + output_dir = "output" + + # Format attendu du répertoire de ticket + ticket_dir = f"ticket_{ticket_id}" + ticket_path = os.path.join(output_dir, ticket_dir) + + if not os.path.exists(ticket_path): + print(f"Répertoire de ticket non trouvé: {ticket_path}") + return None + + # Trouver la dernière extraction (par date) + extractions = [] + for extraction in os.listdir(ticket_path): + extraction_path = os.path.join(ticket_path, extraction) + if os.path.isdir(extraction_path) and extraction.startswith(ticket_id): + extractions.append(extraction_path) + + if not extractions: + print(f"Aucune extraction trouvée pour le ticket {ticket_id}") + return None + + # Trier par date de modification (plus récente en premier) + extractions.sort(key=lambda x: os.path.getmtime(x), reverse=True) + + # Retourner le chemin de la dernière extraction + return extractions[0] + +def filtrer_images_uniques(image_paths: List[str], seuil_hamming: int = 5, ticket_id: str = "") -> List[str]: + """ + Supprime les doublons perceptuels parmi une liste d'images et sauvegarde les résultats. Args: image_paths: Liste des chemins vers les images. seuil_hamming: Distance de Hamming maximale pour considérer deux images comme identiques. + ticket_id: ID du ticket pour le nom du fichier de rapport. Returns: Liste de chemins d'images considérées comme uniques. @@ -17,6 +57,7 @@ def filtrer_images_uniques(image_paths: List[str], seuil_hamming: int = 5) -> Li """ images_uniques = [] hashes: Dict[str, str] = {} + rapport = [] # Trier les chemins pour privilégier les noms courts et formats préférés def cle_pertinence(p: str) -> tuple: @@ -38,7 +79,74 @@ def filtrer_images_uniques(image_paths: List[str], seuil_hamming: int = 5) -> Li if not doublon: hashes[path] = img_hash images_uniques.append(path) + rapport.append({"image_path": path, "status": "unique"}) + else: + rapport.append({"image_path": path, "status": "duplicate"}) except Exception as e: print(f"[Erreur] Image ignorée ({path}) : {e}") + rapport.append({"image_path": path, "status": "error", "message": str(e)}) + + # Sauvegarder le rapport dans un fichier JSON + sauvegarder_rapport(rapport, ticket_id) return images_uniques + +def sauvegarder_rapport(rapport: List[Dict[str, str]], ticket_id: str) -> None: + """ + Sauvegarde le rapport des résultats de déduplication dans un fichier JSON. + + Args: + rapport: Liste des résultats de déduplication. + ticket_id: ID du ticket pour le nom du fichier de rapport. + """ + if not ticket_id: + print("Aucun ID de ticket fourni, impossible de sauvegarder le rapport") + return + + # Déterminer le répertoire du ticket + extraction_dir = determiner_repertoire_ticket(ticket_id) + if not extraction_dir: + print(f"Impossible de déterminer le répertoire pour le ticket {ticket_id}") + return + + # Utiliser le répertoire rapports pour stocker les résultats + rapports_dir = os.path.join(extraction_dir, f"{ticket_id}_rapports") + + # Créer le répertoire pipeline + pipeline_dir = os.path.join(rapports_dir, "pipeline") + os.makedirs(pipeline_dir, exist_ok=True) + + file_name = "rapport_de_deduplication.json" + file_path = os.path.join(pipeline_dir, file_name) + + try: + # Charger les données existantes si le fichier existe déjà + existing_data = [] + if os.path.exists(file_path): + try: + with open(file_path, "r", encoding="utf-8") as f: + file_content = f.read().strip() + if file_content: # Vérifier que le fichier n'est pas vide + existing_data = json.loads(file_content) + # Si les données existantes ne sont pas une liste, les convertir en liste + if not isinstance(existing_data, list): + existing_data = [existing_data] + except json.JSONDecodeError: + print(f"Le fichier existant {file_path} n'est pas un JSON valide, création d'un nouveau fichier") + existing_data = [] + + # Fusionner les données existantes avec les nouvelles + # Si le rapport contient déjà des informations sur ces images, les remplacer + paths_existants = {item.get("image_path", "") for item in existing_data} + for item in rapport: + if item.get("image_path", "") in paths_existants: + # Supprimer l'ancien élément + existing_data = [entry for entry in existing_data + if entry.get("image_path", "") != item.get("image_path", "")] + existing_data.append(item) + + with open(file_path, "w", encoding="utf-8") as f: + json.dump(existing_data, f, indent=2, ensure_ascii=False) + print(f"Rapport de déduplication sauvegardé dans {file_path} ({len(existing_data)} entrées)") + except Exception as e: + print(f"Erreur lors de la sauvegarde du rapport: {e}") \ No newline at end of file