#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Workflow complet d'analyse de ticket avec génération de rapport. Ce module implémente un workflow qui combine tous les agents : 1. Analyse du ticket 2. Tri des images 3. Analyse des images pertinentes 4. Génération d'un rapport final """ import os import sys import json import logging import time import argparse from typing import Dict, Any, Optional, List # Import des factories from tests.common.agent_factory import ( create_ticket_analyser, create_image_sorter, create_image_analyser, create_report_generator ) from tests.common.llm_factory import create_llm from tests.common.ticket_utils import get_ticket_json, get_ticket_images logger = logging.getLogger("CompleteAnalysisWorkflow") def execute_workflow( ticket_id: str, output_dir: str = "output", text_model: str = "mistral_large", vision_model: str = "pixtral_large", generate_report: bool = True, save_results: bool = True ) -> Dict[str, Any]: """ Exécute le workflow complet d'analyse de ticket et génération de rapport. Args: ticket_id: Identifiant du ticket output_dir: Répertoire contenant les tickets text_model: Nom du modèle à utiliser pour l'analyse de ticket et la génération de rapport vision_model: Nom du modèle à utiliser pour l'analyse d'image generate_report: Si True, génère un rapport final save_results: Si True, sauvegarde les résultats intermédiaires Returns: Dict avec les résultats du workflow """ workflow_start = time.time() logger.info(f"Démarrage du workflow complet pour le ticket {ticket_id}") print(f"Démarrage du workflow complet pour le ticket {ticket_id}") # ÉTAPE 1: Initialiser les modèles LLM try: print("Initialisation des modèles LLM...") text_llm = create_llm(text_model) vision_llm = create_llm(vision_model) print(f"Modèles initialisés: {text_model}, {vision_model}") except Exception as e: logger.error(f"Erreur lors de l'initialisation des modèles: {e}") print(f"ERREUR: Impossible d'initialiser les modèles: {e}") return {"error": str(e), "stage": "init_llm"} # ÉTAPE 2: Initialiser les agents try: print("Création des agents...") ticket_agent = create_ticket_analyser(text_llm) image_sorter = create_image_sorter(vision_llm) image_analyser = create_image_analyser(vision_llm) report_generator = create_report_generator(text_llm) if generate_report else None print("Agents créés avec succès") except Exception as e: logger.error(f"Erreur lors de la création des agents: {e}") print(f"ERREUR: Impossible de créer les agents: {e}") return {"error": str(e), "stage": "init_agents"} # ÉTAPE 3: Charger les données du ticket try: print("Chargement des données du ticket...") json_file, ticket_data = get_ticket_json(ticket_id, output_dir) if not json_file or not ticket_data: error_msg = f"Impossible de charger les données du ticket {ticket_id}" logger.error(error_msg) print(f"ERREUR: {error_msg}") return {"error": error_msg, "stage": "load_ticket"} print(f"Données du ticket chargées: {json_file}") except Exception as e: logger.error(f"Erreur lors du chargement du ticket: {e}") print(f"ERREUR: Impossible de charger le ticket: {e}") return {"error": str(e), "stage": "load_ticket"} # ÉTAPE 4: Analyser le ticket try: print("Analyse du ticket en cours...") ticket_analysis = ticket_agent.executer(ticket_data) print(f"Analyse du ticket terminée: {len(ticket_analysis) if ticket_analysis else 0} caractères") # Sauvegarder l'analyse si demandé if save_results: save_dir = os.path.join("results", ticket_id) os.makedirs(save_dir, exist_ok=True) save_file = os.path.join(save_dir, f"ticket_analysis_{text_model}.json") with open(save_file, "w", encoding="utf-8") as f: json.dump({ "ticket_id": ticket_id, "model": text_model, "analysis": ticket_analysis, "timestamp": time.strftime("%Y-%m-%d %H:%M:%S") }, f, ensure_ascii=False, indent=2) print(f"Analyse du ticket sauvegardée: {save_file}") except Exception as e: logger.error(f"Erreur lors de l'analyse du ticket: {e}") print(f"ERREUR: Impossible d'analyser le ticket: {e}") return {"error": str(e), "stage": "analyse_ticket", "ticket_data": ticket_data} # ÉTAPE 5: Récupérer et trier les images relevant_images = [] image_sorting_results = {} all_images = [] try: print("Récupération et tri des images...") all_images = get_ticket_images(ticket_id, output_dir) if not all_images: print("Aucune image trouvée pour ce ticket") else: print(f"Images trouvées: {len(all_images)}") # Trier les images for img_path in all_images: print(f"Évaluation de l'image: {os.path.basename(img_path)}") try: sorting_result = image_sorter.executer(img_path) is_relevant = sorting_result.get("is_relevant", False) reason = sorting_result.get("reason", "") image_sorting_results[img_path] = sorting_result if is_relevant: print(f" => Pertinente: {reason[:50]}...") relevant_images.append(img_path) else: print(f" => Non pertinente: {reason[:50]}...") except Exception as e: logger.error(f"Erreur lors du tri de l'image {img_path}: {e}") print(f"ERREUR: Impossible de trier l'image {img_path}: {e}") image_sorting_results[img_path] = {"error": str(e), "is_relevant": False} print(f"Images pertinentes: {len(relevant_images)}/{len(all_images)}") # Sauvegarder les résultats du tri si demandé if save_results and image_sorting_results: save_dir = os.path.join("results", ticket_id) os.makedirs(save_dir, exist_ok=True) save_file = os.path.join(save_dir, f"image_sorting_{vision_model}.json") with open(save_file, "w", encoding="utf-8") as f: json.dump({ "ticket_id": ticket_id, "model": vision_model, "images_total": len(all_images), "images_relevant": len(relevant_images), "results": {os.path.basename(k): v for k, v in image_sorting_results.items()}, "timestamp": time.strftime("%Y-%m-%d %H:%M:%S") }, f, ensure_ascii=False, indent=2) print(f"Résultats du tri d'images sauvegardés: {save_file}") except Exception as e: logger.error(f"Erreur lors de la récupération/tri des images: {e}") print(f"ERREUR: Impossible de récupérer/trier les images: {e}") if generate_report: # On continue quand même pour générer le rapport, même sans images print("On continue sans images...") else: return { "error": str(e), "stage": "sort_images", "ticket_id": ticket_id, "ticket_data": ticket_data, "ticket_analysis": ticket_analysis } # ÉTAPE 6: Analyser les images pertinentes avec contexte image_analysis_results = {} if relevant_images: print("Analyse des images pertinentes avec contexte...") for img_path in relevant_images: img_name = os.path.basename(img_path) print(f"Analyse de l'image: {img_name}") try: analysis_result = image_analyser.executer(img_path, contexte=ticket_analysis) image_analysis_results[img_path] = analysis_result print(f" => Analyse terminée: {len(analysis_result.get('analyse', '')) if analysis_result else 0} caractères") except Exception as e: logger.error(f"Erreur lors de l'analyse de l'image {img_path}: {e}") print(f"ERREUR: Impossible d'analyser l'image {img_path}: {e}") image_analysis_results[img_path] = {"error": str(e)} # Sauvegarder les analyses d'images si demandé if save_results and image_analysis_results: save_dir = os.path.join("results", ticket_id) os.makedirs(save_dir, exist_ok=True) save_file = os.path.join(save_dir, f"image_analysis_{vision_model}.json") with open(save_file, "w", encoding="utf-8") as f: json.dump({ "ticket_id": ticket_id, "model": vision_model, "images_analysed": len(image_analysis_results), "results": {os.path.basename(k): v for k, v in image_analysis_results.items()}, "timestamp": time.strftime("%Y-%m-%d %H:%M:%S") }, f, ensure_ascii=False, indent=2) print(f"Analyses d'images sauvegardées: {save_file}") else: print("Aucune image pertinente à analyser") # ÉTAPE 7: Générer le rapport final report_file = None report_content = None if generate_report and report_generator is not None: print("Génération du rapport final...") try: # Préparer les données pour le rapport rapport_data = { "ticket_id": ticket_id, "ticket_data": ticket_data, "ticket_analyse": ticket_analysis, "analyse_images": {} } # Ajouter les résultats d'analyse d'images for img_path, analysis in image_analysis_results.items(): sorting_result = image_sorting_results.get(img_path, {}) rapport_data["analyse_images"][img_path] = { "sorting": sorting_result, "analysis": analysis } # Définir le répertoire de destination pour le rapport reports_dir = os.path.join("reports", ticket_id) os.makedirs(reports_dir, exist_ok=True) # Générer le rapport report_result = report_generator.executer(rapport_data, reports_dir) # Vérifier si le rapport a été généré avec succès if report_result and isinstance(report_result, dict): report_file = report_result.get("report_file") report_content = report_result.get("content") print(f"Rapport généré avec succès: {report_file}") else: print("Rapport généré mais le format de retour n'est pas standard") except Exception as e: logger.error(f"Erreur lors de la génération du rapport: {e}") print(f"ERREUR: Impossible de générer le rapport: {e}") elif generate_report: logger.warning("Génération de rapport demandée mais l'agent n'a pas été initialisé correctement") print("ATTENTION: Impossible de générer le rapport (agent non initialisé)") # Calcul du temps total d'exécution workflow_time = time.time() - workflow_start print(f"Workflow complet terminé en {workflow_time:.2f} secondes") # Retourner les résultats return { "ticket_id": ticket_id, "ticket_analysis": ticket_analysis, "images_count": len(all_images), "relevant_images": len(relevant_images), "images_analysed": len(image_analysis_results), "report_file": report_file, "execution_time": workflow_time } def main(): # Configuration de l'analyseur d'arguments parser = argparse.ArgumentParser(description="Exécuter le workflow complet d'analyse de ticket") parser.add_argument("ticket_id", help="ID du ticket à analyser (ex: T1234)") parser.add_argument("--output_dir", default="output", help="Répertoire des tickets (défaut: output)") parser.add_argument("--text_model", default="mistral_large", help="Modèle texte à utiliser (défaut: mistral_large)") parser.add_argument("--vision_model", default="pixtral_large", help="Modèle vision à utiliser (défaut: pixtral_large)") parser.add_argument("--no-report", action="store_true", help="Ne pas générer de rapport final") parser.add_argument("--no-save", action="store_true", help="Ne pas sauvegarder les résultats intermédiaires") parser.add_argument("--verbose", "-v", action="store_true", help="Mode verbeux") # Analyser les arguments args = parser.parse_args() # Configurer le logging log_level = logging.DEBUG if args.verbose else logging.INFO log_file = f"complete_workflow_{args.ticket_id}.log" handlers: List[logging.Handler] = [logging.StreamHandler()] if log_file: handlers.append(logging.FileHandler(log_file)) logging.basicConfig( level=log_level, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=handlers ) # Exécuter le workflow results = execute_workflow( ticket_id=args.ticket_id, output_dir=args.output_dir, text_model=args.text_model, vision_model=args.vision_model, generate_report=not args.no_report, save_results=not args.no_save ) # Afficher un résumé des résultats if "error" in results: print(f"\nErreur lors de l'exécution du workflow: {results['error']}") print(f"Étape en échec: {results.get('stage', 'inconnue')}") return 1 print("\n=== Résumé du workflow complet ===") print(f"Ticket: {results['ticket_id']}") print(f"Temps d'exécution total: {results['execution_time']:.2f} secondes") print(f"Analyse du ticket: {len(results['ticket_analysis']) if results.get('ticket_analysis') else 0} caractères") print(f"Images trouvées: {results['images_count']}") print(f"Images pertinentes: {results['relevant_images']}") print(f"Images analysées: {results['images_analysed']}") if results.get("report_file"): print(f"Rapport généré: {results['report_file']}") elif not args.no_report: print("Rapport: Non généré (échec ou aucune donnée disponible)") else: print("Rapport: Désactivé") return 0 if __name__ == "__main__": sys.exit(main())