llm_ticket3/scripts/analyze_image_contexte.py
2025-04-02 11:43:26 +02:00

384 lines
15 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script d'analyse d'image avec contexte pour les tickets de support.
Extrait des informations pertinentes d'une image en fonction du contexte du ticket.
"""
import os
import sys
import json
import argparse
import logging
from typing import Dict, Any, Optional
# Configuration du logger
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("analyze_image.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger("analyze_image")
try:
from llm import Pixtral
except ImportError:
logger.error("Module LLM non trouvé. Veuillez vous assurer que le répertoire parent est dans PYTHONPATH.")
sys.exit(1)
class ImageAnalyzer:
"""
Analyseur d'image qui extrait des informations pertinentes en fonction du contexte.
"""
def __init__(self, api_key: Optional[str] = None):
"""
Initialise l'analyseur d'image.
Args:
api_key: Clé API pour le modèle de vision
"""
self.llm = Pixtral(api_key=api_key)
# Configurer le modèle de vision
try:
self.llm.model = "pixtral-12b-2409"
self.llm.temperature = 0.3
self.llm.max_tokens = 1024
except Exception as e:
logger.warning(f"Impossible de configurer le modèle: {e}")
self.historique = []
def ajouter_historique(self, action: str, entree: str, resultat: str) -> None:
"""
Ajoute une entrée à l'historique des actions.
Args:
action: Type d'action effectuée
entree: Entrée de l'action
resultat: Résultat de l'action
"""
self.historique.append({
"action": action,
"entree": entree,
"resultat": resultat
})
def analyser_image(self, image_path: str, contexte: Optional[str] = None) -> Dict[str, Any]:
"""
Analyse une image en fonction du contexte donné.
Args:
image_path: Chemin vers l'image à analyser
contexte: Contexte du ticket pour aider à l'analyse
Returns:
Résultat de l'analyse de l'image
"""
if not os.path.exists(image_path):
logger.error(f"Image introuvable: {image_path}")
return {
"success": False,
"erreur": "Image introuvable",
"path": image_path
}
# Vérifier que le fichier est une image
_, extension = os.path.splitext(image_path)
if extension.lower() not in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']:
logger.error(f"Format de fichier non supporté: {extension}")
return {
"success": False,
"erreur": f"Format de fichier non supporté: {extension}",
"path": image_path
}
# Préparer le prompt pour l'analyse
prompt_base = """
Tu es un expert en analyse technique d'interfaces utilisateur et de captures d'écran.
Analyse cette image en détail et extrait les informations suivantes:
1. Type d'image: capture d'écran, photo, schéma, etc.
2. Interface visible: nom du logiciel, type d'interface, fonctionnalités visibles
3. Éléments importants: boutons, menus, messages d'erreur, données visibles
4. Problème potentiel: erreurs, anomalies, incohérences visibles
5. Contexte technique: environnement logiciel, version potentielle, plateforme
Pour les captures d'écran, identifie précisément:
- Le nom exact de la fenêtre/dialogue
- Les champs/formulaires visibles
- Les valeurs/données affichées
- Les messages d'erreur ou d'avertissement
- Les boutons/actions disponibles
Réponds de manière structurée en format Markdown avec des sections claires.
Sois précis et factuel, en te concentrant sur les éléments techniques visibles.
"""
# Ajouter le contexte si disponible
if contexte:
prompt_base += f"""
CONTEXTE DU TICKET:
{contexte}
En tenant compte du contexte ci-dessus, explique également:
- En quoi cette image est pertinente pour le problème décrit
- Quels éléments de l'image correspondent au problème mentionné
- Comment cette image peut aider à résoudre le problème
"""
try:
# Appeler le modèle de vision
try:
resultat = self.llm.analyze_image(image_path, prompt_base)
self.ajouter_historique("analyze_image", os.path.basename(image_path), "Analyse effectuée")
except Exception as e:
logger.error(f"Erreur lors de l'appel au modèle de vision: {str(e)}")
return {
"success": False,
"erreur": f"Erreur lors de l'appel au modèle de vision: {str(e)}",
"path": image_path
}
# Extraire le contenu de la réponse
contenu = resultat.get("content", "")
if not contenu:
logger.error("Réponse vide du modèle de vision")
return {
"success": False,
"erreur": "Réponse vide du modèle de vision",
"path": image_path
}
# Créer le résultat final
resultat_analyse = {
"success": True,
"path": image_path,
"analyse": contenu,
"contexte_fourni": bool(contexte)
}
# Essayer d'extraire des informations structurées à partir de l'analyse
try:
# Rechercher le type d'image
import re
type_match = re.search(r"Type d['']image\s*:\s*([^\n\.]+)", contenu, re.IGNORECASE)
if type_match:
resultat_analyse["type_image"] = type_match.group(1).strip()
# Rechercher l'interface
interface_match = re.search(r'Interface\s*:\s*([^\n\.]+)', contenu, re.IGNORECASE)
interface_match2 = re.search(r'Interface visible\s*:\s*([^\n\.]+)', contenu, re.IGNORECASE)
if interface_match:
resultat_analyse["interface"] = interface_match.group(1).strip()
elif interface_match2:
resultat_analyse["interface"] = interface_match2.group(1).strip()
# Rechercher le problème
probleme_match = re.search(r'Problème\s*:\s*([^\n\.]+)', contenu, re.IGNORECASE)
probleme_match2 = re.search(r'Problème potentiel\s*:\s*([^\n\.]+)', contenu, re.IGNORECASE)
if probleme_match:
resultat_analyse["probleme"] = probleme_match.group(1).strip()
elif probleme_match2:
resultat_analyse["probleme"] = probleme_match2.group(1).strip()
except Exception as e:
logger.warning(f"Impossible d'extraire des informations structurées: {str(e)}")
return resultat_analyse
except Exception as e:
logger.error(f"Erreur lors de l'analyse de l'image {image_path}: {str(e)}")
return {
"success": False,
"erreur": str(e),
"path": image_path
}
def generer_rapport_markdown(self, analyse: Dict[str, Any]) -> str:
"""
Génère un rapport Markdown à partir de l'analyse d'image.
Args:
analyse: Résultat de l'analyse d'image
Returns:
Rapport au format Markdown
"""
if not analyse.get("success", False):
return f"# Échec de l'analyse d'image\n\nErreur: {analyse.get('erreur', 'Inconnue')}\n\nImage: {analyse.get('path', 'Inconnue')}"
# En-tête du rapport
image_path = analyse.get("path", "Inconnue")
image_name = os.path.basename(image_path)
rapport = f"# Analyse de l'image: {image_name}\n\n"
# Ajouter l'analyse brute
rapport += analyse.get("analyse", "Aucune analyse disponible")
# Ajouter des métadonnées
rapport += "\n\n## Métadonnées\n\n"
rapport += f"- **Chemin de l'image**: `{image_path}`\n"
rapport += f"- **Contexte fourni**: {'Oui' if analyse.get('contexte_fourni', False) else 'Non'}\n"
if "type_image" in analyse:
rapport += f"- **Type d'image détecté**: {analyse['type_image']}\n"
if "interface" in analyse:
rapport += f"- **Interface identifiée**: {analyse['interface']}\n"
if "probleme" in analyse:
rapport += f"- **Problème détecté**: {analyse['probleme']}\n"
# Ajouter les paramètres du modèle
rapport += "\n## Paramètres du modèle\n\n"
rapport += f"- **Modèle**: {getattr(self.llm, 'model', 'pixtral-12b-2409')}\n"
rapport += f"- **Température**: {getattr(self.llm, 'temperature', 0.3)}\n"
return rapport
def charger_config():
"""
Charge la configuration depuis config.json.
Returns:
Configuration chargée
"""
config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config.json")
if not os.path.exists(config_path):
logger.warning(f"Fichier de configuration non trouvé: {config_path}")
return {"llm": {"api_key": None}}
try:
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
return config
except Exception as e:
logger.error(f"Erreur lors du chargement de la configuration: {str(e)}")
return {"llm": {"api_key": None}}
def main():
"""
Point d'entrée du script.
"""
parser = argparse.ArgumentParser(description="Analyse une image en fonction du contexte du ticket.")
parser.add_argument("--image", "-i", required=True, help="Chemin vers l'image à analyser")
parser.add_argument("--contexte", "-c", help="Chemin vers un fichier contenant le contexte du ticket")
parser.add_argument("--ticket-info", "-t", help="Chemin vers un fichier ticket_info.json pour extraire le contexte")
parser.add_argument("--output", "-o", help="Chemin du fichier de sortie pour le rapport Markdown (par défaut: <image>_analyse.md)")
parser.add_argument("--format", "-f", choices=["json", "md", "both"], default="both",
help="Format de sortie (json, md, both)")
parser.add_argument("--verbose", "-v", action="store_true", help="Afficher plus d'informations")
args = parser.parse_args()
# Configurer le niveau de log
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
# Vérifier que l'image existe
if not os.path.exists(args.image):
logger.error(f"Image non trouvée: {args.image}")
sys.exit(1)
# Charger le contexte si disponible
contexte = None
if args.contexte and os.path.exists(args.contexte):
try:
with open(args.contexte, 'r', encoding='utf-8') as f:
contexte = f.read()
logger.info(f"Contexte chargé depuis {args.contexte}")
except Exception as e:
logger.warning(f"Impossible de charger le contexte depuis {args.contexte}: {str(e)}")
# Extraire le contexte depuis ticket_info.json si disponible
if not contexte and args.ticket_info and os.path.exists(args.ticket_info):
try:
with open(args.ticket_info, 'r', encoding='utf-8') as f:
ticket_info = json.load(f)
if isinstance(ticket_info, dict):
contexte = f"""
TICKET: {ticket_info.get('code', 'Inconnu')} - {ticket_info.get('name', 'Sans titre')}
DESCRIPTION:
{ticket_info.get('description', 'Aucune description')}
"""
logger.info(f"Contexte extrait depuis {args.ticket_info}")
except Exception as e:
logger.warning(f"Impossible de charger le contexte depuis {args.ticket_info}: {str(e)}")
# Déterminer les chemins de sortie
if not args.output:
output_base = os.path.splitext(args.image)[0]
output_md = f"{output_base}_analyse.md"
output_json = f"{output_base}_analyse.json"
else:
output_base = os.path.splitext(args.output)[0]
output_md = f"{output_base}.md"
output_json = f"{output_base}.json"
# Charger la configuration
config = charger_config()
api_key = config.get("llm", {}).get("api_key")
# Initialiser l'analyseur d'image
analyzer = ImageAnalyzer(api_key=api_key)
try:
# Analyser l'image
resultat = analyzer.analyser_image(args.image, contexte)
if not resultat.get("success", False):
logger.error(f"Échec de l'analyse: {resultat.get('erreur', 'Erreur inconnue')}")
sys.exit(1)
# Générer le rapport Markdown
rapport_md = analyzer.generer_rapport_markdown(resultat)
# Sauvegarder les résultats selon le format demandé
if args.format in ["json", "both"]:
with open(output_json, 'w', encoding='utf-8') as f:
json.dump(resultat, f, indent=2, ensure_ascii=False)
logger.info(f"Résultat JSON sauvegardé: {output_json}")
if args.format in ["md", "both"]:
with open(output_md, 'w', encoding='utf-8') as f:
f.write(rapport_md)
logger.info(f"Rapport Markdown sauvegardé: {output_md}")
# Afficher un résumé
print("\nRésumé de l'analyse:")
print(f"Image: {os.path.basename(args.image)}")
if "type_image" in resultat:
print(f"Type d'image: {resultat['type_image']}")
if "interface" in resultat:
print(f"Interface: {resultat['interface']}")
if "probleme" in resultat:
print(f"Problème: {resultat['probleme']}")
if args.format in ["json", "both"]:
print(f"Résultat JSON: {output_json}")
if args.format in ["md", "both"]:
print(f"Rapport Markdown: {output_md}")
except Exception as e:
logger.error(f"Erreur lors de l'analyse: {str(e)}")
import traceback
logger.debug(f"Détails: {traceback.format_exc()}")
sys.exit(1)
if __name__ == "__main__":
main()