llm_ticket3/tests/workflows/complete_analysis_workflow.py
2025-04-18 17:34:21 +02:00

347 lines
15 KiB
Python

#!/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())