from ..base_agent import BaseAgent from typing import Dict, Any import logging 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") class AgentTicketAnalyser(BaseAgent): """ Agent pour analyser les tickets (JSON ou Markdown) et en extraire les informations importantes. Utilisé pour contextualiser les tickets avant l'analyse des captures d'écran. """ def __init__(self, llm): super().__init__("AgentTicketAnalyser", llm) # Configuration adaptée à l'analyse structurée pour Mistral Large self.temperature = 0.1 self.top_p = 0.8 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). 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) 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 2. Informations techniques détectées 3. Fil de discussion (filtré, classé, nettoyé) 4. Points visuels à analyser (éléments à retrouver dans les captures) 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. - 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() self._appliquer_config_locale() logger.info("AgentTicketAnalyser initialisé") def _appliquer_config_locale(self) -> None: """Applique les paramètres et le prompt système au modèle""" if hasattr(self.llm, "prompt_system"): self.llm.prompt_system = self.system_prompt if hasattr(self.llm, "configurer"): self.llm.configurer( temperature=self.temperature, top_p=self.top_p, max_tokens=self.max_tokens ) def executer(self, ticket_data: Dict[str, Any]) -> str: """Analyse le ticket donné sous forme de dict""" if isinstance(ticket_data, str) and os.path.exists(ticket_data): ticket_data = self.ticket_loader.charger(ticket_data) if not isinstance(ticket_data, dict): logger.error("Les données du ticket ne sont pas valides") return "ERREUR: Format de ticket invalide" ticket_code = ticket_data.get("code", "Inconnu") logger.info(f"Analyse du ticket {ticket_code}") print(f"AgentTicketAnalyser: analyse du ticket {ticket_code}") prompt = self._generer_prompt(ticket_data) try: 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)}" self.ajouter_historique( "analyse_ticket", { "ticket_id": ticket_code, "prompt": prompt, "timestamp": self._get_timestamp() }, response ) return response def _generer_prompt(self, ticket_data: Dict[str, Any]) -> str: """ Prépare un prompt texte structuré à partir des données brutes du ticket. """ ticket_code = ticket_data.get("code", "Inconnu") name = ticket_data.get("name", "").strip() description = ticket_data.get("description", "").strip() # En-tête texte = f"### TICKET {ticket_code}\n\n" if name: texte += f"#### NOM DU TICKET\n{name}\n\n" if description: texte += f"#### DESCRIPTION\n{description}\n\n" # Informations techniques utiles (hors champs exclus) champs_exclus = {"code", "name", "description", "messages", "metadata"} info_techniques = [ f"- {k}: {json.dumps(v, ensure_ascii=False)}" if isinstance(v, (dict, list)) else f"- {k}: {v}" for k, v in ticket_data.items() if k not in champs_exclus and v ] if info_techniques: texte += "#### INFORMATIONS TECHNIQUES\n" + "\n".join(info_techniques) + "\n\n" # Fil des échanges client/support messages = ticket_data.get("messages", []) if messages: texte += "#### ÉCHANGES CLIENT / SUPPORT\n" for i, msg in enumerate(messages): if not isinstance(msg, dict): continue 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() texte += f"{i+1}. [{role}] {auteur} ({date})\n{contenu}\n\n" # Métadonnées utiles metadata = ticket_data.get("metadata", {}) meta_text = [ f"- {k}: {json.dumps(v, ensure_ascii=False)}" if isinstance(v, (dict, list)) else f"- {k}: {v}" for k, v in metadata.items() if k not in {"format", "source_file"} and v ] if meta_text: texte += "#### MÉTADONNÉES\n" + "\n".join(meta_text) + "\n" return texte def _formater_date(self, date_str: str) -> str: """ Formate la date en DD/MM/YYYY HH:MM si possible Args: date_str: Chaîne de caractères représentant une date Returns: Date formatée ou la chaîne originale si le format n'est pas reconnu """ if not date_str: return "date inconnue" try: # Essayer différents formats courants formats = [ "%Y-%m-%dT%H:%M:%S.%fZ", # Format ISO avec microsecondes et Z "%Y-%m-%dT%H:%M:%SZ", # Format ISO sans microsecondes avec Z "%Y-%m-%dT%H:%M:%S", # Format ISO sans Z "%Y-%m-%d %H:%M:%S", # Format standard "%d/%m/%Y %H:%M:%S", # Format français "%d/%m/%Y" # Format date simple ] for fmt in formats: try: dt = datetime.strptime(date_str, fmt) return dt.strftime("%d/%m/%Y %H:%M") except ValueError: continue # Si aucun format ne correspond, retourner la chaîne originale return date_str except Exception: return date_str def _get_timestamp(self) -> str: """ Génère un timestamp au format YYYYMMDD_HHMMSS Returns: Timestamp formaté """ return datetime.now().strftime("%Y%m%d_%H%M%S")