mirror of
https://github.com/Ladebeze66/llm_ticket3.git
synced 2025-12-16 00:36:52 +01:00
384 lines
15 KiB
Python
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() |