llm_ticket3/agents/mistral_large/agent_ticket_analyser.py
2025-04-17 08:48:26 +02:00

194 lines
7.3 KiB
Python

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")