mirror of
https://github.com/Ladebeze66/llm_ticket3.git
synced 2025-12-13 15:46:52 +01:00
340 lines
14 KiB
Python
340 lines
14 KiB
Python
from ..base_agent import BaseAgent
|
|
from typing import Any, Dict
|
|
import logging
|
|
import os
|
|
from PIL import Image
|
|
import base64
|
|
import io
|
|
|
|
logger = logging.getLogger("AgentImageAnalyser")
|
|
|
|
class AgentImageAnalyser(BaseAgent):
|
|
"""
|
|
Agent pour analyser les images et extraire les informations pertinentes.
|
|
"""
|
|
def __init__(self, llm):
|
|
super().__init__("AgentImageAnalyser", llm)
|
|
|
|
# Configuration locale de l'agent
|
|
self.temperature = 0.2
|
|
self.top_p = 0.9
|
|
self.max_tokens = 3000
|
|
|
|
# Centralisation des instructions d'analyse pour éviter la duplication
|
|
self.instructions_analyse = """
|
|
1. Description objective
|
|
Décris précisément ce que montre l'image :
|
|
- Interface logicielle, menus, fenêtres, onglets
|
|
- Messages d'erreur, messages système, code ou script
|
|
- Nom ou titre du logiciel ou du module si visible
|
|
|
|
2. Éléments techniques clés
|
|
Identifie :
|
|
- Versions logicielles ou modules affichés
|
|
- Codes d'erreur visibles
|
|
- Paramètres configurables (champs de texte, sliders, dropdowns, cases à cocher)
|
|
- Valeurs affichées ou préremplies dans les champs
|
|
- Éléments désactivés, grisés ou masqués (souvent non modifiables)
|
|
- Boutons actifs/inactifs
|
|
|
|
3. Éléments mis en évidence
|
|
- Recherche les zones entourées, encadrées, surlignées ou fléchées
|
|
- Ces éléments sont souvent importants pour le client ou le support
|
|
- Mentionne explicitement leur contenu et leur style de mise en valeur
|
|
|
|
4. Relation avec le problème
|
|
- Établis le lien entre les éléments visibles et le problème décrit dans le ticket
|
|
- Indique si des composants semblent liés à une mauvaise configuration ou une erreur
|
|
|
|
5. Réponses potentielles
|
|
- Détermine si l'image apporte des éléments de réponse à une question posée dans :
|
|
- Le titre du ticket
|
|
- La description du problème
|
|
|
|
6. Lien avec la discussion
|
|
- Vérifie si l'image fait écho à une étape décrite dans le fil de discussion
|
|
- Note les correspondances (ex: même module, même message d'erreur que précédemment mentionné)
|
|
|
|
Règles importantes :
|
|
- Ne fais AUCUNE interprétation ni diagnostic
|
|
- Ne propose PAS de solution ou recommandation
|
|
- Reste strictement factuel et objectif
|
|
- Concentre-toi uniquement sur ce qui est visible dans l'image
|
|
- Reproduis les textes exacts(ex : messages d'erreur, libellés de paramètres)
|
|
- Prête une attention particulière aux éléments modifiables (interactifs) et non modifiables (grisés)
|
|
"""
|
|
|
|
# Prompt système construit à partir des instructions centralisées
|
|
self.system_prompt = f"""Tu es un expert en analyse d'images pour le support technique de BRG-Lab pour la société CBAO.
|
|
Ta mission est d'analyser des captures d'écran en lien avec le contexte du ticket de support.
|
|
|
|
Structure ton analyse d'image de façon factuelle:
|
|
{self.instructions_analyse}
|
|
|
|
Ton analyse sera utilisée comme élément factuel pour un rapport technique plus complet."""
|
|
|
|
# Appliquer la configuration au LLM
|
|
self._appliquer_config_locale()
|
|
|
|
logger.info("AgentImageAnalyser initialisé")
|
|
|
|
def _appliquer_config_locale(self) -> None:
|
|
"""
|
|
Applique la configuration locale au modèle LLM.
|
|
"""
|
|
# Appliquer le prompt système
|
|
if hasattr(self.llm, "prompt_system"):
|
|
self.llm.prompt_system = self.system_prompt
|
|
|
|
# Appliquer les paramètres
|
|
if hasattr(self.llm, "configurer"):
|
|
params = {
|
|
"temperature": self.temperature,
|
|
"top_p": self.top_p,
|
|
"max_tokens": self.max_tokens
|
|
}
|
|
|
|
self.llm.configurer(**params)
|
|
|
|
def _verifier_image(self, image_path: str) -> bool:
|
|
"""
|
|
Vérifie si l'image existe et est accessible
|
|
|
|
Args:
|
|
image_path: Chemin vers l'image
|
|
|
|
Returns:
|
|
True si l'image existe et est accessible, False sinon
|
|
"""
|
|
try:
|
|
# Vérifier que le fichier existe
|
|
if not os.path.exists(image_path):
|
|
logger.error(f"L'image n'existe pas: {image_path}")
|
|
return False
|
|
|
|
# Vérifier que le fichier est accessible en lecture
|
|
if not os.access(image_path, os.R_OK):
|
|
logger.error(f"L'image n'est pas accessible en lecture: {image_path}")
|
|
return False
|
|
|
|
# Vérifier que le fichier peut être ouvert comme une image
|
|
with Image.open(image_path) as img:
|
|
# Vérifier les dimensions de l'image
|
|
width, height = img.size
|
|
if width <= 0 or height <= 0:
|
|
logger.error(f"Dimensions d'image invalides: {width}x{height}")
|
|
return False
|
|
|
|
logger.info(f"Image vérifiée avec succès: {image_path} ({width}x{height})")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Erreur lors de la vérification de l'image {image_path}: {str(e)}")
|
|
return False
|
|
|
|
def _encoder_image_base64(self, image_path: str) -> str:
|
|
"""
|
|
Encode l'image en base64 pour l'inclure directement dans le prompt
|
|
|
|
Args:
|
|
image_path: Chemin vers l'image
|
|
|
|
Returns:
|
|
Chaîne de caractères au format data URI avec l'image encodée en base64
|
|
"""
|
|
try:
|
|
# Ouvrir l'image et la redimensionner si trop grande
|
|
with Image.open(image_path) as img:
|
|
# Redimensionner l'image si elle est trop grande (max 800x800)
|
|
max_size = 800
|
|
if img.width > max_size or img.height > max_size:
|
|
img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
|
|
|
|
# Convertir en RGB si nécessaire (pour les formats comme PNG)
|
|
if img.mode != "RGB":
|
|
img = img.convert("RGB")
|
|
|
|
# Sauvegarder l'image en JPEG dans un buffer mémoire
|
|
buffer = io.BytesIO()
|
|
img.save(buffer, format="JPEG", quality=85)
|
|
buffer.seek(0)
|
|
|
|
# Encoder en base64
|
|
img_base64 = base64.b64encode(buffer.read()).decode("utf-8")
|
|
|
|
# Construire le data URI
|
|
data_uri = f"data:image/jpeg;base64,{img_base64}"
|
|
|
|
return data_uri
|
|
except Exception as e:
|
|
logger.error(f"Erreur lors de l'encodage de l'image {image_path}: {str(e)}")
|
|
return ""
|
|
|
|
def _generer_prompt_analyse(self, contexte: str, prefix: str = "") -> str:
|
|
"""
|
|
Génère le prompt d'analyse d'image en utilisant les instructions centralisées
|
|
|
|
Args:
|
|
contexte: Contexte du ticket à inclure dans le prompt
|
|
prefix: Préfixe optionnel (pour inclure l'image en base64 par exemple)
|
|
|
|
Returns:
|
|
Prompt formaté pour l'analyse d'image
|
|
"""
|
|
return f"""{prefix}
|
|
|
|
CONTEXTE DU TICKET:
|
|
{contexte}
|
|
|
|
Fournis une analyse STRICTEMENT FACTUELLE de l'image avec les sections suivantes:
|
|
{self.instructions_analyse}"""
|
|
|
|
def executer(self, image_path: str, contexte: str) -> Dict[str, Any]:
|
|
"""
|
|
Analyse une image en tenant compte du contexte du ticket
|
|
|
|
Args:
|
|
image_path: Chemin vers l'image à analyser
|
|
contexte: Contexte du ticket (résultat de l'analyse JSON)
|
|
|
|
Returns:
|
|
Dictionnaire contenant l'analyse détaillée de l'image et les métadonnées d'exécution
|
|
"""
|
|
image_name = os.path.basename(image_path)
|
|
logger.info(f"Analyse de l'image: {image_name} avec contexte")
|
|
print(f" AgentImageAnalyser: Analyse de {image_name}")
|
|
|
|
# Vérifier que l'image existe et est accessible
|
|
if not self._verifier_image(image_path):
|
|
error_message = f"L'image n'est pas accessible ou n'est pas valide: {image_name}"
|
|
logger.error(error_message)
|
|
print(f" ERREUR: {error_message}")
|
|
|
|
return {
|
|
"analyse": f"ERREUR: {error_message}. Veuillez vérifier que l'image existe et est valide.",
|
|
"error": True,
|
|
"metadata": {
|
|
"image_path": image_path,
|
|
"image_name": image_name,
|
|
"timestamp": self._get_timestamp(),
|
|
"error": True
|
|
}
|
|
}
|
|
|
|
# Générer le prompt d'analyse avec les instructions centralisées
|
|
prompt = self._generer_prompt_analyse(contexte, "Analyse cette image en tenant compte du contexte suivant:")
|
|
|
|
try:
|
|
logger.info("Envoi de la requête au LLM")
|
|
|
|
# Utiliser la méthode interroger_avec_image au lieu de interroger
|
|
if hasattr(self.llm, "interroger_avec_image"):
|
|
logger.info(f"Utilisation de la méthode interroger_avec_image pour {image_name}")
|
|
response = self.llm.interroger_avec_image(image_path, prompt)
|
|
else:
|
|
# Fallback vers la méthode standard avec base64 si interroger_avec_image n'existe pas
|
|
logger.warning(f"La méthode interroger_avec_image n'existe pas, utilisation du fallback pour {image_name}")
|
|
img_base64 = self._encoder_image_base64(image_path)
|
|
if img_base64:
|
|
# Utiliser le même générateur de prompt avec l'image en base64
|
|
prompt_base64 = self._generer_prompt_analyse(contexte, f"Analyse cette image:\n{img_base64}")
|
|
|
|
response = self.llm.interroger(prompt_base64)
|
|
else:
|
|
error_message = "Impossible d'encoder l'image en base64"
|
|
logger.error(f"Erreur d'analyse pour {image_name}: {error_message}")
|
|
print(f" ERREUR: {error_message}")
|
|
|
|
# Retourner un résultat d'erreur explicite
|
|
return {
|
|
"analyse": f"ERREUR: {error_message}. Veuillez vérifier que l'image est dans un format standard.",
|
|
"error": True,
|
|
"raw_response": "",
|
|
"metadata": {
|
|
"image_path": image_path,
|
|
"image_name": image_name,
|
|
"timestamp": self._get_timestamp(),
|
|
"error": True
|
|
}
|
|
}
|
|
|
|
# Vérifier si la réponse contient des indications que le modèle ne peut pas analyser l'image
|
|
error_phrases = [
|
|
"je ne peux pas directement visualiser",
|
|
"je n'ai pas accès à l'image",
|
|
"je ne peux pas voir l'image",
|
|
"sans accès direct à l'image",
|
|
"je n'ai pas la possibilité de voir",
|
|
"je ne peux pas accéder directement",
|
|
"erreur: impossible d'analyser l'image"
|
|
]
|
|
|
|
# Vérifier si une des phrases d'erreur est présente dans la réponse
|
|
if any(phrase in response.lower() for phrase in error_phrases):
|
|
logger.warning(f"Le modèle indique qu'il ne peut pas analyser l'image: {image_name}")
|
|
error_message = "Le modèle n'a pas pu analyser l'image correctement"
|
|
logger.error(f"Erreur d'analyse pour {image_name}: {error_message}")
|
|
print(f" ERREUR: {error_message}")
|
|
|
|
# Retourner un résultat d'erreur explicite
|
|
return {
|
|
"analyse": f"ERREUR: {error_message}. Veuillez vérifier que le modèle a accès à l'image ou utiliser un modèle différent.",
|
|
"error": True,
|
|
"raw_response": response,
|
|
"metadata": {
|
|
"image_path": image_path,
|
|
"image_name": image_name,
|
|
"timestamp": self._get_timestamp(),
|
|
"error": True
|
|
}
|
|
}
|
|
|
|
logger.info(f"Réponse reçue pour l'image {image_name}: {response[:100]}...")
|
|
|
|
# Créer un dictionnaire de résultat avec l'analyse et les métadonnées
|
|
result = {
|
|
"analyse": response,
|
|
"metadata": {
|
|
"image_path": image_path,
|
|
"image_name": image_name,
|
|
"timestamp": self._get_timestamp(),
|
|
"model_info": {
|
|
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
|
"temperature": self.temperature,
|
|
"top_p": self.top_p,
|
|
"max_tokens": self.max_tokens
|
|
}
|
|
}
|
|
}
|
|
|
|
# Enregistrer l'analyse dans l'historique avec contexte et prompt
|
|
self.ajouter_historique("analyse_image",
|
|
{
|
|
"image_path": image_path,
|
|
"contexte": contexte,
|
|
"prompt": prompt
|
|
},
|
|
response)
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
error_message = f"Erreur lors de l'analyse de l'image: {str(e)}"
|
|
logger.error(error_message)
|
|
print(f" ERREUR: {error_message}")
|
|
|
|
# Retourner un résultat par défaut en cas d'erreur
|
|
return {
|
|
"analyse": f"ERREUR: {error_message}",
|
|
"error": True,
|
|
"metadata": {
|
|
"image_path": image_path,
|
|
"image_name": image_name,
|
|
"timestamp": self._get_timestamp(),
|
|
"error": True
|
|
}
|
|
}
|
|
|
|
def _get_timestamp(self) -> str:
|
|
"""Retourne un timestamp au format YYYYMMDD_HHMMSS"""
|
|
from datetime import datetime
|
|
return datetime.now().strftime("%Y%m%d_%H%M%S") |