mirror of
https://github.com/Ladebeze66/llm_ticket3.git
synced 2025-12-13 09:06:51 +01:00
14:30
This commit is contained in:
parent
31f9e81645
commit
5ddc0fef57
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,10 @@
|
||||
from .base_agent import BaseAgent
|
||||
from typing import Any
|
||||
from typing import Any, Dict
|
||||
import logging
|
||||
import os
|
||||
from PIL import Image
|
||||
import base64
|
||||
import io
|
||||
|
||||
logger = logging.getLogger("AgentImageAnalyser")
|
||||
|
||||
@ -9,16 +13,308 @@ class AgentImageAnalyser(BaseAgent):
|
||||
Agent pour analyser les images et extraire les informations pertinentes.
|
||||
"""
|
||||
def __init__(self, llm):
|
||||
super().__init__("AgentImageAnalyser", llm, "image_analyser")
|
||||
super().__init__("AgentImageAnalyser", llm)
|
||||
|
||||
# Configuration locale de l'agent (remplace AgentConfig)
|
||||
self.temperature = 0.3
|
||||
self.top_p = 0.9
|
||||
self.max_tokens = 1200
|
||||
self.system_prompt = """Tu es un expert en analyse d'images pour le support technique de BRG_Lab.
|
||||
Ta mission est d'analyser des captures d'écran ou des images techniques en tenant compte du contexte du ticket.
|
||||
|
||||
Pour chaque image, structure ton analyse ainsi:
|
||||
1. Description factuelle: Ce que contient l'image (interface, message d'erreur, etc.)
|
||||
2. Éléments techniques importants: Versions, codes d'erreur, paramètres visibles
|
||||
3. Interprétation: Ce que cette image révèle sur le problème client
|
||||
4. Relation avec le ticket: Comment cette image aide à résoudre le problème décrit
|
||||
|
||||
IMPORTANT: Ne commence JAMAIS ta réponse par "Je n'ai pas accès à l'image" ou "Je ne peux pas directement visualiser l'image".
|
||||
Si tu ne peux pas analyser l'image, réponds simplement "ERREUR: Impossible d'analyser l'image".
|
||||
|
||||
Concentre-toi sur les éléments techniques pertinents pour le support logiciel."""
|
||||
|
||||
# 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
|
||||
|
||||
def executer(self, image_description: str, contexte: str) -> str:
|
||||
logger.info(f"Analyse de l'image: {image_description} avec le contexte: {contexte}")
|
||||
prompt = f"Analyse cette image en tenant compte du contexte suivant : {contexte}. Description de l'image : {image_description}"
|
||||
# Appliquer les paramètres
|
||||
if hasattr(self.llm, "configurer"):
|
||||
params = {
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
|
||||
# Ajustements selon le type de modèle
|
||||
if "mistral_medium" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] += 0.05
|
||||
params["max_tokens"] = 1000
|
||||
elif "pixtral" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] -= 0.05
|
||||
elif "ollama" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] += 0.1
|
||||
params.update({
|
||||
"num_ctx": 2048,
|
||||
"repeat_penalty": 1.1,
|
||||
})
|
||||
|
||||
self.llm.configurer(**params)
|
||||
|
||||
def _verifier_image(self, image_path: str) -> bool:
|
||||
"""
|
||||
Vérifie si l'image existe et est accessible
|
||||
|
||||
logger.info("Envoi de la requête au LLM")
|
||||
response = self.llm.interroger(prompt)
|
||||
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
|
||||
|
||||
logger.info(f"Réponse reçue pour l'image {image_description}: {response[:100]}...")
|
||||
self.ajouter_historique("analyse_image", {"image": image_description, "contexte": contexte}, response)
|
||||
return response
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
||||
# Créer un prompt détaillé pour l'analyse d'image avec le contexte du ticket
|
||||
prompt = f"""Analyse cette image en tenant compte du contexte suivant du ticket de support technique:
|
||||
|
||||
CONTEXTE DU TICKET:
|
||||
{contexte}
|
||||
|
||||
Fournis une analyse structurée de l'image avec les sections suivantes:
|
||||
1. Description factuelle de ce que montre l'image
|
||||
2. Éléments techniques identifiables (messages d'erreur, versions, configurations visibles)
|
||||
3. Interprétation de ce que cette image révèle sur le problème décrit dans le ticket
|
||||
4. En quoi cette image est pertinente pour la résolution du problème
|
||||
|
||||
Sois précis et factuel. Ne fais pas de suppositions non fondées.
|
||||
"""
|
||||
|
||||
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:
|
||||
prompt_base64 = f"""Analyse cette image:
|
||||
{img_base64}
|
||||
|
||||
En tenant compte du contexte suivant du ticket de support technique:
|
||||
|
||||
CONTEXTE DU TICKET:
|
||||
{contexte}
|
||||
|
||||
Fournis une analyse structurée de l'image avec les sections suivantes:
|
||||
1. Description factuelle de ce que montre l'image
|
||||
2. Éléments techniques identifiables (messages d'erreur, versions, configurations visibles)
|
||||
3. Interprétation de ce que cette image révèle sur le problème décrit dans le ticket
|
||||
4. En quoi cette image est pertinente pour la résolution du problème
|
||||
|
||||
Sois précis et factuel. Ne fais pas de suppositions non fondées.
|
||||
"""
|
||||
|
||||
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")
|
||||
@ -1,5 +1,10 @@
|
||||
from .base_agent import BaseAgent
|
||||
import logging
|
||||
import os
|
||||
from typing import Dict, Any, Tuple
|
||||
from PIL import Image
|
||||
import base64
|
||||
import io
|
||||
|
||||
logger = logging.getLogger("AgentImageSorter")
|
||||
|
||||
@ -8,19 +13,373 @@ class AgentImageSorter(BaseAgent):
|
||||
Agent pour trier les images et identifier celles qui sont pertinentes.
|
||||
"""
|
||||
def __init__(self, llm):
|
||||
super().__init__("AgentImageSorter", llm, "image_sorter")
|
||||
super().__init__("AgentImageSorter", llm)
|
||||
|
||||
# Configuration locale de l'agent (remplace AgentConfig)
|
||||
self.temperature = 0.2
|
||||
self.top_p = 0.8
|
||||
self.max_tokens = 300
|
||||
self.system_prompt = """Tu es un expert en tri d'images pour le support technique de BRG_Lab.
|
||||
Ta mission est de déterminer si une image est pertinente pour le support technique de logiciels.
|
||||
|
||||
Images PERTINENTES (réponds "oui" ou "pertinent"):
|
||||
- Captures d'écran de logiciels ou d'interfaces
|
||||
- logo BRG_LAB
|
||||
- Référence à "logociel"
|
||||
- Messages d'erreur
|
||||
- Configurations système
|
||||
- Tableaux de bord ou graphiques techniques
|
||||
- Fenêtres de diagnostic
|
||||
|
||||
Images NON PERTINENTES (réponds "non" ou "non pertinent"):
|
||||
- Photos personnelles
|
||||
- Images marketing/promotionnelles
|
||||
- Logos ou images de marque
|
||||
- Paysages, personnes ou objets non liés à l'informatique
|
||||
|
||||
IMPORTANT: Ne commence JAMAIS ta réponse par "Je ne peux pas directement visualiser l'image".
|
||||
Si tu ne peux pas analyser l'image, réponds simplement "ERREUR: Impossible d'analyser l'image".
|
||||
|
||||
Analyse d'abord ce que montre l'image, puis réponds par "oui"/"pertinent" ou "non"/"non pertinent"."""
|
||||
|
||||
# Appliquer la configuration au LLM
|
||||
self._appliquer_config_locale()
|
||||
|
||||
logger.info("AgentImageSorter 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
|
||||
|
||||
def executer(self, image_path: str) -> bool:
|
||||
logger.info(f"Évaluation de la pertinence de l'image: {image_path}")
|
||||
print(f" AgentImageSorter: Évaluation de {image_path}")
|
||||
# Appliquer les paramètres
|
||||
if hasattr(self.llm, "configurer"):
|
||||
params = {
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
|
||||
# Ajustements selon le type de modèle
|
||||
if "mistral_medium" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] += 0.05
|
||||
params["max_tokens"] = 1000
|
||||
elif "pixtral" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] -= 0.05
|
||||
elif "ollama" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] += 0.1
|
||||
params.update({
|
||||
"num_ctx": 2048,
|
||||
"repeat_penalty": 1.1,
|
||||
})
|
||||
|
||||
self.llm.configurer(**params)
|
||||
|
||||
def _verifier_image(self, image_path: str) -> bool:
|
||||
"""
|
||||
Vérifie si l'image existe et est accessible
|
||||
|
||||
prompt = f"Détermine si cette image est pertinente pour un ticket technique: {image_path}"
|
||||
response = self.llm.interroger(prompt)
|
||||
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
|
||||
|
||||
# Simple heuristique: si la réponse contient "oui", "pertinent" ou "important"
|
||||
is_relevant = any(mot in response.lower() for mot in ["oui", "pertinent", "important", "utile", "yes", "relevant"])
|
||||
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 ""
|
||||
|
||||
logger.info(f"Image {image_path} considérée comme {'pertinente' if is_relevant else 'non pertinente'}")
|
||||
self.ajouter_historique("tri_image", image_path, response)
|
||||
return is_relevant
|
||||
def executer(self, image_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Évalue si une image est pertinente pour l'analyse d'un ticket technique
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser
|
||||
|
||||
Returns:
|
||||
Dictionnaire contenant la décision de pertinence, l'analyse et les métadonnées
|
||||
"""
|
||||
image_name = os.path.basename(image_path)
|
||||
logger.info(f"Évaluation de la pertinence de l'image: {image_name}")
|
||||
print(f" AgentImageSorter: Évaluation 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 {
|
||||
"is_relevant": False,
|
||||
"reason": f"Erreur d'accès: {error_message}",
|
||||
"raw_response": "",
|
||||
"error": True,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True
|
||||
}
|
||||
}
|
||||
|
||||
# Utiliser une référence au fichier image que le modèle peut comprendre
|
||||
try:
|
||||
# Préparation du prompt
|
||||
prompt = f"""Est-ce une image pertinente pour un ticket de support technique?
|
||||
Réponds simplement par 'oui' ou 'non' suivi d'une brève explication."""
|
||||
|
||||
# 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:
|
||||
prompt_base64 = f"""Analyse cette image:
|
||||
{img_base64}
|
||||
|
||||
Est-ce une image pertinente pour un ticket de support technique?
|
||||
Réponds simplement par 'oui' ou 'non' suivi d'une brève explication."""
|
||||
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}")
|
||||
|
||||
return {
|
||||
"is_relevant": False,
|
||||
"reason": f"Erreur d'analyse: {error_message}",
|
||||
"raw_response": "",
|
||||
"error": True,
|
||||
"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 {
|
||||
"is_relevant": False,
|
||||
"reason": f"Erreur d'analyse: {error_message}",
|
||||
"raw_response": response,
|
||||
"error": True,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True
|
||||
}
|
||||
}
|
||||
|
||||
# Analyse de la réponse pour déterminer la pertinence
|
||||
is_relevant, reason = self._analyser_reponse(response)
|
||||
|
||||
logger.info(f"Image {image_name} considérée comme {'pertinente' if is_relevant else 'non pertinente'}")
|
||||
print(f" Décision: Image {image_name} {'pertinente' if is_relevant else 'non pertinente'}")
|
||||
|
||||
# Préparer le résultat
|
||||
result = {
|
||||
"is_relevant": is_relevant,
|
||||
"reason": reason,
|
||||
"raw_response": 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 la décision et le raisonnement dans l'historique
|
||||
self.ajouter_historique("tri_image",
|
||||
{
|
||||
"image_path": image_path,
|
||||
"prompt": prompt
|
||||
},
|
||||
{
|
||||
"response": response,
|
||||
"is_relevant": is_relevant,
|
||||
"reason": reason
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'analyse de l'image {image_name}: {str(e)}")
|
||||
print(f" ERREUR: Impossible d'analyser l'image {image_name}")
|
||||
|
||||
# Retourner un résultat par défaut en cas d'erreur
|
||||
return {
|
||||
"is_relevant": False, # Par défaut, considérer non pertinent en cas d'erreur
|
||||
"reason": f"Erreur d'analyse: {str(e)}",
|
||||
"raw_response": "",
|
||||
"error": True,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True
|
||||
}
|
||||
}
|
||||
|
||||
def _analyser_reponse(self, response: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Analyse la réponse du LLM pour déterminer la pertinence et extraire le raisonnement
|
||||
|
||||
Args:
|
||||
response: Réponse brute du LLM
|
||||
|
||||
Returns:
|
||||
Tuple (is_relevant, reason) contenant la décision et le raisonnement
|
||||
"""
|
||||
# Convertir en minuscule pour faciliter la comparaison
|
||||
response_lower = response.lower()
|
||||
|
||||
# Détection directe des réponses négatives en début de texte
|
||||
first_line = response_lower.split('\n')[0] if '\n' in response_lower else response_lower[:50]
|
||||
starts_with_non = first_line.strip().startswith("non") or first_line.strip().startswith("non.")
|
||||
|
||||
# Détection explicite d'une réponse négative au début de la réponse
|
||||
explicit_negative = starts_with_non or any(neg_start in first_line for neg_start in ["non pertinent", "pas pertinent"])
|
||||
|
||||
# Détection explicite d'une réponse positive au début de la réponse
|
||||
explicit_positive = first_line.strip().startswith("oui") or first_line.strip().startswith("pertinent")
|
||||
|
||||
# Si une réponse explicite est détectée, l'utiliser directement
|
||||
if explicit_negative:
|
||||
is_relevant = False
|
||||
elif explicit_positive:
|
||||
is_relevant = True
|
||||
else:
|
||||
# Sinon, utiliser l'analyse par mots-clés
|
||||
# Mots clés positifs forts
|
||||
positive_keywords = ["oui", "pertinent", "pertinente", "utile", "important", "relevante",
|
||||
"capture d'écran", "message d'erreur", "interface logicielle",
|
||||
"configuration", "technique", "diagnostic"]
|
||||
|
||||
# Mots clés négatifs forts
|
||||
negative_keywords = ["non", "pas pertinent", "non pertinente", "inutile", "irrelevant",
|
||||
"photo personnelle", "marketing", "sans rapport", "hors sujet",
|
||||
"décorative", "logo"]
|
||||
|
||||
# Compter les occurrences de mots clés
|
||||
positive_count = sum(1 for kw in positive_keywords if kw in response_lower)
|
||||
negative_count = sum(1 for kw in negative_keywords if kw in response_lower)
|
||||
|
||||
# Heuristique de décision basée sur la prépondérance des mots clés
|
||||
is_relevant = positive_count > negative_count
|
||||
|
||||
# Extraire le raisonnement (les dernières phrases de la réponse)
|
||||
lines = response.split('\n')
|
||||
reason_lines = []
|
||||
for line in reversed(lines):
|
||||
if line.strip():
|
||||
reason_lines.insert(0, line.strip())
|
||||
if len(reason_lines) >= 2: # Prendre les 2 dernières lignes non vides
|
||||
break
|
||||
|
||||
reason = " ".join(reason_lines) if reason_lines else "Décision basée sur l'analyse des mots-clés"
|
||||
|
||||
# Log détaillé de l'analyse
|
||||
logger.debug(f"Analyse de la réponse: \n - Réponse brute: {response[:100]}...\n"
|
||||
f" - Commence par 'non': {starts_with_non}\n"
|
||||
f" - Détection explicite négative: {explicit_negative}\n"
|
||||
f" - Détection explicite positive: {explicit_positive}\n"
|
||||
f" - Décision finale: {'pertinente' if is_relevant else 'non pertinente'}\n"
|
||||
f" - Raison: {reason}")
|
||||
|
||||
return is_relevant, reason
|
||||
|
||||
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")
|
||||
@ -1,25 +1,160 @@
|
||||
from .base_agent import BaseAgent
|
||||
from typing import Dict, Any
|
||||
import logging
|
||||
import json
|
||||
|
||||
logger = logging.getLogger("AgentJsonAnalyser")
|
||||
logger = logging.getLogger("AgentJSONAnalyser")
|
||||
|
||||
class AgentJsonAnalyser(BaseAgent):
|
||||
"""
|
||||
Agent pour analyser les fichiers JSON et extraire les informations pertinentes.
|
||||
Agent pour analyser les tickets JSON et en extraire les informations importantes.
|
||||
"""
|
||||
def __init__(self, llm):
|
||||
super().__init__("AgentJsonAnalyser", llm, "json_analyser")
|
||||
super().__init__("AgentJsonAnalyser", llm)
|
||||
|
||||
# Configuration locale de l'agent (remplace AgentConfig)
|
||||
self.temperature = 0.1 # Besoin d'analyse très précise
|
||||
self.top_p = 0.8
|
||||
self.max_tokens = 1500
|
||||
self.system_prompt = """Tu es un expert en analyse de tickets pour le support informatique de BRG_Lab.
|
||||
Ton rôle est d'extraire et d'analyser les informations importantes des tickets JSON.
|
||||
Organise ta réponse avec les sections suivantes:
|
||||
1. Résumé du problème
|
||||
2. Informations techniques essentielles (logiciels, versions, etc.)
|
||||
3. Contexte client (urgence, impact)
|
||||
4. Pistes d'analyse suggérées
|
||||
|
||||
Sois précis, factuel et synthétique dans ton analyse."""
|
||||
|
||||
# Appliquer la configuration au LLM
|
||||
self._appliquer_config_locale()
|
||||
|
||||
logger.info("AgentJsonAnalyser 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
|
||||
|
||||
def executer(self, ticket_json: Dict) -> str:
|
||||
logger.info(f"Analyse du JSON: {str(ticket_json)[:100]}...")
|
||||
prompt = f"Analyse ce ticket JSON et identifie les éléments importants : {ticket_json}"
|
||||
# Appliquer les paramètres
|
||||
if hasattr(self.llm, "configurer"):
|
||||
params = {
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
|
||||
# Ajustements selon le type de modèle
|
||||
if "mistral_medium" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] += 0.05
|
||||
params["max_tokens"] = 1000
|
||||
elif "pixtral" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] -= 0.05
|
||||
elif "ollama" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] += 0.1
|
||||
params.update({
|
||||
"num_ctx": 2048,
|
||||
"repeat_penalty": 1.1,
|
||||
})
|
||||
|
||||
self.llm.configurer(**params)
|
||||
|
||||
def executer(self, ticket_data: Dict) -> str:
|
||||
"""
|
||||
Analyse un ticket JSON pour en extraire les informations pertinentes
|
||||
|
||||
logger.info("Envoi de la requête au LLM")
|
||||
print(f" AgentJsonAnalyser: Envoi de la requête au LLM {self.llm.modele}")
|
||||
response = self.llm.interroger(prompt)
|
||||
Args:
|
||||
ticket_data: Dictionnaire contenant les données du ticket à analyser
|
||||
|
||||
Returns:
|
||||
Réponse formatée contenant l'analyse du ticket
|
||||
"""
|
||||
logger.info(f"Analyse du ticket: {ticket_data.get('code', 'Inconnu')}")
|
||||
print(f"AgentJsonAnalyser: Analyse du ticket {ticket_data.get('code', 'Inconnu')}")
|
||||
|
||||
logger.info(f"Réponse reçue: {response[:100]}...")
|
||||
self.ajouter_historique("analyse_json", ticket_json, response)
|
||||
return response
|
||||
# Préparer le ticket pour l'analyse
|
||||
ticket_formate = self._formater_ticket_pour_analyse(ticket_data)
|
||||
|
||||
# Créer le prompt pour l'analyse
|
||||
prompt = f"""Analyse ce ticket de support technique et fournis une synthèse structurée:
|
||||
|
||||
{ticket_formate}
|
||||
|
||||
Réponds de manière factuelle, en te basant uniquement sur les informations fournies."""
|
||||
|
||||
try:
|
||||
logger.info("Interrogation du LLM")
|
||||
response = self.llm.interroger(prompt)
|
||||
logger.info(f"Réponse reçue: {len(response)} caractères")
|
||||
print(f" Analyse terminée: {len(response)} caractères")
|
||||
except Exception as e:
|
||||
error_message = f"Erreur lors de l'analyse du ticket: {str(e)}"
|
||||
logger.error(error_message)
|
||||
response = f"ERREUR: {error_message}"
|
||||
print(f" ERREUR: {error_message}")
|
||||
|
||||
# Enregistrer l'historique avec le prompt complet pour la traçabilité
|
||||
self.ajouter_historique("analyse_ticket",
|
||||
{
|
||||
"ticket_id": ticket_data.get("code", "Inconnu"),
|
||||
"prompt": prompt,
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens,
|
||||
"timestamp": self._get_timestamp()
|
||||
},
|
||||
response)
|
||||
|
||||
return response
|
||||
|
||||
def _formater_ticket_pour_analyse(self, ticket_data: Dict) -> str:
|
||||
"""
|
||||
Formate les données du ticket pour l'analyse LLM
|
||||
|
||||
Args:
|
||||
ticket_data: Les données du ticket
|
||||
|
||||
Returns:
|
||||
Représentation textuelle formatée du ticket
|
||||
"""
|
||||
# Initialiser avec les informations de base
|
||||
info = f"## TICKET {ticket_data.get('code', 'Inconnu')}: {ticket_data.get('name', 'Sans titre')}\n\n"
|
||||
|
||||
# Ajouter la description
|
||||
description = ticket_data.get('description', '')
|
||||
if description:
|
||||
info += f"## DESCRIPTION\n{description}\n\n"
|
||||
|
||||
# Ajouter les informations du ticket
|
||||
info += "## INFORMATIONS DU TICKET\n"
|
||||
for key, value in ticket_data.items():
|
||||
if key not in ['code', 'name', 'description', 'messages', 'metadata'] and value:
|
||||
info += f"- {key}: {value}\n"
|
||||
info += "\n"
|
||||
|
||||
# Ajouter les messages (conversations)
|
||||
messages = ticket_data.get('messages', [])
|
||||
if messages:
|
||||
info += "## ÉCHANGES ET MESSAGES\n"
|
||||
for i, msg in enumerate(messages):
|
||||
sender = msg.get('from', 'Inconnu')
|
||||
date = msg.get('date', 'Date inconnue')
|
||||
content = msg.get('content', '')
|
||||
info += f"### Message {i+1} - De: {sender} - Date: {date}\n{content}\n\n"
|
||||
|
||||
# Ajouter les métadonnées techniques si présentes
|
||||
metadata = ticket_data.get('metadata', {})
|
||||
if metadata:
|
||||
info += "## MÉTADONNÉES TECHNIQUES\n"
|
||||
info += json.dumps(metadata, indent=2, ensure_ascii=False)
|
||||
info += "\n"
|
||||
|
||||
return info
|
||||
|
||||
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")
|
||||
@ -2,181 +2,363 @@ import json
|
||||
import os
|
||||
from .base_agent import BaseAgent
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Tuple
|
||||
from typing import Dict, Any, Tuple, Optional
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("AgentReportGenerator")
|
||||
|
||||
class AgentReportGenerator(BaseAgent):
|
||||
"""
|
||||
Agent pour générer un rapport à partir des informations collectées.
|
||||
Agent pour générer un rapport complet à partir des analyses de ticket et d'images
|
||||
"""
|
||||
def __init__(self, llm):
|
||||
super().__init__("AgentReportGenerator", llm, "report_generator")
|
||||
super().__init__("AgentReportGenerator", llm)
|
||||
|
||||
# Configuration locale de l'agent (remplace AgentConfig)
|
||||
self.temperature = 0.4 # Génération de rapport factuelle mais bien structurée
|
||||
self.top_p = 0.9
|
||||
self.max_tokens = 2500
|
||||
self.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG_Lab.
|
||||
Ta mission est de synthétiser toutes les analyses (JSON et images) en un rapport structuré et exploitable.
|
||||
|
||||
Structure ton rapport ainsi:
|
||||
1. Résumé exécutif: Synthèse du problème et des conclusions principales
|
||||
2. Analyse du ticket: Détails extraits du ticket client
|
||||
3. Analyse des images: Résumé des images pertinentes et leur contribution
|
||||
4. Diagnostic technique: Interprétation consolidée des informations
|
||||
5. Recommandations: Actions suggérées pour résoudre le problème
|
||||
|
||||
Chaque section doit être factuelle, précise et orientée solution.
|
||||
Inclus tous les détails techniques pertinents (versions, configurations, messages d'erreur).
|
||||
Assure une traçabilité complète entre les données sources et tes conclusions."""
|
||||
|
||||
# Appliquer la configuration au LLM
|
||||
self._appliquer_config_locale()
|
||||
|
||||
logger.info("AgentReportGenerator 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
|
||||
|
||||
def executer(self, rapport_data: Dict, filename: str) -> Tuple[str, str]:
|
||||
logger.info(f"Génération du rapport pour le fichier: {filename}")
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
# Appliquer les paramètres
|
||||
if hasattr(self.llm, "configurer"):
|
||||
params = {
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
|
||||
# Ajustements selon le type de modèle
|
||||
if "mistral_medium" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] += 0.05
|
||||
params["max_tokens"] = 1000
|
||||
elif "pixtral" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] -= 0.05
|
||||
elif "ollama" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] += 0.1
|
||||
params.update({
|
||||
"num_ctx": 2048,
|
||||
"repeat_penalty": 1.1,
|
||||
})
|
||||
|
||||
self.llm.configurer(**params)
|
||||
|
||||
# Ajouter les métadonnées des LLM utilisés
|
||||
if "metadata" not in rapport_data:
|
||||
rapport_data["metadata"] = {}
|
||||
def executer(self, rapport_data: Dict, rapport_dir: str) -> Tuple[Optional[str], Optional[str]]:
|
||||
"""
|
||||
Génère un rapport à partir des analyses effectuées
|
||||
|
||||
rapport_data["metadata"]["report_generator"] = {
|
||||
Args:
|
||||
rapport_data: Dictionnaire contenant toutes les données analysées
|
||||
rapport_dir: Répertoire où sauvegarder le rapport
|
||||
|
||||
Returns:
|
||||
Tuple (chemin vers le rapport JSON, chemin vers le rapport Markdown)
|
||||
"""
|
||||
# Récupérer l'ID du ticket depuis les données
|
||||
ticket_id = rapport_data.get("ticket_id", "")
|
||||
if not ticket_id and "ticket_data" in rapport_data and isinstance(rapport_data["ticket_data"], dict):
|
||||
ticket_id = rapport_data["ticket_data"].get("code", "")
|
||||
|
||||
if not ticket_id:
|
||||
ticket_id = os.path.basename(os.path.dirname(rapport_dir))
|
||||
if not ticket_id.startswith("T"):
|
||||
# Dernier recours, utiliser le dernier segment du chemin
|
||||
ticket_id = os.path.basename(rapport_dir)
|
||||
|
||||
logger.info(f"Génération du rapport pour le ticket: {ticket_id}")
|
||||
print(f"AgentReportGenerator: Génération du rapport pour {ticket_id}")
|
||||
|
||||
# S'assurer que le répertoire existe
|
||||
if not os.path.exists(rapport_dir):
|
||||
os.makedirs(rapport_dir)
|
||||
|
||||
try:
|
||||
# Préparer les données formatées pour l'analyse
|
||||
ticket_analyse = rapport_data.get("ticket_analyse", "")
|
||||
if not ticket_analyse and "analyse_json" in rapport_data:
|
||||
ticket_analyse = rapport_data.get("analyse_json", "Aucune analyse de ticket disponible")
|
||||
|
||||
# Préparer les données d'analyse d'images
|
||||
images_analyses = []
|
||||
analyse_images_data = rapport_data.get("analyse_images", {})
|
||||
|
||||
# Collecter des informations sur les agents et LLM utilisés
|
||||
agents_info = self._collecter_info_agents(rapport_data)
|
||||
|
||||
# Transformer les analyses d'images en liste structurée pour le prompt
|
||||
for image_path, analyse_data in analyse_images_data.items():
|
||||
image_name = os.path.basename(image_path)
|
||||
|
||||
# Récupérer l'analyse détaillée si elle existe
|
||||
analyse_detail = None
|
||||
if "analysis" in analyse_data and analyse_data["analysis"]:
|
||||
if isinstance(analyse_data["analysis"], dict) and "analyse" in analyse_data["analysis"]:
|
||||
analyse_detail = analyse_data["analysis"]["analyse"]
|
||||
elif isinstance(analyse_data["analysis"], dict):
|
||||
analyse_detail = str(analyse_data["analysis"])
|
||||
|
||||
# Si l'analyse n'a pas été trouvée mais que le tri indique que l'image est pertinente
|
||||
if not analyse_detail and "sorting" in analyse_data and analyse_data["sorting"].get("is_relevant", False):
|
||||
analyse_detail = f"Image marquée comme pertinente. Raison: {analyse_data['sorting'].get('reason', 'Non spécifiée')}"
|
||||
|
||||
# Ajouter l'analyse à la liste si elle existe
|
||||
if analyse_detail:
|
||||
images_analyses.append({
|
||||
"image_name": image_name,
|
||||
"analyse": analyse_detail
|
||||
})
|
||||
|
||||
num_images = len(images_analyses)
|
||||
|
||||
# Créer un prompt détaillé
|
||||
prompt = f"""Génère un rapport technique complet pour le ticket #{ticket_id}, en te basant sur les analyses suivantes.
|
||||
|
||||
## ANALYSE DU TICKET
|
||||
{ticket_analyse}
|
||||
|
||||
## ANALYSES DES IMAGES ({num_images} images)
|
||||
"""
|
||||
|
||||
# Ajouter l'analyse de chaque image
|
||||
for i, img_analyse in enumerate(images_analyses, 1):
|
||||
image_name = img_analyse.get("image_name", f"Image {i}")
|
||||
analyse = img_analyse.get("analyse", "Analyse non disponible")
|
||||
prompt += f"\n### IMAGE {i}: {image_name}\n{analyse}\n"
|
||||
|
||||
prompt += f"""
|
||||
Ton rapport doit être structuré avec les sections suivantes:
|
||||
1. Résumé exécutif
|
||||
2. Analyse détaillée du ticket
|
||||
3. Analyse des images pertinentes
|
||||
4. Diagnostic technique
|
||||
5. Recommandations
|
||||
|
||||
Fournir le rapport en format Markdown, avec des titres clairs et une structure cohérente.
|
||||
Assure-toi d'inclure toutes les informations techniques importantes et les liens entre les différentes analyses.
|
||||
"""
|
||||
|
||||
# Appeler le LLM pour générer le rapport
|
||||
logger.info("Interrogation du LLM pour la génération du rapport")
|
||||
rapport_contenu = self.llm.interroger(prompt)
|
||||
|
||||
# Créer les noms de fichiers pour la sauvegarde
|
||||
timestamp = self._get_timestamp()
|
||||
base_filename = f"{ticket_id}_{timestamp}"
|
||||
json_path = os.path.join(rapport_dir, f"{base_filename}.json")
|
||||
md_path = os.path.join(rapport_dir, f"{base_filename}.md")
|
||||
|
||||
# Collecter les métadonnées du rapport avec détails sur les agents et LLM utilisés
|
||||
metadata = {
|
||||
"timestamp": timestamp,
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens,
|
||||
"system_prompt": self.system_prompt,
|
||||
"agents_info": agents_info
|
||||
}
|
||||
|
||||
# Sauvegarder le rapport au format JSON (données brutes + rapport généré)
|
||||
rapport_data_complet = rapport_data.copy()
|
||||
rapport_data_complet["rapport_genere"] = rapport_contenu
|
||||
rapport_data_complet["metadata"] = metadata
|
||||
|
||||
with open(json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(rapport_data_complet, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# Générer et sauvegarder le rapport au format Markdown basé directement sur le JSON
|
||||
markdown_content = self._generer_markdown_depuis_json(rapport_data_complet)
|
||||
|
||||
with open(md_path, "w", encoding="utf-8") as f:
|
||||
f.write(markdown_content)
|
||||
|
||||
logger.info(f"Rapport sauvegardé: {json_path} et {md_path}")
|
||||
|
||||
except Exception as e:
|
||||
error_message = f"Erreur lors de la génération du rapport: {str(e)}"
|
||||
logger.error(error_message)
|
||||
print(f" ERREUR: {error_message}")
|
||||
return None, None
|
||||
|
||||
# Enregistrer l'historique
|
||||
self.ajouter_historique("generation_rapport",
|
||||
{
|
||||
"rapport_dir": rapport_dir,
|
||||
"rapport_data": rapport_data,
|
||||
"prompt": prompt,
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
},
|
||||
{
|
||||
"json_path": json_path,
|
||||
"md_path": md_path,
|
||||
"rapport_contenu": rapport_contenu[:300] + ("..." if len(rapport_contenu) > 300 else "")
|
||||
})
|
||||
|
||||
message = f"Rapports générés dans: {rapport_dir}"
|
||||
print(f" {message}")
|
||||
|
||||
return json_path, md_path
|
||||
|
||||
def _collecter_info_agents(self, rapport_data: Dict) -> Dict:
|
||||
"""
|
||||
Collecte des informations sur les agents utilisés dans l'analyse
|
||||
|
||||
Args:
|
||||
rapport_data: Données du rapport
|
||||
|
||||
Returns:
|
||||
Dictionnaire contenant les informations sur les agents
|
||||
"""
|
||||
agents_info = {}
|
||||
|
||||
# Informations sur l'agent JSON Analyser
|
||||
if "analyse_json" in rapport_data:
|
||||
json_analysis = rapport_data["analyse_json"]
|
||||
# Vérifier si l'analyse JSON contient des métadonnées
|
||||
if isinstance(json_analysis, dict) and "metadata" in json_analysis:
|
||||
agents_info["json_analyser"] = json_analysis["metadata"]
|
||||
|
||||
# Informations sur les agents d'image
|
||||
if "analyse_images" in rapport_data and rapport_data["analyse_images"]:
|
||||
# Image Sorter
|
||||
sorter_info = {}
|
||||
analyser_info = {}
|
||||
|
||||
for img_path, img_data in rapport_data["analyse_images"].items():
|
||||
# Collecter info du sorter
|
||||
if "sorting" in img_data and isinstance(img_data["sorting"], dict) and "metadata" in img_data["sorting"]:
|
||||
if "model_info" in img_data["sorting"]["metadata"]:
|
||||
sorter_info = img_data["sorting"]["metadata"]["model_info"]
|
||||
|
||||
# Collecter info de l'analyser
|
||||
if "analysis" in img_data and img_data["analysis"] and isinstance(img_data["analysis"], dict) and "metadata" in img_data["analysis"]:
|
||||
if "model_info" in img_data["analysis"]["metadata"]:
|
||||
analyser_info = img_data["analysis"]["metadata"]["model_info"]
|
||||
|
||||
# Une fois qu'on a trouvé les deux, on peut sortir
|
||||
if sorter_info and analyser_info:
|
||||
break
|
||||
|
||||
if sorter_info:
|
||||
agents_info["image_sorter"] = sorter_info
|
||||
if analyser_info:
|
||||
agents_info["image_analyser"] = analyser_info
|
||||
|
||||
# Ajouter les informations de l'agent report generator
|
||||
agents_info["report_generator"] = {
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
"configuration": self.config.to_dict()
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
|
||||
# Créer les dossiers si nécessaire
|
||||
reports_dir = "../reports"
|
||||
json_dir = os.path.join(reports_dir, "json_reports")
|
||||
md_dir = os.path.join(reports_dir, "markdown_reports")
|
||||
os.makedirs(json_dir, exist_ok=True)
|
||||
os.makedirs(md_dir, exist_ok=True)
|
||||
return agents_info
|
||||
|
||||
def _generer_markdown_depuis_json(self, rapport_data: Dict) -> str:
|
||||
"""
|
||||
Génère un rapport Markdown directement à partir des données JSON
|
||||
|
||||
# Sauvegarde JSON
|
||||
json_path = f"{json_dir}/{filename}_{timestamp}.json"
|
||||
with open(json_path, "w", encoding="utf-8") as f_json:
|
||||
json.dump(rapport_data, f_json, ensure_ascii=False, indent=4)
|
||||
Args:
|
||||
rapport_data: Données JSON complètes du rapport
|
||||
|
||||
Returns:
|
||||
Contenu Markdown du rapport
|
||||
"""
|
||||
ticket_id = rapport_data.get("ticket_id", "")
|
||||
timestamp = rapport_data.get("metadata", {}).get("timestamp", self._get_timestamp())
|
||||
|
||||
logger.info(f"Rapport JSON sauvegardé à: {json_path}")
|
||||
# Contenu de base du rapport (partie générée par le LLM)
|
||||
rapport_contenu = rapport_data.get("rapport_genere", "")
|
||||
|
||||
# Sauvegarde Markdown
|
||||
md_path = f"{md_dir}/{filename}_{timestamp}.md"
|
||||
with open(md_path, "w", encoding="utf-8") as f_md:
|
||||
# En-tête du rapport
|
||||
ticket_id = rapport_data.get("ticket_id", filename)
|
||||
f_md.write(f"# Rapport d'Analyse du Ticket {ticket_id}\n\n")
|
||||
f_md.write(f"*Généré le: {datetime.now().strftime('%d/%m/%Y à %H:%M:%S')}*\n\n")
|
||||
|
||||
# Résumé
|
||||
metadata = rapport_data.get("metadata", {})
|
||||
etapes = metadata.get("etapes", [])
|
||||
f_md.write("## Résumé\n\n")
|
||||
f_md.write(f"- **ID Ticket**: {ticket_id}\n")
|
||||
f_md.write(f"- **Date d'analyse**: {metadata.get('timestamp_debut', timestamp)}\n")
|
||||
f_md.write(f"- **Nombre d'étapes**: {len(etapes)}\n\n")
|
||||
|
||||
# Vue d'ensemble des agents et modèles utilisés
|
||||
f_md.write("## Modèles et Paramètres Utilisés\n\n")
|
||||
f_md.write("### Vue d'ensemble\n\n")
|
||||
f_md.write("| Agent | Modèle | Température | Top-P | Max Tokens |\n")
|
||||
f_md.write("|-------|--------|-------------|-------|------------|\n")
|
||||
|
||||
for agent_name in ["json_agent", "image_sorter", "image_analyser", "report_generator"]:
|
||||
agent_info = metadata.get(agent_name, {})
|
||||
if agent_info.get("status") == "non configuré":
|
||||
continue
|
||||
|
||||
model = agent_info.get("model", "N/A")
|
||||
config = agent_info.get("configuration", {}).get("config", {})
|
||||
temp = config.get("temperature", "N/A")
|
||||
top_p = config.get("top_p", "N/A")
|
||||
max_tokens = config.get("max_tokens", "N/A")
|
||||
|
||||
f_md.write(f"| {agent_name} | {model} | {temp} | {top_p} | {max_tokens} |\n")
|
||||
|
||||
f_md.write("\n")
|
||||
|
||||
# Détails des paramètres par agent
|
||||
f_md.write("### Détails des Paramètres\n\n")
|
||||
f_md.write("```json\n")
|
||||
agents_config = {}
|
||||
for agent_name in ["json_agent", "image_sorter", "image_analyser", "report_generator"]:
|
||||
if agent_name in metadata and "configuration" in metadata[agent_name]:
|
||||
agents_config[agent_name] = metadata[agent_name]["configuration"]
|
||||
f_md.write(json.dumps(agents_config, indent=2))
|
||||
f_md.write("\n```\n\n")
|
||||
|
||||
# Détails des prompts système
|
||||
f_md.write("### Prompts Système\n\n")
|
||||
for agent_name in ["json_agent", "image_sorter", "image_analyser", "report_generator"]:
|
||||
agent_info = metadata.get(agent_name, {})
|
||||
if agent_info.get("status") == "non configuré":
|
||||
continue
|
||||
|
||||
config = agent_info.get("configuration", {}).get("config", {})
|
||||
prompt = config.get("system_prompt", "")
|
||||
if prompt:
|
||||
f_md.write(f"**{agent_name}**:\n")
|
||||
f_md.write("```\n")
|
||||
f_md.write(prompt)
|
||||
f_md.write("\n```\n\n")
|
||||
|
||||
# Étapes d'analyse
|
||||
f_md.write("## Étapes d'Analyse\n\n")
|
||||
for i, etape in enumerate(etapes, 1):
|
||||
agent = etape.get("agent", "")
|
||||
action = etape.get("action", "")
|
||||
timestamp = etape.get("timestamp", "")
|
||||
image = etape.get("image", "")
|
||||
|
||||
title = f"### {i}. {agent.replace('_', ' ').title()}"
|
||||
if image:
|
||||
title += f" - Image: {image}"
|
||||
|
||||
f_md.write(f"{title}\n\n")
|
||||
f_md.write(f"- **Action**: {action}\n")
|
||||
f_md.write(f"- **Timestamp**: {timestamp}\n")
|
||||
|
||||
# Métadonnées du modèle pour cette étape
|
||||
etape_metadata = etape.get("metadata", {})
|
||||
model = etape_metadata.get("model", "")
|
||||
config = etape_metadata.get("configuration", {}).get("config", {})
|
||||
duree = etape_metadata.get("duree_traitement", "")
|
||||
|
||||
f_md.write(f"- **Modèle**: {model}\n")
|
||||
if duree:
|
||||
f_md.write(f"- **Durée de traitement**: {duree}\n")
|
||||
|
||||
# Paramètres spécifiques pour cette exécution
|
||||
if config:
|
||||
f_md.write("- **Paramètres**:\n")
|
||||
f_md.write("```json\n")
|
||||
params = {k: v for k, v in config.items() if k != "system_prompt"}
|
||||
f_md.write(json.dumps(params, indent=2))
|
||||
f_md.write("\n```\n")
|
||||
|
||||
# Input/Output (limités en taille)
|
||||
input_data = etape.get("input", "")
|
||||
output_data = etape.get("output", "")
|
||||
|
||||
if input_data:
|
||||
f_md.write("- **Entrée**:\n")
|
||||
f_md.write("```\n")
|
||||
f_md.write(str(input_data)[:300] + ("..." if len(str(input_data)) > 300 else ""))
|
||||
f_md.write("\n```\n")
|
||||
|
||||
if output_data:
|
||||
f_md.write("- **Sortie**:\n")
|
||||
f_md.write("```\n")
|
||||
f_md.write(str(output_data)[:300] + ("..." if len(str(output_data)) > 300 else ""))
|
||||
f_md.write("\n```\n")
|
||||
|
||||
f_md.write("\n")
|
||||
|
||||
# Résultats des analyses
|
||||
if "analyse_json" in rapport_data:
|
||||
f_md.write("## Résultat de l'Analyse JSON\n\n")
|
||||
f_md.write("```\n")
|
||||
f_md.write(str(rapport_data["analyse_json"]))
|
||||
f_md.write("\n```\n\n")
|
||||
|
||||
# Analyses des images
|
||||
if "analyse_images" in rapport_data and rapport_data["analyse_images"]:
|
||||
f_md.write("## Analyses des Images\n\n")
|
||||
for image_path, analysis in rapport_data["analyse_images"].items():
|
||||
image_name = os.path.basename(image_path)
|
||||
f_md.write(f"### Image: {image_name}\n\n")
|
||||
f_md.write("```\n")
|
||||
f_md.write(str(analysis))
|
||||
f_md.write("\n```\n\n")
|
||||
|
||||
# Informations supplémentaires
|
||||
f_md.write("## Informations Supplémentaires\n\n")
|
||||
f_md.write(f"Pour plus de détails, consultez le fichier JSON complet: `{os.path.basename(json_path)}`\n\n")
|
||||
# Entête du document
|
||||
markdown = f"# Rapport d'analyse du ticket #{ticket_id}\n\n"
|
||||
markdown += f"*Généré le: {timestamp}*\n\n"
|
||||
|
||||
logger.info(f"Rapport généré: {md_path}")
|
||||
self.ajouter_historique("generation_rapport", filename, f"Rapport généré: {md_path}")
|
||||
return json_path, md_path
|
||||
# Ajouter le rapport principal
|
||||
markdown += rapport_contenu + "\n\n"
|
||||
|
||||
# Ajouter les informations techniques (agents, LLM, paramètres)
|
||||
markdown += "## Informations techniques\n\n"
|
||||
|
||||
# Ajouter les informations sur les agents utilisés
|
||||
agents_info = rapport_data.get("metadata", {}).get("agents_info", {})
|
||||
if agents_info:
|
||||
markdown += "### Agents et modèles utilisés\n\n"
|
||||
|
||||
# Agent JSON Analyser
|
||||
if "json_analyser" in agents_info:
|
||||
info = agents_info["json_analyser"]
|
||||
markdown += "#### Agent d'analyse de texte\n"
|
||||
markdown += f"- **Modèle**: {info.get('model', 'Non spécifié')}\n"
|
||||
markdown += f"- **Température**: {info.get('temperature', 'Non spécifiée')}\n"
|
||||
markdown += f"- **Top-p**: {info.get('top_p', 'Non spécifié')}\n"
|
||||
markdown += f"- **Max tokens**: {info.get('max_tokens', 'Non spécifié')}\n\n"
|
||||
|
||||
# Agent Image Sorter
|
||||
if "image_sorter" in agents_info:
|
||||
info = agents_info["image_sorter"]
|
||||
markdown += "#### Agent de tri d'images\n"
|
||||
markdown += f"- **Modèle**: {info.get('model', 'Non spécifié')}\n"
|
||||
markdown += f"- **Température**: {info.get('temperature', 'Non spécifiée')}\n"
|
||||
markdown += f"- **Top-p**: {info.get('top_p', 'Non spécifié')}\n"
|
||||
markdown += f"- **Max tokens**: {info.get('max_tokens', 'Non spécifié')}\n\n"
|
||||
|
||||
# Agent Image Analyser
|
||||
if "image_analyser" in agents_info:
|
||||
info = agents_info["image_analyser"]
|
||||
markdown += "#### Agent d'analyse d'images\n"
|
||||
markdown += f"- **Modèle**: {info.get('model', 'Non spécifié')}\n"
|
||||
markdown += f"- **Température**: {info.get('temperature', 'Non spécifiée')}\n"
|
||||
markdown += f"- **Top-p**: {info.get('top_p', 'Non spécifié')}\n"
|
||||
markdown += f"- **Max tokens**: {info.get('max_tokens', 'Non spécifié')}\n\n"
|
||||
|
||||
# Agent Report Generator
|
||||
if "report_generator" in agents_info:
|
||||
info = agents_info["report_generator"]
|
||||
markdown += "#### Agent de génération de rapport\n"
|
||||
markdown += f"- **Modèle**: {info.get('model', 'Non spécifié')}\n"
|
||||
markdown += f"- **Température**: {info.get('temperature', 'Non spécifiée')}\n"
|
||||
markdown += f"- **Top-p**: {info.get('top_p', 'Non spécifié')}\n"
|
||||
markdown += f"- **Max tokens**: {info.get('max_tokens', 'Non spécifié')}\n\n"
|
||||
|
||||
# Statistiques d'analyse
|
||||
markdown += "### Statistiques\n\n"
|
||||
if "metadata" in rapport_data:
|
||||
metadata = rapport_data["metadata"]
|
||||
if "images_analysees" in metadata:
|
||||
markdown += f"- **Images analysées**: {metadata['images_analysees']}\n"
|
||||
if "images_pertinentes" in metadata:
|
||||
markdown += f"- **Images pertinentes**: {metadata['images_pertinentes']}\n"
|
||||
|
||||
return markdown
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""Retourne un timestamp au format YYYYMMDD_HHMMSS"""
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
@ -1,59 +1,19 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Dict, Any, Optional
|
||||
from .utils.agent_config import AgentConfig
|
||||
|
||||
class BaseAgent(ABC):
|
||||
"""
|
||||
Classe de base pour les agents.
|
||||
"""
|
||||
def __init__(self, nom: str, llm: Any, role: Optional[str] = None):
|
||||
def __init__(self, nom: str, llm: Any):
|
||||
self.nom = nom
|
||||
self.llm = llm
|
||||
self.historique: List[Dict[str, Any]] = []
|
||||
|
||||
# Détecter le type de modèle
|
||||
model_type = self._detecter_model_type()
|
||||
|
||||
# Définir le rôle par défaut si non spécifié
|
||||
agent_role = role if role is not None else nom.lower().replace("agent", "").strip()
|
||||
|
||||
# Créer la configuration d'agent
|
||||
self.config = AgentConfig(agent_role, model_type)
|
||||
|
||||
# Appliquer les paramètres au LLM
|
||||
self._appliquer_config()
|
||||
|
||||
def _detecter_model_type(self) -> str:
|
||||
"""
|
||||
Détecte le type de modèle LLM.
|
||||
"""
|
||||
llm_class_name = self.llm.__class__.__name__.lower()
|
||||
if "mistral" in llm_class_name:
|
||||
return "mistral"
|
||||
elif "pixtral" in llm_class_name:
|
||||
return "pixtral"
|
||||
elif "ollama" in llm_class_name:
|
||||
return "ollama"
|
||||
else:
|
||||
return "generic"
|
||||
|
||||
def _appliquer_config(self) -> None:
|
||||
"""
|
||||
Applique la configuration au modèle LLM.
|
||||
"""
|
||||
# Appliquer le prompt système
|
||||
if hasattr(self.llm, "prompt_system"):
|
||||
self.llm.prompt_system = self.config.get_system_prompt()
|
||||
|
||||
# Appliquer les paramètres
|
||||
if hasattr(self.llm, "configurer"):
|
||||
self.llm.configurer(**self.config.get_params())
|
||||
|
||||
def ajouter_historique(self, action: str, input_data: Any, output_data: Any):
|
||||
# Ajouter les informations sur le modèle et les paramètres utilisés
|
||||
metadata = {
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
"configuration": self.config.to_dict(),
|
||||
"duree_traitement": str(getattr(self.llm, "dureeTraitement", "N/A"))
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
from .agent_config import AgentConfig
|
||||
|
||||
__all__ = ['AgentConfig']
|
||||
@ -1,114 +0,0 @@
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
class AgentConfig:
|
||||
"""
|
||||
Classe pour configurer les paramètres LLM par rôle d'agent.
|
||||
Cette classe permet d'harmoniser les paramètres entre différents LLM.
|
||||
"""
|
||||
|
||||
# Configurations par défaut pour chaque rôle d'agent
|
||||
DEFAULT_CONFIGS = {
|
||||
"json_analyser": {
|
||||
"temperature": 0.2, # Besoin d'analyse précise
|
||||
"top_p": 0.9,
|
||||
"max_tokens": 1500,
|
||||
"system_prompt": "Tu es un assistant spécialisé dans l'analyse de tickets JSON. Extrais les informations pertinentes et structure ta réponse de manière claire.",
|
||||
},
|
||||
"image_sorter": {
|
||||
"temperature": 0.3, # Décision de classification binaire
|
||||
"top_p": 0.9,
|
||||
"max_tokens": 200,
|
||||
"system_prompt": "Tu es un assistant spécialisé dans le tri d'images. Tu dois décider si une image est pertinente ou non pour BRG_Lab.",
|
||||
},
|
||||
"image_analyser": {
|
||||
"temperature": 0.5, # Analyse créative mais factuelle
|
||||
"top_p": 0.95,
|
||||
"max_tokens": 1000,
|
||||
"system_prompt": "Tu es un assistant spécialisé dans l'analyse d'images. Décris ce que tu vois en tenant compte du contexte fourni.",
|
||||
},
|
||||
"report_generator": {
|
||||
"temperature": 0.7, # Génération de rapport plus créative
|
||||
"top_p": 1.0,
|
||||
"max_tokens": 2000,
|
||||
"system_prompt": "Tu es un assistant spécialisé dans la génération de rapports. Synthétise les informations fournies de manière claire et professionnelle.",
|
||||
}
|
||||
}
|
||||
|
||||
# Paramètres spécifiques à chaque type de modèle pour harmoniser les performances
|
||||
MODEL_ADJUSTMENTS = {
|
||||
"mistral": {}, # Pas d'ajustement nécessaire, modèle de référence
|
||||
"pixtral": {
|
||||
"temperature": -0.1, # Légèrement plus conservateur
|
||||
},
|
||||
"ollama": {
|
||||
"temperature": +0.1, # Légèrement plus créatif
|
||||
"num_ctx": 2048,
|
||||
"repeat_penalty": 1.1,
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, role: str, model_type: str = "mistral"):
|
||||
"""
|
||||
Initialise la configuration avec les paramètres appropriés pour le rôle et le modèle.
|
||||
|
||||
Args:
|
||||
role: Rôle de l'agent ('json_analyser', 'image_sorter', etc.)
|
||||
model_type: Type de modèle ('mistral', 'pixtral', 'ollama', etc.)
|
||||
"""
|
||||
self.role = role
|
||||
self.model_type = model_type.lower()
|
||||
|
||||
# Récupérer la configuration de base pour ce rôle
|
||||
if role in self.DEFAULT_CONFIGS:
|
||||
self.config = self.DEFAULT_CONFIGS[role].copy()
|
||||
else:
|
||||
# Configuration par défaut
|
||||
self.config = {
|
||||
"temperature": 0.5,
|
||||
"top_p": 0.9,
|
||||
"max_tokens": 1000,
|
||||
"system_prompt": ""
|
||||
}
|
||||
|
||||
# Appliquer les ajustements spécifiques au modèle
|
||||
if model_type.lower() in self.MODEL_ADJUSTMENTS:
|
||||
adjustments = self.MODEL_ADJUSTMENTS[model_type.lower()]
|
||||
for key, adjustment in adjustments.items():
|
||||
if key in self.config:
|
||||
if isinstance(adjustment, (int, float)) and isinstance(self.config[key], (int, float)):
|
||||
self.config[key] += adjustment # Ajustement relatif
|
||||
else:
|
||||
self.config[key] = adjustment # Remplacement direct
|
||||
else:
|
||||
self.config[key] = adjustment # Ajout d'un nouveau paramètre
|
||||
|
||||
def get_params(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Retourne tous les paramètres sauf le prompt système.
|
||||
"""
|
||||
params = self.config.copy()
|
||||
if "system_prompt" in params:
|
||||
params.pop("system_prompt")
|
||||
return params
|
||||
|
||||
def get_system_prompt(self) -> str:
|
||||
"""
|
||||
Retourne le prompt système.
|
||||
"""
|
||||
return self.config.get("system_prompt", "")
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Retourne toute la configuration sous forme de dictionnaire.
|
||||
"""
|
||||
return {
|
||||
"role": self.role,
|
||||
"model_type": self.model_type,
|
||||
"config": self.config
|
||||
}
|
||||
|
||||
def update(self, **kwargs) -> None:
|
||||
"""
|
||||
Met à jour la configuration avec les nouveaux paramètres.
|
||||
"""
|
||||
self.config.update(kwargs)
|
||||
@ -13,11 +13,10 @@ class BaseLLM(abc.ABC):
|
||||
self.params: Dict[str, Any] = {
|
||||
"temperature": 0.8,
|
||||
"top_p": 0.9,
|
||||
"top_k": 40,
|
||||
"max_tokens": 1000,
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 0,
|
||||
"stop": None
|
||||
"stop": []
|
||||
}
|
||||
|
||||
self.dureeTraitement: timedelta = timedelta()
|
||||
@ -46,6 +45,20 @@ class BaseLLM(abc.ABC):
|
||||
def _traiter_reponse(self, reponse: requests.Response) -> str:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def interroger_avec_image(self, image_path: str, question: str) -> str:
|
||||
"""
|
||||
Méthode abstraite pour interroger le LLM avec une image
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser
|
||||
question: Question ou instructions pour l'analyse de l'image
|
||||
|
||||
Returns:
|
||||
Réponse du LLM
|
||||
"""
|
||||
pass
|
||||
|
||||
def interroger(self, question: str) -> str:
|
||||
url = self.urlBase() + self.urlFonction()
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from .base_llm import BaseLLM
|
||||
import requests
|
||||
import os
|
||||
|
||||
class MistralLarge(BaseLLM):
|
||||
|
||||
@ -29,3 +30,25 @@ class MistralLarge(BaseLLM):
|
||||
def _traiter_reponse(self, reponse: requests.Response) -> str:
|
||||
data = reponse.json()
|
||||
return data["choices"][0]["message"]["content"]
|
||||
|
||||
def interroger_avec_image(self, image_path: str, question: str) -> str:
|
||||
"""
|
||||
Ce modèle ne supporte pas directement l'analyse d'images, cette méthode est fournie
|
||||
pour la compatibilité avec l'interface BaseLLM mais utilise uniquement le texte.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser (ignoré par ce modèle)
|
||||
question: Question ou instructions pour l'analyse
|
||||
|
||||
Returns:
|
||||
Réponse du modèle à la question, en indiquant que l'analyse d'image n'est pas supportée
|
||||
"""
|
||||
image_name = os.path.basename(image_path)
|
||||
prompt = f"""[Note: Ce modèle n'a pas accès à l'image demandée: {image_name}]
|
||||
|
||||
Question concernant l'image:
|
||||
{question}
|
||||
|
||||
Veuillez noter que je ne peux pas traiter directement les images. Voici une réponse basée uniquement sur le texte de la question."""
|
||||
|
||||
return self.interroger(prompt)
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
from .base_llm import BaseLLM
|
||||
import requests
|
||||
import os
|
||||
import base64
|
||||
from PIL import Image
|
||||
import io
|
||||
from datetime import datetime
|
||||
|
||||
class MistralLargePixtral(BaseLLM):
|
||||
|
||||
@ -29,3 +34,116 @@ class MistralLargePixtral(BaseLLM):
|
||||
def _traiter_reponse(self, reponse: requests.Response) -> str:
|
||||
data = reponse.json()
|
||||
return data["choices"][0]["message"]["content"]
|
||||
|
||||
def _encoder_image_base64(self, image_path: str) -> str:
|
||||
"""
|
||||
Encode une image en base64 pour l'API.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à encoder
|
||||
|
||||
Returns:
|
||||
Image encodée en base64 avec préfixe approprié
|
||||
"""
|
||||
if not os.path.isfile(image_path):
|
||||
raise FileNotFoundError(f"L'image {image_path} n'a pas été trouvée")
|
||||
|
||||
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
|
||||
encoded_string = base64.b64encode(buffer.read()).decode("utf-8")
|
||||
except Exception as e:
|
||||
# Si échec avec PIL, essayer avec la méthode simple
|
||||
with open(image_path, "rb") as image_file:
|
||||
encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
|
||||
|
||||
# Détecter le type de fichier
|
||||
file_extension = os.path.splitext(image_path)[1].lower()
|
||||
if file_extension in ['.jpg', '.jpeg']:
|
||||
mime_type = 'image/jpeg'
|
||||
elif file_extension == '.png':
|
||||
mime_type = 'image/png'
|
||||
elif file_extension == '.gif':
|
||||
mime_type = 'image/gif'
|
||||
elif file_extension in ['.webp']:
|
||||
mime_type = 'image/webp'
|
||||
else:
|
||||
# Par défaut, on suppose JPEG
|
||||
mime_type = 'image/jpeg'
|
||||
|
||||
return f"data:{mime_type};base64,{encoded_string}"
|
||||
|
||||
def interroger_avec_image(self, image_path: str, question: str) -> str:
|
||||
"""
|
||||
Analyse une image avec le modèle Pixtral
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser
|
||||
question: Question ou instructions pour l'analyse
|
||||
|
||||
Returns:
|
||||
Réponse générée par le modèle
|
||||
"""
|
||||
url = self.urlBase() + self.urlFonction()
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {self.cleAPI()}"
|
||||
}
|
||||
|
||||
try:
|
||||
# Encoder l'image en base64
|
||||
encoded_image = self._encoder_image_base64(image_path)
|
||||
|
||||
# Préparer le contenu avec l'image
|
||||
contenu = {
|
||||
"model": self.modele,
|
||||
"messages": [
|
||||
{"role": "system", "content": self.prompt_system},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": question},
|
||||
{"type": "image_url", "image_url": {"url": encoded_image}}
|
||||
]
|
||||
}
|
||||
],
|
||||
**self.params
|
||||
}
|
||||
|
||||
self.heureDepart = datetime.now()
|
||||
|
||||
# Envoyer la requête
|
||||
response = requests.post(url=url, headers=headers, json=contenu, timeout=180) # Timeout plus long pour les images
|
||||
|
||||
self.heureFin = datetime.now()
|
||||
if self.heureDepart is not None and self.heureFin is not None:
|
||||
self.dureeTraitement = self.heureFin - self.heureDepart
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
self.reponseErreur = False
|
||||
return self._traiter_reponse(response)
|
||||
else:
|
||||
self.reponseErreur = True
|
||||
return f"Erreur API ({response.status_code}): {response.text}"
|
||||
|
||||
except Exception as e:
|
||||
self.heureFin = datetime.now()
|
||||
if self.heureDepart is not None and self.heureFin is not None:
|
||||
self.dureeTraitement = self.heureFin - self.heureDepart
|
||||
self.reponseErreur = True
|
||||
return f"Erreur lors de l'analyse de l'image: {str(e)}"
|
||||
|
||||
@ -1,22 +1,28 @@
|
||||
from .base_llm import BaseLLM
|
||||
import requests
|
||||
import os
|
||||
|
||||
class MistralMedium(BaseLLM):
|
||||
|
||||
"""
|
||||
Classe pour interagir avec le modèle Mistral Medium
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("mistral-medium-latest")
|
||||
self.configurer(temperature=0.2, top_p=1)
|
||||
super().__init__("mistral-medium")
|
||||
self.configurer(temperature=0.3, top_p=0.9) # Paramètres par défaut
|
||||
|
||||
def urlBase(self) -> str:
|
||||
return "https://api.mistral.ai/v1/"
|
||||
|
||||
def cleAPI(self) -> str:
|
||||
return "2iGzTzE9csRQ9IoASoUjplHwEjA200Vh"
|
||||
return "2iGzTzE9csRQ9IoASoUjplHwEjA200Vh" # Clé API d'exemple, à remplacer par la vraie clé
|
||||
|
||||
def urlFonction(self) -> str:
|
||||
return "chat/completions"
|
||||
|
||||
def _preparer_contenu(self, question: str) -> dict:
|
||||
"""
|
||||
Prépare le contenu de la requête pour l'API Mistral
|
||||
"""
|
||||
return {
|
||||
"model": self.modele,
|
||||
"messages": [
|
||||
@ -27,5 +33,33 @@ class MistralMedium(BaseLLM):
|
||||
}
|
||||
|
||||
def _traiter_reponse(self, reponse: requests.Response) -> str:
|
||||
data = reponse.json()
|
||||
return data["choices"][0]["message"]["content"]
|
||||
"""
|
||||
Traite la réponse de l'API Mistral
|
||||
"""
|
||||
try:
|
||||
data = reponse.json()
|
||||
return data["choices"][0]["message"]["content"]
|
||||
except Exception as e:
|
||||
return f"Erreur lors du traitement de la réponse: {str(e)}\nRéponse brute: {reponse.text}"
|
||||
|
||||
def interroger_avec_image(self, image_path: str, question: str) -> str:
|
||||
"""
|
||||
Ce modèle ne supporte pas directement l'analyse d'images, cette méthode est fournie
|
||||
pour la compatibilité avec l'interface BaseLLM mais utilise uniquement le texte.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser (ignoré par ce modèle)
|
||||
question: Question ou instructions pour l'analyse
|
||||
|
||||
Returns:
|
||||
Réponse du modèle à la question, en indiquant que l'analyse d'image n'est pas supportée
|
||||
"""
|
||||
image_name = os.path.basename(image_path)
|
||||
prompt = f"""[Note: Ce modèle n'a pas accès à l'image demandée: {image_name}]
|
||||
|
||||
Question concernant l'image:
|
||||
{question}
|
||||
|
||||
Veuillez noter que je ne peux pas traiter directement les images. Voici une réponse basée uniquement sur le texte de la question."""
|
||||
|
||||
return self.interroger(prompt)
|
||||
|
||||
@ -2,6 +2,7 @@ from .base_llm import BaseLLM
|
||||
import requests
|
||||
from datetime import timedelta
|
||||
from typing import Dict, Any
|
||||
import os
|
||||
|
||||
class Ollama(BaseLLM):
|
||||
"""
|
||||
@ -90,3 +91,25 @@ class Ollama(BaseLLM):
|
||||
for key, value in kwargs.items():
|
||||
if key in self.params:
|
||||
self.params[key] = value
|
||||
|
||||
def interroger_avec_image(self, image_path: str, question: str) -> str:
|
||||
"""
|
||||
Cette implémentation d'Ollama ne supporte pas directement l'analyse d'images.
|
||||
Cette méthode est fournie pour la compatibilité avec l'interface BaseLLM mais utilise uniquement le texte.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser (ignoré par ce modèle)
|
||||
question: Question ou instructions pour l'analyse
|
||||
|
||||
Returns:
|
||||
Réponse du modèle à la question, en indiquant que l'analyse d'image n'est pas supportée
|
||||
"""
|
||||
image_name = os.path.basename(image_path)
|
||||
prompt = f"""[Note: Ce modèle n'a pas accès à l'image demandée: {image_name}]
|
||||
|
||||
Question concernant l'image:
|
||||
{question}
|
||||
|
||||
Veuillez noter que je ne peux pas traiter directement les images. Voici une réponse basée uniquement sur le texte de la question."""
|
||||
|
||||
return self.interroger(prompt)
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
from .base_llm import BaseLLM
|
||||
import requests
|
||||
import os
|
||||
import base64
|
||||
from PIL import Image
|
||||
import io
|
||||
from datetime import datetime
|
||||
|
||||
class Pixtral12b(BaseLLM):
|
||||
|
||||
@ -29,3 +34,116 @@ class Pixtral12b(BaseLLM):
|
||||
def _traiter_reponse(self, reponse: requests.Response) -> str:
|
||||
data = reponse.json()
|
||||
return data["choices"][0]["message"]["content"]
|
||||
|
||||
def _encoder_image_base64(self, image_path: str) -> str:
|
||||
"""
|
||||
Encode une image en base64 pour l'API.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à encoder
|
||||
|
||||
Returns:
|
||||
Image encodée en base64 avec préfixe approprié
|
||||
"""
|
||||
if not os.path.isfile(image_path):
|
||||
raise FileNotFoundError(f"L'image {image_path} n'a pas été trouvée")
|
||||
|
||||
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
|
||||
encoded_string = base64.b64encode(buffer.read()).decode("utf-8")
|
||||
except Exception as e:
|
||||
# Si échec avec PIL, essayer avec la méthode simple
|
||||
with open(image_path, "rb") as image_file:
|
||||
encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
|
||||
|
||||
# Détecter le type de fichier
|
||||
file_extension = os.path.splitext(image_path)[1].lower()
|
||||
if file_extension in ['.jpg', '.jpeg']:
|
||||
mime_type = 'image/jpeg'
|
||||
elif file_extension == '.png':
|
||||
mime_type = 'image/png'
|
||||
elif file_extension == '.gif':
|
||||
mime_type = 'image/gif'
|
||||
elif file_extension in ['.webp']:
|
||||
mime_type = 'image/webp'
|
||||
else:
|
||||
# Par défaut, on suppose JPEG
|
||||
mime_type = 'image/jpeg'
|
||||
|
||||
return f"data:{mime_type};base64,{encoded_string}"
|
||||
|
||||
def interroger_avec_image(self, image_path: str, question: str) -> str:
|
||||
"""
|
||||
Analyse une image avec le modèle Pixtral
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser
|
||||
question: Question ou instructions pour l'analyse
|
||||
|
||||
Returns:
|
||||
Réponse générée par le modèle
|
||||
"""
|
||||
url = self.urlBase() + self.urlFonction()
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {self.cleAPI()}"
|
||||
}
|
||||
|
||||
try:
|
||||
# Encoder l'image en base64
|
||||
encoded_image = self._encoder_image_base64(image_path)
|
||||
|
||||
# Préparer le contenu avec l'image
|
||||
contenu = {
|
||||
"model": self.modele,
|
||||
"messages": [
|
||||
{"role": "system", "content": self.prompt_system},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": question},
|
||||
{"type": "image_url", "image_url": {"url": encoded_image}}
|
||||
]
|
||||
}
|
||||
],
|
||||
**self.params
|
||||
}
|
||||
|
||||
self.heureDepart = datetime.now()
|
||||
|
||||
# Envoyer la requête
|
||||
response = requests.post(url=url, headers=headers, json=contenu, timeout=180) # Timeout plus long pour les images
|
||||
|
||||
self.heureFin = datetime.now()
|
||||
if self.heureDepart is not None and self.heureFin is not None:
|
||||
self.dureeTraitement = self.heureFin - self.heureDepart
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
self.reponseErreur = False
|
||||
return self._traiter_reponse(response)
|
||||
else:
|
||||
self.reponseErreur = True
|
||||
return f"Erreur API ({response.status_code}): {response.text}"
|
||||
|
||||
except Exception as e:
|
||||
self.heureFin = datetime.now()
|
||||
if self.heureDepart is not None and self.heureFin is not None:
|
||||
self.dureeTraitement = self.heureFin - self.heureDepart
|
||||
self.reponseErreur = True
|
||||
return f"Erreur lors de l'analyse de l'image: {str(e)}"
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
from .base_llm import BaseLLM
|
||||
import requests
|
||||
import os
|
||||
import base64
|
||||
from PIL import Image
|
||||
import io
|
||||
from datetime import datetime
|
||||
|
||||
class PixtralLarge(BaseLLM):
|
||||
|
||||
@ -29,3 +34,116 @@ class PixtralLarge(BaseLLM):
|
||||
def _traiter_reponse(self, reponse: requests.Response) -> str:
|
||||
data = reponse.json()
|
||||
return data["choices"][0]["message"]["content"]
|
||||
|
||||
def _encoder_image_base64(self, image_path: str) -> str:
|
||||
"""
|
||||
Encode une image en base64 pour l'API.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à encoder
|
||||
|
||||
Returns:
|
||||
Image encodée en base64 avec préfixe approprié
|
||||
"""
|
||||
if not os.path.isfile(image_path):
|
||||
raise FileNotFoundError(f"L'image {image_path} n'a pas été trouvée")
|
||||
|
||||
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
|
||||
encoded_string = base64.b64encode(buffer.read()).decode("utf-8")
|
||||
except Exception as e:
|
||||
# Si échec avec PIL, essayer avec la méthode simple
|
||||
with open(image_path, "rb") as image_file:
|
||||
encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
|
||||
|
||||
# Détecter le type de fichier
|
||||
file_extension = os.path.splitext(image_path)[1].lower()
|
||||
if file_extension in ['.jpg', '.jpeg']:
|
||||
mime_type = 'image/jpeg'
|
||||
elif file_extension == '.png':
|
||||
mime_type = 'image/png'
|
||||
elif file_extension == '.gif':
|
||||
mime_type = 'image/gif'
|
||||
elif file_extension in ['.webp']:
|
||||
mime_type = 'image/webp'
|
||||
else:
|
||||
# Par défaut, on suppose JPEG
|
||||
mime_type = 'image/jpeg'
|
||||
|
||||
return f"data:{mime_type};base64,{encoded_string}"
|
||||
|
||||
def interroger_avec_image(self, image_path: str, question: str) -> str:
|
||||
"""
|
||||
Analyse une image avec le modèle Pixtral
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser
|
||||
question: Question ou instructions pour l'analyse
|
||||
|
||||
Returns:
|
||||
Réponse générée par le modèle
|
||||
"""
|
||||
url = self.urlBase() + self.urlFonction()
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {self.cleAPI()}"
|
||||
}
|
||||
|
||||
try:
|
||||
# Encoder l'image en base64
|
||||
encoded_image = self._encoder_image_base64(image_path)
|
||||
|
||||
# Préparer le contenu avec l'image
|
||||
contenu = {
|
||||
"model": self.modele,
|
||||
"messages": [
|
||||
{"role": "system", "content": self.prompt_system},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": question},
|
||||
{"type": "image_url", "image_url": {"url": encoded_image}}
|
||||
]
|
||||
}
|
||||
],
|
||||
**self.params
|
||||
}
|
||||
|
||||
self.heureDepart = datetime.now()
|
||||
|
||||
# Envoyer la requête
|
||||
response = requests.post(url=url, headers=headers, json=contenu, timeout=180) # Timeout plus long pour les images
|
||||
|
||||
self.heureFin = datetime.now()
|
||||
if self.heureDepart is not None and self.heureFin is not None:
|
||||
self.dureeTraitement = self.heureFin - self.heureDepart
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
self.reponseErreur = False
|
||||
return self._traiter_reponse(response)
|
||||
else:
|
||||
self.reponseErreur = True
|
||||
return f"Erreur API ({response.status_code}): {response.text}"
|
||||
|
||||
except Exception as e:
|
||||
self.heureFin = datetime.now()
|
||||
if self.heureDepart is not None and self.heureFin is not None:
|
||||
self.dureeTraitement = self.heureFin - self.heureDepart
|
||||
self.reponseErreur = True
|
||||
return f"Erreur lors de l'analyse de l'image: {str(e)}"
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
from .base_llm import BaseLLM
|
||||
import requests
|
||||
|
||||
class PixtralMedium(BaseLLM):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("pixtral-medium-latest")
|
||||
self.configurer(temperature=0.2, top_p=1)
|
||||
|
||||
def urlBase(self) -> str:
|
||||
return "https://api.mistral.ai/v1/"
|
||||
|
||||
def cleAPI(self) -> str:
|
||||
return "2iGzTzE9csRQ9IoASoUjplHwEjA200Vh"
|
||||
|
||||
def urlFonction(self) -> str:
|
||||
return "chat/completions"
|
||||
|
||||
def _preparer_contenu(self, question: str) -> dict:
|
||||
return {
|
||||
"model": self.modele,
|
||||
"messages": [
|
||||
{"role": "system", "content": self.prompt_system},
|
||||
{"role": "user", "content": question}
|
||||
],
|
||||
**self.params
|
||||
}
|
||||
|
||||
def _traiter_reponse(self, reponse: requests.Response) -> str:
|
||||
data = reponse.json()
|
||||
return data["choices"][0]["message"]["content"]
|
||||
516
orchestrator.py
516
orchestrator.py
@ -1,7 +1,9 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from typing import List, Dict, Any, Optional
|
||||
import time
|
||||
import traceback
|
||||
from typing import List, Dict, Any, Optional, Union
|
||||
from agents.base_agent import BaseAgent
|
||||
|
||||
# Configuration du logging
|
||||
@ -19,85 +21,215 @@ class Orchestrator:
|
||||
|
||||
self.output_dir = output_dir
|
||||
|
||||
# Assignation directe des agents (qui peuvent être injectés lors des tests)
|
||||
# Assignation directe des agents
|
||||
self.json_agent = json_agent
|
||||
self.image_sorter = image_sorter
|
||||
self.image_analyser = image_analyser
|
||||
self.report_generator = report_generator
|
||||
|
||||
# Collecter et enregistrer les informations détaillées sur les agents
|
||||
agents_info = self._collecter_info_agents()
|
||||
|
||||
logger.info(f"Orchestrator initialisé avec output_dir: {output_dir}")
|
||||
logger.info(f"JSON Agent: {json_agent}")
|
||||
logger.info(f"Image Sorter: {image_sorter}")
|
||||
logger.info(f"Image Analyser: {image_analyser}")
|
||||
logger.info(f"Report Generator: {report_generator}")
|
||||
logger.info(f"Agents disponibles: JSON={json_agent is not None}, ImageSorter={image_sorter is not None}, ImageAnalyser={image_analyser is not None}, ReportGenerator={report_generator is not None}")
|
||||
logger.info(f"Configuration des agents: {json.dumps(agents_info, indent=2)}")
|
||||
|
||||
def _collecter_info_agents(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""
|
||||
Collecte des informations détaillées sur les agents configurés
|
||||
"""
|
||||
agents_info = {}
|
||||
|
||||
# Information sur l'agent JSON
|
||||
if self.json_agent:
|
||||
agents_info["json_agent"] = self._get_agent_info(self.json_agent)
|
||||
|
||||
# Information sur l'agent Image Sorter
|
||||
if self.image_sorter:
|
||||
agents_info["image_sorter"] = self._get_agent_info(self.image_sorter)
|
||||
|
||||
# Information sur l'agent Image Analyser
|
||||
if self.image_analyser:
|
||||
agents_info["image_analyser"] = self._get_agent_info(self.image_analyser)
|
||||
|
||||
# Information sur l'agent Report Generator
|
||||
if self.report_generator:
|
||||
agents_info["report_generator"] = self._get_agent_info(self.report_generator)
|
||||
|
||||
return agents_info
|
||||
|
||||
def detecter_tickets(self) -> List[str]:
|
||||
"""Détecte tous les tickets disponibles dans le répertoire de sortie"""
|
||||
logger.info(f"Recherche de tickets dans: {self.output_dir}")
|
||||
tickets = []
|
||||
|
||||
if not os.path.exists(self.output_dir):
|
||||
logger.warning(f"Le répertoire de sortie {self.output_dir} n'existe pas")
|
||||
print(f"ERREUR: Le répertoire {self.output_dir} n'existe pas")
|
||||
return tickets
|
||||
|
||||
for ticket_dir in os.listdir(self.output_dir):
|
||||
ticket_path = os.path.join(self.output_dir, ticket_dir)
|
||||
if os.path.isdir(ticket_path) and ticket_dir.startswith("ticket_"):
|
||||
tickets.append(ticket_path)
|
||||
|
||||
logger.info(f"Tickets trouvés: {tickets}")
|
||||
logger.info(f"Tickets trouvés: {len(tickets)}")
|
||||
print(f"Tickets détectés: {len(tickets)}")
|
||||
return tickets
|
||||
|
||||
def traiter_ticket(self, ticket_path: str):
|
||||
logger.info(f"Début du traitement du ticket: {ticket_path}")
|
||||
print(f"Traitement du ticket: {ticket_path}")
|
||||
def lister_tickets(self) -> Dict[int, str]:
|
||||
"""Liste les tickets disponibles et retourne un dictionnaire {index: chemin}"""
|
||||
tickets = self.detecter_tickets()
|
||||
ticket_dict = {}
|
||||
|
||||
print("\nTickets disponibles:")
|
||||
for i, ticket_path in enumerate(tickets, 1):
|
||||
ticket_id = os.path.basename(ticket_path)
|
||||
ticket_dict[i] = ticket_path
|
||||
print(f"{i}. {ticket_id}")
|
||||
|
||||
return ticket_dict
|
||||
|
||||
def trouver_rapport(self, extraction_path: str, ticket_id: str) -> Dict[str, Optional[str]]:
|
||||
"""
|
||||
Cherche le rapport du ticket dans différents emplacements possibles (JSON ou MD)
|
||||
|
||||
Args:
|
||||
extraction_path: Chemin de l'extraction
|
||||
ticket_id: ID du ticket (ex: T0101)
|
||||
|
||||
Returns:
|
||||
Un dictionnaire avec les chemins des fichiers JSON et MD s'ils sont trouvés
|
||||
"""
|
||||
result: Dict[str, Optional[str]] = {"json_path": None, "md_path": None}
|
||||
|
||||
# Liste des emplacements possibles pour les rapports
|
||||
possible_locations = [
|
||||
# 1. Dans le répertoire d'extraction directement
|
||||
extraction_path,
|
||||
|
||||
# 2. Dans un sous-répertoire "data"
|
||||
os.path.join(extraction_path, "data"),
|
||||
|
||||
# 3. Dans un sous-répertoire spécifique au ticket pour les rapports
|
||||
os.path.join(extraction_path, f"{ticket_id}_rapports"),
|
||||
|
||||
# 4. Dans un sous-répertoire "rapports"
|
||||
os.path.join(extraction_path, "rapports")
|
||||
]
|
||||
|
||||
# Vérifier chaque emplacement
|
||||
for base_location in possible_locations:
|
||||
# Chercher le fichier JSON
|
||||
json_path = os.path.join(base_location, f"{ticket_id}_rapport.json")
|
||||
if os.path.exists(json_path):
|
||||
logger.info(f"Rapport JSON trouvé à: {json_path}")
|
||||
result["json_path"] = json_path
|
||||
|
||||
# Chercher le fichier Markdown
|
||||
md_path = os.path.join(base_location, f"{ticket_id}_rapport.md")
|
||||
if os.path.exists(md_path):
|
||||
logger.info(f"Rapport Markdown trouvé à: {md_path}")
|
||||
result["md_path"] = md_path
|
||||
|
||||
if not result["json_path"] and not result["md_path"]:
|
||||
logger.warning(f"Aucun rapport trouvé pour {ticket_id} dans {extraction_path}")
|
||||
|
||||
return result
|
||||
|
||||
def traiter_ticket(self, ticket_path: str) -> bool:
|
||||
"""Traite un ticket spécifique et retourne True si le traitement a réussi"""
|
||||
logger.info(f"Début du traitement du ticket: {ticket_path}")
|
||||
print(f"\nTraitement du ticket: {os.path.basename(ticket_path)}")
|
||||
|
||||
success = False
|
||||
extractions_trouvees = False
|
||||
|
||||
if not os.path.exists(ticket_path):
|
||||
logger.error(f"Le chemin du ticket n'existe pas: {ticket_path}")
|
||||
print(f"ERREUR: Le chemin du ticket n'existe pas: {ticket_path}")
|
||||
return False
|
||||
|
||||
ticket_id = os.path.basename(ticket_path).replace("ticket_", "")
|
||||
|
||||
for extraction in os.listdir(ticket_path):
|
||||
extraction_path = os.path.join(ticket_path, extraction)
|
||||
if os.path.isdir(extraction_path):
|
||||
extractions_trouvees = True
|
||||
logger.info(f"Traitement de l'extraction: {extraction_path}")
|
||||
logger.info(f"Traitement de l'extraction: {extraction}")
|
||||
print(f" Traitement de l'extraction: {extraction}")
|
||||
|
||||
# Recherche des rapports (JSON et MD) dans différents emplacements
|
||||
rapports = self.trouver_rapport(extraction_path, ticket_id)
|
||||
|
||||
# Dossier des pièces jointes
|
||||
attachments_dir = os.path.join(extraction_path, "attachments")
|
||||
rapport_json_path = os.path.join(extraction_path, f"{extraction.split('_')[0]}_rapport.json")
|
||||
rapports_dir = os.path.join(extraction_path, f"{extraction.split('_')[0]}_rapports")
|
||||
|
||||
logger.info(f"Vérification des chemins: rapport_json_path={rapport_json_path}, existe={os.path.exists(rapport_json_path)}")
|
||||
print(f" Vérification du rapport JSON: {os.path.exists(rapport_json_path)}")
|
||||
|
||||
# Dossier pour les rapports générés
|
||||
rapports_dir = os.path.join(extraction_path, f"{ticket_id}_rapports")
|
||||
os.makedirs(rapports_dir, exist_ok=True)
|
||||
|
||||
if os.path.exists(rapport_json_path):
|
||||
with open(rapport_json_path, 'r', encoding='utf-8') as file:
|
||||
ticket_json = json.load(file)
|
||||
|
||||
logger.info(f"Rapport JSON chargé: {rapport_json_path}")
|
||||
|
||||
# Préparer les données du ticket à partir des rapports trouvés
|
||||
ticket_data = self._preparer_donnees_ticket(rapports, ticket_id)
|
||||
|
||||
if ticket_data:
|
||||
success = True
|
||||
logger.info(f"Données du ticket chargées avec succès")
|
||||
print(f" Données du ticket chargées")
|
||||
|
||||
# Traitement JSON avec l'agent JSON
|
||||
if self.json_agent:
|
||||
logger.info("Exécution de l'agent JSON")
|
||||
print(" Analyse du JSON en cours...")
|
||||
json_analysis = self.json_agent.executer(ticket_json)
|
||||
logger.info(f"Résultat de l'analyse JSON: {json_analysis[:100]}...")
|
||||
print(" Analyse du ticket en cours...")
|
||||
|
||||
# Log détaillé sur l'agent JSON
|
||||
agent_info = self._get_agent_info(self.json_agent)
|
||||
logger.info(f"Agent JSON: {json.dumps(agent_info, indent=2)}")
|
||||
|
||||
json_analysis = self.json_agent.executer(ticket_data)
|
||||
logger.info("Analyse JSON terminée")
|
||||
else:
|
||||
logger.warning("Agent JSON non disponible")
|
||||
json_analysis = None
|
||||
print(" Agent JSON non disponible, analyse ignorée")
|
||||
|
||||
# Traitement des images
|
||||
relevant_images = []
|
||||
images_analyses = {}
|
||||
if os.path.exists(attachments_dir):
|
||||
logger.info(f"Vérification des pièces jointes dans: {attachments_dir}")
|
||||
print(f" Vérification des pièces jointes: {attachments_dir}")
|
||||
print(f" Vérification des pièces jointes...")
|
||||
|
||||
# Log détaillé sur l'agent Image Sorter
|
||||
if self.image_sorter:
|
||||
agent_info = self._get_agent_info(self.image_sorter)
|
||||
logger.info(f"Agent Image Sorter: {json.dumps(agent_info, indent=2)}")
|
||||
|
||||
images_count = 0
|
||||
for attachment in os.listdir(attachments_dir):
|
||||
attachment_path = os.path.join(attachments_dir, attachment)
|
||||
if attachment.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):
|
||||
images_count += 1
|
||||
logger.info(f"Image trouvée: {attachment_path}")
|
||||
|
||||
if self.image_sorter:
|
||||
logger.info(f"Analyse de la pertinence de l'image: {attachment_path}")
|
||||
print(f" Analyse de la pertinence de l'image: {attachment}")
|
||||
is_relevant = self.image_sorter.executer(attachment_path)
|
||||
logger.info(f"Image pertinente: {is_relevant}")
|
||||
print(f" Analyse de l'image: {attachment}")
|
||||
sorting_result = self.image_sorter.executer(attachment_path)
|
||||
is_relevant = sorting_result.get("is_relevant", False)
|
||||
reason = sorting_result.get("reason", "")
|
||||
|
||||
# Ajouter les métadonnées de tri à la liste des analyses
|
||||
images_analyses[attachment_path] = {
|
||||
"sorting": sorting_result,
|
||||
"analysis": None # Sera rempli plus tard si pertinent
|
||||
}
|
||||
|
||||
if is_relevant:
|
||||
logger.info(f"Image pertinente identifiée: {attachment} ({reason})")
|
||||
print(f" => Pertinente: {reason}")
|
||||
relevant_images.append(attachment_path)
|
||||
else:
|
||||
logger.info(f"Image non pertinente: {attachment} ({reason})")
|
||||
print(f" => Non pertinente: {reason}")
|
||||
else:
|
||||
logger.warning("Image Sorter non disponible")
|
||||
|
||||
@ -105,55 +237,327 @@ class Orchestrator:
|
||||
print(f" Images analysées: {images_count}, Images pertinentes: {len(relevant_images)}")
|
||||
else:
|
||||
logger.warning(f"Répertoire des pièces jointes non trouvé: {attachments_dir}")
|
||||
print(f" Répertoire des pièces jointes non trouvé: {attachments_dir}")
|
||||
print(f" Répertoire des pièces jointes non trouvé")
|
||||
|
||||
image_analysis_results = {}
|
||||
# Analyse approfondie des images pertinentes
|
||||
# Log détaillé sur l'agent Image Analyser
|
||||
if self.image_analyser:
|
||||
agent_info = self._get_agent_info(self.image_analyser)
|
||||
logger.info(f"Agent Image Analyser: {json.dumps(agent_info, indent=2)}")
|
||||
|
||||
for image_path in relevant_images:
|
||||
if self.image_analyser and json_analysis:
|
||||
logger.info(f"Analyse de l'image: {image_path}")
|
||||
print(f" Analyse de l'image: {os.path.basename(image_path)}")
|
||||
image_analysis_results[image_path] = self.image_analyser.executer(
|
||||
image_path,
|
||||
contexte=json_analysis
|
||||
)
|
||||
logger.info(f"Résultat de l'analyse d'image: {image_analysis_results[image_path][:100]}...")
|
||||
image_name = os.path.basename(image_path)
|
||||
logger.info(f"Analyse approfondie de l'image: {image_name}")
|
||||
print(f" Analyse approfondie de l'image: {image_name}")
|
||||
analysis_result = self.image_analyser.executer(image_path, contexte=json_analysis)
|
||||
|
||||
# Ajouter l'analyse au dictionnaire des analyses d'images
|
||||
if image_path in images_analyses:
|
||||
images_analyses[image_path]["analysis"] = analysis_result
|
||||
else:
|
||||
images_analyses[image_path] = {
|
||||
"sorting": {"is_relevant": True, "reason": "Auto-sélectionné"},
|
||||
"analysis": analysis_result
|
||||
}
|
||||
|
||||
logger.info(f"Analyse complétée pour {image_name}")
|
||||
|
||||
# Génération du rapport final
|
||||
rapport_data = {
|
||||
"ticket_data": ticket_data,
|
||||
"ticket_id": ticket_id,
|
||||
"analyse_json": json_analysis,
|
||||
"analyse_images": image_analysis_results
|
||||
"analyse_images": images_analyses,
|
||||
"metadata": {
|
||||
"timestamp_debut": self._get_timestamp(),
|
||||
"ticket_id": ticket_id,
|
||||
"images_analysees": images_count if 'images_count' in locals() else 0,
|
||||
"images_pertinentes": len(relevant_images)
|
||||
}
|
||||
}
|
||||
|
||||
if self.report_generator:
|
||||
logger.info("Génération du rapport final")
|
||||
print(" Génération du rapport final")
|
||||
rapport_path = os.path.join(rapports_dir, extraction.split('_')[0])
|
||||
|
||||
# Log détaillé sur l'agent Report Generator
|
||||
agent_info = self._get_agent_info(self.report_generator)
|
||||
logger.info(f"Agent Report Generator: {json.dumps(agent_info, indent=2)}")
|
||||
|
||||
rapport_path = os.path.join(rapports_dir, ticket_id)
|
||||
os.makedirs(rapport_path, exist_ok=True)
|
||||
self.report_generator.executer(rapport_data, rapport_path)
|
||||
logger.info(f"Rapport généré à: {rapport_path}")
|
||||
print(f" Rapport généré à: {rapport_path}")
|
||||
else:
|
||||
logger.warning("Report Generator non disponible")
|
||||
print(" Report Generator non disponible, génération de rapport ignorée")
|
||||
|
||||
print(f"Traitement du ticket {ticket_path} terminé.\n")
|
||||
logger.info(f"Traitement du ticket {ticket_path} terminé.")
|
||||
print(f"Traitement du ticket {os.path.basename(ticket_path)} terminé avec succès.\n")
|
||||
logger.info(f"Traitement du ticket {ticket_path} terminé avec succès.")
|
||||
else:
|
||||
logger.warning(f"Fichier rapport JSON non trouvé: {rapport_json_path}")
|
||||
print(f" ERREUR: Fichier rapport JSON non trouvé: {rapport_json_path}")
|
||||
logger.warning(f"Aucune donnée de ticket trouvée pour: {ticket_id}")
|
||||
print(f" ERREUR: Aucune donnée de ticket trouvée pour {ticket_id}")
|
||||
|
||||
if not extractions_trouvees:
|
||||
logger.warning(f"Aucune extraction trouvée dans le ticket: {ticket_path}")
|
||||
print(f" ERREUR: Aucune extraction trouvée dans le ticket: {ticket_path}")
|
||||
print(f" ERREUR: Aucune extraction trouvée dans le ticket")
|
||||
|
||||
return success
|
||||
|
||||
def _preparer_donnees_ticket(self, rapports: Dict[str, Optional[str]], ticket_id: str) -> Optional[Dict]:
|
||||
"""
|
||||
Prépare les données du ticket à partir des rapports trouvés (JSON et/ou MD)
|
||||
|
||||
Args:
|
||||
rapports: Dictionnaire avec les chemins des rapports JSON et MD
|
||||
ticket_id: ID du ticket
|
||||
|
||||
Returns:
|
||||
Dictionnaire avec les données du ticket, ou None si aucun rapport n'est trouvé
|
||||
"""
|
||||
ticket_data = None
|
||||
|
||||
# Essayer d'abord le fichier JSON
|
||||
if rapports["json_path"]:
|
||||
try:
|
||||
with open(rapports["json_path"], 'r', encoding='utf-8') as file:
|
||||
ticket_data = json.load(file)
|
||||
logger.info(f"Données JSON chargées depuis: {rapports['json_path']}")
|
||||
print(f" Rapport JSON chargé: {os.path.basename(rapports['json_path'])}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du chargement du JSON: {e}")
|
||||
print(f" ERREUR: Impossible de charger le fichier JSON: {e}")
|
||||
|
||||
# Si pas de JSON ou erreur, essayer le Markdown
|
||||
if not ticket_data and rapports["md_path"]:
|
||||
try:
|
||||
# Créer une structure de données à partir du contenu Markdown
|
||||
ticket_data = self._extraire_donnees_de_markdown(rapports["md_path"])
|
||||
logger.info(f"Données Markdown chargées depuis: {rapports['md_path']}")
|
||||
print(f" Rapport Markdown chargé: {os.path.basename(rapports['md_path'])}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du chargement du Markdown: {e}")
|
||||
print(f" ERREUR: Impossible de charger le fichier Markdown: {e}")
|
||||
|
||||
# Assurer que l'ID du ticket est correct
|
||||
if ticket_data:
|
||||
ticket_data["code"] = ticket_id
|
||||
|
||||
return ticket_data
|
||||
|
||||
def _extraire_donnees_de_markdown(self, md_path: str) -> Dict:
|
||||
"""
|
||||
Extrait les données d'un fichier Markdown et les structure
|
||||
|
||||
Args:
|
||||
md_path: Chemin vers le fichier Markdown
|
||||
|
||||
Returns:
|
||||
Dictionnaire structuré avec les données du ticket
|
||||
"""
|
||||
with open(md_path, 'r', encoding='utf-8') as file:
|
||||
content = file.read()
|
||||
|
||||
# Initialiser la structure de données
|
||||
ticket_data = {
|
||||
"id": "",
|
||||
"code": "",
|
||||
"name": "",
|
||||
"description": "",
|
||||
"messages": [],
|
||||
"metadata": {
|
||||
"source_file": md_path,
|
||||
"format": "markdown"
|
||||
}
|
||||
}
|
||||
|
||||
# Extraire le titre (première ligne)
|
||||
lines = content.split('\n')
|
||||
if lines and lines[0].startswith('# '):
|
||||
title = lines[0].replace('# ', '')
|
||||
ticket_parts = title.split(':')
|
||||
if len(ticket_parts) >= 1:
|
||||
ticket_data["code"] = ticket_parts[0].strip()
|
||||
if len(ticket_parts) >= 2:
|
||||
ticket_data["name"] = ticket_parts[1].strip()
|
||||
|
||||
# Extraire la description
|
||||
description_section = self._extraire_section(content, "description")
|
||||
if description_section:
|
||||
ticket_data["description"] = description_section.strip()
|
||||
|
||||
# Extraire les informations du ticket
|
||||
info_section = self._extraire_section(content, "Informations du ticket")
|
||||
if info_section:
|
||||
for line in info_section.split('\n'):
|
||||
if ':' in line and line.startswith('- **'):
|
||||
key = line.split('**')[1].strip()
|
||||
value = line.split(':')[1].strip()
|
||||
if key == "id":
|
||||
ticket_data["id"] = value
|
||||
elif key == "code":
|
||||
ticket_data["code"] = value
|
||||
# Ajouter d'autres champs au besoin
|
||||
|
||||
# Extraire les messages
|
||||
messages_section = self._extraire_section(content, "Messages")
|
||||
if messages_section:
|
||||
message_blocks = messages_section.split("### Message ")
|
||||
for block in message_blocks[1:]: # Ignorer le premier élément (vide)
|
||||
message = {}
|
||||
|
||||
# Extraire les en-têtes du message
|
||||
lines = block.split('\n')
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith('**') and ':' in line:
|
||||
key = line.split('**')[1].lower()
|
||||
value = line.split(':')[1].strip()
|
||||
message[key] = value
|
||||
|
||||
# Extraire le contenu du message (tout ce qui n'est pas un en-tête)
|
||||
content_start = 0
|
||||
for i, line in enumerate(lines):
|
||||
if i > 0 and not line.startswith('**') and line and content_start == 0:
|
||||
content_start = i
|
||||
break
|
||||
|
||||
if content_start > 0:
|
||||
content_end = -1
|
||||
for i in range(content_start, len(lines)):
|
||||
if lines[i].startswith('**attachment_ids**') or lines[i].startswith('---'):
|
||||
content_end = i
|
||||
break
|
||||
|
||||
if content_end == -1:
|
||||
message["content"] = "\n".join(lines[content_start:])
|
||||
else:
|
||||
message["content"] = "\n".join(lines[content_start:content_end])
|
||||
|
||||
# Extraire les pièces jointes
|
||||
attachments = []
|
||||
for line in lines:
|
||||
if line.startswith('- ') and '[ID:' in line:
|
||||
attachments.append(line.strip('- ').strip())
|
||||
|
||||
if attachments:
|
||||
message["attachments"] = attachments
|
||||
|
||||
ticket_data["messages"].append(message)
|
||||
|
||||
return ticket_data
|
||||
|
||||
def _extraire_section(self, content: str, section_title: str) -> Optional[str]:
|
||||
"""
|
||||
Extrait une section du contenu Markdown
|
||||
|
||||
Args:
|
||||
content: Contenu Markdown complet
|
||||
section_title: Titre de la section à extraire
|
||||
|
||||
Returns:
|
||||
Contenu de la section ou None si non trouvée
|
||||
"""
|
||||
import re
|
||||
|
||||
# Chercher les sections de niveau 2 (##)
|
||||
pattern = r'## ' + re.escape(section_title) + r'\s*\n(.*?)(?=\n## |$)'
|
||||
match = re.search(pattern, content, re.DOTALL)
|
||||
|
||||
if match:
|
||||
return match.group(1).strip()
|
||||
|
||||
# Si pas trouvé, chercher les sections de niveau 3 (###)
|
||||
pattern = r'### ' + re.escape(section_title) + r'\s*\n(.*?)(?=\n### |$)'
|
||||
match = re.search(pattern, content, re.DOTALL)
|
||||
|
||||
if match:
|
||||
return match.group(1).strip()
|
||||
|
||||
return None
|
||||
|
||||
def executer(self):
|
||||
def executer(self, ticket_specifique: Optional[str] = None):
|
||||
"""
|
||||
Exécute l'orchestrateur soit sur un ticket spécifique, soit permet de choisir
|
||||
|
||||
Args:
|
||||
ticket_specifique: Chemin du ticket spécifique à traiter (optionnel)
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
# Stocker le ticket spécifique
|
||||
self.ticket_specifique = ticket_specifique
|
||||
|
||||
# Obtenir la liste des tickets
|
||||
if ticket_specifique:
|
||||
# Utiliser juste le ticket spécifique
|
||||
ticket_dirs = self.detecter_tickets()
|
||||
ticket_dirs = [t for t in ticket_dirs if t.endswith(ticket_specifique)]
|
||||
logger.info(f"Ticket spécifique à traiter: {ticket_specifique}")
|
||||
else:
|
||||
# Lister tous les tickets
|
||||
ticket_dirs = self.detecter_tickets()
|
||||
logger.info(f"Tickets à traiter: {len(ticket_dirs)}")
|
||||
|
||||
if not ticket_dirs:
|
||||
logger.warning("Aucun ticket trouvé dans le répertoire de sortie")
|
||||
return
|
||||
|
||||
# Un seul log de début d'exécution
|
||||
logger.info("Début de l'exécution de l'orchestrateur")
|
||||
print("Début de l'exécution de l'orchestrateur")
|
||||
|
||||
tickets = self.detecter_tickets()
|
||||
if not tickets:
|
||||
logger.warning("Aucun ticket détecté dans le répertoire de sortie")
|
||||
print("ATTENTION: Aucun ticket détecté dans le répertoire de sortie")
|
||||
# Traitement des tickets
|
||||
for ticket_dir in ticket_dirs:
|
||||
if ticket_specifique and not ticket_dir.endswith(ticket_specifique):
|
||||
continue
|
||||
|
||||
try:
|
||||
self.traiter_ticket(ticket_dir)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du traitement du ticket {ticket_dir}: {str(e)}")
|
||||
print(f"Erreur lors du traitement du ticket {ticket_dir}: {str(e)}")
|
||||
traceback.print_exc()
|
||||
|
||||
for ticket in tickets:
|
||||
self.traiter_ticket(ticket)
|
||||
# Calcul de la durée d'exécution
|
||||
duration = time.time() - start_time
|
||||
logger.info(f"Fin de l'exécution de l'orchestrateur (durée: {duration:.2f} secondes)")
|
||||
print(f"Fin de l'exécution de l'orchestrateur (durée: {duration:.2f} secondes)")
|
||||
|
||||
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")
|
||||
|
||||
def _get_agent_info(self, agent: Optional[BaseAgent]) -> Dict:
|
||||
"""
|
||||
Récupère les informations détaillées sur un agent.
|
||||
"""
|
||||
if not agent:
|
||||
return {"status": "non configuré"}
|
||||
|
||||
logger.info("Fin de l'exécution de l'orchestrateur")
|
||||
print("Fin de l'exécution de l'orchestrateur")
|
||||
# Récupérer les informations du modèle
|
||||
model_info = {
|
||||
"nom": agent.nom,
|
||||
"model": getattr(agent.llm, "modele", str(type(agent.llm))),
|
||||
}
|
||||
|
||||
# Ajouter les paramètres de configuration s'ils sont disponibles directement dans l'agent
|
||||
# Utiliser getattr avec une valeur par défaut pour éviter les erreurs
|
||||
model_info["temperature"] = getattr(agent, "temperature", None)
|
||||
model_info["top_p"] = getattr(agent, "top_p", None)
|
||||
model_info["max_tokens"] = getattr(agent, "max_tokens", None)
|
||||
|
||||
# Ajouter le prompt système s'il est disponible
|
||||
if hasattr(agent, "system_prompt"):
|
||||
prompt_preview = getattr(agent, "system_prompt", "")
|
||||
# Tronquer le prompt s'il est trop long
|
||||
if prompt_preview and len(prompt_preview) > 200:
|
||||
prompt_preview = prompt_preview[:200] + "..."
|
||||
model_info["system_prompt_preview"] = prompt_preview
|
||||
|
||||
# Supprimer les valeurs None
|
||||
model_info = {k: v for k, v in model_info.items() if v is not None}
|
||||
|
||||
return model_info
|
||||
@ -0,0 +1,111 @@
|
||||
{
|
||||
"ticket_data": {
|
||||
"id": "113",
|
||||
"code": "T0101",
|
||||
"name": "ACTIVATION LOGICIEL",
|
||||
"description": "Problème de licence.",
|
||||
"project_name": "Demandes",
|
||||
"stage_name": "Clôturé",
|
||||
"user_id": "",
|
||||
"partner_id_email_from": "PROVENCALE S.A, Bruno Vernet <bruno.vernet@provencale.com>",
|
||||
"create_date": "26/03/2020 14:46:36",
|
||||
"write_date_last_modification": "03/10/2024 13:10:50",
|
||||
"date_deadline": "25/05/2020 00:00:00",
|
||||
"messages": [
|
||||
{
|
||||
"author_id": "PROVENCALE S.A",
|
||||
"date": "26/03/2020 14:43:45",
|
||||
"message_type": "E-mail",
|
||||
"subject": "ACTIVATION LOGICIEL",
|
||||
"id": "10758",
|
||||
"content": "Bonjour,\n\nAu vu de la situation liée au Coronavirus, nous avons dû passer en télétravail.\n\nPour ce faire et avoir accès aux différents logiciels nécessaires, ESQ a été réinstallé sur un autre serveur afin de pouvoir travailler en bureau à distance.\n\nDu coup le logiciel nous demande une activation mais je ne sais pas si le N° de licence a été modifié suite à un achat version réseau faite par JB Lafitte en 2019 ou si le problème est autre.\n\nCi-dessous la fenêtre au lancement du logiciel.\n\nMerci d’avance pour votre aide.\n\nCordialement\n\n\n- image006.jpg (image/jpeg) [ID: 31760]\n- image005.jpg (image/jpeg) [ID: 31758]\n\n---\n"
|
||||
}
|
||||
],
|
||||
"date_d'extraction": "04/04/2025 17:02:42",
|
||||
"répertoire": "output/ticket_T0101/T0101_20250404_170239"
|
||||
},
|
||||
"ticket_id": "T0101",
|
||||
"analyse_json": "1. Résumé du problème\nLe client PROVENCALE S.A a réinstallé le logiciel ESQ sur un nouveau serveur pour permettre le télétravail de ses employés. Cependant, lors du lancement du logiciel, une activation est demandée et le client n'est pas certain si le numéro de licence a été modifié suite à un achat de version réseau en 2019.\n2. Informations techniques essentielles\nLogiciel : ESQ\nVersion : non spécifiée\nNouveau serveur installé pour le télétravail\nAchat d'une version réseau en 2019 : possible modification du numéro de licence\n3. Contexte client (urgence, impact)\nLe client a dû passer en télétravail en raison de la situation liée au Coronavirus. Le problème d'activation du logiciel ESQ sur le nouveau serveur peut avoir un impact sur la productivité des employés de PROVENCALE S.A. La date limite pour résoudre ce problème est le 25/05/2020.\n4. Pistes d'analyse suggérées\nVérifier les informations de licence pour la version réseau achetée en 2019 et comparer avec le numéro de licence actuellement utilisé. Si nécessaire, contacter le fournisseur du logiciel ESQ pour obtenir de l'aide pour l'activation ou pour obtenir un nouveau numéro de licence. Vérifier également la configuration du nouveau serveur pour s'assurer qu'il répond aux exigences système du logiciel ESQ.",
|
||||
"analyse_images": {
|
||||
"output/ticket_T0101/T0101_20250404_170239/attachments/image006.jpg": {
|
||||
"sorting": {
|
||||
"is_relevant": false,
|
||||
"reason": "Non. Cette image montre le logo d'une entreprise nommée \"Provençale Carbone de Calcium\". Elle n'est pas pertinente pour le support technique de logiciels, car elle ne contient pas de captures d'écran de logiciels, de messages d'erreur, de configurations système, ou d'autres éléments techniques liés à l'informatique.",
|
||||
"raw_response": "Non. Cette image montre le logo d'une entreprise nommée \"Provençale Carbone de Calcium\". Elle n'est pas pertinente pour le support technique de logiciels, car elle ne contient pas de captures d'écran de logiciels, de messages d'erreur, de configurations système, ou d'autres éléments techniques liés à l'informatique.",
|
||||
"metadata": {
|
||||
"image_path": "output/ticket_T0101/T0101_20250404_170239/attachments/image006.jpg",
|
||||
"image_name": "image006.jpg",
|
||||
"timestamp": "20250407_142051",
|
||||
"model_info": {
|
||||
"model": "pixtral-12b-latest",
|
||||
"temperature": 0.2,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 300
|
||||
}
|
||||
}
|
||||
},
|
||||
"analysis": null
|
||||
},
|
||||
"output/ticket_T0101/T0101_20250404_170239/attachments/image005.jpg": {
|
||||
"sorting": {
|
||||
"is_relevant": true,
|
||||
"reason": "oui. L'image montre une fenêtre d'activation de logiciel, ce qui est pertinent pour le support technique de logiciels.",
|
||||
"raw_response": "oui. L'image montre une fenêtre d'activation de logiciel, ce qui est pertinent pour le support technique de logiciels.",
|
||||
"metadata": {
|
||||
"image_path": "output/ticket_T0101/T0101_20250404_170239/attachments/image005.jpg",
|
||||
"image_name": "image005.jpg",
|
||||
"timestamp": "20250407_142051",
|
||||
"model_info": {
|
||||
"model": "pixtral-12b-latest",
|
||||
"temperature": 0.2,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 300
|
||||
}
|
||||
}
|
||||
},
|
||||
"analysis": {
|
||||
"analyse": "### Analyse de l'image\n\n#### 1. Description factuelle de ce que montre l'image\nL'image montre une interface de fenêtre de logiciel intitulée \"Activation du logiciel\". La fenêtre contient un champ pour entrer l'ID du logiciel, un message d'instructions, et trois options pour l'activation du logiciel : par internet, par carte (4 ans restants), et par téléphone.\n\n#### 2. Éléments techniques identifiables\n- **Titre de la fenêtre** : \"Activation du logiciel\"\n- **Champ ID du logiciel** : Pré-rempli avec \"ID00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"metadata": {
|
||||
"image_path": "output/ticket_T0101/T0101_20250404_170239/attachments/image005.jpg",
|
||||
"image_name": "image005.jpg",
|
||||
"timestamp": "20250407_142056",
|
||||
"model_info": {
|
||||
"model": "pixtral-12b-latest",
|
||||
"temperature": 0.3,
|
||||
"top_p": 0.9,
|
||||
"max_tokens": 1200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"timestamp": "20250407_142114",
|
||||
"model": "mistral-medium",
|
||||
"temperature": 0.4,
|
||||
"top_p": 0.9,
|
||||
"max_tokens": 2500,
|
||||
"system_prompt": "Tu es un expert en génération de rapports techniques pour BRG_Lab.\nTa mission est de synthétiser toutes les analyses (JSON et images) en un rapport structuré et exploitable.\n\nStructure ton rapport ainsi:\n1. Résumé exécutif: Synthèse du problème et des conclusions principales\n2. Analyse du ticket: Détails extraits du ticket client\n3. Analyse des images: Résumé des images pertinentes et leur contribution\n4. Diagnostic technique: Interprétation consolidée des informations\n5. Recommandations: Actions suggérées pour résoudre le problème\n\nChaque section doit être factuelle, précise et orientée solution.\nInclus tous les détails techniques pertinents (versions, configurations, messages d'erreur).\nAssure une traçabilité complète entre les données sources et tes conclusions.",
|
||||
"agents_info": {
|
||||
"image_sorter": {
|
||||
"model": "pixtral-12b-latest",
|
||||
"temperature": 0.2,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 300
|
||||
},
|
||||
"image_analyser": {
|
||||
"model": "pixtral-12b-latest",
|
||||
"temperature": 0.3,
|
||||
"top_p": 0.9,
|
||||
"max_tokens": 1200
|
||||
},
|
||||
"report_generator": {
|
||||
"model": "mistral-medium",
|
||||
"temperature": 0.4,
|
||||
"top_p": 0.9,
|
||||
"max_tokens": 2500
|
||||
}
|
||||
}
|
||||
},
|
||||
"rapport_genere": "# Rapport technique pour le ticket #T0101\n\n## 1. Résumé exécutif\n\nLe client PROVENCALE S.A a récemment installé le logiciel ESQ sur un nouveau serveur pour permettre le télétravail de ses employés. Cependant, lors de l'activation du logiciel, le client n'est pas certain si le numéro de licence a été modifié suite à un achat de version réseau en 2019.\n\nLes analyses effectuées indiquent que le numéro de licence actuellement utilisé ne correspond pas à celui d'une version réseau. Il est recommandé de contacter le fournisseur du logiciel ESQ pour obtenir de l'aide pour l'activation ou pour obtenir un nouveau numéro de licence correspondant à la version réseau achetée en 2019.\n\n## 2. Analyse détaillée du ticket\n\nLe client PROVENCALE S.A a réinstallé le logiciel ESQ sur un nouveau serveur pour permettre le télétravail de ses employés. Cependant, lors du lancement du logiciel, une activation est demandée et le client n'est pas certain si le numéro de licence a été modifié suite à un achat de version réseau en 2019.\n\nLes informations techniques essentielles fournies par le client sont les suivantes :\n\n- Logiciel : ESQ\n- Version : non spécifiée\n- Nouveau serveur installé pour le télétravail\n- Achat d'une version réseau en 2019 : possible modification du numéro de licence\n\nLe client a dû passer en télétravail en raison de la situation liée au Coronavirus. Le problème d'activation du logiciel ESQ sur le nouveau serveur peut avoir un impact sur la productivité des employés de PROVENCALE S.A. La date limite pour résoudre ce problème est le 25/05/2020.\n\n## 3. Analyse des images pertinentes\n\n### 3.1 Image 1 : image005.jpg\n\n#### 3.1.1 Description factuelle\n\nL'image montre une interface de fenêtre de logiciel intitulée \"Activation du logiciel\". La fenêtre contient un champ pour entrer l'ID du logiciel, un message d'instructions, et trois options pour l'activation du logiciel : par internet, par carte (4 ans restants), et par téléphone.\n\n#### 3.1.2 Éléments techniques identifiables\n\n- **Titre de la fenêtre** : \"Activation du logiciel\"\n- **Champ ID du logiciel** : Pré-rempli avec \"ID00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
# Rapport d'analyse du ticket #T0101
|
||||
|
||||
*Généré le: 20250407_142114*
|
||||
|
||||
# Rapport technique pour le ticket #T0101
|
||||
|
||||
## 1. Résumé exécutif
|
||||
|
||||
Le client PROVENCALE S.A a récemment installé le logiciel ESQ sur un nouveau serveur pour permettre le télétravail de ses employés. Cependant, lors de l'activation du logiciel, le client n'est pas certain si le numéro de licence a été modifié suite à un achat de version réseau en 2019.
|
||||
|
||||
Les analyses effectuées indiquent que le numéro de licence actuellement utilisé ne correspond pas à celui d'une version réseau. Il est recommandé de contacter le fournisseur du logiciel ESQ pour obtenir de l'aide pour l'activation ou pour obtenir un nouveau numéro de licence correspondant à la version réseau achetée en 2019.
|
||||
|
||||
## 2. Analyse détaillée du ticket
|
||||
|
||||
Le client PROVENCALE S.A a réinstallé le logiciel ESQ sur un nouveau serveur pour permettre le télétravail de ses employés. Cependant, lors du lancement du logiciel, une activation est demandée et le client n'est pas certain si le numéro de licence a été modifié suite à un achat de version réseau en 2019.
|
||||
|
||||
Les informations techniques essentielles fournies par le client sont les suivantes :
|
||||
|
||||
- Logiciel : ESQ
|
||||
- Version : non spécifiée
|
||||
- Nouveau serveur installé pour le télétravail
|
||||
- Achat d'une version réseau en 2019 : possible modification du numéro de licence
|
||||
|
||||
Le client a dû passer en télétravail en raison de la situation liée au Coronavirus. Le problème d'activation du logiciel ESQ sur le nouveau serveur peut avoir un impact sur la productivité des employés de PROVENCALE S.A. La date limite pour résoudre ce problème est le 25/05/2020.
|
||||
|
||||
## 3. Analyse des images pertinentes
|
||||
|
||||
### 3.1 Image 1 : image005.jpg
|
||||
|
||||
#### 3.1.1 Description factuelle
|
||||
|
||||
L'image montre une interface de fenêtre de logiciel intitulée "Activation du logiciel". La fenêtre contient un champ pour entrer l'ID du logiciel, un message d'instructions, et trois options pour l'activation du logiciel : par internet, par carte (4 ans restants), et par téléphone.
|
||||
|
||||
#### 3.1.2 Éléments techniques identifiables
|
||||
|
||||
- **Titre de la fenêtre** : "Activation du logiciel"
|
||||
- **Champ ID du logiciel** : Pré-rempli avec "ID00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
## Informations techniques
|
||||
|
||||
### Agents et modèles utilisés
|
||||
|
||||
#### Agent de tri d'images
|
||||
- **Modèle**: pixtral-12b-latest
|
||||
- **Température**: 0.2
|
||||
- **Top-p**: 0.8
|
||||
- **Max tokens**: 300
|
||||
|
||||
#### Agent d'analyse d'images
|
||||
- **Modèle**: pixtral-12b-latest
|
||||
- **Température**: 0.3
|
||||
- **Top-p**: 0.9
|
||||
- **Max tokens**: 1200
|
||||
|
||||
#### Agent de génération de rapport
|
||||
- **Modèle**: mistral-medium
|
||||
- **Température**: 0.4
|
||||
- **Top-p**: 0.9
|
||||
- **Max tokens**: 2500
|
||||
|
||||
### Statistiques
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
requests>=2.25.0
|
||||
beautifulsoup4>=4.9.0
|
||||
html2text>=2020.0.0
|
||||
html2text>=2020.0.0
|
||||
Pillow>=9.0.0
|
||||
25
test_models.py
Normal file
25
test_models.py
Normal file
@ -0,0 +1,25 @@
|
||||
from llm_classes.mistral_medium import MistralMedium
|
||||
from llm_classes.pixtral_12b import Pixtral12b
|
||||
|
||||
print("Initialisation des modèles LLM...")
|
||||
|
||||
# Initialisation des modèles
|
||||
try:
|
||||
text_model = MistralMedium()
|
||||
image_model = Pixtral12b()
|
||||
print("Modèles initialisés avec succès!")
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de l'initialisation des modèles: {str(e)}")
|
||||
exit(1)
|
||||
|
||||
# Test d'interrogation simple
|
||||
try:
|
||||
question = "Quelle est la capitale de la France?"
|
||||
print(f"\nTest d'interrogation simple sur MistralMedium:")
|
||||
print(f"Question: {question}")
|
||||
response = text_model.interroger(question)
|
||||
print(f"Réponse: {response[:100]}...")
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de l'interrogation simple: {str(e)}")
|
||||
|
||||
print("\nTests terminés!")
|
||||
@ -4,19 +4,27 @@ from agents.agent_image_sorter import AgentImageSorter
|
||||
from agents.agent_image_analyser import AgentImageAnalyser
|
||||
from agents.agent_report_generator import AgentReportGenerator
|
||||
|
||||
from llm_classes.mistral_large import MistralLarge
|
||||
# Utilisation des modèles Medium pour les tests
|
||||
from llm_classes.mistral_medium import MistralMedium
|
||||
from llm_classes.pixtral_12b import Pixtral12b
|
||||
from llm_classes.ollama import Ollama
|
||||
|
||||
import os
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
|
||||
# Configuration du logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
filename='test_orchestrator.log', filemode='w')
|
||||
logger = logging.getLogger("TestOrchestrator")
|
||||
|
||||
def test_orchestrator():
|
||||
def test_orchestrator(ticket_id=None):
|
||||
"""
|
||||
Exécute l'orchestrateur avec les agents définis
|
||||
|
||||
Args:
|
||||
ticket_id: Identifiant du ticket à traiter (optionnel)
|
||||
"""
|
||||
# Vérifier que le dossier output existe
|
||||
if not os.path.exists("output/"):
|
||||
os.makedirs("output/")
|
||||
@ -25,42 +33,44 @@ def test_orchestrator():
|
||||
|
||||
# Vérifier le contenu du dossier output
|
||||
tickets = [d for d in os.listdir("output/") if d.startswith("ticket_") and os.path.isdir(os.path.join("output/", d))]
|
||||
logger.info(f"Tickets trouvés dans output/: {tickets}")
|
||||
print(f"Tickets existants dans output/: {tickets}")
|
||||
logger.info(f"Tickets trouvés dans output/: {len(tickets)}")
|
||||
print(f"Tickets existants dans output/: {len(tickets)}")
|
||||
|
||||
if len(tickets) == 0:
|
||||
logger.error("Aucun ticket trouvé dans le dossier output/")
|
||||
print("ERREUR: Aucun ticket trouvé dans le dossier output/")
|
||||
return
|
||||
|
||||
# Initialisation des LLM
|
||||
json_llm = MistralLarge()
|
||||
logger.info("LLM MistralLarge initialisé")
|
||||
print("LLM MistralLarge initialisé")
|
||||
print("Initialisation des modèles LLM...")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Utilisation de Mistral Medium pour l'analyse JSON et la génération de rapports
|
||||
json_llm = MistralMedium()
|
||||
logger.info("LLM MistralMedium initialisé pour l'analyse JSON")
|
||||
|
||||
# Utilisation de Pixtral12b pour le tri et l'analyse d'images
|
||||
image_sorter_llm = Pixtral12b()
|
||||
logger.info("LLM Pixtral12b initialisé")
|
||||
print("LLM Pixtral12b initialisé")
|
||||
logger.info("LLM Pixtral12b initialisé pour le tri d'images")
|
||||
|
||||
image_analyser_llm = Ollama()
|
||||
logger.info("LLM Ollama initialisé")
|
||||
print("LLM Ollama initialisé")
|
||||
image_analyser_llm = Pixtral12b()
|
||||
logger.info("LLM Pixtral12b initialisé pour l'analyse d'images")
|
||||
|
||||
report_generator_llm = MistralLarge()
|
||||
logger.info("Deuxième LLM MistralLarge initialisé")
|
||||
print("Deuxième LLM MistralLarge initialisé")
|
||||
report_generator_llm = MistralMedium()
|
||||
logger.info("LLM MistralMedium initialisé pour la génération de rapports")
|
||||
|
||||
llm_init_time = time.time() - start_time
|
||||
print(f"Tous les modèles LLM ont été initialisés en {llm_init_time:.2f} secondes")
|
||||
|
||||
# Création des agents
|
||||
print("Création des agents...")
|
||||
json_agent = AgentJsonAnalyser(json_llm)
|
||||
logger.info("Agent JSON analyser initialisé")
|
||||
print("Agent JSON analyser initialisé")
|
||||
|
||||
image_sorter = AgentImageSorter(image_sorter_llm)
|
||||
logger.info("Agent Image sorter initialisé")
|
||||
print("Agent Image sorter initialisé")
|
||||
|
||||
image_analyser = AgentImageAnalyser(image_analyser_llm)
|
||||
logger.info("Agent Image analyser initialisé")
|
||||
print("Agent Image analyser initialisé")
|
||||
|
||||
report_generator = AgentReportGenerator(report_generator_llm)
|
||||
logger.info("Agent Rapport générateur initialisé")
|
||||
print("Agent Rapport générateur initialisé")
|
||||
|
||||
print("Tous les agents ont été créés")
|
||||
|
||||
# Initialisation de l'orchestrateur avec les agents
|
||||
logger.info("Initialisation de l'orchestrateur")
|
||||
@ -74,16 +84,39 @@ def test_orchestrator():
|
||||
report_generator=report_generator
|
||||
)
|
||||
|
||||
# Exécution complète de l'orchestrateur
|
||||
# Vérification du ticket spécifique si fourni
|
||||
specific_ticket_path = None
|
||||
if ticket_id:
|
||||
target_ticket = f"ticket_{ticket_id}"
|
||||
specific_ticket_path = os.path.join("output", target_ticket)
|
||||
|
||||
if not os.path.exists(specific_ticket_path):
|
||||
logger.error(f"Le ticket {target_ticket} n'existe pas")
|
||||
print(f"ERREUR: Le ticket {target_ticket} n'existe pas")
|
||||
return
|
||||
|
||||
logger.info(f"Ticket spécifique à traiter: {specific_ticket_path}")
|
||||
print(f"Ticket spécifique à traiter: {target_ticket}")
|
||||
|
||||
# Exécution de l'orchestrateur
|
||||
total_start_time = time.time()
|
||||
logger.info("Début de l'exécution de l'orchestrateur")
|
||||
print("Début de l'exécution de l'orchestrateur")
|
||||
|
||||
orchestrator.executer()
|
||||
orchestrator.executer(specific_ticket_path)
|
||||
|
||||
logger.info("Fin de l'exécution de l'orchestrateur")
|
||||
print("Fin de l'exécution de l'orchestrateur")
|
||||
total_time = time.time() - total_start_time
|
||||
logger.info(f"Fin de l'exécution de l'orchestrateur (durée: {total_time:.2f} secondes)")
|
||||
print(f"Fin de l'exécution de l'orchestrateur (durée: {total_time:.2f} secondes)")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Démarrage du test de l'orchestrateur")
|
||||
test_orchestrator()
|
||||
|
||||
# Vérifier si un ID de ticket est passé en argument
|
||||
ticket_id = None
|
||||
if len(sys.argv) > 1:
|
||||
ticket_id = sys.argv[1]
|
||||
print(f"ID de ticket fourni en argument: {ticket_id}")
|
||||
|
||||
test_orchestrator(ticket_id)
|
||||
print("Test terminé")
|
||||
@ -2,4 +2,4 @@ home = /usr/bin
|
||||
include-system-site-packages = false
|
||||
version = 3.12.3
|
||||
executable = /usr/bin/python3.12
|
||||
command = /home/fgras-ca/llm-ticket3/venv/bin/python3 -m venv /home/fgras-ca/llm-ticket3/venv
|
||||
command = /usr/bin/python3 -m venv /home/fgras-ca/llm-ticket3/venv
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user