mirror of
https://github.com/Ladebeze66/llm_ticket3.git
synced 2025-12-13 10:46:51 +01:00
273 lines
14 KiB
Python
273 lines
14 KiB
Python
from ..base_agent import BaseAgent
|
|
from typing import Dict, Any
|
|
import logging
|
|
import os
|
|
import json
|
|
import traceback
|
|
from datetime import datetime
|
|
from ..utils.pipeline_logger import sauvegarder_donnees
|
|
|
|
# Configuration du logger pour plus de détails
|
|
logger = logging.getLogger("AgentReportGenerator")
|
|
logger.setLevel(logging.DEBUG) # Augmenter le niveau de logging pour le débogage
|
|
|
|
class AgentReportGenerator(BaseAgent):
|
|
def __init__(self, llm):
|
|
super().__init__("AgentReportGenerator", llm)
|
|
|
|
self.params = {
|
|
"temperature": 0.2,
|
|
"top_p": 0.8,
|
|
"max_tokens": 8000
|
|
}
|
|
|
|
self.system_prompt = """Tu es un expert en support technique chargé de générer un rapport final à partir des analyses d'un ticket de support.
|
|
Ton rôle est de croiser les informations provenant :
|
|
- de l'analyse textuelle du ticket client
|
|
- des analyses détaillées de plusieurs captures d'écran
|
|
|
|
Tu dois structurer ta réponse en format question/réponse de manière claire, en gardant l'intégralité des points importants.
|
|
|
|
Ne propose jamais de solution. Ne reformule pas le contexte.
|
|
Ta seule mission est de croiser les données textuelles et visuelles et d'en tirer des observations claires, en listant les éléments factuels visibles dans les captures qui appuient ou complètent le texte du ticket.
|
|
|
|
Structure du rapport attendu :
|
|
1. Contexte général (résumé du ticket textuel en une phrase)
|
|
2. Problèmes ou questions identifiés (sous forme de questions claires)
|
|
3. Résumé croisé image/texte pour chaque question
|
|
4. Liste d'observations supplémentaires pertinentes (si applicable)
|
|
5. Tableau chronologique d'échanges
|
|
- Inclure un tableau structuré des échanges entre client et support
|
|
- Format : Émetteur | Type | Date | Contenu | Éléments visuels pertinents
|
|
- Ne pas mentionner les noms réels des personnes, utiliser "CLIENT" et "SUPPORT"
|
|
- Synthétiser le contenu tout en conservant les informations importantes
|
|
- Associer les éléments visuels des captures d'écran aux échanges correspondants
|
|
|
|
Règles pour le tableau d'échanges :
|
|
- TYPE peut être : question, réponse, information, complément visuel
|
|
- Pour chaque échange du client mentionnant un problème, ajoute les éléments visuels des captures qui contextualisent ce problème
|
|
- Pour chaque réponse du support, ajoute les éléments visuels qui confirment ou infirment la réponse
|
|
- N'invente aucun contenu ni aucune date
|
|
- Utilise les données factuelles des images pour enrichir la compréhension des échanges
|
|
|
|
Reste strictement factuel. Ne fais aucune hypothèse. Ne suggère pas d'étapes ni d'interprétation."""
|
|
|
|
self._appliquer_config_locale()
|
|
logger.info("AgentReportGenerator initialisé")
|
|
|
|
def _appliquer_config_locale(self) -> None:
|
|
if hasattr(self.llm, "prompt_system"):
|
|
self.llm.prompt_system = self.system_prompt
|
|
if hasattr(self.llm, "configurer"):
|
|
self.llm.configurer(**self.params)
|
|
|
|
def _verifier_donnees_entree(self, rapport_data: Dict[str, Any]) -> bool:
|
|
"""
|
|
Vérifie que les données d'entrée contiennent les éléments nécessaires.
|
|
|
|
Args:
|
|
rapport_data: Données pour générer le rapport
|
|
|
|
Returns:
|
|
bool: True si les données sont valides, False sinon
|
|
"""
|
|
ticket_id = rapport_data.get("ticket_id")
|
|
if not ticket_id:
|
|
logger.error("Erreur de validation: ticket_id manquant")
|
|
return False
|
|
|
|
ticket_analyse = rapport_data.get("ticket_analyse")
|
|
if not ticket_analyse:
|
|
logger.error(f"Erreur de validation pour {ticket_id}: analyse de ticket manquante")
|
|
return False
|
|
|
|
analyses_images = rapport_data.get("analyse_images", {})
|
|
if not analyses_images:
|
|
logger.warning(f"Avertissement pour {ticket_id}: aucune analyse d'image disponible")
|
|
# On continue quand même car on peut générer un rapport sans images
|
|
|
|
# Vérifier si au moins une image a été analysée
|
|
images_analysees = 0
|
|
for img_path, img_data in analyses_images.items():
|
|
if img_data.get("analysis") and img_data["analysis"].get("analyse"):
|
|
images_analysees += 1
|
|
|
|
if images_analysees == 0 and analyses_images:
|
|
logger.warning(f"Avertissement pour {ticket_id}: {len(analyses_images)} images trouvées mais aucune n'a été analysée")
|
|
|
|
logger.info(f"Validation pour {ticket_id}: OK, {images_analysees} images analysées sur {len(analyses_images)} images")
|
|
return True
|
|
|
|
def executer(self, rapport_data: Dict[str, Any]) -> str:
|
|
ticket_id = rapport_data.get("ticket_id", "Inconnu")
|
|
print(f"AgentReportGenerator : génération du rapport pour le ticket {ticket_id}")
|
|
|
|
try:
|
|
# Vérifier et enregistrer les données d'entrée pour le débogage
|
|
logger.debug(f"Données reçues pour {ticket_id}: {json.dumps(rapport_data, default=str)[:500]}...")
|
|
|
|
# Vérifier que les données d'entrée sont valides
|
|
if not self._verifier_donnees_entree(rapport_data):
|
|
error_msg = f"Impossible de générer le rapport: données d'entrée invalides pour {ticket_id}"
|
|
print(f"ERREUR: {error_msg}")
|
|
return f"ERREUR: {error_msg}"
|
|
|
|
print(f"Préparation du prompt pour le ticket {ticket_id}...")
|
|
prompt = self._generer_prompt(rapport_data)
|
|
logger.debug(f"Prompt généré ({len(prompt)} caractères): {prompt[:500]}...")
|
|
|
|
print(f"Analyse en cours pour le ticket {ticket_id}...")
|
|
response = self.llm.interroger(prompt)
|
|
print(f"Analyse terminée: {len(response)} caractères")
|
|
logger.debug(f"Réponse reçue ({len(response)} caractères): {response[:500]}...")
|
|
|
|
# Création du résultat complet avec métadonnées
|
|
result = {
|
|
"prompt": prompt,
|
|
"response": response,
|
|
"metadata": {
|
|
"ticket_id": ticket_id,
|
|
"timestamp": self._get_timestamp(),
|
|
"source_agent": self.nom,
|
|
"model_info": {
|
|
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
|
**getattr(self.llm, "params", {})
|
|
}
|
|
}
|
|
}
|
|
|
|
# Sauvegarder le résultat dans le pipeline
|
|
logger.info(f"Sauvegarde du rapport final pour le ticket {ticket_id}")
|
|
|
|
# Créer un fichier de débogage direct pour vérifier le problème de sauvegarde
|
|
try:
|
|
debug_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../debug"))
|
|
os.makedirs(debug_dir, exist_ok=True)
|
|
debug_path = os.path.join(debug_dir, f"rapport_debug_{ticket_id}.json")
|
|
with open(debug_path, "w", encoding="utf-8") as f:
|
|
json.dump(result, f, ensure_ascii=False, indent=2)
|
|
print(f"Fichier de débogage créé: {debug_path}")
|
|
except Exception as e:
|
|
print(f"Erreur lors de la création du fichier de débogage: {str(e)}")
|
|
|
|
# Utiliser uniquement la fonction standard de sauvegarde
|
|
try:
|
|
sauvegarder_donnees(ticket_id, "rapport_final", result, base_dir=None, is_resultat=True)
|
|
print(f"Rapport final généré et sauvegardé pour le ticket {ticket_id}")
|
|
except Exception as e:
|
|
logger.error(f"Erreur lors de la sauvegarde via sauvegarder_donnees: {str(e)}")
|
|
print(f"Erreur de sauvegarde standard: {str(e)}")
|
|
|
|
# Sauvegarder aussi une version en texte brut pour faciliter la lecture
|
|
try:
|
|
# Trouver le chemin de ticket le plus récent
|
|
output_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../output"))
|
|
ticket_dir = os.path.join(output_dir, f"ticket_{ticket_id}")
|
|
|
|
if os.path.exists(ticket_dir):
|
|
# Trouver l'extraction la plus récente
|
|
extractions = [d for d in os.listdir(ticket_dir) if os.path.isdir(os.path.join(ticket_dir, d)) and d.startswith(ticket_id)]
|
|
if extractions:
|
|
extractions.sort(reverse=True)
|
|
latest_extraction = extractions[0]
|
|
rapports_dir = os.path.join(ticket_dir, latest_extraction, f"{ticket_id}_rapports")
|
|
pipeline_dir = os.path.join(rapports_dir, "pipeline")
|
|
|
|
# S'assurer que le répertoire existe
|
|
os.makedirs(pipeline_dir, exist_ok=True)
|
|
|
|
# Sauvegarder en format texte uniquement
|
|
rapport_txt_path = os.path.join(pipeline_dir, f"rapport_final_mistral-large-latest.txt")
|
|
with open(rapport_txt_path, "w", encoding="utf-8") as f:
|
|
f.write(f"RAPPORT D'ANALYSE DU TICKET {ticket_id}\n")
|
|
f.write("="*50 + "\n\n")
|
|
f.write(response)
|
|
print(f"Version texte sauvegardée dans: {rapport_txt_path}")
|
|
except Exception as e:
|
|
print(f"Erreur lors de la sauvegarde de la version texte: {str(e)}")
|
|
logger.error(f"Erreur de sauvegarde texte: {str(e)}")
|
|
|
|
# Sauvegarder aussi dans le dossier reports pour compatibilité
|
|
try:
|
|
reports_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../reports"))
|
|
ticket_reports_dir = os.path.join(reports_dir, ticket_id)
|
|
os.makedirs(ticket_reports_dir, exist_ok=True)
|
|
|
|
# Sauvegarder aussi en texte pour faciliter la lecture
|
|
rapport_txt_path = os.path.join(ticket_reports_dir, f"rapport_final_{ticket_id}.txt")
|
|
with open(rapport_txt_path, "w", encoding="utf-8") as f:
|
|
f.write(f"RAPPORT D'ANALYSE DU TICKET {ticket_id}\n")
|
|
f.write("="*50 + "\n\n")
|
|
f.write(response)
|
|
|
|
logger.info(f"Rapport texte sauvegardé dans {rapport_txt_path}")
|
|
print(f"Rapport également sauvegardé en texte dans {ticket_reports_dir}")
|
|
except Exception as e:
|
|
logger.warning(f"Impossible de sauvegarder le rapport texte dans reports/: {str(e)}")
|
|
print(f"Erreur de sauvegarde reports/: {str(e)}")
|
|
|
|
# Ajouter à l'historique
|
|
self.ajouter_historique("rapport_final", {
|
|
"ticket_id": ticket_id,
|
|
"prompt": prompt,
|
|
"timestamp": self._get_timestamp()
|
|
}, response)
|
|
|
|
print(f"Traitement du rapport terminé pour le ticket {ticket_id}")
|
|
return response
|
|
|
|
except Exception as e:
|
|
logger.error(f"Erreur lors de la génération du rapport : {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
print(f"ERREUR CRITIQUE lors de la génération du rapport: {str(e)}")
|
|
return f"ERREUR: {str(e)}"
|
|
|
|
|
|
def _generer_prompt(self, rapport_data: Dict[str, Any]) -> str:
|
|
ticket_text = rapport_data.get("ticket_analyse", "")
|
|
image_blocs = []
|
|
analyses_images = rapport_data.get("analyse_images", {})
|
|
|
|
# Ajouter des logs pour vérifier les données d'images
|
|
logger.info(f"Nombre d'images à analyser: {len(analyses_images)}")
|
|
|
|
for chemin_image, analyse_obj in analyses_images.items():
|
|
# Vérifier si l'image est pertinente
|
|
is_relevant = analyse_obj.get("sorting", {}).get("is_relevant", False)
|
|
|
|
# Récupérer l'analyse si elle existe
|
|
analyse = ""
|
|
if "analysis" in analyse_obj and analyse_obj["analysis"]:
|
|
analyse = analyse_obj["analysis"].get("analyse", "")
|
|
|
|
if analyse:
|
|
image_blocs.append(f"--- IMAGE : {os.path.basename(chemin_image)} ---\n{analyse}\n")
|
|
logger.info(f"Ajout de l'analyse de l'image {os.path.basename(chemin_image)} ({len(analyse)} caractères)")
|
|
else:
|
|
logger.warning(f"Image {os.path.basename(chemin_image)} sans analyse")
|
|
|
|
bloc_images = "\n".join(image_blocs)
|
|
|
|
# Log pour vérifier la taille des données
|
|
logger.info(f"Taille de l'analyse ticket: {len(ticket_text)} caractères")
|
|
logger.info(f"Taille du bloc images: {len(bloc_images)} caractères")
|
|
|
|
prompt = (
|
|
f"Voici les données d'analyse pour un ticket de support :\n\n"
|
|
f"=== ANALYSE DU TICKET ===\n{ticket_text}\n\n"
|
|
f"=== ANALYSES D'IMAGES ===\n{bloc_images}\n\n"
|
|
f"Génère un rapport croisé en suivant les instructions précédentes, incluant un tableau chronologique des échanges entre CLIENT et SUPPORT. "
|
|
f"Utilise le format suivant pour le tableau :\n"
|
|
f"| ÉMETTEUR | TYPE | DATE | CONTENU | ÉLÉMENTS VISUELS |\n"
|
|
f"| --- | --- | --- | --- | --- |\n"
|
|
f"| CLIENT | question | date | texte de la question | éléments pertinents des images |\n"
|
|
f"| SUPPORT | réponse | date | texte de la réponse | éléments pertinents des images |\n\n"
|
|
f"Ce tableau doit synthétiser les échanges tout en intégrant les données pertinentes des images avec le maximum de contexte technique."
|
|
)
|
|
|
|
return prompt
|
|
|
|
def _get_timestamp(self) -> str:
|
|
from datetime import datetime
|
|
return datetime.now().strftime("%Y%m%d_%H%M%S")
|