from agents.utils.base_agent import BaseAgent import logging import os from typing import List, Dict, Any, Optional import json logger = logging.getLogger("AgentImageSorter") class AgentImageSorter(BaseAgent): """ Agent pour trier les images et identifier celles qui sont pertinentes pour l'analyse. """ def __init__(self, llm): super().__init__(llm) # Configuration locale de l'agent self.system_prompt = """Tu es un agent spécialisé dans l'analyse et le tri d'images pour le support technique. Ta mission est d'identifier les images pertinentes pour comprendre un problème technique, en distinguant celles qui contiennent des informations utiles (captures d'écran, photos de produits défectueux, etc.) de celles qui sont décoratives ou non informatives. Suis ces directives pour évaluer chaque image: 1. Identifie le contenu principal de l'image (capture d'écran, photo, schéma, etc.) 2. Évalue si l'image contient des informations utiles pour comprendre le problème technique 3. Détermine si l'image montre un problème, une erreur, ou une situation anormale 4. Examine si l'image contient du texte ou des messages d'erreur importants Pour chaque image, tu dois fournir: - Une description concise du contenu (1-2 phrases) - Un niveau de pertinence (Élevé/Moyen/Faible) - Une justification de ton évaluation""" self.image_batch_size = 3 # Nombre d'images à analyser par lot def executer(self, attachments_dir: str, contexte: Optional[Dict] = None) -> Dict[str, Dict[str, Any]]: """ Trie les images dans un répertoire de pièces jointes et identifie celles qui sont pertinentes. Args: attachments_dir: Chemin vers le répertoire des pièces jointes contexte: Contexte optionnel sur le ticket pour aider à l'analyse Returns: Dictionnaire avec les chemins des images comme clés et les résultats d'analyse comme valeurs """ logger.info(f"Tri des images dans: {attachments_dir}") # Vérifier si attachments_dir est un fichier ou un dossier if os.path.isfile(attachments_dir): logger.info(f"Le chemin fourni est un fichier et non un dossier: {attachments_dir}") # Si c'est un fichier image, on le traite directement if attachments_dir.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.tiff')): images = [attachments_dir] # Le vrai dossier est le répertoire parent attachments_dir = os.path.dirname(attachments_dir) else: logger.error(f"Le fichier n'est pas une image: {attachments_dir}") return {} # Vérifier que le répertoire existe elif not os.path.exists(attachments_dir): logger.error(f"Le répertoire {attachments_dir} n'existe pas") return {} else: # Lister les images du répertoire images = [os.path.join(attachments_dir, f) for f in os.listdir(attachments_dir) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.tiff'))] if not images: logger.info(f"Aucune image trouvée dans {attachments_dir}") return {} logger.info(f"Nombre d'images trouvées: {len(images)}") # Analyser les images individuellement ou par lots selon la configuration resultats = {} # Préparer un contexte spécifique pour l'analyse des images contexte_analyse = "Aucun contexte disponible." if contexte: # Extraire des informations pertinentes du contexte sujet = contexte.get("sujet", "") description = contexte.get("description", "") if sujet and description: contexte_analyse = f"Sujet du ticket: {sujet}\nDescription du problème: {description}" elif sujet: contexte_analyse = f"Sujet du ticket: {sujet}" elif description: contexte_analyse = f"Description du problème: {description}" # Traitement image par image for image_path in images: image_name = os.path.basename(image_path) logger.info(f"Analyse de l'image: {image_name}") prompt = f"""Analyse cette image dans le contexte suivant: {contexte_analyse} Réponds au format JSON avec la structure suivante: {{ "description": "Description concise du contenu", "pertinence": "Élevé/Moyen/Faible", "justification": "Pourquoi cette image est pertinente ou non", "contenu_technique": true/false }}""" # Analyser l'image try: resultat_brut = self.llm.interroger_avec_image(image_path, prompt) # Extraire le JSON de la réponse json_str = self._extraire_json(resultat_brut) if json_str: try: # Charger le JSON analyse = json.loads(json_str) # Ajouter le chemin complet pour référence analyse["image_path"] = image_path resultats[image_path] = analyse pertinence = analyse.get("pertinence", "").lower() logger.info(f"Image {image_name} - Pertinence: {pertinence}") except json.JSONDecodeError: logger.error(f"Erreur de décodage JSON pour {image_name}") resultats[image_path] = { "description": "Erreur d'analyse", "pertinence": "Inconnue", "justification": "Erreur de traitement de la réponse", "contenu_technique": False, "image_path": image_path } else: logger.error(f"Format de réponse incorrect pour {image_name}") # Créer une entrée avec les informations disponibles resultats[image_path] = { "description": "Analyse non disponible", "pertinence": "Inconnue", "justification": "Format de réponse incorrect", "contenu_technique": False, "image_path": image_path } except Exception as e: logger.error(f"Erreur lors de l'analyse de l'image {image_name}: {str(e)}") resultats[image_path] = { "description": "Erreur d'analyse", "pertinence": "Inconnue", "justification": f"Exception: {str(e)}", "contenu_technique": False, "image_path": image_path } return resultats def _extraire_json(self, texte: str) -> Optional[str]: """ Extrait le contenu JSON d'une chaîne de texte. Args: texte: Texte contenant potentiellement du JSON Returns: Chaîne JSON extraite ou None si aucun JSON n'est trouvé """ # Chercher des accolades ouvrantes et fermantes debut = texte.find('{') fin = texte.rfind('}') if debut != -1 and fin != -1 and fin > debut: return texte[debut:fin+1] return None def filtrer_images_pertinentes(self, resultats: Dict[str, Dict[str, Any]]) -> List[str]: """ Filtre les images pour ne conserver que celles qui sont pertinentes. Args: resultats: Dictionnaire avec les résultats d'analyse des images Returns: Liste des chemins des images pertinentes """ pertinentes = [] for image_path, analyse in resultats.items(): pertinence = analyse.get("pertinence", "").lower() contenu_technique = analyse.get("contenu_technique", False) # Considérer comme pertinentes les images avec pertinence élevée ou moyenne # ou celles marquées comme ayant un contenu technique if pertinence in ["élevé", "moyen", "eleve", "elevé", "medium", "high", "moyenne"] or contenu_technique: pertinentes.append(image_path) return pertinentes