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