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 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 = 6000 # 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). 4. 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 """ 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") 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("from", "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" # 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")