2104-16:56

This commit is contained in:
Ladebeze66 2025-04-21 16:56:29 +02:00
parent 826f2cde93
commit 797607f566
25 changed files with 949 additions and 2618 deletions

View File

@ -1,4 +1,4 @@
ÉMETTEUR,TYPE,DATE,CONTENU,ÉLÉMENTS VISUELS
CLIENT,question,03/04/2025 08:34,"Bonjour, Je ne parviens pas à accéder au lessai au bleu : Merci par avance pour votre. Cordialement","Message d'erreur : ""Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com"" (image_145435.png)"
SUPPORT,réponse,03/04/2025 12:17,"Bonjour, Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/ Voici ce que vous devriez voir affiché : Si ce n'est pas le cas, pouvez-vous me faire une capture d'écran de ce qui est affiché? Je reste à votre entière disposition pour toute information complémentaire. Cordialement,","Page de succès de Tomcat : ""If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!"" (image.png)"
CLIENT,information,03/04/2025 12:21,"Bonjour, Le problème sest résolu seul par la suite. Je vous remercie pour votre retour. Bonne journée PS : ladresse fonctionne","Page de succès de Tomcat : ""If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!"" (image.png)"
CLIENT,question,03/04/2025 08:34,Je ne parviens pas à accéder à lessai au bleu.,L'image (image_145435.png) montre que l'essai au bleu de méthylène (MB) est accessible.
SUPPORT,réponse,03/04/2025 12:17,Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/,L'image (image.png) confirme que la page https://zk1.brg-lab.com/ est accessible et que Tomcat est correctement installé.
CLIENT,information,03/04/2025 12:21,Le problème sest résolu seul par la suite. Je vous remercie pour votre retour. Bonne journée PS : ladresse fonctionne,L'image (image_145435.png) indique un problème de connexion ou de configuration réseau avec l'adresse IP du serveur de zk1.brg-lab.com.

1 ÉMETTEUR TYPE DATE CONTENU ÉLÉMENTS VISUELS
2 CLIENT question 03/04/2025 08:34 Bonjour, Je ne parviens pas à accéder au l’essai au bleu : Merci par avance pour votre. Cordialement Je ne parviens pas à accéder à l’essai au bleu. Message d'erreur : "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com" (image_145435.png) L'image (image_145435.png) montre que l'essai au bleu de méthylène (MB) est accessible.
3 SUPPORT réponse 03/04/2025 12:17 Bonjour, Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/ Voici ce que vous devriez voir affiché : Si ce n'est pas le cas, pouvez-vous me faire une capture d'écran de ce qui est affiché? Je reste à votre entière disposition pour toute information complémentaire. Cordialement, Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/ Page de succès de Tomcat : "If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!" (image.png) L'image (image.png) confirme que la page https://zk1.brg-lab.com/ est accessible et que Tomcat est correctement installé.
4 CLIENT information 03/04/2025 12:21 Bonjour, Le problème s’est résolu seul par la suite. Je vous remercie pour votre retour. Bonne journée PS : l’adresse fonctionne Le problème s’est résolu seul par la suite. Je vous remercie pour votre retour. Bonne journée PS : l’adresse fonctionne Page de succès de Tomcat : "If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!" (image.png) L'image (image_145435.png) indique un problème de connexion ou de configuration réseau avec l'adresse IP du serveur de zk1.brg-lab.com.

View File

@ -1,2 +1,2 @@
Question,Réponse,Contexte Question,Contexte Réponse
"Bonjour, Je ne parviens pas à accéder au lessai au bleu : Merci par avance pour votre. Cordialement","Bonjour, Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/ Voici ce que vous devriez voir affiché : Si ce n'est pas le cas, pouvez-vous me faire une capture d'écran de ce qui est affiché? Je reste à votre entière disposition pour toute information complémentaire. Cordialement,","Message d'erreur : ""Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com"" (image_145435.png)","Page de succès de Tomcat : ""If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!"" (image.png)"
Je ne parviens pas à accéder à lessai au bleu.,Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/,L'image (image_145435.png) montre que l'essai au bleu de méthylène (MB) est accessible.,L'image (image.png) confirme que la page https://zk1.brg-lab.com/ est accessible et que Tomcat est correctement installé.

1 Question Réponse Contexte Question Contexte Réponse
2 Bonjour, Je ne parviens pas à accéder au l’essai au bleu : Merci par avance pour votre. Cordialement Je ne parviens pas à accéder à l’essai au bleu. Bonjour, Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/ Voici ce que vous devriez voir affiché : Si ce n'est pas le cas, pouvez-vous me faire une capture d'écran de ce qui est affiché? Je reste à votre entière disposition pour toute information complémentaire. Cordialement, Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/ Message d'erreur : "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com" (image_145435.png) L'image (image_145435.png) montre que l'essai au bleu de méthylène (MB) est accessible. Page de succès de Tomcat : "If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!" (image.png) L'image (image.png) confirme que la page https://zk1.brg-lab.com/ est accessible et que Tomcat est correctement installé.

View File

@ -1,10 +1,9 @@
from ..base_agent import BaseAgent
from typing import Any, Dict
import logging
import os
from typing import Dict, Any
from PIL import Image
import base64
import io
from ..utils.pipeline_logger import sauvegarder_donnees
logger = logging.getLogger("AgentImageAnalyser")
@ -15,247 +14,166 @@ class AgentImageAnalyser(BaseAgent):
def __init__(self, llm):
super().__init__("AgentImageAnalyser", llm)
# Configuration locale de l'agent
self.temperature = 0.2
self.top_p = 0.9
self.max_tokens = 3000
# Configuration personnalisable
self.params = {
"temperature": 0.2,
"top_p": 0.8,
"max_tokens": 3000
}
# Centralisation des instructions d'analyse pour éviter la duplication
self.instructions_analyse = """
1. Description objective
Décris précisément ce que montre l'image :
- Interface logicielle, menus, fenêtres, onglets
- Messages d'erreur, messages système, code ou script
- Nom ou titre du logiciel ou du module si visible
self.instructions_analyse = (
"""
1. Description objective
Décris précisément ce que montre l'image :
- Interface logicielle, menus, fenêtres, onglets
- Messages d'erreur, messages système, code ou script
- Nom ou titre du logiciel ou du module si visible
- Distingue clairement le nom complet des essais/tests/modules (par exemple, "Essai au bleu de méthylène" au lieu de simplement "essai bleu")
2. Éléments techniques clés
Identifie :
- Versions logicielles ou modules affichés
- Codes d'erreur visibles
- Paramètres configurables (champs de texte, sliders, dropdowns, cases à cocher)
- Valeurs affichées ou préremplies dans les champs
- Éléments désactivés, grisés ou masqués (souvent non modifiables)
- Boutons actifs/inactifs
2. Éléments techniques clés
Identifie :
- Versions logicielles ou modules affichés
- Codes d'erreur visibles
- Paramètres configurables (champs de texte, sliders, dropdowns, cases à cocher)
- Valeurs affichées ou préremplies dans les champs
- Éléments désactivés, grisés ou masqués (souvent non modifiables)
- Boutons actifs/inactifs
- Boutons RAZ ou réinitialisation (souvent marqués "RAZ" et non "PAZ")
- Précise si des éléments colorés font partie de l'interface standard (ex: bouton toujours rouge) ou s'ils semblent être liés au problème
3. Éléments mis en évidence
- Recherche les zones entourées, encadrées, surlignées ou fléchées
- Ces éléments sont souvent importants pour le client ou le support
- Mentionne explicitement leur contenu et leur style de mise en valeur
3. Éléments mis en évidence
- Recherche les zones entourées, encadrées, surlignées ou fléchées
- Ces éléments sont souvent importants pour le client ou le support
- Mentionne explicitement leur contenu et leur style de mise en valeur
- Vérifie spécifiquement si des messages d'erreur sont visibles en bas ou en haut de l'écran
4. Relation avec le problème
- Établis le lien entre les éléments visibles et le problème décrit dans le ticket
- Indique si des composants semblent liés à une mauvaise configuration ou une erreur
4. Relation avec le problème
- Établis le lien entre les éléments visibles et le problème décrit dans le ticket
- Indique si des composants semblent liés à une mauvaise configuration ou une erreur
- Précise le nom complet du module/essai concerné par le problème (par exemple "Essai au bleu de méthylène (MB)" et pas seulement "essai bleu")
- Identifie si l'utilisateur a accès à l'écran d'essai mais avec des erreurs, ou s'il n'y a pas d'accès du tout
5. Réponses potentielles
- Détermine si l'image apporte des éléments de réponse à une question posée dans :
- Le titre du ticket
- La description du problème
5. Réponses potentielles
- Détermine si l'image apporte des éléments de réponse à une question posée dans :
- Le titre du ticket
- La description du problème
- Tente d'extrapoler le contexte technique précis en observant l'interface (ex: "l'essai au bleu" mentionné par le client correspond clairement à "l'essai au bleu de méthylène (MB) - NF EN 933-9")
6. Lien avec la discussion
- Vérifie si l'image fait écho à une étape décrite dans le fil de discussion
- Note les correspondances (ex: même module, même message d'erreur que précédemment mentionné)
6. Lien avec la discussion
- Vérifie si l'image fait écho à une étape décrite dans le fil de discussion
- Note les correspondances (ex: même module, même message d'erreur que précédemment mentionné)
- Établis des connections explicites entre le vocabulaire utilisé par le client et ce qui est visible dans l'interface
Règles importantes :
- Ne fais AUCUNE interprétation ni diagnostic
- Ne propose PAS de solution ou recommandation
- Reste strictement factuel et objectif
- Concentre-toi uniquement sur ce qui est visible dans l'image
- Reproduis les textes exacts(ex : messages d'erreur, libellés de paramètres)
- Prête une attention particulière aux éléments modifiables (interactifs) et non modifiables (grisés)
"""
7. Contexte technique élargi
- Identifie le contexte plus large de l'application (laboratoire, tests techniques, essais normalisés)
- Relève toutes les références à des normes ou standards (ex: NF EN 933-9)
- Mentionne tous les codes ou identifiants visibles qui pourraient être utiles (ex: numéros d'échantillons)
# Prompt système construit à partir des instructions centralisées
self.system_prompt = f"""Tu es un expert en analyse d'images pour le support technique de BRG-Lab pour la société CBAO.
Ta mission est d'analyser des captures d'écran en lien avec le contexte du ticket de support.
Règles importantes :
- Ne fais AUCUNE interprétation ni diagnostic sur les causes possibles
- Ne propose PAS de solution ou recommandation
- Reste strictement factuel et objectif, mais fais des liens explicites avec les termes utilisés par le client
- Concentre-toi uniquement sur ce qui est visible dans l'image
- Reproduis les textes exacts (ex : messages d'erreur, libellés de paramètres)
- Prête une attention particulière aux éléments modifiables (interactifs) et non modifiables (grisés)
- Utilise systématiquement le nom complet et précis des modules et essais
- Vérifie la lecture correcte des boutons et menus (attention aux confusions comme PAZ/RAZ)
"""
)
Structure ton analyse d'image de façon factuelle:
{self.instructions_analyse}
self.system_prompt = (
"""
Tu es un expert en analyse d'images pour le support technique de BRG-Lab pour la société CBAO.
Ta mission est d'analyser des captures d'écran en lien avec le contexte du ticket de support.
Ton analyse sera utilisée comme élément factuel pour un rapport technique plus complet."""
Tu dois être extrêmement précis dans ta lecture des interfaces et des éléments techniques.
Les clients utilisent souvent des termes abrégés (comme "essai bleu") alors que l'interface montre le terme complet ("Essai au bleu de méthylène"). Tu dois faire le lien entre ces termes.
Certains éléments dans l'interface peuvent prêter à confusion :
- Les boutons "RAZ" (réinitialisation) sont parfois difficiles à lire
- Des éléments en couleur peuvent faire partie de l'interface standard (et non du problème)
- Les messages d'erreur sont souvent en bas de l'écran et contiennent des informations cruciales
Structure ton analyse d'image de façon factuelle:
{instructions}
Ton analyse sera utilisée comme élément factuel pour un rapport technique plus complet et pour faire le lien entre le vocabulaire du client et les éléments techniques réels.
"""
).format(
instructions=self.instructions_analyse
)
# Appliquer la configuration au LLM
self._appliquer_config_locale()
logger.info("AgentImageAnalyser initialisé")
def _appliquer_config_locale(self) -> None:
"""
Applique la configuration locale au modèle LLM.
"""
# Appliquer le prompt système
if hasattr(self.llm, "prompt_system"):
self.llm.prompt_system = self.system_prompt
# Appliquer les paramètres
if hasattr(self.llm, "configurer"):
params = {
"temperature": self.temperature,
"top_p": self.top_p,
"max_tokens": self.max_tokens
}
self.llm.configurer(**params)
self.llm.configurer(**self.params)
def _verifier_image(self, image_path: str) -> bool:
"""
Vérifie si l'image existe et est accessible
Args:
image_path: Chemin vers l'image
Returns:
True si l'image existe et est accessible, False sinon
"""
try:
# Vérifier que le fichier existe
if not os.path.exists(image_path):
logger.error(f"L'image n'existe pas: {image_path}")
if not os.path.exists(image_path) or not os.access(image_path, os.R_OK):
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
return width > 0 and height > 0
except Exception as e:
logger.error(f"Erreur lors de la vérification de l'image {image_path}: {str(e)}")
logger.error(f"Vérification impossible pour {image_path}: {e}")
return False
def _encoder_image_base64(self, image_path: str) -> str:
"""
Encode l'image en base64 pour l'inclure directement dans le prompt
Args:
image_path: Chemin vers l'image
Returns:
Chaîne de caractères au format data URI avec l'image encodée en base64
"""
try:
# Ouvrir l'image et la redimensionner si trop grande
with Image.open(image_path) as img:
# Redimensionner l'image si elle est trop grande (max 800x800)
max_size = 800
if img.width > max_size or img.height > max_size:
img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
# Convertir en RGB si nécessaire (pour les formats comme PNG)
if img.mode != "RGB":
img = img.convert("RGB")
# Sauvegarder l'image en JPEG dans un buffer mémoire
buffer = io.BytesIO()
img.save(buffer, format="JPEG", quality=85)
buffer.seek(0)
# Encoder en base64
img_base64 = base64.b64encode(buffer.read()).decode("utf-8")
# Construire le data URI
data_uri = f"data:image/jpeg;base64,{img_base64}"
return data_uri
except Exception as e:
logger.error(f"Erreur lors de l'encodage de l'image {image_path}: {str(e)}")
return ""
def _generer_prompt_analyse(self, contexte: str, prefix: str = "") -> str:
"""
Génère le prompt d'analyse d'image en utilisant les instructions centralisées
Args:
contexte: Contexte du ticket à inclure dans le prompt
prefix: Préfixe optionnel (pour inclure l'image en base64 par exemple)
Returns:
Prompt formaté pour l'analyse d'image
Génère le prompt d'analyse d'image
"""
return f"""{prefix}
CONTEXTE DU TICKET:
{contexte}
INSTRUCTIONS IMPORTANTES:
- Lis attentivement tous les textes visibles dans l'image et reporte-les avec précision.
- Veille à bien distinguer les boutons "RAZ" (réinitialisation) s'ils sont présents.
- Identifie précisément le nom complet des essais ou tests mentionnés (ex: "Essai au bleu de méthylène" plutôt que juste "essai bleu").
- Si l'interface montre un essai ou module en particulier, précise son nom complet et sa norme associée.
- Fais attention aux messages d'erreur qui peuvent apparaître en bas ou en haut de l'écran.
Fournis une analyse STRICTEMENT FACTUELLE de l'image avec les sections suivantes:
{self.instructions_analyse}"""
def executer(self, image_path: str, contexte: str) -> Dict[str, Any]:
def executer(self, image_path: str, contexte: str = "") -> Dict[str, Any]:
"""
Analyse une image en tenant compte du contexte du ticket
Args:
image_path: Chemin vers l'image à analyser
contexte: Contexte du ticket (résultat de l'analyse JSON)
Returns:
Dictionnaire contenant l'analyse détaillée de l'image et les métadonnées d'exécution
"""
image_name = os.path.basename(image_path)
logger.info(f"Analyse de l'image: {image_name} avec contexte")
print(f" AgentImageAnalyser: Analyse de {image_name}")
# Vérifier que l'image existe et est accessible
if not self._verifier_image(image_path):
error_message = f"L'image n'est pas accessible ou n'est pas valide: {image_name}"
logger.error(error_message)
print(f" ERREUR: {error_message}")
return {
"analyse": f"ERREUR: {error_message}. Veuillez vérifier que l'image existe et est valide.",
"error": True,
"metadata": {
"image_path": image_path,
"image_name": image_name,
"timestamp": self._get_timestamp(),
"error": True
}
}
# Générer le prompt d'analyse avec les instructions centralisées
prompt = self._generer_prompt_analyse(contexte, "Analyse cette image en tenant compte du contexte suivant:")
return self._erreur("Erreur d'accès ou image invalide", image_path)
try:
logger.info("Envoi de la requête au LLM")
prompt = self._generer_prompt_analyse(contexte, "Analyse cette image en tenant compte du contexte suivant:")
# 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)
elif hasattr(self.llm, "_encoder_image_base64"):
img_base64 = self.llm._encoder_image_base64(image_path)
if img_base64:
# Utiliser le même générateur de prompt avec l'image en base64
prompt_base64 = self._generer_prompt_analyse(contexte, f"Analyse cette image:\n{img_base64}")
response = self.llm.interroger(prompt_base64)
else:
error_message = "Impossible d'encoder l'image en base64"
logger.error(f"Erreur d'analyse pour {image_name}: {error_message}")
print(f" ERREUR: {error_message}")
# Retourner un résultat d'erreur explicite
return {
"analyse": f"ERREUR: {error_message}. Veuillez vérifier que l'image est dans un format standard.",
"error": True,
"raw_response": "",
"metadata": {
"image_path": image_path,
"image_name": image_name,
"timestamp": self._get_timestamp(),
"error": True
}
}
return self._erreur("Impossible d'encoder l'image en base64", image_path)
else:
return self._erreur("Le modèle ne supporte pas l'analyse d'images", image_path)
# Vérifier si la réponse contient des indications que le modèle ne peut pas analyser l'image
error_phrases = [
@ -268,71 +186,74 @@ Fournis une analyse STRICTEMENT FACTUELLE de l'image avec les sections suivantes
"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}")
return self._erreur("Le modèle n'a pas pu analyser l'image correctement", image_path, raw=response)
# 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
}
}
# Effectuer des corrections sur la réponse si nécessaire
response = self._corriger_termes_courants(response)
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,
"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
}
**getattr(self.llm, "params", {})
},
"source_agent": self.nom
}
}
# Enregistrer l'analyse dans l'historique avec contexte et prompt
self.ajouter_historique("analyse_image",
{
"image_path": image_path,
"contexte": contexte,
"prompt": prompt
},
response)
# Sauvegarder les données dans le fichier
sauvegarder_donnees(None, "analyse_image", result, base_dir="reports", is_resultat=True)
# Enregistrer l'analyse dans l'historique
self.ajouter_historique(
"analyse_image",
{"image_path": image_path, "contexte": contexte, "prompt": prompt},
result
)
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}")
return self._erreur(str(e), image_path)
# Retourner un résultat par défaut en cas d'erreur
return {
"analyse": f"ERREUR: {error_message}",
def _corriger_termes_courants(self, texte: str) -> str:
"""
Corrige certains termes couramment mal interprétés par le modèle.
"""
corrections = {
"PAZ": "RAZ",
"Essai bleu": "Essai au bleu de méthylène",
"essai bleu": "essai au bleu de méthylène",
"Essai au bleu": "Essai au bleu de méthylène"
}
for terme_incorrect, terme_correct in corrections.items():
texte = texte.replace(terme_incorrect, terme_correct)
return texte
def _erreur(self, message: str, path: str, raw: str = "") -> Dict[str, Any]:
"""
Crée un dictionnaire de réponse en cas d'erreur
"""
return {
"analyse": f"ERREUR: {message}",
"raw_response": raw,
"error": True,
"metadata": {
"image_path": path,
"image_name": os.path.basename(path),
"timestamp": self._get_timestamp(),
"error": True,
"metadata": {
"image_path": image_path,
"image_name": image_name,
"timestamp": self._get_timestamp(),
"error": True
}
"source_agent": self.nom
}
}
def _get_timestamp(self) -> str:
"""Retourne un timestamp au format YYYYMMDD_HHMMSS"""

View File

@ -3,8 +3,7 @@ import logging
import os
from typing import Dict, Any, Tuple
from PIL import Image
import base64
import io
from ..utils.pipeline_logger import sauvegarder_donnees
logger = logging.getLogger("AgentImageSorter")
@ -15,261 +14,103 @@ class AgentImageSorter(BaseAgent):
def __init__(self, llm):
super().__init__("AgentImageSorter", llm)
# Configuration locale de l'agent
self.temperature = 0.2
self.top_p = 0.8
self.max_tokens = 300
# Configuration centralisée dans un dictionnaire
self.params = {
"temperature": 0.2,
"top_p": 0.8,
"max_tokens": 300
# Ajoutez facilement d'autres paramètres ici sans modifier _appliquer_config_locale
}
# Centralisation des critères de pertinence
self.criteres_pertinence = """
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
self.criteres_pertinence = (
"""
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
"""
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
"""
)
# Centralisation des instructions d'analyse
self.instructions_analyse = """
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".
self.instructions_analyse = (
"""
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".
"""
Analyse d'abord ce que montre l'image, puis réponds par "oui"/"pertinent" ou "non"/"non pertinent".
"""
)
# Construction du système prompt à partir des éléments centralisés
self.system_prompt = f"""Tu es un expert en tri d'images pour le support technique de BRG_Lab pour la société CBAO.
Ta mission est de déterminer si une image est pertinente pour le support technique de logiciels.
{self.criteres_pertinence}
{self.instructions_analyse}"""
self.system_prompt = (
"""
Tu es un expert en tri d'images pour le support technique de BRG_Lab pour la société CBAO.
Ta mission est de déterminer si une image est pertinente pour le support technique de logiciels.
{criteres}
{instructions}
"""
).format(
criteres=self.criteres_pertinence,
instructions=self.instructions_analyse
)
# 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
# Appliquer les paramètres
if hasattr(self.llm, "configurer"):
params = {
"temperature": self.temperature,
"top_p": self.top_p,
"max_tokens": self.max_tokens
}
self.llm.configurer(**params)
# Utiliser directement le dictionnaire de paramètres
self.llm.configurer(**self.params)
def _verifier_image(self, image_path: str) -> bool:
"""
Vérifie si l'image existe et est accessible
Args:
image_path: Chemin vers l'image
Returns:
True si l'image existe et est accessible, False sinon
"""
try:
# Vérifier que le fichier existe
if not os.path.exists(image_path):
logger.error(f"L'image n'existe pas: {image_path}")
if not os.path.exists(image_path) or not os.access(image_path, os.R_OK):
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
return width > 0 and height > 0
except Exception as e:
logger.error(f"Erreur lors de la vérification de l'image {image_path}: {str(e)}")
logger.error(f"Vérification impossible pour {image_path}: {e}")
return False
def _encoder_image_base64(self, image_path: str) -> str:
"""
Encode l'image en base64 pour l'inclure directement dans le prompt
Args:
image_path: Chemin vers l'image
Returns:
Chaîne de caractères au format data URI avec l'image encodée en base64
"""
try:
# Ouvrir l'image et la redimensionner si trop grande
with Image.open(image_path) as img:
# Redimensionner l'image si elle est trop grande (max 800x800)
max_size = 800
if img.width > max_size or img.height > max_size:
img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
# Convertir en RGB si nécessaire (pour les formats comme PNG)
if img.mode != "RGB":
img = img.convert("RGB")
# Sauvegarder l'image en JPEG dans un buffer mémoire
buffer = io.BytesIO()
img.save(buffer, format="JPEG", quality=85)
buffer.seek(0)
# Encoder en base64
img_base64 = base64.b64encode(buffer.read()).decode("utf-8")
# Construire le data URI
data_uri = f"data:image/jpeg;base64,{img_base64}"
return data_uri
except Exception as e:
logger.error(f"Erreur lors de l'encodage de l'image {image_path}: {str(e)}")
return ""
def _generer_prompt_analyse(self, prefix: str = "", avec_image_base64: bool = False) -> str:
"""
Génère le prompt d'analyse standardisé
Args:
prefix: Préfixe optionnel (pour inclure l'image en base64 par exemple)
avec_image_base64: Indique si le prompt inclut déjà une image en base64
Returns:
Prompt formaté pour l'analyse
"""
return f"""{prefix}
Est-ce une image pertinente pour un ticket de support technique?
Réponds simplement par 'oui' ou 'non' suivi d'une brève explication."""
def _generer_prompt_analyse(self, prefix: str = "") -> str:
return f"{prefix}\n\nEst-ce une image pertinente pour un ticket de support technique?\nRéponds simplement par 'oui' ou 'non' suivi d'une brève explication."
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 self._erreur("Erreur d'accès ou image invalide", image_path)
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 standardisé
prompt = self._generer_prompt_analyse()
# 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)
elif hasattr(self.llm, "_encoder_image_base64"):
img_base64 = self.llm._encoder_image_base64(image_path)
prompt = self._generer_prompt_analyse(f"Analyse cette image:\n{img_base64}")
response = self.llm.interroger(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 = self._generer_prompt_analyse(f"Analyse cette image:\n{img_base64}", True)
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 self._erreur("Le modèle ne supporte pas les images", image_path)
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
}
}
if any(err in response.lower() for err in [
"je ne peux pas", "je n'ai pas accès", "impossible d'analyser"]):
return self._erreur("Réponse du modèle invalide", image_path, raw=response)
# 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,
@ -280,114 +121,49 @@ Réponds simplement par 'oui' ou 'non' suivi d'une brève explication."""
"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
}
**getattr(self.llm, "params", {})
},
"source_agent": self.nom
}
}
# 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
})
# Sauvegarder les données dans le fichier
sauvegarder_donnees(None, "tri_image", result, base_dir="reports", is_resultat=True)
self.ajouter_historique("tri_image", {"image_path": image_path, "prompt": prompt}, result)
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
}
}
return self._erreur(str(e), image_path)
def _analyser_reponse(self, response: str) -> Tuple[bool, str]:
"""
Analyse la réponse du LLM pour déterminer la pertinence et extraire le raisonnement
r = response.lower()
first_line = r.split('\n')[0] if '\n' in r else r[:50].strip()
if first_line.startswith("non") or "non pertinent" in first_line:
return False, response.strip()
if first_line.startswith("oui") or "pertinent" in first_line:
return True, response.strip()
Args:
response: Réponse brute du LLM
pos_keywords = ["pertinent", "utile", "important", "diagnostic"]
neg_keywords = ["inutile", "photo", "hors sujet", "marketing", "non pertinent"]
score = sum(kw in r for kw in pos_keywords) - sum(kw in r for kw in neg_keywords)
return score > 0, response.strip()
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 _erreur(self, message: str, path: str, raw: str = "") -> Dict[str, Any]:
return {
"is_relevant": False,
"reason": message,
"raw_response": raw,
"error": True,
"metadata": {
"image_path": path,
"image_name": os.path.basename(path),
"timestamp": self._get_timestamp(),
"error": True,
"source_agent": self.nom
}
}
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")

View File

@ -1,609 +1,272 @@
import json
import os
from ..base_agent import BaseAgent
from datetime import datetime
from typing import Dict, Any, Tuple, Optional, List
from typing import Dict, Any
import logging
import os
import json
import traceback
import re
import sys
from ..utils.report_utils import extraire_et_traiter_json
from ..utils.report_formatter import extraire_sections_texte, generer_rapport_markdown, construire_rapport_json
from ..utils.agent_info_collector import collecter_info_agents, collecter_prompts_agents
from datetime import datetime
from ..utils.pipeline_logger import sauvegarder_donnees
logger = logging.getLogger("AgentReportGeneratorQwen")
# Configuration du logger pour plus de détails
logger = logging.getLogger("AgentReportGenerator")
logger.setLevel(logging.DEBUG) # Augmenter le niveau de logging pour le débogage
class AgentReportGenerator(BaseAgent):
"""
Agent spécialisé pour générer des rapports avec le modèle Qwen.
Adapté pour gérer les limitations spécifiques de Qwen et optimiser les résultats.
Cet agent utilise une approche en plusieurs étapes pour éviter les timeouts
et s'assurer que tous les éléments du rapport soient bien générés.
"""
def __init__(self, llm):
super().__init__("AgentReportGeneratorQwen", llm)
super().__init__("AgentReportGenerator", llm)
# Configuration locale de l'agent
self.temperature = 0.2
self.top_p = 0.9
self.max_tokens = 10000 # Réduit pour Qwen pour éviter les timeouts
self.params = {
"temperature": 0.2,
"top_p": 0.8,
"max_tokens": 8000
}
# Prompt système principal - Simplifié et optimisé pour Qwen
self.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG-Lab.
Ta mission est de synthétiser les analyses en un rapport clair et structuré.
self.system_prompt = """Tu es un expert en support technique chargé de générer un rapport final à partir des analyses d'un ticket de support.
Ton rôle est de croiser les informations provenant :
- de l'analyse textuelle du ticket client
- des analyses détaillées de plusieurs captures d'écran
TON RAPPORT DOIT OBLIGATOIREMENT INCLURE DANS CET ORDRE:
1. Un résumé du problème initial
2. Une analyse des images pertinentes (courte)
3. Une synthèse globale des analyses d'images (très brève)
4. Une reconstitution du fil de discussion
5. Un tableau des échanges au format JSON
6. Un diagnostic technique des causes probables
Tu dois structurer ta réponse en format question/réponse de manière claire, en gardant l'intégralité des points importants.
Le format JSON des échanges DOIT être exactement:
```json
{
"chronologie_echanges": [
{"date": "date exacte", "emetteur": "CLIENT", "type": "Question", "contenu": "contenu synthétisé"},
{"date": "date exacte", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "contenu avec liens"}
]
}
```
Ne propose jamais de solution. Ne reformule pas le contexte.
Ta seule mission est de croiser les données textuelles et visuelles et d'en tirer des observations claires, en listant les éléments factuels visibles dans les captures qui appuient ou complètent le texte du ticket.
IMPORTANT: La structure JSON correcte est la partie la plus critique!"""
Structure du rapport attendu :
1. Contexte général (résumé du ticket textuel en une phrase)
2. Problèmes ou questions identifiés (sous forme de questions claires)
3. Résumé croisé image/texte pour chaque question
4. Liste d'observations supplémentaires pertinentes (si applicable)
5. Tableau chronologique d'échanges
- Inclure un tableau structuré des échanges entre client et support
- Format : Émetteur | Type | Date | Contenu | Éléments visuels pertinents
- Ne pas mentionner les noms réels des personnes, utiliser "CLIENT" et "SUPPORT"
- Synthétiser le contenu tout en conservant les informations importantes
- Associer les éléments visuels des captures d'écran aux échanges correspondants
# Version du prompt pour la traçabilité
self.prompt_version = "qwen-v1.1"
Règles pour le tableau d'échanges :
- TYPE peut être : question, réponse, information, complément visuel
- Pour chaque échange du client mentionnant un problème, ajoute les éléments visuels des captures qui contextualisent ce problème
- Pour chaque réponse du support, ajoute les éléments visuels qui confirment ou infirment la réponse
- N'invente aucun contenu ni aucune date
- Utilise les données factuelles des images pour enrichir la compréhension des échanges
# Flag pour indiquer si on doit utiliser l'approche en 2 étapes
self.use_two_step_approach = True
Reste strictement factuel. Ne fais aucune hypothèse. Ne suggère pas d'étapes ni d'interprétation."""
# Appliquer la configuration au LLM
self._appliquer_config_locale()
logger.info("AgentReportGeneratorQwen initialisé")
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
# Appliquer les paramètres
if hasattr(self.llm, "configurer"):
params = {
"temperature": self.temperature,
"top_p": self.top_p,
"max_tokens": self.max_tokens,
"timeout": 60 # Timeout réduit pour Qwen
}
self.llm.configurer(**params)
logger.info(f"Configuration appliquée au modèle Qwen: {str(params)}")
self.llm.configurer(**self.params)
def _formater_prompt_pour_rapport_etape1(self, ticket_analyse: str, images_analyses: List[Dict]) -> str:
def _verifier_donnees_entree(self, rapport_data: Dict[str, Any]) -> bool:
"""
Formate le prompt pour la première étape: résumé, analyse d'images et synthèse
Vérifie que les données d'entrée contiennent les éléments nécessaires.
Args:
rapport_data: Données pour générer le rapport
Returns:
bool: True si les données sont valides, False sinon
"""
num_images = len(images_analyses)
logger.info(f"Formatage du prompt étape 1 avec {num_images} analyses d'images")
ticket_id = rapport_data.get("ticket_id")
if not ticket_id:
logger.error("Erreur de validation: ticket_id manquant")
return False
# Construire la section d'analyse du ticket
prompt = f"""Génère les 3 premières sections d'un rapport technique basé sur les analyses suivantes.
ticket_analyse = rapport_data.get("ticket_analyse")
if not ticket_analyse:
logger.error(f"Erreur de validation pour {ticket_id}: analyse de ticket manquante")
return False
## ANALYSE DU TICKET
{ticket_analyse}
"""
analyses_images = rapport_data.get("analyse_images", {})
if not analyses_images:
logger.warning(f"Avertissement pour {ticket_id}: aucune analyse d'image disponible")
# On continue quand même car on peut générer un rapport sans images
# Ajouter la section d'analyse des images si présente
if num_images > 0:
prompt += f"\n## ANALYSES DES IMAGES ({num_images} images)\n"
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"
else:
prompt += "\n## ANALYSES DES IMAGES\nAucune image n'a été fournie pour ce ticket.\n"
# Vérifier si au moins une image a été analysée
images_analysees = 0
for img_path, img_data in analyses_images.items():
if img_data.get("analysis") and img_data["analysis"].get("analyse"):
images_analysees += 1
# Instructions pour le rapport
prompt += """
## INSTRUCTIONS POUR LE RAPPORT (ÉTAPE 1)
if images_analysees == 0 and analyses_images:
logger.warning(f"Avertissement pour {ticket_id}: {len(analyses_images)} images trouvées mais aucune n'a été analysée")
GÉNÈRE UNIQUEMENT LES 3 PREMIÈRES SECTIONS:
1. Résumé du problème (## Résumé du problème)
2. Analyse des images (## Analyse des images)
3. Synthèse globale des analyses d'images (## 3.1 Synthèse globale des analyses d'images)
logger.info(f"Validation pour {ticket_id}: OK, {images_analysees} images analysées sur {len(analyses_images)} images")
return True
POUR LA SECTION ANALYSE DES IMAGES:
- Décris chaque image de manière factuelle
- Mets en évidence les éléments encadrés ou surlignés
- Explique la relation avec le problème initial
def executer(self, rapport_data: Dict[str, Any]) -> str:
ticket_id = rapport_data.get("ticket_id", "Inconnu")
print(f"AgentReportGenerator : génération du rapport pour le ticket {ticket_id}")
POUR LA SECTION SYNTHÈSE GLOBALE:
- Titre à utiliser OBLIGATOIREMENT: ## 3.1 Synthèse globale des analyses d'images
- Premier sous-titre à utiliser OBLIGATOIREMENT: _Analyse transversale des captures d'écran_
- Explique comment les images se complètent
- Identifie les points communs entre les images
- Montre comment elles confirment les informations du support
NE GÉNÈRE PAS ENCORE:
- Le fil de discussion
- Le tableau des échanges
- Le diagnostic technique
Reste factuel et précis dans ton analyse.
"""
return prompt
def _formater_prompt_pour_rapport_etape2(self, ticket_analyse: str, etape1_resultat: str) -> str:
"""
Formate le prompt pour la seconde étape: fil de discussion, tableau JSON et diagnostic
"""
logger.info(f"Formatage du prompt étape 2")
# Extraire le résumé et l'analyse des images de l'étape 1
resume_match = re.search(r'## Résumé du problème(.*?)(?=##|$)', etape1_resultat, re.DOTALL)
resume = resume_match.group(1).strip() if resume_match else "Résumé non disponible."
prompt = f"""Génère le tableau JSON des échanges pour le ticket en te basant sur l'analyse du ticket.
## ANALYSE DU TICKET (UTILISE CES DONNÉES POUR CRÉER LES ÉCHANGES)
{ticket_analyse}
## RÉSUMÉ DU PROBLÈME
{resume}
## INSTRUCTIONS POUR LE TABLEAU JSON
CRÉE UNIQUEMENT UN TABLEAU JSON avec cette structure:
```json
{{
"chronologie_echanges": [
{{"date": "14/03/2023 10:48:53", "emetteur": "CLIENT", "type": "Question", "contenu": "Création échantillons - Opérateur de prélèvement : Saisie manuelle non possible. Dans l'ancienne version, on saisissait nous même la personne qui a prélevé l'échantillon, mais cette option ne semble plus disponible."}},
{{"date": "14/03/2023 13:25:45", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Pour des raisons normatives, l'opérateur de prélèvement doit obligatoirement faire partie de la liste des utilisateurs du logiciel et appartenir au groupe 'Opérateur de prélèvement'. Il n'est donc pas possible d'ajouter une personne tierce."}}
]
}}
```
IMPORTANT:
- AJOUTE OBLIGATOIREMENT une entrée pour la question initiale du client extraite du nom ou de la description du ticket
- INCLUS OBLIGATOIREMENT la réponse du support
- AJOUTE OBLIGATOIREMENT une entrée "Complément visuel" qui synthétise l'apport des images
- UTILISE les dates et le contenu exact des messages du ticket
- Format à suivre pour le complément visuel:
```json
{{
"chronologie_echanges": [
// ... question et réponse ...
{{"date": "DATE_ACTUELLE", "emetteur": "SUPPORT", "type": "Complément visuel", "contenu": "L'analyse de l'image confirme visuellement le problème: la liste déroulante des opérateurs de prélèvement affiche 'Aucun opérateur trouvé', ce qui concorde avec l'explication fournie concernant les restrictions normatives."}}
]
}}
```
"""
return prompt
def _creer_fil_discussion_dynamique(self, ticket_data: Dict, echanges_json: Dict) -> str:
"""
Génère un fil de discussion dynamiquement à partir des données du ticket et des échanges
"""
logger.info("Génération du fil de discussion dynamique")
# Initialiser le fil de discussion
fil_discussion = "## Fil de discussion\n\n"
# Extraire les informations du ticket
ticket_name = ticket_data.get("name", "")
ticket_description = ticket_data.get("description", "")
ticket_create_date = ticket_data.get("create_date", "")
# Générer la section question initiale
fil_discussion += "### Question initiale du client\n"
if ticket_create_date:
fil_discussion += f"**Date**: {ticket_create_date}\n"
if ticket_name:
fil_discussion += f"**Sujet**: {ticket_name}\n"
if ticket_description:
# Nettoyer et formater la description
description_clean = ticket_description.replace("\n\n", "\n").strip()
fil_discussion += f"**Contenu**: {description_clean}\n\n"
# Ajouter les réponses du support et compléments visuels
if echanges_json and "chronologie_echanges" in echanges_json:
for echange in echanges_json["chronologie_echanges"]:
emetteur = echange.get("emetteur", "")
type_msg = echange.get("type", "")
date = echange.get("date", "")
contenu = echange.get("contenu", "")
# Uniquement les messages du support, pas les questions client déjà incluses
if emetteur.upper() == "SUPPORT":
if type_msg.upper() == "RÉPONSE" or type_msg.upper() == "REPONSE":
fil_discussion += f"### Réponse du support technique\n"
if date:
fil_discussion += f"**Date**: {date}\n"
fil_discussion += f"**Contenu**:\n{contenu}\n\n"
elif type_msg.upper() == "COMPLÉMENT VISUEL" or type_msg.upper() == "COMPLEMENT VISUEL":
fil_discussion += f"### Analyse visuelle\n"
if date:
fil_discussion += f"**Date**: {date}\n"
fil_discussion += f"**Contenu**:\n{contenu}\n\n"
return fil_discussion
def executer(self, rapport_data: Dict, rapport_dir: str) -> Tuple[Optional[str], Optional[str]]:
"""
Génère un rapport à partir des analyses effectuées, en utilisant une approche
en deux étapes adaptée aux contraintes du modèle Qwen
"""
try:
# 1. PRÉPARATION
ticket_id = self._extraire_ticket_id(rapport_data, rapport_dir)
logger.info(f"Génération du rapport Qwen pour le ticket: {ticket_id}")
print(f"AgentReportGeneratorQwen: Génération du rapport pour {ticket_id}")
# Vérifier et enregistrer les données d'entrée pour le débogage
logger.debug(f"Données reçues pour {ticket_id}: {json.dumps(rapport_data, default=str)[:500]}...")
# Créer le répertoire de sortie si nécessaire
os.makedirs(rapport_dir, exist_ok=True)
# Vérifier que les données d'entrée sont valides
if not self._verifier_donnees_entree(rapport_data):
error_msg = f"Impossible de générer le rapport: données d'entrée invalides pour {ticket_id}"
print(f"ERREUR: {error_msg}")
return f"ERREUR: {error_msg}"
# 2. EXTRACTION DES DONNÉES
ticket_analyse = self._extraire_analyse_ticket(rapport_data)
images_analyses = self._extraire_analyses_images(rapport_data)
print(f"Préparation du prompt pour le ticket {ticket_id}...")
prompt = self._generer_prompt(rapport_data)
logger.debug(f"Prompt généré ({len(prompt)} caractères): {prompt[:500]}...")
# Extraire les données du ticket pour utilisation ultérieure
ticket_data = rapport_data.get("ticket_data", {})
print(f"Analyse en cours pour le ticket {ticket_id}...")
response = self.llm.interroger(prompt)
print(f"Analyse terminée: {len(response)} caractères")
logger.debug(f"Réponse reçue ({len(response)} caractères): {response[:500]}...")
# 3. COLLECTE DES INFORMATIONS SUR LES AGENTS
agent_info = {
"model": getattr(self.llm, "modele", str(type(self.llm))),
"temperature": self.temperature,
"top_p": self.top_p,
"max_tokens": self.max_tokens,
"prompt_version": self.prompt_version
}
agents_info = collecter_info_agents(rapport_data, agent_info)
prompts_utilises = collecter_prompts_agents(self.system_prompt)
# 4. GÉNÉRATION DU RAPPORT (APPROCHE EN DEUX ÉTAPES)
start_time = datetime.now()
if self.use_two_step_approach:
logger.info("Utilisation de l'approche en deux étapes pour Qwen")
print(f" Génération du rapport en deux étapes...")
# ÉTAPE 1: Résumé, analyse d'images et synthèse
logger.info("ÉTAPE 1: Génération du résumé, analyse d'images et synthèse")
prompt_etape1 = self._formater_prompt_pour_rapport_etape1(ticket_analyse, images_analyses)
try:
etape1_resultat = self.llm.interroger(prompt_etape1)
logger.info(f"Étape 1 complétée: {len(etape1_resultat)} caractères")
print(f" Étape 1 complétée: {len(etape1_resultat)} caractères")
except Exception as e:
logger.error(f"Erreur lors de l'étape 1: {str(e)}")
etape1_resultat = "## Résumé du problème\nUne erreur est survenue lors de la génération du résumé.\n\n## Analyse des images\nLes images n'ont pas pu être analysées correctement.\n\n## Synthèse globale des analyses d'images\nImpossible de fournir une synthèse complète en raison d'une erreur de génération."
# ÉTAPE 2: Tableau JSON uniquement
logger.info("ÉTAPE 2: Génération du tableau JSON")
prompt_etape2 = self._formater_prompt_pour_rapport_etape2(ticket_analyse, etape1_resultat)
try:
etape2_resultat = self.llm.interroger(prompt_etape2)
logger.info(f"Étape 2 complétée: {len(etape2_resultat)} caractères")
print(f" Étape 2 complétée: {len(etape2_resultat)} caractères")
# Extraire uniquement le JSON si c'est tout ce qui est généré
json_match = re.search(r'```json\s*(.*?)\s*```', etape2_resultat, re.DOTALL)
if json_match:
json_content = json_match.group(1)
etape2_resultat = f"## Tableau questions/réponses\n```json\n{json_content}\n```\n\n## Diagnostic technique\nLe problème d'affichage des utilisateurs est dû à deux configurations possibles:\n\n1. Les utilisateurs sans laboratoire principal assigné n'apparaissent pas par défaut dans la liste. La solution est d'activer l'option \"Affiche les laboratoires secondaires\".\n\n2. Les utilisateurs dont le compte a été dévalidé n'apparaissent pas par défaut. Il faut cocher l'option \"Affiche les utilisateurs non valides\" pour les voir apparaître (en grisé dans la liste)."
except Exception as e:
logger.error(f"Erreur lors de l'étape 2: {str(e)}")
# Créer une structure JSON minimale pour éviter les erreurs
etape2_resultat = """## Tableau questions/réponses\n```json\n{"chronologie_echanges": []}\n```\n\n## Diagnostic technique\nUne erreur est survenue lors de la génération du diagnostic."""
# Extraire le JSON généré ou utiliser un JSON par défaut
json_match = re.search(r'```json\s*(.*?)\s*```', etape2_resultat, re.DOTALL)
if json_match:
try:
echanges_json = json.loads(json_match.group(1))
except:
echanges_json = {"chronologie_echanges": []}
else:
echanges_json = {"chronologie_echanges": []}
# AJOUT: S'assurer qu'il y a une question initiale du client
if not any(e.get("emetteur", "").upper() == "CLIENT" and e.get("type", "").upper() == "QUESTION" for e in echanges_json.get("chronologie_echanges", [])):
# Ajouter une question initiale extraite du ticket
question_initiale = {
"date": ticket_data.get("create_date", datetime.now().strftime("%d/%m/%Y %H:%M:%S")),
"emetteur": "CLIENT",
"type": "Question",
"contenu": f"{ticket_data.get('name', '')}. {ticket_data.get('description', '').split('\n')[0]}"
# Création du résultat complet avec métadonnées
result = {
"prompt": prompt,
"response": response,
"metadata": {
"ticket_id": ticket_id,
"timestamp": self._get_timestamp(),
"source_agent": self.nom,
"model_info": {
"model": getattr(self.llm, "modele", str(type(self.llm))),
**getattr(self.llm, "params", {})
}
# Insérer au début de la chronologie
if "chronologie_echanges" in echanges_json and echanges_json["chronologie_echanges"]:
echanges_json["chronologie_echanges"].insert(0, question_initiale)
else:
echanges_json["chronologie_echanges"] = [question_initiale]
# AJOUT: S'assurer qu'il y a un complément visuel si des images sont disponibles
if images_analyses and not any(e.get("type", "").upper() in ["COMPLÉMENT VISUEL", "COMPLEMENT VISUEL"] for e in echanges_json.get("chronologie_echanges", [])):
# Créer un complément visuel basé sur les images disponibles
complement_visuel = {
"date": datetime.now().strftime("%d/%m/%Y %H:%M:%S"),
"emetteur": "SUPPORT",
"type": "Complément visuel",
"contenu": f"L'analyse de {len(images_analyses)} image(s) confirme visuellement le problème: la liste déroulante des opérateurs de prélèvement affiche 'Aucun opérateur trouvé', ce qui concorde avec l'explication fournie concernant les restrictions normatives."
}
# Ajouter à la fin de la chronologie
if "chronologie_echanges" in echanges_json:
echanges_json["chronologie_echanges"].append(complement_visuel)
# Mettre à jour le JSON dans etape2_resultat
etape2_resultat_updated = re.sub(
r'```json\s*.*?\s*```',
f'```json\n{json.dumps(echanges_json, indent=2, ensure_ascii=False)}\n```',
etape2_resultat,
flags=re.DOTALL
)
# Générer le fil de discussion dynamiquement à partir des données réelles
fil_discussion = self._creer_fil_discussion_dynamique(ticket_data, echanges_json)
# Combiner les résultats des deux étapes
rapport_genere = f"# Rapport d'analyse: {ticket_id}\n\n{etape1_resultat}\n\n{fil_discussion}\n\n{etape2_resultat_updated}"
else:
# APPROCHE STANDARD EN UNE ÉTAPE (FALLBACK)
logger.info("Utilisation de l'approche standard en une étape")
print(f" Génération du rapport avec le LLM en une étape...")
# Version simplifiée pour générer le rapport en une seule étape
prompt = f"""Génère un rapport technique complet sur le ticket {ticket_id}.
## ANALYSE DU TICKET
{ticket_analyse}
## ANALYSES DES IMAGES ({len(images_analyses)} images)
[Résumé des analyses d'images disponible]
## STRUCTURE OBLIGATOIRE
1. Résumé du problème
2. Analyse des images
3. Synthèse globale
4. Fil de discussion
5. Tableau JSON des échanges
6. Diagnostic technique
IMPORTANT: INCLUS ABSOLUMENT un tableau JSON des échanges avec cette structure:
```json
{{
"chronologie_echanges": [
{{"date": "date", "emetteur": "CLIENT", "type": "Question", "contenu": "contenu"}}
]
}}
```
"""
try:
rapport_genere = self.llm.interroger(prompt)
except Exception as e:
logger.error(f"Erreur lors de la génération en une étape: {str(e)}")
rapport_genere = f"# Rapport d'analyse: {ticket_id}\n\n## Erreur\nUne erreur est survenue lors de la génération du rapport complet.\n\n## Tableau questions/réponses\n```json\n{{\"chronologie_echanges\": []}}\n```"
# Calculer le temps total de génération
generation_time = (datetime.now() - start_time).total_seconds()
logger.info(f"Rapport généré: {len(rapport_genere)} caractères en {generation_time} secondes")
print(f" Rapport généré: {len(rapport_genere)} caractères en {generation_time:.2f} secondes")
# 5. VÉRIFICATION ET CORRECTION DU TABLEAU JSON
rapport_traite, echanges_json, _ = extraire_et_traiter_json(rapport_genere)
# Si aucun JSON n'est trouvé, créer une structure minimale
if echanges_json is None:
logger.warning("Aucun échange JSON extrait, tentative de génération manuelle")
# Créer une structure JSON minimale basée sur le ticket
echanges_json = {"chronologie_echanges": []}
try:
# Extraire la question du ticket
ticket_name = ticket_data.get("name", "")
ticket_description = ticket_data.get("description", "")
# Créer une entrée pour la question cliente
echanges_json["chronologie_echanges"].append({
"date": ticket_data.get("create_date", datetime.now().strftime("%d/%m/%Y %H:%M:%S")),
"emetteur": "CLIENT",
"type": "Question",
"contenu": f"{ticket_name}. {ticket_description.split('\n')[0] if ticket_description else ''}"
})
# Ajouter les réponses support
for message in ticket_data.get("messages", []):
author = message.get("author_id", "")
date = message.get("date", "")
content = message.get("content", "")
if author and date and content:
echanges_json["chronologie_echanges"].append({
"date": date,
"emetteur": "SUPPORT",
"type": "Réponse",
"contenu": content.split("\n\n")[0] if "\n\n" in content else content
})
# Ajouter une entrée visuelle si des images sont disponibles
if images_analyses:
echanges_json["chronologie_echanges"].append({
"date": datetime.now().strftime("%d/%m/%Y %H:%M:%S"),
"emetteur": "SUPPORT",
"type": "Complément visuel",
"contenu": f"Analyse des {len(images_analyses)} images disponibles montrant les interfaces et options pertinentes."
})
except Exception as e:
logger.error(f"Erreur lors de la création manuelle du JSON: {str(e)}")
# Extraire les sections textuelles
resume, analyse_images, diagnostic = extraire_sections_texte(rapport_genere)
# 6. CRÉATION DU RAPPORT JSON
agent_metadata = {
"model": getattr(self.llm, "modele", str(type(self.llm))),
"model_version": getattr(self.llm, "version", "non spécifiée"),
"temperature": self.temperature,
"top_p": self.top_p,
"max_tokens": self.max_tokens,
"generation_time": generation_time,
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"agents": agents_info,
"approach": "two_step" if self.use_two_step_approach else "single_step"
}
}
# Construire le rapport JSON
rapport_json = construire_rapport_json(
rapport_genere=rapport_genere,
rapport_data=rapport_data,
ticket_id=ticket_id,
ticket_analyse=ticket_analyse,
images_analyses=images_analyses,
generation_time=generation_time,
resume=resume,
analyse_images=analyse_images,
diagnostic=diagnostic,
echanges_json=echanges_json,
agent_metadata=agent_metadata,
prompts_utilises=prompts_utilises
)
# Sauvegarder le résultat dans le pipeline
logger.info(f"Sauvegarde du rapport final pour le ticket {ticket_id}")
# 7. SAUVEGARDE DU RAPPORT JSON
json_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.json")
# Créer un fichier de débogage direct pour vérifier le problème de sauvegarde
try:
debug_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../debug"))
os.makedirs(debug_dir, exist_ok=True)
debug_path = os.path.join(debug_dir, f"rapport_debug_{ticket_id}.json")
with open(debug_path, "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
print(f"Fichier de débogage créé: {debug_path}")
except Exception as e:
print(f"Erreur lors de la création du fichier de débogage: {str(e)}")
with open(json_path, "w", encoding="utf-8") as f:
json.dump(rapport_json, f, ensure_ascii=False, indent=2)
# Utiliser uniquement la fonction standard de sauvegarde
try:
sauvegarder_donnees(ticket_id, "rapport_final", result, base_dir=None, is_resultat=True)
print(f"Rapport final généré et sauvegardé pour le ticket {ticket_id}")
except Exception as e:
logger.error(f"Erreur lors de la sauvegarde via sauvegarder_donnees: {str(e)}")
print(f"Erreur de sauvegarde standard: {str(e)}")
logger.info(f"Rapport JSON sauvegardé: {json_path}")
print(f" Rapport JSON sauvegardé: {json_path}")
# Sauvegarder aussi une version en texte brut pour faciliter la lecture
try:
# Trouver le chemin de ticket le plus récent
output_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../output"))
ticket_dir = os.path.join(output_dir, f"ticket_{ticket_id}")
# 8. GÉNÉRATION DU RAPPORT MARKDOWN
md_path = generer_rapport_markdown(json_path)
if os.path.exists(ticket_dir):
# Trouver l'extraction la plus récente
extractions = [d for d in os.listdir(ticket_dir) if os.path.isdir(os.path.join(ticket_dir, d)) and d.startswith(ticket_id)]
if extractions:
extractions.sort(reverse=True)
latest_extraction = extractions[0]
rapports_dir = os.path.join(ticket_dir, latest_extraction, f"{ticket_id}_rapports")
pipeline_dir = os.path.join(rapports_dir, "pipeline")
if md_path:
logger.info(f"Rapport Markdown généré: {md_path}")
print(f" Rapport Markdown généré: {md_path}")
else:
logger.error("Échec de la génération du rapport Markdown")
print(f" ERREUR: Échec de la génération du rapport Markdown")
# S'assurer que le répertoire existe
os.makedirs(pipeline_dir, exist_ok=True)
return json_path, md_path
# Sauvegarder en format texte uniquement
rapport_txt_path = os.path.join(pipeline_dir, f"rapport_final_mistral-large-latest.txt")
with open(rapport_txt_path, "w", encoding="utf-8") as f:
f.write(f"RAPPORT D'ANALYSE DU TICKET {ticket_id}\n")
f.write("="*50 + "\n\n")
f.write(response)
print(f"Version texte sauvegardée dans: {rapport_txt_path}")
except Exception as e:
print(f"Erreur lors de la sauvegarde de la version texte: {str(e)}")
logger.error(f"Erreur de sauvegarde texte: {str(e)}")
# Sauvegarder aussi dans le dossier reports pour compatibilité
try:
reports_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../reports"))
ticket_reports_dir = os.path.join(reports_dir, ticket_id)
os.makedirs(ticket_reports_dir, exist_ok=True)
# Sauvegarder aussi en texte pour faciliter la lecture
rapport_txt_path = os.path.join(ticket_reports_dir, f"rapport_final_{ticket_id}.txt")
with open(rapport_txt_path, "w", encoding="utf-8") as f:
f.write(f"RAPPORT D'ANALYSE DU TICKET {ticket_id}\n")
f.write("="*50 + "\n\n")
f.write(response)
logger.info(f"Rapport texte sauvegardé dans {rapport_txt_path}")
print(f"Rapport également sauvegardé en texte dans {ticket_reports_dir}")
except Exception as e:
logger.warning(f"Impossible de sauvegarder le rapport texte dans reports/: {str(e)}")
print(f"Erreur de sauvegarde reports/: {str(e)}")
# Ajouter à l'historique
self.ajouter_historique("rapport_final", {
"ticket_id": ticket_id,
"prompt": prompt,
"timestamp": self._get_timestamp()
}, response)
print(f"Traitement du rapport terminé pour le ticket {ticket_id}")
return response
except Exception as e:
error_message = f"Erreur lors de la génération du rapport Qwen: {str(e)}"
logger.error(error_message)
logger.error(f"Erreur lors de la génération du rapport : {str(e)}")
logger.error(traceback.format_exc())
print(f" ERREUR: {error_message}")
return None, None
print(f"ERREUR CRITIQUE lors de la génération du rapport: {str(e)}")
return f"ERREUR: {str(e)}"
def _extraire_ticket_id(self, rapport_data: Dict, rapport_dir: str) -> str:
"""Extrait l'ID du ticket des données ou du chemin"""
# Essayer d'extraire depuis les données du rapport
ticket_id = rapport_data.get("ticket_id", "")
# Si pas d'ID direct, essayer depuis les données du ticket
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", "")
def _generer_prompt(self, rapport_data: Dict[str, Any]) -> str:
ticket_text = rapport_data.get("ticket_analyse", "")
image_blocs = []
analyses_images = rapport_data.get("analyse_images", {})
# En dernier recours, extraire depuis le chemin
if not ticket_id:
# Essayer d'extraire un ID de ticket (format Txxxx) du chemin
match = re.search(r'T\d+', rapport_dir)
if match:
ticket_id = match.group(0)
else:
# Sinon, utiliser le dernier segment du chemin
ticket_id = os.path.basename(rapport_dir)
# Ajouter des logs pour vérifier les données d'images
logger.info(f"Nombre d'images à analyser: {len(analyses_images)}")
return ticket_id
def _extraire_analyse_ticket(self, rapport_data: Dict) -> str:
"""Extrait l'analyse du ticket des données"""
# Essayer les différentes clés possibles
for key in ["ticket_analyse", "analyse_json", "analyse_ticket"]:
if key in rapport_data and rapport_data[key]:
logger.info(f"Utilisation de {key}")
return rapport_data[key]
# Créer une analyse par défaut si aucune n'est disponible
logger.warning("Aucune analyse de ticket disponible, création d'un message par défaut")
ticket_data = rapport_data.get("ticket_data", {})
ticket_name = ticket_data.get("name", "Sans titre")
ticket_desc = ticket_data.get("description", "Pas de description disponible")
return f"Analyse par défaut du ticket:\nNom: {ticket_name}\nDescription: {ticket_desc}\n(Aucune analyse détaillée n'a été fournie)"
def _extraire_analyses_images(self, rapport_data: Dict) -> List[Dict]:
"""
Extrait et formate les analyses d'images pertinentes
"""
images_analyses = []
analyse_images_data = rapport_data.get("analyse_images", {})
# Parcourir toutes les images
for image_path, analyse_data in analyse_images_data.items():
for chemin_image, analyse_obj in analyses_images.items():
# Vérifier si l'image est pertinente
is_relevant = False
if "sorting" in analyse_data and isinstance(analyse_data["sorting"], dict):
is_relevant = analyse_data["sorting"].get("is_relevant", False)
is_relevant = analyse_obj.get("sorting", {}).get("is_relevant", False)
# Si l'image est pertinente, extraire son analyse
if is_relevant:
image_name = os.path.basename(image_path)
analyse = self._extraire_analyse_image(analyse_data)
# Récupérer l'analyse si elle existe
analyse = ""
if "analysis" in analyse_obj and analyse_obj["analysis"]:
analyse = analyse_obj["analysis"].get("analyse", "")
if analyse:
images_analyses.append({
"image_name": image_name,
"image_path": image_path,
"analyse": analyse,
"sorting_info": analyse_data.get("sorting", {}),
"metadata": analyse_data.get("analysis", {}).get("metadata", {})
})
logger.info(f"Analyse de l'image {image_name} ajoutée")
if analyse:
image_blocs.append(f"--- IMAGE : {os.path.basename(chemin_image)} ---\n{analyse}\n")
logger.info(f"Ajout de l'analyse de l'image {os.path.basename(chemin_image)} ({len(analyse)} caractères)")
else:
logger.warning(f"Image {os.path.basename(chemin_image)} sans analyse")
return images_analyses
bloc_images = "\n".join(image_blocs)
def _extraire_analyse_image(self, analyse_data: Dict) -> Optional[str]:
"""
Extrait l'analyse d'une image depuis les données
"""
# Si pas de données d'analyse, retourner None
if not "analysis" in analyse_data or not analyse_data["analysis"]:
if "sorting" in analyse_data and isinstance(analyse_data["sorting"], dict):
reason = analyse_data["sorting"].get("reason", "Non spécifiée")
return f"Image marquée comme pertinente. Raison: {reason}"
return None
# Log pour vérifier la taille des données
logger.info(f"Taille de l'analyse ticket: {len(ticket_text)} caractères")
logger.info(f"Taille du bloc images: {len(bloc_images)} caractères")
# Extraire l'analyse selon le format des données
analysis = analyse_data["analysis"]
prompt = (
f"Voici les données d'analyse pour un ticket de support :\n\n"
f"=== ANALYSE DU TICKET ===\n{ticket_text}\n\n"
f"=== ANALYSES D'IMAGES ===\n{bloc_images}\n\n"
f"Génère un rapport croisé en suivant les instructions précédentes, incluant un tableau chronologique des échanges entre CLIENT et SUPPORT. "
f"Utilise le format suivant pour le tableau :\n"
f"| ÉMETTEUR | TYPE | DATE | CONTENU | ÉLÉMENTS VISUELS |\n"
f"| --- | --- | --- | --- | --- |\n"
f"| CLIENT | question | date | texte de la question | éléments pertinents des images |\n"
f"| SUPPORT | réponse | date | texte de la réponse | éléments pertinents des images |\n\n"
f"Ce tableau doit synthétiser les échanges tout en intégrant les données pertinentes des images avec le maximum de contexte technique."
)
# Structure type 1: {"analyse": "texte"}
if isinstance(analysis, dict) and "analyse" in analysis:
return analysis["analyse"]
return prompt
# Structure type 2: {"error": false, ...} - contient d'autres données utiles
if isinstance(analysis, dict) and "error" in analysis and not analysis.get("error", True):
return str(analysis)
# Structure type 3: texte d'analyse direct
if isinstance(analysis, str):
return analysis
# Structure type 4: autre format de dictionnaire - convertir en JSON
if isinstance(analysis, dict):
return json.dumps(analysis, ensure_ascii=False, indent=2)
# Aucun format reconnu
return None
def _get_timestamp(self) -> str:
from datetime import datetime
return datetime.now().strftime("%Y%m%d_%H%M%S")

View File

@ -1,31 +1,24 @@
from ..base_agent import BaseAgent
from typing import Dict, Any, Optional
from typing import Dict, Any
import logging
import json
import os
import sys
from datetime import datetime
# Ajout du chemin des utilitaires au PATH pour pouvoir les importer
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from loaders.ticket_data_loader import TicketDataLoader
from ..utils.pipeline_logger import sauvegarder_donnees
logger = logging.getLogger("AgentTicketAnalyser")
class AgentTicketAnalyser(BaseAgent):
"""
Agent pour analyser les tickets (JSON ou Markdown) et en extraire les informations importantes.
Remplace l'ancien AgentJsonAnalyser avec des fonctionnalités améliorées.
"""
def __init__(self, llm):
super().__init__("AgentTicketAnalyser", llm)
# Configuration locale de l'agent
self.temperature = 0.1 # Besoin d'analyse très précise
self.top_p = 0.8
self.max_tokens = 8000
self.params = {
"temperature": 0.1,
"top_p": 0.5,
"max_tokens": 4000
}
# Prompt système optimisé
self.system_prompt = """Tu es un expert en analyse de tickets pour le support informatique de BRG-Lab pour la société CBAO.
Tu interviens avant l'analyse des captures d'écran pour contextualiser le ticket, identifier les questions posées, et structurer les échanges de manière claire.
@ -86,216 +79,117 @@ IMPORTANT :
- Reste strictement factuel en te basant uniquement sur les informations fournies
- Ne reformule pas les messages, conserve les formulations exactes sauf nettoyage de forme"""
# Initialiser le loader de données
self.ticket_loader = TicketDataLoader()
# Appliquer la configuration au LLM
self._appliquer_config_locale()
logger.info("AgentTicketAnalyser initialisé")
def _appliquer_config_locale(self) -> None:
"""
Applique la configuration locale au modèle LLM.
"""
# Appliquer le prompt système
if hasattr(self.llm, "prompt_system"):
self.llm.prompt_system = self.system_prompt
# Appliquer les paramètres
if hasattr(self.llm, "configurer"):
params = {
"temperature": self.temperature,
"top_p": self.top_p,
"max_tokens": self.max_tokens
}
self.llm.configurer(**params)
self.llm.configurer(**self.params)
def executer(self, ticket_data: Dict[str, Any]) -> str:
"""
Analyse un ticket pour en extraire les informations pertinentes
Args:
ticket_data: Dictionnaire contenant les données du ticket à analyser
ou chemin vers un fichier de ticket (JSON ou Markdown)
Returns:
Réponse formatée contenant l'analyse du ticket
"""
# Détecter si ticket_data est un chemin de fichier ou un dictionnaire
if isinstance(ticket_data, str) and os.path.exists(ticket_data):
try:
ticket_data = self.ticket_loader.charger(ticket_data)
logger.info(f"Données chargées depuis le fichier: {ticket_data}")
except Exception as e:
error_message = f"Erreur lors du chargement du fichier: {str(e)}"
logger.error(error_message)
return f"ERREUR: {error_message}"
ticket_data = self.ticket_loader.charger(ticket_data)
# Vérifier que les données sont bien un dictionnaire
if not isinstance(ticket_data, dict):
error_message = "Les données du ticket doivent être un dictionnaire ou un chemin de fichier valide"
logger.error(error_message)
return f"ERREUR: {error_message}"
logger.error("Les données du ticket ne sont pas valides")
return "ERREUR: Format de ticket invalide"
ticket_code = ticket_data.get('code', 'Inconnu')
logger.info(f"Analyse du ticket: {ticket_code}")
print(f"AgentTicketAnalyser: Analyse du ticket {ticket_code}")
ticket_code = ticket_data.get("code", "Inconnu")
logger.info(f"Analyse du ticket {ticket_code}")
print(f"AgentTicketAnalyser: analyse du ticket {ticket_code}")
# Récupérer les métadonnées sur la source des données
source_format = "inconnu"
source_file = "non spécifié"
if "metadata" in ticket_data and isinstance(ticket_data["metadata"], dict):
source_format = ticket_data["metadata"].get("format", "inconnu")
source_file = ticket_data["metadata"].get("source_file", "non spécifié")
logger.info(f"Format source: {source_format}, Fichier source: {source_file}")
# Préparer le ticket pour l'analyse
ticket_formate = self._formater_ticket_pour_analyse(ticket_data)
# Créer le prompt pour l'analyse, adapté au format source
prompt = f"""Analyse ce ticket pour en extraire les informations clés et préparer une synthèse structurée.
SOURCE: {source_format.upper()}
{ticket_formate}
RAPPEL IMPORTANT:
- CONSERVE TOUS les liens (FAQ, documentation, manuels) présents dans les messages
- Extrais et organise chronologiquement les échanges client/support
- Identifie les éléments techniques à observer dans les captures d'écran
- Reste factuel et précis sans proposer de solution"""
prompt = self._generer_prompt(ticket_data)
try:
logger.info("Interrogation du LLM")
response = self.llm.interroger(prompt)
logger.info(f"Réponse reçue: {len(response)} caractères")
logger.info("Analyse du ticket terminée avec succès")
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_code,
"format_source": source_format,
"source_file": source_file,
"prompt": prompt,
"temperature": self.temperature,
"top_p": self.top_p,
"max_tokens": self.max_tokens,
"timestamp": self._get_timestamp()
},
response)
result = {
"prompt": prompt,
"response": response,
"metadata": {
"timestamp": self._get_timestamp(),
"source_agent": self.nom,
"ticket_id": ticket_code,
"model_info": {
"model": getattr(self.llm, "modele", str(type(self.llm))),
**getattr(self.llm, "params", {})
},
"source_agent": self.nom
}
}
sauvegarder_donnees(ticket_code, "analyse_ticket", result, base_dir="reports", is_resultat=True)
except Exception as e:
logger.error(f"Erreur d'analyse: {str(e)}")
return f"ERREUR: {str(e)}"
self.ajouter_historique("analyse_ticket", {
"ticket_id": ticket_code,
"prompt": prompt,
"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, avec une meilleure
gestion des différents formats et structures de données.
def _generer_prompt(self, ticket_data: Dict[str, Any]) -> str:
ticket_code = ticket_data.get("code", "Inconnu")
name = ticket_data.get("name", "").strip()
description = ticket_data.get("description", "").strip()
create_date = ticket_data.get("create_date", "")
auteur_initial = ticket_data.get("partner_id_email_from", "Client")
Args:
ticket_data: Les données du ticket
texte = f"### TICKET {ticket_code}\n\n"
Returns:
Représentation textuelle formatée du ticket
"""
# Initialiser avec les informations de base
ticket_name = ticket_data.get('name', 'Sans titre')
ticket_code = ticket_data.get('code', 'Inconnu')
if name:
texte += f"--- MESSAGE INITIAL DU CLIENT ---\n"
texte += f"Auteur : {auteur_initial}\n"
texte += f"Date : {self._formater_date(create_date)}\n"
texte += f"Contenu :\n{name}\n"
if description and description != "<p><br></p>":
texte += f"{description}\n\n"
info = f"## TICKET {ticket_code}: {ticket_name}\n\n"
info += f"## NOM DE LA DEMANDE (PROBLÈME INITIAL)\n{ticket_name}\n\n"
messages = ticket_data.get("messages", [])
for i, msg in enumerate(messages):
if not isinstance(msg, dict):
continue
auteur = msg.get("author_id", "inconnu")
date = self._formater_date(msg.get("date", "inconnue"))
sujet = msg.get("subject", "").strip()
message_type = msg.get("message_type", "").strip()
contenu = msg.get("content", "").strip()
# Ajouter la description
description = ticket_data.get('description', '')
if description:
info += f"## DESCRIPTION DU PROBLÈME\n{description}\n\n"
texte += f"--- MESSAGE {i+1} ---\n"
texte += f"Auteur : {auteur}\n"
texte += f"Date : {date}\n"
texte += f"Type : {message_type}\n"
if sujet:
texte += f"Sujet : {sujet}\n"
texte += f"Contenu :\n{contenu}\n\n"
# Ajouter les informations du ticket (exclure certains champs spécifiques)
champs_a_exclure = ['code', 'name', 'description', 'messages', 'metadata']
info += "## INFORMATIONS TECHNIQUES DU TICKET\n"
for key, value in ticket_data.items():
if key not in champs_a_exclure and value:
# Formater les valeurs complexes si nécessaire
if isinstance(value, (dict, list)):
value = json.dumps(value, ensure_ascii=False, indent=2)
info += f"- {key}: {value}\n"
info += "\n"
return texte
# Ajouter les messages (conversations) avec un formatage amélioré pour distinguer client/support
messages = ticket_data.get('messages', [])
if messages:
info += "## CHRONOLOGIE DES ÉCHANGES CLIENT/SUPPORT\n"
for i, msg in enumerate(messages):
# Vérifier que le message est bien un dictionnaire
if not isinstance(msg, dict):
continue
sender = msg.get('from', 'Inconnu')
date = msg.get('date', 'Date inconnue')
content = msg.get('content', '')
# Identifier si c'est client ou support
sender_type = "CLIENT" if "client" in sender.lower() else "SUPPORT" if "support" in sender.lower() else "AUTRE"
# Formater correctement la date si possible
try:
if date != 'Date inconnue':
# Essayer différents formats de date
for date_format in ['%Y-%m-%d %H:%M:%S', '%Y-%m-%d', '%d/%m/%Y']:
try:
date_obj = datetime.strptime(date, date_format)
date = date_obj.strftime('%d/%m/%Y %H:%M')
break
except ValueError:
continue
except Exception:
pass # Garder la date d'origine en cas d'erreur
info += f"### Message {i+1} - [{sender_type}] De: {sender} - Date: {date}\n{content}\n\n"
# Ajouter les métadonnées techniques si présentes
metadata = ticket_data.get('metadata', {})
# Exclure certaines métadonnées internes
for key in ['source_file', 'format']:
if key in metadata:
metadata.pop(key)
if metadata:
info += "## MÉTADONNÉES TECHNIQUES\n"
for key, value in metadata.items():
if isinstance(value, (dict, list)):
value = json.dumps(value, ensure_ascii=False, indent=2)
info += f"- {key}: {value}\n"
info += "\n"
return info
def analyser_depuis_fichier(self, chemin_fichier: str) -> str:
"""
Analyse un ticket à partir d'un fichier (JSON ou Markdown)
Args:
chemin_fichier: Chemin vers le fichier à analyser
Returns:
Résultat de l'analyse
"""
def _formater_date(self, date_str: str) -> str:
if not date_str:
return "date inconnue"
try:
ticket_data = self.ticket_loader.charger(chemin_fichier)
return self.executer(ticket_data)
except Exception as e:
error_message = f"Erreur lors de l'analyse du fichier {chemin_fichier}: {str(e)}"
logger.error(error_message)
return f"ERREUR: {error_message}"
formats = [
"%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ",
"%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S",
"%d/%m/%Y %H:%M:%S", "%d/%m/%Y"
]
for fmt in formats:
try:
dt = datetime.strptime(date_str, fmt)
return dt.strftime("%d/%m/%Y %H:%M")
except ValueError:
continue
return date_str
except Exception:
return date_str
def _get_timestamp(self) -> str:
"""Retourne un timestamp au format YYYYMMDD_HHMMSS"""
return datetime.now().strftime("%Y%m%d_%H%M%S")

View File

@ -1,107 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import csv
import os
import sys
def generate_csv_from_json(json_file, model_name=None):
"""
Génère un fichier CSV à partir des données du tableau questions/réponses
contenues dans le fichier JSON du rapport.
Args:
json_file (str): Chemin du fichier JSON contenant les données
model_name (str, optional): Nom du modèle à inclure dans le nom du fichier
Returns:
str: Chemin du fichier CSV généré
"""
# Extraire l'ID du ticket du nom du fichier
ticket_id = os.path.basename(json_file).split('_')[0]
# Créer le répertoire CSV à la racine du projet
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
csv_root_dir = os.path.join(project_root, 'CSV')
ticket_csv_dir = os.path.join(csv_root_dir, ticket_id)
# Créer les répertoires si nécessaire
os.makedirs(ticket_csv_dir, exist_ok=True)
# Définir le nom du fichier CSV de sortie
if model_name:
csv_file = os.path.join(ticket_csv_dir, f"{ticket_id}_{model_name}.csv")
else:
# Si le modèle n'est pas spécifié, utiliser les métadonnées du JSON
with open(json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
model_name = data.get('metadata', {}).get('model', 'unknown')
csv_file = os.path.join(ticket_csv_dir, f"{ticket_id}_{model_name}.csv")
# Ouvrir le fichier JSON
with open(json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
# Extraire les échanges
exchanges = data.get('chronologie_echanges', [])
if not exchanges:
print(f"Aucun échange trouvé dans {json_file}")
return None
# Ouvrir le fichier CSV pour écriture
with open(csv_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
# Écrire l'en-tête
writer.writerow(['Question', 'Réponse'])
current_question = None
current_answers = []
# Parcourir les échanges pour les combiner en paires questions/réponses
for exchange in exchanges:
emetteur = exchange.get('emetteur', '').upper()
type_msg = exchange.get('type', '').lower()
contenu = exchange.get('contenu', '')
# Si c'est une question client
if emetteur == 'CLIENT' and (type_msg == 'question' or '?' in contenu):
# Si une question précédente existe, l'écrire avec ses réponses
if current_question:
combined_answer = "\n".join(current_answers) if current_answers else "Pas de réponse"
writer.writerow([current_question, combined_answer])
# Réinitialiser pour la nouvelle question
current_question = contenu
current_answers = []
# Si c'est une réponse ou un complément du support
elif emetteur == 'SUPPORT' and (type_msg == 'réponse' or type_msg == 'complément visuel' or type_msg == 'information technique'):
if current_question: # S'assurer qu'il y a une question en cours
# Ajouter le contenu sans préfixe
current_answers.append(contenu)
# Écrire la dernière question et ses réponses
if current_question:
combined_answer = "\n".join(current_answers) if current_answers else "Pas de réponse"
writer.writerow([current_question, combined_answer])
print(f"Fichier CSV créé: {csv_file}")
return csv_file
if __name__ == "__main__":
# Vérifier si un chemin de fichier est fourni en argument
if len(sys.argv) > 1:
json_file = sys.argv[1]
else:
print("Erreur: Veuillez spécifier le chemin du fichier JSON.")
print("Usage: python csv_exporter.py chemin/vers/rapport_final.json [nom_modele]")
sys.exit(1)
# Vérifier si un nom de modèle est fourni en second argument
model_name = sys.argv[2] if len(sys.argv) > 2 else None
# Générer le CSV
generate_csv_from_json(json_file, model_name)

View File

@ -12,10 +12,156 @@ import sys
import re
import logging
from typing import List, Dict, Any, Optional, Tuple
from datetime import datetime
# Configuration du logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger("report_csv_exporter")
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('ReportCSVExporter')
class ReportCSVExporter:
"""
Classe responsable de l'exportation des rapports au format CSV.
Permet de générer des fichiers CSV à partir des données JSON des rapports.
"""
@staticmethod
def generate_csv_from_json(json_file, model_name=None, output_dir=None):
"""
Génère un fichier CSV à partir des données du tableau questions/réponses
contenues dans le fichier JSON du rapport.
Args:
json_file (str): Chemin du fichier JSON contenant les données
model_name (str, optional): Nom du modèle à inclure dans le nom du fichier
output_dir (str, optional): Répertoire de sortie personnalisé
Returns:
str: Chemin du fichier CSV généré
"""
try:
# Vérifier que le fichier JSON existe
if not os.path.exists(json_file):
logger.error(f"Le fichier JSON n'existe pas: {json_file}")
return None
# Extraire l'ID du ticket du nom du fichier
filename = os.path.basename(json_file)
ticket_id = filename.split('_')[0] if '_' in filename else 'unknown'
# Définir le répertoire de sortie
if output_dir:
csv_dir = output_dir
else:
# Créer le répertoire CSV à la racine du projet
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
csv_root_dir = os.path.join(project_root, 'CSV')
csv_dir = os.path.join(csv_root_dir, ticket_id)
# Créer les répertoires si nécessaire
os.makedirs(csv_dir, exist_ok=True)
# Charger les données JSON
with open(json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
# Utiliser le modèle depuis les métadonnées si non spécifié
if not model_name:
model_name = data.get('metadata', {}).get('model', 'unknown')
# Ajouter un timestamp pour éviter les écrasements
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
csv_filename = f"{ticket_id}_{model_name}_{timestamp}.csv"
csv_file = os.path.join(csv_dir, csv_filename)
# Extraire les échanges
exchanges = data.get('chronologie_echanges', [])
if not exchanges:
logger.warning(f"Aucun échange trouvé dans {json_file}")
return None
# Créer et écrire dans le fichier CSV
with open(csv_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
# Écrire l'en-tête avec métadonnées du rapport
writer.writerow(['Question', 'Réponse'])
current_question = None
current_answers = []
# Parcourir les échanges pour les combiner en paires questions/réponses
for exchange in exchanges:
emetteur = exchange.get('emetteur', '').upper()
type_msg = exchange.get('type', '').lower()
contenu = exchange.get('contenu', '')
# Si c'est une question client
if emetteur == 'CLIENT' and (type_msg == 'question' or '?' in contenu):
# Si une question précédente existe, l'écrire avec ses réponses
if current_question:
combined_answer = "\n".join(current_answers) if current_answers else "Pas de réponse"
writer.writerow([current_question, combined_answer])
# Réinitialiser pour la nouvelle question
current_question = contenu
current_answers = []
# Si c'est une réponse ou un complément du support
elif emetteur == 'SUPPORT' and (type_msg == 'réponse' or type_msg == 'complément visuel' or type_msg == 'information technique'):
if current_question: # S'assurer qu'il y a une question en cours
current_answers.append(contenu)
# Écrire la dernière question et ses réponses
if current_question:
combined_answer = "\n".join(current_answers) if current_answers else "Pas de réponse"
writer.writerow([current_question, combined_answer])
logger.info(f"Fichier CSV créé: {csv_file}")
return csv_file
except Exception as e:
logger.error(f"Erreur lors de la génération du CSV: {str(e)}")
return None
@staticmethod
def process_batch(json_directory, output_directory=None):
"""
Traite tous les fichiers JSON de rapport dans un répertoire.
Args:
json_directory (str): Répertoire contenant les fichiers JSON à traiter
output_directory (str, optional): Répertoire de sortie personnalisé
Returns:
list: Liste des chemins des fichiers CSV générés
"""
csv_files = []
if not os.path.exists(json_directory):
logger.error(f"Le répertoire n'existe pas: {json_directory}")
return csv_files
# Parcourir tous les fichiers JSON dans le répertoire
for filename in os.listdir(json_directory):
if filename.endswith('.json') and 'rapport_final' in filename:
json_file = os.path.join(json_directory, filename)
csv_file = ReportCSVExporter.generate_csv_from_json(
json_file=json_file,
output_dir=output_directory
)
if csv_file:
csv_files.append(csv_file)
if not csv_files:
logger.warning(f"Aucun fichier CSV généré à partir de {json_directory}")
else:
logger.info(f"{len(csv_files)} fichiers CSV générés avec succès")
return csv_files
def extraire_tableau_markdown(texte: str) -> List[List[str]]:
"""
@ -386,31 +532,31 @@ def traiter_rapports_ticket(ticket_id: str) -> None:
logger.info(f"CSV Q/R généré: {csv_qr_path}")
if __name__ == "__main__":
# Configurer l'analyse des arguments
if len(sys.argv) < 2:
print("Usage: python report_csv_exporter.py <ticket_id|rapport.json>")
print("Exemples:")
print(" python report_csv_exporter.py T11143")
print(" python report_csv_exporter.py chemin/vers/rapport_final.json")
print("Erreur: arguments insuffisants")
print("Usage:")
print(" 1. Traiter un seul fichier: python report_csv_exporter.py chemin/vers/rapport_final.json [nom_modele] [rep_sortie]")
print(" 2. Traiter un répertoire: python report_csv_exporter.py --batch chemin/vers/repertoire [rep_sortie]")
sys.exit(1)
arg = sys.argv[1]
# Traitement par lot
if sys.argv[1] == '--batch':
if len(sys.argv) < 3:
print("Erreur: veuillez spécifier le répertoire contenant les fichiers JSON")
sys.exit(1)
# Vérifier si l'argument est un fichier JSON existant
if os.path.isfile(arg) and arg.endswith(".json"):
# Générer les CSV directement à partir du fichier JSON spécifié
csv_path = generer_csv_depuis_rapport(arg)
csv_qr_path = generer_csv_qr(arg)
json_directory = sys.argv[2]
output_directory = sys.argv[3] if len(sys.argv) > 3 else None
if csv_path:
print(f"CSV généré: {csv_path}")
if csv_qr_path:
print(f"CSV Q/R généré: {csv_qr_path}")
ReportCSVExporter.process_batch(json_directory, output_directory)
# Traitement d'un seul fichier
else:
# Sinon, considérer l'argument comme un ID de ticket
ticket_id = arg
if not ticket_id.startswith("T"):
ticket_id = f"T{ticket_id}"
json_file = sys.argv[1]
model_name = sys.argv[2] if len(sys.argv) > 2 else None
output_directory = sys.argv[3] if len(sys.argv) > 3 else None
traiter_rapports_ticket(ticket_id)
ReportCSVExporter.generate_csv_from_json(json_file, model_name, output_directory)
print("Terminé!")

File diff suppressed because one or more lines are too long

View File

@ -8,28 +8,28 @@ RÉSULTATS DE L'ANALYSE ANALYSE_IMAGE - TICKET T11143
#### 1. Description objective
L'image montre une page web affichée dans un navigateur. Voici les éléments visibles :
- **Titre de la page** : "It works!"
- **Message principal** : "If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!"
- **Message principal** : "If you're seeing this page via a web browser, it means you've setup Tomcat successfully. Congratulations!"
- **Contenu de la page** :
- La page indique que Tomcat a été installé avec succès.
- Elle fournit des informations sur l'emplacement du fichier `index.jsp` (`/var/lib/tomcat7/webapps/ROOT/index.jsp`).
- Elle mentionne que Tomcat a été installé avec `CATALINA_HOME` défini sur `/var/lib/tomcat7` et `CATALINA_BASE` sur `/var/lib/tomcat7`.
- Elle explique comment accéder aux exemples et à la documentation de Tomcat via les liens suivants :
- `tomcat7-docs` : Documentation locale de Tomcat.
- `tomcat7-examples` : Exemples de servlets et JSP.
- `tomcat7-admin` : Interface d'administration de Tomcat.
- Elle précise que l'accès aux interfaces d'administration est restreint aux utilisateurs avec les rôles "manager-gui" et "admin-gui".
- Elle fournit des informations sur l'emplacement du fichier `index.html` de Tomcat (`/var/lib/tomcat7/webapps/ROOT/index.html`).
- Elle mentionne que Tomcat a été installé avec les packages `CATALINA_HOME` et `CATALINA_BASE` définis sur `/var/lib/tomcat7`.
- Elle décrit les packages disponibles :
- `tomcat7-docs` : Documentation locale accessible via `http://localhost:8080/docs`.
- `tomcat7-examples` : Exemples accessibles via `http://localhost:8080/examples`.
- `tomcat7-admin` : Interface d'administration accessible via `http://localhost:8080/admin`.
- Une note de sécurité précise que l'accès aux interfaces `manager` et `host-manager` est restreint aux utilisateurs avec les rôles `manager-gui` et `admin-gui`.
#### 2. Éléments techniques clés
- **Logiciel/module affiché** : Apache Tomcat.
- **Version logicielle** : Tomcat 7 (mentionné implicitement dans les liens et le chemin `/var/lib/tomcat7`).
- **Codes d'erreur visibles** : Aucun code d'erreur n'est visible.
- **Paramètres configurables** : Aucun paramètre configurable n'est visible dans cette interface.
- **Logiciel/module visible** : Apache Tomcat.
- **Version logicielle** : Tomcat 7 (mentionné implicitement via les packages `tomcat7-docs`, `tomcat7-examples`, `tomcat7-admin`).
- **Codes d'erreur visibles** : Aucun.
- **Paramètres configurables** : Aucun paramètre configurable n'est visible dans l'image.
- **Valeurs affichées ou préremplies** :
- `CATALINA_HOME` : `/var/lib/tomcat7`
- `CATALINA_BASE` : `/var/lib/tomcat7`
- **Éléments désactivés, grisés ou masqués** : Aucun élément désactivé ou grisé n'est visible.
- **Boutons actifs/inactifs** : Aucun bouton n'est visible sur cette page.
- **Boutons RAZ ou réinitialisation** : Aucun bouton "RAZ" ou de réinitialisation n'est visible.
- Emplacement du fichier `index.html` : `/var/lib/tomcat7/webapps/ROOT/index.html`.
- Chemins des packages : `CATALINA_HOME` et `CATALINA_BASE` définis sur `/var/lib/tomcat7`.
- **Éléments désactivés, grisés ou masqués** : Aucun élément de ce type n'est visible.
- **Boutons actifs/inactifs** : Aucun bouton n'est visible dans l'image.
- **Boutons RAZ ou réinitialisation** : Aucun bouton de ce type n'est visible.
- **Éléments colorés** : Aucun élément coloré spécifique n'est visible, hormis le texte standard de la page.
#### 3. Éléments mis en évidence
@ -38,33 +38,27 @@ L'image montre une page web affichée dans un navigateur. Voici les éléments v
#### 4. Relation avec le problème
- **Lien avec le problème décrit dans le ticket** :
- La page affichée indique que Tomcat est installé et fonctionne correctement.
- Cela correspond à la vérification demandée par le support technique pour accéder à l'adresse `https://zk1.brg-lab.com/`.
- Le client a confirmé que l'adresse fonctionne, ce qui est cohérent avec le contenu de cette page.
- **Module/essai concerné** : Cette page ne concerne pas directement un essai ou un module spécifique comme "Essai au bleu de méthylène de méthylène". Elle valide uniquement l'installation de Tomcat.
- **Accès à l'écran** : L'utilisateur a accès à cette page, ce qui confirme que le serveur Tomcat est opérationnel.
- La page affichée indique que Tomcat est installé et fonctionne correctement. Cela correspond à la vérification demandée par le support technique concernant l'accès à l'adresse `https://zk1.brg-lab.com/`.
- L'image ne montre pas directement l'essai au bleu (probablement "Essai au bleu de méthylène de méthylène"), mais elle confirme que l'environnement Tomcat est opérationnel.
- **Accès à l'essai** : L'image ne permet pas de déterminer si l'essai au bleu est accessible ou non.
#### 5. Réponses potentielles
- **Réponse à la question du ticket** :
- La page confirme que Tomcat est installé et fonctionne correctement, ce qui répond à la demande de vérification de l'accès à l'adresse `https://zk1.brg-lab.com/`.
- Cependant, cette page ne fournit pas d'informations sur l'essai au bleu de méthylène ou sur une éventuelle erreur liée à cet essai.
- L'image confirme que l'adresse `https://zk1.brg-lab.com/` fonctionne et que Tomcat est correctement installé.
- Elle ne fournit pas d'informations sur l'essai au bleu de méthylène.
#### 6. Lien avec la discussion
- **Correspondance avec le fil de discussion** :
- Cette image correspond à l'étape où le support technique demande au client de vérifier l'accès à l'adresse `https://zk1.brg-lab.com/`.
- Le client a confirmé que l'adresse fonctionne, ce qui est cohérent avec le contenu de cette page.
- **Vocabulaire utilisé par le client** :
- Le client mentionne "l'essai au bleu", mais cette page ne fait pas référence à cet essai. Elle valide uniquement l'installation de Tomcat.
- L'image correspond à l'étape où le support technique demande au client de vérifier l'accès à l'adresse `https://zk1.brg-lab.com/`.
- Le client a confirmé que l'adresse fonctionne, ce qui est cohérent avec le contenu de l'image.
#### 7. Contexte technique élargi
- **Contexte de l'application** :
- La page concerne l'installation et la configuration d'Apache Tomcat, un serveur d'applications Java.
- Elle ne fait pas référence à des essais normalisés ou à des normes spécifiques comme NF EN 933-9.
- **Références à des normes ou standards** : Aucune référence à des normes ou standards n'est visible.
- **Codes ou identifiants visibles** : Aucun code ou identifiant spécifique n'est visible, hormis les chemins de fichiers et les liens vers les exemples et la documentation de Tomcat.
- **Contexte de l'application** : L'image montre un environnement Tomcat, utilisé pour héberger des applications web. Cela suggère que l'essai au bleu de méthylène pourrait être une application ou un module hébergé sur ce serveur.
- **Références à des normes ou standards** : Aucune référence à des normes ou standards n'est visible dans l'image.
- **Codes ou identifiants visibles** : Aucun code ou identifiant spécifique n'est visible.
### Conclusion
Cette image confirme que le serveur Tomcat est installé et fonctionne correctement. Elle ne fournit pas d'informations sur l'essai au bleu de méthylène ou sur une éventuelle erreur liée à cet essai. Elle valide uniquement l'accès à l'adresse `https://zk1.brg-lab.com/`, ce qui correspond à la demande du support technique.
L'image confirme que l'adresse `https://zk1.brg-lab.com/` est accessible et que Tomcat est correctement installé. Elle ne fournit pas d'informations sur l'essai au bleu de méthylène.
----------------------------------------
@ -73,85 +67,91 @@ Cette image confirme que le serveur Tomcat est installé et fonctionne correctem
### Analyse de l'image
#### 1. Description objective
L'image montre une interface logicielle de l'application **BRG-LAB**. L'écran affiche le module **"Essai au bleu de méthylène de méthylène (MB) - NF EN 933-9 (02-2022)"**. Voici les éléments visibles :
- **Titre du module** : "Essai au bleu de méthylène de méthylène (MB) - NF EN 933-9 (02-2022)".
- **Informations sur l'échantillon** :
- Échantillon : n° 25-00075
- Réceptionné le 02/04/2025 par BOLLÉE Victor
- Prélevé le 02/04/2025 par BOLLÉE Victor, n° prélèvement : 25-00075
- Matériau : Sable 0/2 C - CARRIÈRE ADCE9
L'image montre une interface logicielle du système **BRG-LAB**. Voici les éléments visibles :
- **Titre de l'interface** : "Essai au bleu de méthylène de méthylène (MB) - NF EN 933-9 (02-2022)"
- Il s'agit du module spécifique à l'essai au bleu de méthylène, conforme à la norme **NF EN 933-9**.
- **Menus et onglets** :
- Onglets principaux : ESSAI, MATÉRIEL, PORTEFEUILLE, OBSERVATIONS, SMO, HISTORIQUE.
- Menu latéral gauche avec les sections suivantes :
- ESSAIS
- RÉSULTATS
- RAZ (probablement "Plan d'Assurance Qualité")
- RAPPORTS
- Liste des essais disponibles (ex. : "Essai au bleu de méthylène de méthylène (MB) - NF EN 933-9").
- **Boutons** :
- Un bouton avec une icône de flèche circulaire (probablement pour actualiser ou réinitialiser).
- Un bouton avec une icône de document (probablement pour générer ou consulter un rapport).
- **Message système** :
- En bas de l'écran : "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com".
- En haut de l'interface, plusieurs onglets sont visibles :
- **ESSAI** (actif, en surbrillance)
- **MATÉRIEL**
- **PORTEFEUILLE**
- **OBSERVATIONS**
- **SMO**
- **HISTORIQUE**
- **Informations sur l'échantillon** :
- **Échantillon** : n° 25-00075
- **Réceptionné le** : 02/04/2025
- **Prélevé le** : 02/04/2025
- **Préleveur** : BOLLÉE Victor
- **Matériau** : Sable 0/2 C - CARRIÈRE ADCE9
- **Menu latéral gauche** :
- **Boutons visibles** :
- **RAZ** (rouge, probablement pour réinitialisation)
- **ENREGISTRER**
- **EXPORTER**
- **Nouveau fichier d'essai**
- **Lire BIRAUD**
- **Voir les statistiques**
- **Nouveau fichier d'essai**
- **Aide**
- **Quitter**
- **Messages ou alertes** :
- Aucun message d'erreur n'est visible dans cette capture.
- **Autres éléments** :
- Un logo ou icône en bas à droite indique : "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com".
#### 2. Éléments techniques clés
- **Version logicielle ou module affiché** :
- Module : "Essai au bleu de méthylène de méthylène (MB) - NF EN 933-9 (02-2022)".
- **Codes d'erreur visibles** :
- Aucun code d'erreur spécifique n'est visible, mais un message système indique : "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com".
- **Module affiché** : "Essai au bleu de méthylène de méthylène (MB) - NF EN 933-9 (02-2022)"
- **Codes d'erreur visibles** : Aucun code d'erreur n'est visible dans cette capture.
- **Paramètres configurables** :
- Aucun paramètre configurable n'est visible dans cette capture d'écran.
- **Valeurs affichées ou préremplies dans les champs** :
- Les champs d'informations sur l'échantillon sont préremplis (ex. : n° échantillon, date de réception, matériau).
- **Éléments désactivés, grisés ou masqués** :
- Aucun élément n'apparaît grisé ou désactivé dans cette capture.
- Aucun champ de texte, slider, dropdown ou case à cocher n'est visible dans cette partie de l'interface.
- **Valeurs affichées** :
- Les informations sur l'échantillon (numéro, dates, préleveur, matériau) sont préremplies.
- **Éléments désactivés ou grisés** :
- Aucun élément ne semble désactivé ou grisé dans cette capture.
- **Boutons actifs/inactifs** :
- Les boutons avec les icônes de flèche circulaire et de document semblent actifs.
- **Boutons RAZ ou réinitialisation** :
- Aucun bouton "RAZ" n'est visible dans cette capture.
- Les boutons **RAZ**, **ENREGISTRER**, **EXPORTER**, **Nouveau fichier d'essai**, **Lire BIRAUD**, **Voir les statistiques**, **Aide** et **Quitter** sont visibles et semblent actifs.
- **Boutons RAZ** :
- Un bouton **RAZ** est visible en rouge dans le menu latéral gauche.
- **Éléments colorés** :
- Le bouton avec l'icône de flèche circulaire est rouge, ce qui semble faire partie de l'interface standard.
- Le bouton **RAZ** est rouge, ce qui semble faire partie de l'interface standard.
#### 3. Éléments mis en évidence
- **Zones entourées, encadrées, surlignées ou fléchées** :
- Aucune zone n'est mise en évidence de cette manière dans cette capture.
- **Messages d'erreur** :
- Un message système est visible en bas de l'écran : "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com".
- Aucun élément n'est entouré, encadré, surligné ou fléché dans cette capture.
- Aucun message d'erreur n'est visible en bas ou en haut de l'écran.
#### 4. Relation avec le problème
- **Lien avec le problème décrit dans le ticket** :
- Le client mentionne un problème d'accès à l'essai au bleu. L'image montre que l'écran de l'essai au bleu de méthylène (MB) est accessible, mais un message système indique un problème de connexion au serveur ("Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com").
- Le problème d'accès pourrait être lié à cette erreur de connexion.
- **Nom complet du module/essai concerné** :
- "Essai au bleu de méthylène de méthylène (MB) - NF EN 933-9 (02-2022)".
- **Accès à l'écran d'essai** :
- L'utilisateur semble avoir accès à l'écran d'essai, mais un message d'erreur est affiché.
- Le client mentionne un problème d'accès à "l'essai au bleu". Cette capture montre clairement que l'utilisateur a accès à l'écran de l'essai au bleu de méthylène (MB), conforme à la norme **NF EN 933-9**.
- Il n'y a pas d'indication visible d'erreur ou de blocage dans cette interface.
- **Nom complet du module/essai** : "Essai au bleu de méthylène de méthylène (MB) - NF EN 933-9 (02-2022)".
- **Accès à l'écran d'essai** : L'utilisateur semble avoir accès à l'écran d'essai, sans erreur visible.
#### 5. Réponses potentielles
- **Éléments de réponse apportés par l'image** :
- L'image montre que l'écran de l'essai au bleu de méthylène (MB) est accessible, mais un problème de connexion au serveur est signalé.
- Cela pourrait expliquer pourquoi le client a rencontré des difficultés d'accès à l'essai.
- **Contexte technique précis** :
- Le terme "essai au bleu" utilisé par le client correspond clairement à "Essai au bleu de méthylène de méthylène (MB) - NF EN 933-9".
- **Réponse à la question "Pourquoi l'essai au bleu est-il inaccessible ?"** :
- Cette capture montre que l'essai au bleu de méthylène (MB) est accessible. Il n'y a pas d'indication visible d'un problème d'accès.
- **Réponse à la question "Comment accéder à la page https://zk1.brg-lab.com/ ?"** :
- L'image ne montre pas directement la page https://zk1.brg-lab.com/, mais elle indique que l'adresse IP du serveur de zk1.brg-lab.com n'a pas été trouvée. Cela pourrait être lié à un problème de connexion ou de configuration réseau.
#### 6. Lien avec la discussion
- **Correspondances avec le fil de discussion** :
- Le client a mentionné un problème d'accès à l'essai au bleu, ce qui correspond à l'écran visible dans l'image.
- Le support a demandé de vérifier l'accès à l'adresse https://zk1.brg-lab.com/, et le client a confirmé que l'adresse fonctionnait. Cependant, l'image montre un message d'erreur lié à cette adresse.
- **Connexions explicites** :
- Le terme "essai au bleu" utilisé par le client correspond à "Essai au bleu de méthylène de méthylène (MB) - NF EN 933-9" visible dans l'interface.
- **Correspondance avec le fil de discussion** :
- Le client mentionne un problème d'accès à "l'essai au bleu", qui correspond clairement à "l'essai au bleu de méthylène (MB) - NF EN 933-9" visible dans l'image.
- Le support demande de vérifier l'accès à la page https://zk1.brg-lab.com/. L'image montre un message indiquant que l'adresse IP du serveur de zk1.brg-lab.com n'a pas été trouvée, ce qui pourrait être lié à cette vérification.
- **Connexion avec le vocabulaire du client** :
- Le terme "essai au bleu" utilisé par le client correspond à "Essai au bleu de méthylène de méthylène (MB)" visible dans l'interface.
#### 7. Contexte technique élargi
- **Contexte de l'application** :
- L'application BRG-LAB est utilisée pour des essais techniques en laboratoire, notamment pour des essais normalisés comme l'essai au bleu de méthylène.
- L'application est utilisée dans un contexte de laboratoire pour des essais techniques normalisés.
- **Références à des normes ou standards** :
- La norme "NF EN 933-9" est clairement mentionnée pour l'essai au bleu de méthylène.
- La norme **NF EN 933-9 (02-2022)** est clairement mentionnée pour l'essai au bleu de méthylène (MB).
- **Codes ou identifiants visibles** :
- Numéro d'échantillon : 25-00075
- Numéro de l'échantillon : 25-00075
- Matériau : Sable 0/2 C - CARRIÈRE ADCE9
### Conclusion
L'image montre que l'écran de l'essai au bleu de méthylène (MB) est accessible, mais un message d'erreur indique un problème de connexion au serveur. Cela pourrait être lié au problème d'accès mentionné par le client. Le terme "essai au bleu" utilisé par le client correspond à "Essai au bleu de méthylène de méthylène (MB) - NF EN 933-9".
Cette analyse montre que l'utilisateur a accès à l'essai au bleu de méthylène (MB) et qu'aucun message d'erreur n'est visible dans cette capture. Le message concernant l'adresse IP du serveur de zk1.brg-lab.com pourrait être lié à un problème de connexion ou de configuration réseau.
----------------------------------------

View File

@ -1,9 +1,9 @@
[
{
"prompt": "### TICKET T11143\n\n--- MESSAGE INITIAL DU CLIENT ---\nAuteur : GIRAUD TP (JCG), Victor BOLLÉE, v.bollee@labojcg.fr\nDate : 03/04/2025 08:34\nContenu :\nBRGLAB - Essai inaccessible\n*Contenu non extractible*\n\n--- MESSAGE 1 ---\nAuteur : Fabien LAFAY\nDate : 03/04/2025 08:35\nType : Système\nSujet : Re: [T11143] BRGLAB - Essai inaccessible\nContenu :\nGIRAUD TP (JCG), Victor BOLLÉE\n-\nil y a 9 minutes\n;\nFabien LAFAY\n;\nRomuald GRUSON\n;\nsupport\n;\nsupport\n-\nQuentin FAIVRE\n-\nFabien LAFAY\n-\nRomuald GRUSON\nBonjour,\nJe ne parviens pas à accéder au lessai au bleu :\nMerci par avance pour votre.\nCordialement\n![Image](https://odoo.cbao.fr/web/image/145435?access_token=608ac9e7-3627-4a13-a8ec-06ff5046ebf3)\n\n---\n\n--- MESSAGE 2 ---\nAuteur : Fabien LAFAY\nDate : 03/04/2025 12:17\nType : E-mail\nSujet : Re: [T11143] - BRGLAB - Essai inaccessible\nContenu :\nBonjour\n,\nPouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur :\nhttps://zk1.brg-lab.com/\nVoici ce que vous devriez voir affiché :\nSi ce n'est pas le cas, pouvez-vous me faire une capture d'écran de ce qui est affiché?\nJe reste à votre entière disposition pour toute information complémentaire.\nCordialement,\n---\nSupport technique\n\n- image.png (image/png) [ID: 145453]\n\n---\n\n--- MESSAGE 3 ---\nAuteur : Victor BOLLÉE\nDate : 03/04/2025 12:21\nType : E-mail\nSujet : TR: [T11143] - BRGLAB - Essai inaccessible\nContenu :\nBonjour,\nLe problème sest résolu seul par la suite.\nJe vous remercie pour votre retour.\nBonne journée\nPS : ladresse fonctionne\nDe :\n\n---\n\n",
"response": "### Résumé du contexte\n- **Client** : Victor BOLLÉE, v.bollee@labojcg.fr\n- **Sujet du ticket reformulé en une ou plusieurs questions** :\n - Pourquoi l'essai au bleu est-il inaccessible ?\n - Comment résoudre le problème d'accès à l'essai au bleu ?\n- **Description technique synthétique** :\n - Le client ne parvient pas à accéder à l'essai au bleu.\n - Le problème s'est résolu de lui-même par la suite.\n - L'adresse https://zk1.brg-lab.com/ fonctionne.\n\n### Informations techniques détectées\n- **Logiciels/modules mentionnés** :\n - Essai au bleu\n- **Paramètres évoqués** :\n - Aucun paramètre spécifique mentionné.\n- **Fonctionnalités impactées** :\n - Accès à l'essai au bleu.\n- **Conditions spécifiques** :\n - Aucune condition spécifique mentionnée.\n\n### Fil de discussion (filtrée, nettoyée, classée)\n\n---\n\n**Intervenant** : Victor BOLLÉE (Client)\n**Date** : 03/04/2025 08:34\n**Contenu** :\nBRGLAB - Essai inaccessible\nBonjour,\nJe ne parviens pas à accéder au lessai au bleu :\nMerci par avance pour votre.\nCordialement\n![Image](https://odoo.cbao.fr/web/image/145435?access_token=608ac9e7-3627-4a13-a8ec-06ff5046ebf3)\n\n---\n\n**Intervenant** : Fabien LAFAY (Support)\n**Date** : 03/04/2025 12:17\n**Contenu** :\nBonjour,\nPouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur :\nhttps://zk1.brg-lab.com/\nVoici ce que vous devriez voir affiché :\nSi ce n'est pas le cas, pouvez-vous me faire une capture d'écran de ce qui est affiché?\nJe reste à votre entière disposition pour toute information complémentaire.\nCordialement,\n---\nSupport technique\n- image.png (image/png) [ID: 145453]\n\n---\n\n**Intervenant** : Victor BOLLÉE (Client)\n**Date** : 03/04/2025 12:21\n**Contenu** :\nBonjour,\nLe problème sest résolu seul par la suite.\nJe vous remercie pour votre retour.\nBonne journée\nPS : ladresse fonctionne\n\n---\n\n### Éléments liés à l'analyse visuelle\n- **Nombre d'images attachées** : 2\n- **Références aux interfaces ou options à visualiser** :\n - Capture d'écran de l'essai au bleu inaccessible.\n - Capture d'écran de la page https://zk1.brg-lab.com/.\n- **Points à vérifier dans les captures** :\n - Vérifier l'interface de l'essai au bleu pour identifier les éléments inaccessibles.\n - Comparer la capture d'écran de la page https://zk1.brg-lab.com/ avec ce qui devrait être affiché.",
"response": "### Résumé du contexte\n- **Client** : Victor BOLLÉE, v.bollee@labojcg.fr\n- **Sujet du ticket reformulé en une ou plusieurs questions** :\n - Pourquoi l'essai au bleu est-il inaccessible ?\n - Comment accéder à la page https://zk1.brg-lab.com/ ?\n- **Description technique synthétique** :\n - Le client ne parvient pas à accéder à l'essai au bleu.\n - Le problème s'est résolu de lui-même par la suite.\n - L'adresse https://zk1.brg-lab.com/ fonctionne.\n\n### Informations techniques détectées\n- **Logiciels/modules mentionnés** :\n - Essai au bleu\n- **Paramètres évoqués** :\n - Aucun paramètre spécifique mentionné.\n- **Fonctionnalités impactées** :\n - Accès à l'essai au bleu\n- **Conditions spécifiques** :\n - Aucune condition spécifique mentionnée.\n\n### Fil de discussion (filtrée, nettoyée, classée)\n\n---\n**Intervenant** : Victor BOLLÉE (Client)\n**Date** : 03/04/2025 08:34\n**Contenu** :\nBonjour,\nJe ne parviens pas à accéder à lessai au bleu.\nMerci par avance pour votre.\nCordialement\n![Image](https://odoo.cbao.fr/web/image/145435?access_token=608ac9e7-3627-4a13-a8ec-06ff5046ebf3)\n\n---\n**Intervenant** : Fabien LAFAY (Support)\n**Date** : 03/04/2025 12:17\n**Contenu** :\nBonjour,\nPouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur :\nhttps://zk1.brg-lab.com/\nVoici ce que vous devriez voir affiché :\nSi ce n'est pas le cas, pouvez-vous me faire une capture d'écran de ce qui est affiché?\nJe reste à votre entière disposition pour toute information complémentaire.\nCordialement,\n---\nSupport technique\n- image.png (image/png) [ID: 145453]\n\n---\n**Intervenant** : Victor BOLLÉE (Client)\n**Date** : 03/04/2025 12:21\n**Contenu** :\nBonjour,\nLe problème sest résolu seul par la suite.\nJe vous remercie pour votre retour.\nBonne journée\nPS : ladresse fonctionne\n\n---\n\n### Éléments liés à l'analyse visuelle\n- **Nombre d'images attachées** : 2\n- **Références aux interfaces ou options à visualiser** :\n - Essai au bleu\n - Page https://zk1.brg-lab.com/\n- **Points à vérifier dans les captures** :\n - Vérifier l'accès à l'essai au bleu.\n - Vérifier l'affichage de la page https://zk1.brg-lab.com/.",
"metadata": {
"timestamp": "20250421_160935",
"timestamp": "20250421_164827",
"source_agent": "AgentTicketAnalyser",
"ticket_id": "T11143",
"model_info": {

View File

@ -7,7 +7,7 @@ RÉSULTATS DE L'ANALYSE ANALYSE_TICKET - TICKET T11143
- **Client** : Victor BOLLÉE, v.bollee@labojcg.fr
- **Sujet du ticket reformulé en une ou plusieurs questions** :
- Pourquoi l'essai au bleu est-il inaccessible ?
- Comment résoudre le problème d'accès à l'essai au bleu ?
- Comment accéder à la page https://zk1.brg-lab.com/ ?
- **Description technique synthétique** :
- Le client ne parvient pas à accéder à l'essai au bleu.
- Le problème s'est résolu de lui-même par la suite.
@ -19,26 +19,23 @@ RÉSULTATS DE L'ANALYSE ANALYSE_TICKET - TICKET T11143
- **Paramètres évoqués** :
- Aucun paramètre spécifique mentionné.
- **Fonctionnalités impactées** :
- Accès à l'essai au bleu.
- Accès à l'essai au bleu
- **Conditions spécifiques** :
- Aucune condition spécifique mentionnée.
### Fil de discussion (filtrée, nettoyée, classée)
---
**Intervenant** : Victor BOLLÉE (Client)
**Date** : 03/04/2025 08:34
**Contenu** :
BRGLAB - Essai inaccessible
Bonjour,
Je ne parviens pas à accéder au lessai au bleu :
Je ne parviens pas à accéder à lessai au bleu.
Merci par avance pour votre.
Cordialement
![Image](https://odoo.cbao.fr/web/image/145435?access_token=608ac9e7-3627-4a13-a8ec-06ff5046ebf3)
---
**Intervenant** : Fabien LAFAY (Support)
**Date** : 03/04/2025 12:17
**Contenu** :
@ -54,7 +51,6 @@ Support technique
- image.png (image/png) [ID: 145453]
---
**Intervenant** : Victor BOLLÉE (Client)
**Date** : 03/04/2025 12:21
**Contenu** :
@ -62,18 +58,18 @@ Bonjour,
Le problème sest résolu seul par la suite.
Je vous remercie pour votre retour.
Bonne journée
PS : ladresse fonctionne
PS : ladresse fonctionne
---
### Éléments liés à l'analyse visuelle
- **Nombre d'images attachées** : 2
- **Références aux interfaces ou options à visualiser** :
- Capture d'écran de l'essai au bleu inaccessible.
- Capture d'écran de la page https://zk1.brg-lab.com/.
- Essai au bleu
- Page https://zk1.brg-lab.com/
- **Points à vérifier dans les captures** :
- Vérifier l'interface de l'essai au bleu pour identifier les éléments inaccessibles.
- Comparer la capture d'écran de la page https://zk1.brg-lab.com/ avec ce qui devrait être affiché.
- Vérifier l'accès à l'essai au bleu.
- Vérifier l'affichage de la page https://zk1.brg-lab.com/.
----------------------------------------

View File

@ -4,30 +4,33 @@ RAPPORT D'ANALYSE DU TICKET T11143
### Rapport Final
#### 1. Contexte général
Le client rencontre un problème d'accès à l'essai au bleu, qui se résout ensuite de lui-même, et l'adresse https://zk1.brg-lab.com/ fonctionne.
Le client ne parvient pas à accéder à l'essai au bleu, mais le problème s'est résolu de lui-même par la suite.
#### 2. Problèmes ou questions identifiés
- Pourquoi l'essai au bleu est-il inaccessible ?
- Comment résoudre le problème d'accès à l'essai au bleu ?
- Comment accéder à la page https://zk1.brg-lab.com/ ?
#### 3. Résumé croisé image/texte pour chaque question
**Pourquoi l'essai au bleu est-il inaccessible ?**
- **Texte du ticket** : Le client mentionne qu'il ne parvient pas à accéder à l'essai au bleu.
- **Image correspondante (image_145435.png)** : L'image montre un message d'erreur indiquant "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com".
- **Image (image_145435.png)** : L'image montre que l'utilisateur a accès à l'écran de l'essai au bleu de méthylène (MB), conforme à la norme NF EN 933-9. Aucun message d'erreur n'est visible dans cette capture.
**Comment résoudre le problème d'accès à l'essai au bleu ?**
- **Texte du ticket** : Le client indique que le problème s'est résolu de lui-même par la suite.
- **Image correspondante (image.png)** : L'image confirme que l'adresse https://zk1.brg-lab.com/ fonctionne et que Tomcat est installé correctement.
**Comment accéder à la page https://zk1.brg-lab.com/ ?**
- **Texte du ticket** : Le support demande au client de vérifier l'accès à la page https://zk1.brg-lab.com/.
- **Image (image.png)** : L'image montre que la page https://zk1.brg-lab.com/ est accessible et que Tomcat est correctement installé.
- **Image (image_145435.png)** : L'image indique que l'adresse IP du serveur de zk1.brg-lab.com n'a pas été trouvée, ce qui pourrait être lié à un problème de connexion ou de configuration réseau.
#### 4. Liste d'observations supplémentaires pertinentes
- Le client a confirmé que l'adresse https://zk1.brg-lab.com/ fonctionne, ce qui est cohérent avec l'image montrant la page de succès de Tomcat.
- Le message d'erreur "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com" visible dans l'image_145435.png pourrait être lié au problème initial d'accès à l'essai au bleu.
- Le client a confirmé que l'adresse https://zk1.brg-lab.com/ fonctionne.
- L'image (image.png) confirme que Tomcat est correctement installé et fonctionne.
- L'image (image_145435.png) montre que l'essai au bleu de méthylène (MB) est accessible et conforme à la norme NF EN 933-9.
- L'image (image_145435.png) indique un problème de connexion ou de configuration réseau avec l'adresse IP du serveur de zk1.brg-lab.com.
#### 5. Tableau chronologique d'échanges
| ÉMETTEUR | TYPE | DATE | CONTENU | ÉLÉMENTS VISUELS |
| --- | --- | --- | --- | --- |
| CLIENT | question | 03/04/2025 08:34 | Bonjour, Je ne parviens pas à accéder au lessai au bleu : Merci par avance pour votre. Cordialement | Message d'erreur : "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com" (image_145435.png) |
| SUPPORT | réponse | 03/04/2025 12:17 | Bonjour, Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/ Voici ce que vous devriez voir affiché : Si ce n'est pas le cas, pouvez-vous me faire une capture d'écran de ce qui est affiché? Je reste à votre entière disposition pour toute information complémentaire. Cordialement, | Page de succès de Tomcat : "If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!" (image.png) |
| CLIENT | information | 03/04/2025 12:21 | Bonjour, Le problème sest résolu seul par la suite. Je vous remercie pour votre retour. Bonne journée PS : ladresse fonctionne | Page de succès de Tomcat : "If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!" (image.png) |
| CLIENT | question | 03/04/2025 08:34 | Je ne parviens pas à accéder à lessai au bleu. | L'image (image_145435.png) montre que l'essai au bleu de méthylène (MB) est accessible. |
| SUPPORT | réponse | 03/04/2025 12:17 | Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/ | L'image (image.png) confirme que la page https://zk1.brg-lab.com/ est accessible et que Tomcat est correctement installé. |
| CLIENT | information | 03/04/2025 12:21 | Le problème sest résolu seul par la suite. Je vous remercie pour votre retour. Bonne journée PS : ladresse fonctionne | L'image (image_145435.png) indique un problème de connexion ou de configuration réseau avec l'adresse IP du serveur de zk1.brg-lab.com. |

View File

@ -6,33 +6,36 @@ RÉSULTATS DE L'ANALYSE RAPPORT_FINAL - TICKET T11143
### Rapport Final
#### 1. Contexte général
Le client rencontre un problème d'accès à l'essai au bleu, qui se résout ensuite de lui-même, et l'adresse https://zk1.brg-lab.com/ fonctionne.
Le client ne parvient pas à accéder à l'essai au bleu, mais le problème s'est résolu de lui-même par la suite.
#### 2. Problèmes ou questions identifiés
- Pourquoi l'essai au bleu est-il inaccessible ?
- Comment résoudre le problème d'accès à l'essai au bleu ?
- Comment accéder à la page https://zk1.brg-lab.com/ ?
#### 3. Résumé croisé image/texte pour chaque question
**Pourquoi l'essai au bleu est-il inaccessible ?**
- **Texte du ticket** : Le client mentionne qu'il ne parvient pas à accéder à l'essai au bleu.
- **Image correspondante (image_145435.png)** : L'image montre un message d'erreur indiquant "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com".
- **Image (image_145435.png)** : L'image montre que l'utilisateur a accès à l'écran de l'essai au bleu de méthylène (MB), conforme à la norme NF EN 933-9. Aucun message d'erreur n'est visible dans cette capture.
**Comment résoudre le problème d'accès à l'essai au bleu ?**
- **Texte du ticket** : Le client indique que le problème s'est résolu de lui-même par la suite.
- **Image correspondante (image.png)** : L'image confirme que l'adresse https://zk1.brg-lab.com/ fonctionne et que Tomcat est installé correctement.
**Comment accéder à la page https://zk1.brg-lab.com/ ?**
- **Texte du ticket** : Le support demande au client de vérifier l'accès à la page https://zk1.brg-lab.com/.
- **Image (image.png)** : L'image montre que la page https://zk1.brg-lab.com/ est accessible et que Tomcat est correctement installé.
- **Image (image_145435.png)** : L'image indique que l'adresse IP du serveur de zk1.brg-lab.com n'a pas été trouvée, ce qui pourrait être lié à un problème de connexion ou de configuration réseau.
#### 4. Liste d'observations supplémentaires pertinentes
- Le client a confirmé que l'adresse https://zk1.brg-lab.com/ fonctionne, ce qui est cohérent avec l'image montrant la page de succès de Tomcat.
- Le message d'erreur "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com" visible dans l'image_145435.png pourrait être lié au problème initial d'accès à l'essai au bleu.
- Le client a confirmé que l'adresse https://zk1.brg-lab.com/ fonctionne.
- L'image (image.png) confirme que Tomcat est correctement installé et fonctionne.
- L'image (image_145435.png) montre que l'essai au bleu de méthylène (MB) est accessible et conforme à la norme NF EN 933-9.
- L'image (image_145435.png) indique un problème de connexion ou de configuration réseau avec l'adresse IP du serveur de zk1.brg-lab.com.
#### 5. Tableau chronologique d'échanges
| ÉMETTEUR | TYPE | DATE | CONTENU | ÉLÉMENTS VISUELS |
| --- | --- | --- | --- | --- |
| CLIENT | question | 03/04/2025 08:34 | Bonjour, Je ne parviens pas à accéder au lessai au bleu : Merci par avance pour votre. Cordialement | Message d'erreur : "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com" (image_145435.png) |
| SUPPORT | réponse | 03/04/2025 12:17 | Bonjour, Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/ Voici ce que vous devriez voir affiché : Si ce n'est pas le cas, pouvez-vous me faire une capture d'écran de ce qui est affiché? Je reste à votre entière disposition pour toute information complémentaire. Cordialement, | Page de succès de Tomcat : "If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!" (image.png) |
| CLIENT | information | 03/04/2025 12:21 | Bonjour, Le problème sest résolu seul par la suite. Je vous remercie pour votre retour. Bonne journée PS : ladresse fonctionne | Page de succès de Tomcat : "If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!" (image.png) |
| CLIENT | question | 03/04/2025 08:34 | Je ne parviens pas à accéder à lessai au bleu. | L'image (image_145435.png) montre que l'essai au bleu de méthylène (MB) est accessible. |
| SUPPORT | réponse | 03/04/2025 12:17 | Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/ | L'image (image.png) confirme que la page https://zk1.brg-lab.com/ est accessible et que Tomcat est correctement installé. |
| CLIENT | information | 03/04/2025 12:21 | Le problème sest résolu seul par la suite. Je vous remercie pour votre retour. Bonne journée PS : ladresse fonctionne | L'image (image_145435.png) indique un problème de connexion ou de configuration réseau avec l'adresse IP du serveur de zk1.brg-lab.com. |
----------------------------------------

View File

@ -6,7 +6,7 @@
"metadata": {
"image_path": "output/ticket_T11143/T11143_20250418_151735/attachments/image.png",
"image_name": "image.png",
"timestamp": "20250421_160959",
"timestamp": "20250421_164830",
"model_info": {
"model": "pixtral-large-latest",
"temperature": 0.2,
@ -28,7 +28,7 @@
"metadata": {
"image_path": "output/ticket_T11143/T11143_20250418_151735/attachments/image_145435.png",
"image_name": "image_145435.png",
"timestamp": "20250421_161001",
"timestamp": "20250421_164833",
"model_info": {
"model": "pixtral-large-latest",
"temperature": 0.2,
@ -45,12 +45,12 @@
},
{
"is_relevant": false,
"reason": "Non.\n\nL'image montre uniquement le logo de la société CBAO, ce qui n'est pas pertinent pour un ticket de support technique.",
"raw_response": "Non.\n\nL'image montre uniquement le logo de la société CBAO, ce qui n'est pas pertinent pour un ticket de support technique.",
"reason": "Non.\n\nCette image est un logo ou une image de marque (CBAO), ce qui n'est pas pertinent pour un ticket de support technique.",
"raw_response": "Non.\n\nCette image est un logo ou une image de marque (CBAO), ce qui n'est pas pertinent pour un ticket de support technique.",
"metadata": {
"image_path": "output/ticket_T11143/T11143_20250418_151735/attachments/543d7da1b54c29ff43ce5712d1a9aa4962ed21795c4e943fcb8cb84fd4d7465a.jpg",
"image_name": "543d7da1b54c29ff43ce5712d1a9aa4962ed21795c4e943fcb8cb84fd4d7465a.jpg",
"timestamp": "20250421_161003",
"timestamp": "20250421_164835",
"model_info": {
"model": "pixtral-large-latest",
"temperature": 0.2,
@ -72,7 +72,7 @@
"metadata": {
"image_path": "output/ticket_T11143/T11143_20250418_151735/attachments/a20f7697fd5e1d1fca3296c6d01228220e0e112c46b4440cc938f74d10934e98.gif",
"image_name": "a20f7697fd5e1d1fca3296c6d01228220e0e112c46b4440cc938f74d10934e98.gif",
"timestamp": "20250421_161017",
"timestamp": "20250421_164904",
"model_info": {
"model": "pixtral-large-latest",
"temperature": 0.2,

View File

@ -21,7 +21,7 @@ L'image montre une capture d'écran d'une interface logicielle de BRG-LAB, ce qu
Non.
L'image montre uniquement le logo de la société CBAO, ce qui n'est pas pertinent pour un ticket de support technique.
Cette image est un logo ou une image de marque (CBAO), ce qui n'est pas pertinent pour un ticket de support technique.
----------------------------------------

View File

@ -4,30 +4,33 @@ RAPPORT D'ANALYSE DU TICKET T11143
### Rapport Final
#### 1. Contexte général
Le client rencontre un problème d'accès à l'essai au bleu, qui se résout ensuite de lui-même, et l'adresse https://zk1.brg-lab.com/ fonctionne.
Le client ne parvient pas à accéder à l'essai au bleu, mais le problème s'est résolu de lui-même par la suite.
#### 2. Problèmes ou questions identifiés
- Pourquoi l'essai au bleu est-il inaccessible ?
- Comment résoudre le problème d'accès à l'essai au bleu ?
- Comment accéder à la page https://zk1.brg-lab.com/ ?
#### 3. Résumé croisé image/texte pour chaque question
**Pourquoi l'essai au bleu est-il inaccessible ?**
- **Texte du ticket** : Le client mentionne qu'il ne parvient pas à accéder à l'essai au bleu.
- **Image correspondante (image_145435.png)** : L'image montre un message d'erreur indiquant "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com".
- **Image (image_145435.png)** : L'image montre que l'utilisateur a accès à l'écran de l'essai au bleu de méthylène (MB), conforme à la norme NF EN 933-9. Aucun message d'erreur n'est visible dans cette capture.
**Comment résoudre le problème d'accès à l'essai au bleu ?**
- **Texte du ticket** : Le client indique que le problème s'est résolu de lui-même par la suite.
- **Image correspondante (image.png)** : L'image confirme que l'adresse https://zk1.brg-lab.com/ fonctionne et que Tomcat est installé correctement.
**Comment accéder à la page https://zk1.brg-lab.com/ ?**
- **Texte du ticket** : Le support demande au client de vérifier l'accès à la page https://zk1.brg-lab.com/.
- **Image (image.png)** : L'image montre que la page https://zk1.brg-lab.com/ est accessible et que Tomcat est correctement installé.
- **Image (image_145435.png)** : L'image indique que l'adresse IP du serveur de zk1.brg-lab.com n'a pas été trouvée, ce qui pourrait être lié à un problème de connexion ou de configuration réseau.
#### 4. Liste d'observations supplémentaires pertinentes
- Le client a confirmé que l'adresse https://zk1.brg-lab.com/ fonctionne, ce qui est cohérent avec l'image montrant la page de succès de Tomcat.
- Le message d'erreur "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com" visible dans l'image_145435.png pourrait être lié au problème initial d'accès à l'essai au bleu.
- Le client a confirmé que l'adresse https://zk1.brg-lab.com/ fonctionne.
- L'image (image.png) confirme que Tomcat est correctement installé et fonctionne.
- L'image (image_145435.png) montre que l'essai au bleu de méthylène (MB) est accessible et conforme à la norme NF EN 933-9.
- L'image (image_145435.png) indique un problème de connexion ou de configuration réseau avec l'adresse IP du serveur de zk1.brg-lab.com.
#### 5. Tableau chronologique d'échanges
| ÉMETTEUR | TYPE | DATE | CONTENU | ÉLÉMENTS VISUELS |
| --- | --- | --- | --- | --- |
| CLIENT | question | 03/04/2025 08:34 | Bonjour, Je ne parviens pas à accéder au lessai au bleu : Merci par avance pour votre. Cordialement | Message d'erreur : "Impossible de trouver l'adresse IP du serveur de zk1.brg-lab.com" (image_145435.png) |
| SUPPORT | réponse | 03/04/2025 12:17 | Bonjour, Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/ Voici ce que vous devriez voir affiché : Si ce n'est pas le cas, pouvez-vous me faire une capture d'écran de ce qui est affiché? Je reste à votre entière disposition pour toute information complémentaire. Cordialement, | Page de succès de Tomcat : "If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!" (image.png) |
| CLIENT | information | 03/04/2025 12:21 | Bonjour, Le problème sest résolu seul par la suite. Je vous remercie pour votre retour. Bonne journée PS : ladresse fonctionne | Page de succès de Tomcat : "If you're seeing this page via a web browser, it means you've setup Tomcat successfully! Congratulations!" (image.png) |
| CLIENT | question | 03/04/2025 08:34 | Je ne parviens pas à accéder à lessai au bleu. | L'image (image_145435.png) montre que l'essai au bleu de méthylène (MB) est accessible. |
| SUPPORT | réponse | 03/04/2025 12:17 | Pouvez-vous vérifier si vous avez bien accès à la page suivante en l'ouvrant dans votre navigateur : https://zk1.brg-lab.com/ | L'image (image.png) confirme que la page https://zk1.brg-lab.com/ est accessible et que Tomcat est correctement installé. |
| CLIENT | information | 03/04/2025 12:21 | Le problème sest résolu seul par la suite. Je vous remercie pour votre retour. Bonne journée PS : ladresse fonctionne | L'image (image_145435.png) indique un problème de connexion ou de configuration réseau avec l'adresse IP du serveur de zk1.brg-lab.com. |

View File

@ -1,139 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script pour tester directement l'agent d'analyse de tickets (AgentTicketAnalyser) avec Mistral Large.
Permet d'évaluer rapidement l'analyse d'un ticket.
"""
import os
import sys
import argparse
import json
from typing import Dict, Any, Optional
from llm_classes.mistral_large import MistralLarge
from agents.mistral_large.agent_ticket_analyser import AgentTicketAnalyser
def get_ticket_report_file(ticket_id: str, output_dir: str) -> Optional[str]:
"""
Récupère le fichier de rapport du ticket dans le répertoire codeticket_rapports.
Args:
ticket_id: ID du ticket (ex: T1234)
output_dir: Répertoire de base contenant les tickets
Returns:
Chemin vers le fichier de rapport JSON, ou None si non trouvé
"""
# Construire le chemin vers le répertoire des rapports
ticket_path = os.path.join(output_dir, f"ticket_{ticket_id}")
# Chercher le répertoire d'extraction le plus récent
extractions = [d for d in os.listdir(ticket_path) if os.path.isdir(os.path.join(ticket_path, d)) and d.startswith(ticket_id)]
if not extractions:
return None
# Trier par ordre décroissant pour avoir la plus récente en premier
extractions.sort(reverse=True)
latest_extraction = extractions[0]
# Construire le chemin vers le répertoire des rapports
rapport_dir = os.path.join(ticket_path, latest_extraction, f"{ticket_id}_rapports")
rapport_file = os.path.join(rapport_dir, f"{ticket_id}_rapport.json")
if os.path.exists(rapport_file):
return rapport_file
return None
def main():
# Configuration de l'analyseur d'arguments
parser = argparse.ArgumentParser(description="Tester l'agent d'analyse de tickets directement.")
parser.add_argument("ticket_id", help="ID du ticket à analyser (ex: T1234)")
parser.add_argument("--ticket_file", help="Chemin spécifique vers un fichier JSON du ticket à analyser")
parser.add_argument("--output_dir", default="output", help="Répertoire de sortie contenant les tickets")
# Analyser les arguments
args = parser.parse_args()
# Construire le chemin vers le ticket
ticket_path = os.path.join(args.output_dir, f"ticket_{args.ticket_id}")
# Vérifier que le répertoire du ticket existe
if not os.path.exists(ticket_path):
print(f"ERREUR: Le ticket {args.ticket_id} n'existe pas dans {args.output_dir}")
return 1
# Rechercher la dernière extraction (la plus récente)
extractions = [d for d in os.listdir(ticket_path) if os.path.isdir(os.path.join(ticket_path, d)) and d.startswith(args.ticket_id)]
if not extractions:
print(f"ERREUR: Aucune extraction trouvée pour le ticket {args.ticket_id}")
return 1
# Trier par ordre décroissant pour avoir la plus récente en premier
extractions.sort(reverse=True)
latest_extraction = extractions[0]
extraction_path = os.path.join(ticket_path, latest_extraction)
print(f"Utilisation de l'extraction: {latest_extraction}")
# Initialiser le modèle Mistral Large
try:
print("Initialisation du modèle Mistral Large...")
model = MistralLarge()
agent = AgentTicketAnalyser(model)
print("Agent d'analyse de tickets initialisé avec succès")
except Exception as e:
print(f"ERREUR: Impossible d'initialiser le modèle: {str(e)}")
return 1
# Si un fichier de ticket spécifique est fourni
ticket_data: Dict[str, Any] = {}
if args.ticket_file:
if not os.path.exists(args.ticket_file):
print(f"ERREUR: Le fichier de ticket spécifié n'existe pas: {args.ticket_file}")
return 1
try:
with open(args.ticket_file, "r", encoding="utf-8") as f:
ticket_data = json.load(f)
print(f"Fichier de ticket chargé: {args.ticket_file}")
except json.JSONDecodeError:
print(f"ERREUR: Le fichier {args.ticket_file} n'est pas un JSON valide")
return 1
else:
# Chercher le fichier de rapport du ticket
rapport_file = get_ticket_report_file(args.ticket_id, args.output_dir)
if not rapport_file:
print(f"ERREUR: Aucun fichier de rapport trouvé pour le ticket {args.ticket_id}")
return 1
print(f"Fichier de rapport trouvé: {rapport_file}")
try:
with open(rapport_file, "r", encoding="utf-8") as f:
ticket_data = json.load(f)
print(f"Fichier de rapport chargé: {rapport_file}")
except json.JSONDecodeError:
print(f"ERREUR: Le fichier {rapport_file} n'est pas un JSON valide")
return 1
# Vérifier que les données du ticket contiennent un code
if "code" not in ticket_data:
ticket_data["code"] = args.ticket_id
print(f"Code de ticket ajouté: {args.ticket_id}")
# Exécuter l'analyse du ticket
print(f"Analyse du ticket {ticket_data.get('code', args.ticket_id)} en cours...")
result = agent.executer(ticket_data)
# Afficher le résultat
print("\nRésultat de l'analyse:")
print(result)
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,154 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script pour tester directement l'agent de tri d'images (AgentImageSorter) avec Pixtral-Large.
Permet d'évaluer rapidement si une image est pertinente pour un ticket.
"""
import os
import sys
import argparse
import glob
from typing import List
from llm_classes.pixtral_large import PixtralLarge
from agents.pixtral_large.agent_image_sorter import AgentImageSorter
from utils.image_dedup import filtrer_images_uniques
def get_images_in_extraction(extraction_path: str) -> List[str]:
"""
Récupère toutes les images dans un répertoire d'extraction
Args:
extraction_path: Chemin vers le répertoire d'extraction
Returns:
Liste des chemins d'accès aux images
"""
# Extensions d'images courantes
image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.gif', '*.webp']
# Liste pour stocker les chemins d'images
images = []
# Chercher dans le répertoire principal
for extension in image_extensions:
pattern = os.path.join(extraction_path, extension)
images.extend(glob.glob(pattern))
# Chercher dans le sous-répertoire "attachments"
attachments_path = os.path.join(extraction_path, "attachments")
if os.path.exists(attachments_path) and os.path.isdir(attachments_path):
for extension in image_extensions:
pattern = os.path.join(attachments_path, extension)
images.extend(glob.glob(pattern))
# Chercher dans les sous-répertoires de "attachments" (si des ID sont utilisés)
for subdir in os.listdir(attachments_path):
subdir_path = os.path.join(attachments_path, subdir)
if os.path.isdir(subdir_path):
for extension in image_extensions:
pattern = os.path.join(subdir_path, extension)
images.extend(glob.glob(pattern))
return images
def main():
# Configuration de l'analyseur d'arguments
parser = argparse.ArgumentParser(description="Tester l'agent de tri d'images directement.")
parser.add_argument("ticket_id", help="ID du ticket à analyser (ex: T1234)")
parser.add_argument("--image", help="Chemin spécifique vers une image à analyser")
parser.add_argument("--output_dir", default="output", help="Répertoire de sortie contenant les tickets")
parser.add_argument("--no-dedup", action="store_true", help="Désactiver le préfiltrage des doublons")
parser.add_argument("--seuil", type=int, default=5, help="Seuil de similarité pour détecter les doublons (0-10, défaut=5)")
# Analyser les arguments
args = parser.parse_args()
# Construire le chemin vers le ticket
ticket_path = os.path.join(args.output_dir, f"ticket_{args.ticket_id}")
# Vérifier que le répertoire du ticket existe
if not os.path.exists(ticket_path):
print(f"ERREUR: Le ticket {args.ticket_id} n'existe pas dans {args.output_dir}")
return 1
# Rechercher la dernière extraction (la plus récente)
extractions = [d for d in os.listdir(ticket_path) if os.path.isdir(os.path.join(ticket_path, d)) and d.startswith(args.ticket_id)]
if not extractions:
print(f"ERREUR: Aucune extraction trouvée pour le ticket {args.ticket_id}")
return 1
# Trier par ordre décroissant pour avoir la plus récente en premier
extractions.sort(reverse=True)
latest_extraction = extractions[0]
extraction_path = os.path.join(ticket_path, latest_extraction)
print(f"Utilisation de l'extraction: {latest_extraction}")
# Initialiser le modèle Pixtral-Large
try:
print("Initialisation du modèle Pixtral-Large...")
model = PixtralLarge()
agent = AgentImageSorter(model)
print("Agent de tri d'images initialisé avec succès")
except Exception as e:
print(f"ERREUR: Impossible d'initialiser le modèle: {str(e)}")
return 1
# Si une image spécifique est fournie
if args.image:
image_path = args.image
if not os.path.exists(image_path):
print(f"ERREUR: L'image spécifiée n'existe pas: {image_path}")
return 1
print(f"Analyse de l'image: {os.path.basename(image_path)}")
resultat = agent.executer(image_path)
# Afficher le résultat
print("\nRésultat de l'analyse:")
print(f"Pertinence: {'OUI' if resultat.get('is_relevant', False) else 'NON'}")
print(f"Raison: {resultat.get('reason', 'Non spécifiée')}")
else:
# Récupérer toutes les images de l'extraction
images = get_images_in_extraction(extraction_path)
if not images:
print(f"Aucune image trouvée dans l'extraction {latest_extraction}")
return 1
print(f"Nombre d'images trouvées: {len(images)}")
# Appliquer le préfiltrage de doublons si activé
if not args.no_dedup:
images_avant = len(images)
images = filtrer_images_uniques(images, seuil_hamming=args.seuil, ticket_id=args.ticket_id)
images_apres = len(images)
if images_avant > images_apres:
print(f"Préfiltrage des doublons: {images_avant}{images_apres} images ({images_avant - images_apres} doublons supprimés)")
else:
print("Préfiltrage terminé: aucun doublon détecté")
# Analyser chaque image
results = []
for image_path in images:
print(f"\nAnalyse de l'image: {os.path.basename(image_path)}")
resultat = agent.executer(image_path)
results.append(resultat)
# Afficher le résultat
print(f"Pertinence: {'OUI' if resultat.get('is_relevant', False) else 'NON'}")
print(f"Raison: {resultat.get('reason', 'Non spécifiée')}")
# Afficher un résumé à la fin
pertinentes = sum(1 for r in results if r.get('is_relevant', False))
print(f"\nRésumé: {pertinentes}/{len(results)} images pertinentes")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,674 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script pour tester la chaîne d'analyse complète avec priorité au tri d'images:
1. Tri des images
2. Analyse du ticket
3. Analyse des images pertinentes
4. Génération d'un rapport final
"""
import os
import sys
import argparse
import json
import glob
from typing import Dict, Any, Optional, List
from llm_classes.mistral_large import MistralLarge
from llm_classes.pixtral_large import PixtralLarge
from agents.mistral_large.agent_ticket_analyser import AgentTicketAnalyser
from agents.pixtral_large.agent_image_sorter import AgentImageSorter
from agents.pixtral_large.agent_image_analyser import AgentImageAnalyser
from agents.mistral_large.agent_report_generator import AgentReportGenerator
from utils.image_dedup import filtrer_images_uniques
def get_ticket_report_file(ticket_id: str, output_dir: str) -> Optional[str]:
"""
Récupère le fichier de rapport du ticket dans le répertoire codeticket_rapports.
Args:
ticket_id: ID du ticket (ex: T1234)
output_dir: Répertoire de base contenant les tickets
Returns:
Chemin vers le fichier de rapport JSON, ou None si non trouvé
"""
# Construire le chemin vers le répertoire des rapports
ticket_path = os.path.join(output_dir, f"ticket_{ticket_id}")
# Chercher le répertoire d'extraction le plus récent
extractions = [d for d in os.listdir(ticket_path) if os.path.isdir(os.path.join(ticket_path, d)) and d.startswith(ticket_id)]
if not extractions:
return None
# Trier par ordre décroissant pour avoir la plus récente en premier
extractions.sort(reverse=True)
latest_extraction = extractions[0]
# Construire le chemin vers le répertoire des rapports
rapport_dir = os.path.join(ticket_path, latest_extraction, f"{ticket_id}_rapports")
rapport_file = os.path.join(rapport_dir, f"{ticket_id}_rapport.json")
if os.path.exists(rapport_file):
return rapport_file
return None
def get_images_in_extraction(extraction_path: str) -> List[str]:
"""
Récupère toutes les images dans un répertoire d'extraction
Args:
extraction_path: Chemin vers le répertoire d'extraction
Returns:
Liste des chemins d'accès aux images
"""
# Extensions d'images courantes
image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.gif', '*.webp']
# Liste pour stocker les chemins d'images
images = []
# Chercher dans le répertoire principal
for extension in image_extensions:
pattern = os.path.join(extraction_path, extension)
images.extend(glob.glob(pattern))
# Chercher dans le sous-répertoire "attachments"
attachments_path = os.path.join(extraction_path, "attachments")
if os.path.exists(attachments_path) and os.path.isdir(attachments_path):
for extension in image_extensions:
pattern = os.path.join(attachments_path, extension)
images.extend(glob.glob(pattern))
# Chercher dans les sous-répertoires de "attachments" (si des ID sont utilisés)
for subdir in os.listdir(attachments_path):
subdir_path = os.path.join(attachments_path, subdir)
if os.path.isdir(subdir_path):
for extension in image_extensions:
pattern = os.path.join(subdir_path, extension)
images.extend(glob.glob(pattern))
return images
def main():
# Configuration de l'analyseur d'arguments
parser = argparse.ArgumentParser(description="Tester la chaîne d'analyse avec priorité au tri d'images.")
parser.add_argument("ticket_id", help="ID du ticket à analyser (ex: T1234)")
parser.add_argument("--output_dir", default="output", help="Répertoire de sortie contenant les tickets")
parser.add_argument("--ticket_file", help="Chemin spécifique vers un fichier JSON du ticket à analyser")
parser.add_argument("--no-dedup", action="store_true", help="Désactiver le préfiltrage des doublons")
parser.add_argument("--seuil", type=int, default=5, help="Seuil de similarité pour détecter les doublons (0-10, défaut=5)")
parser.add_argument("--save_results", action="store_true", help="Sauvegarder les résultats dans un fichier JSON")
parser.add_argument("--image", help="Chemin spécifique vers une image à analyser")
parser.add_argument("--show-analysis", action="store_true", help="Afficher l'analyse du ticket et des images")
parser.add_argument("--debug", action="store_true", help="Afficher des informations de débogage supplémentaires")
parser.add_argument("--generate-report", action="store_true", help="Générer un rapport final à partir des analyses")
parser.add_argument("--interactive", action="store_true", help="Utiliser le menu interactif")
parser.add_argument("--mode", type=int, choices=[1, 2, 3, 4],
help="Mode d'analyse: 1=Tri uniquement, 2=Tri+Ticket, 3=Tri+Ticket+Images, 4=Analyse complète avec rapport")
# Analyser les arguments
args = parser.parse_args()
# Mode interactif si demandé
if args.interactive:
args = _interactive_menu(args)
# Si un mode est spécifié, configurer les options correspondantes
if args.mode:
if args.mode >= 1: # Tous les modes incluent le tri
pass # Tri toujours activé
if args.mode >= 2: # Modes 2, 3, 4 incluent l'analyse de ticket
pass # Analyse de ticket toujours activée
if args.mode >= 3: # Modes 3, 4 incluent l'analyse d'images
pass # Analyse d'images toujours activée
if args.mode >= 4: # Mode 4 inclut la génération de rapport
args.generate_report = True
# Construire le chemin vers le ticket
ticket_path = os.path.join(args.output_dir, f"ticket_{args.ticket_id}")
# Vérifier que le répertoire du ticket existe
if not os.path.exists(ticket_path):
print(f"ERREUR: Le ticket {args.ticket_id} n'existe pas dans {args.output_dir}")
return 1
# Rechercher la dernière extraction (la plus récente)
extractions = [d for d in os.listdir(ticket_path) if os.path.isdir(os.path.join(ticket_path, d)) and d.startswith(args.ticket_id)]
if not extractions:
print(f"ERREUR: Aucune extraction trouvée pour le ticket {args.ticket_id}")
return 1
# Trier par ordre décroissant pour avoir la plus récente en premier
extractions.sort(reverse=True)
latest_extraction = extractions[0]
extraction_path = os.path.join(ticket_path, latest_extraction)
print(f"Utilisation de l'extraction: {latest_extraction}")
# ============ TRAITEMENT D'UNE SEULE IMAGE ============
if args.image:
# 1. Initialiser uniquement l'agent de tri d'images
try:
print("Initialisation du modèle Pixtral-Large pour le tri...")
model_tri = PixtralLarge()
# Configuration explicite du modèle
model_tri.configurer(temperature=0.2, top_p=0.8, max_tokens=300)
image_sorter = AgentImageSorter(model_tri)
print("Agent de tri d'images initialisé avec succès")
except Exception as e:
print(f"ERREUR: Impossible d'initialiser le modèle de tri: {str(e)}")
return 1
# 2. Trier l'image
image_path = args.image
if not os.path.exists(image_path):
print(f"ERREUR: L'image spécifiée n'existe pas: {image_path}")
return 1
print(f"Analyse de l'image: {os.path.basename(image_path)}")
resultat = image_sorter.executer(image_path)
# Afficher le résultat du tri
print("\nRésultat de l'analyse:")
print(f"Pertinence: {'OUI' if resultat.get('is_relevant', False) else 'NON'}")
print(f"Raison: {resultat.get('reason', 'Non spécifiée')}")
# Si l'image est pertinente, on peut procéder à l'analyse du ticket puis à l'analyse de l'image
if resultat.get('is_relevant', False):
# 3. Charger et analyser le ticket
ticket_data = _charger_ticket_data(args)
if not ticket_data:
return 1
# 4. Initialiser l'agent d'analyse de tickets et exécuter l'analyse
try:
print("Initialisation du modèle Mistral Large...")
text_model = MistralLarge()
ticket_agent = AgentTicketAnalyser(text_model)
print("Agent d'analyse de tickets initialisé avec succès")
except Exception as e:
print(f"ERREUR: Impossible d'initialiser le modèle d'analyse de tickets: {str(e)}")
return 1
# Exécuter l'analyse du ticket
print(f"\nAnalyse du ticket {ticket_data.get('code', args.ticket_id)} en cours...")
ticket_analysis = ticket_agent.executer(ticket_data)
print(f"Analyse du ticket terminée: {len(ticket_analysis)} caractères")
if args.show_analysis:
print("\nRésultat de l'analyse du ticket:")
print(ticket_analysis)
# 5. Initialiser l'agent d'analyse d'images et exécuter l'analyse
try:
print("Initialisation du modèle Pixtral-Large pour l'analyse d'images...")
model_analyse = PixtralLarge()
# Configuration explicite du modèle
model_analyse.configurer(temperature=0.2, top_p=0.9, max_tokens=3000)
image_analyser = AgentImageAnalyser(model_analyse)
print("Agent d'analyse d'images initialisé avec succès")
except Exception as e:
print(f"ERREUR: Impossible d'initialiser le modèle d'analyse d'images: {str(e)}")
return 1
# Analyser l'image avec le contexte du ticket
print(f"\nAnalyse approfondie de l'image: {os.path.basename(image_path)}")
analysis_result = image_analyser.executer(image_path, contexte=ticket_analysis)
if "error" in analysis_result and analysis_result.get("error", False):
print(f" => Erreur: {analysis_result.get('analyse', '')}")
else:
analyse_text = analysis_result.get("analyse", "")
print(f" => Analyse terminée: {len(analyse_text)} caractères")
if args.show_analysis:
print("\nRésultat de l'analyse d'image:")
print(analyse_text)
else:
print(f" => Début de l'analyse: {analyse_text[:100]}...")
# 6. Générer un rapport final si demandé
if args.generate_report:
try:
print("\nInitialisation de l'agent de génération de rapport...")
report_model = MistralLarge()
report_generator = AgentReportGenerator(report_model)
print("Agent de génération de rapport initialisé avec succès")
# Préparer les données pour le rapport
rapport_data = {
"ticket_id": args.ticket_id,
"ticket_data": ticket_data,
"ticket_analyse": ticket_analysis,
"analyse_images": {
image_path: {
"sorting": resultat,
"analysis": analysis_result
}
}
}
# Générer le rapport
print("\nGénération du rapport final...")
rapport_final = report_generator.executer(rapport_data)
print(f"Rapport généré: {len(rapport_final)} caractères")
if args.show_analysis:
print("\nRapport final:")
print(rapport_final)
else:
print(f"Début du rapport: {rapport_final[:200]}...")
except Exception as e:
print(f"ERREUR: Impossible de générer le rapport: {str(e)}")
return 0
# ============ TRAITEMENT COMPLET DU TICKET ============
# ÉTAPE 1: Récupérer et trier les images avec un agent isolé
# Initialiser uniquement l'agent de tri d'images
try:
print("Initialisation du modèle Pixtral-Large pour le tri...")
model_tri = PixtralLarge()
# Configuration explicite du modèle
model_tri.configurer(temperature=0.2, top_p=0.8, max_tokens=300)
image_sorter = AgentImageSorter(model_tri)
print("Agent de tri d'images initialisé avec succès")
except Exception as e:
print(f"ERREUR: Impossible d'initialiser le modèle de tri: {str(e)}")
return 1
print("\nRécupération et tri des images...")
all_images = get_images_in_extraction(extraction_path)
if not all_images:
print(f"Aucune image trouvée dans l'extraction {latest_extraction}")
return 1
print(f"Nombre d'images trouvées: {len(all_images)}")
# Appliquer le préfiltrage de doublons si activé
if not args.no_dedup:
images_avant = len(all_images)
all_images = filtrer_images_uniques(all_images, seuil_hamming=args.seuil, ticket_id=args.ticket_id)
images_apres = len(all_images)
if images_avant > images_apres:
print(f"Préfiltrage des doublons: {images_avant}{images_apres} images ({images_avant - images_apres} doublons supprimés)")
else:
print("Préfiltrage terminé: aucun doublon détecté")
# Trier les images avec l'agent dédié
relevant_images = []
image_sorting_results = {}
# Analyser chaque image - exactement comme dans test_image_sorter
results = []
for img_path in all_images:
img_name = os.path.basename(img_path)
print(f"\nAnalyse de l'image: {img_name}")
if args.debug:
print(f"DEBUG: Chemin complet de l'image: {img_path}")
print(f"DEBUG: L'image existe: {os.path.exists(img_path)}")
print(f"DEBUG: L'image est accessible: {os.access(img_path, os.R_OK)}")
resultat = image_sorter.executer(img_path)
results.append(resultat)
is_relevant = resultat.get("is_relevant", False)
image_sorting_results[img_path] = resultat
# Afficher les résultats comme dans test_image_sorter
print(f"Pertinence: {'OUI' if is_relevant else 'NON'}")
print(f"Raison: {resultat.get('reason', 'Non spécifiée')}")
if is_relevant:
relevant_images.append(img_path)
# Afficher un résumé à la fin comme dans test_image_sorter
pertinentes = sum(1 for r in results if r.get('is_relevant', False))
print(f"\nRésumé: {pertinentes}/{len(results)} images pertinentes")
# Préparer les résultats pour les modes limités
combined_results = {
"ticket_id": args.ticket_id,
"images_total": len(all_images),
"images_relevant": len(relevant_images),
"analyse_images": {}
}
# Ajouter les résultats de tri
for img_path in all_images:
img_name = os.path.basename(img_path)
combined_results["analyse_images"][img_path] = {
"path": img_path,
"sorting": image_sorting_results.get(img_path, {})
}
# Si on est en mode 1 (tri uniquement), s'arrêter ici
if args.mode == 1:
print("\n=== ANALYSE DE TRI TERMINÉE ===")
print(f"Ticket: {args.ticket_id}")
print(f"Images totales: {len(all_images)}")
print(f"Images pertinentes: {len(relevant_images)}")
# Sauvegarder les résultats si demandé
if args.save_results:
save_dir = os.path.join("results", args.ticket_id)
os.makedirs(save_dir, exist_ok=True)
save_file = os.path.join(save_dir, f"tri_images_{args.ticket_id}.json")
with open(save_file, "w", encoding="utf-8") as f:
json.dump(combined_results, f, ensure_ascii=False, indent=2)
print(f"\nRésultats du tri sauvegardés dans: {save_file}")
# Libérer les ressources de l'agent de tri
del image_sorter
del model_tri
return 0
# Libérer les ressources de l'agent de tri
del image_sorter
del model_tri
# ÉTAPE 2: Charger et analyser le ticket avec un agent isolé
# Initialiser l'agent d'analyse de tickets
try:
print("Initialisation du modèle Mistral Large...")
text_model = MistralLarge()
ticket_agent = AgentTicketAnalyser(text_model)
print("Agent d'analyse de tickets initialisé avec succès")
except Exception as e:
print(f"ERREUR: Impossible d'initialiser le modèle d'analyse de tickets: {str(e)}")
return 1
ticket_data = _charger_ticket_data(args)
if not ticket_data:
return 1
# Exécuter l'analyse du ticket - Format comme dans test_agent_ticket_analyser_mistral_large.py
print(f"\nAnalyse du ticket {ticket_data.get('code', args.ticket_id)} en cours...")
ticket_analysis = ticket_agent.executer(ticket_data)
print(f"Analyse du ticket terminée: {len(ticket_analysis)} caractères")
if args.show_analysis:
print("\nRésultat de l'analyse:")
print(ticket_analysis)
# Ajouter l'analyse du ticket aux résultats
combined_results["ticket_data"] = ticket_data
combined_results["ticket_analyse"] = ticket_analysis
# Si on est en mode 2 (tri + ticket), s'arrêter ici
if args.mode == 2:
print("\n=== ANALYSE DE TICKET TERMINÉE ===")
print(f"Ticket: {args.ticket_id}")
print(f"Images totales: {len(all_images)}")
print(f"Images pertinentes: {len(relevant_images)}")
print(f"Analyse du ticket: {len(ticket_analysis)} caractères")
# Sauvegarder les résultats si demandé
if args.save_results:
save_dir = os.path.join("results", args.ticket_id)
os.makedirs(save_dir, exist_ok=True)
save_file = os.path.join(save_dir, f"tri_ticket_{args.ticket_id}.json")
with open(save_file, "w", encoding="utf-8") as f:
json.dump(combined_results, f, ensure_ascii=False, indent=2)
print(f"\nRésultats du tri et de l'analyse de ticket sauvegardés dans: {save_file}")
# Libérer les ressources de l'agent de tickets
del ticket_agent
del text_model
return 0
# Libérer les ressources de l'agent de tickets
del ticket_agent
del text_model
# ÉTAPE 3: Analyser les images pertinentes avec un agent isolé
image_analysis_results = {}
if relevant_images:
# Initialiser l'agent d'analyse d'images
try:
print("Initialisation du modèle Pixtral-Large pour l'analyse d'images...")
model_analyse = PixtralLarge()
# Configuration explicite du modèle
model_analyse.configurer(temperature=0.2, top_p=0.9, max_tokens=3000)
image_analyser = AgentImageAnalyser(model_analyse)
print("Agent d'analyse d'images initialisé avec succès")
except Exception as e:
print(f"ERREUR: Impossible d'initialiser le modèle d'analyse d'images: {str(e)}")
return 1
print("\nAnalyse des images pertinentes avec contexte du ticket...")
for img_path in relevant_images:
img_name = os.path.basename(img_path)
print(f"\nAnalyse de l'image: {img_name}")
try:
analysis_result = image_analyser.executer(img_path, contexte=ticket_analysis)
image_analysis_results[img_path] = analysis_result
if "error" in analysis_result and analysis_result.get("error", False):
print(f" => Erreur: {analysis_result.get('analyse', '')}")
else:
analyse_text = analysis_result.get("analyse", "")
print(f" => Analyse terminée: {len(analyse_text)} caractères")
if args.show_analysis:
print("\nContenu de l'analyse:")
print(analyse_text)
else:
print(f" => Début de l'analyse: {analyse_text[:100]}...")
except Exception as e:
print(f"ERREUR: Impossible d'analyser l'image {img_name}: {str(e)}")
image_analysis_results[img_path] = {"error": True, "analyse": f"ERREUR: {str(e)}"}
else:
print("Aucune image pertinente à analyser")
# Ajout des résultats d'analyse d'images
combined_results["images_analysed"] = len(image_analysis_results)
# Mise à jour des analyses d'images
for img_path in all_images:
if img_path in image_analysis_results:
combined_results["analyse_images"][img_path]["analysis"] = image_analysis_results[img_path]
# Libérer les ressources de l'agent d'analyse d'images
if relevant_images:
del image_analyser
del model_analyse
# Si on est en mode 3 (tri + ticket + images), s'arrêter ici
if args.mode == 3:
print("\n=== ANALYSE D'IMAGES TERMINÉE ===")
print(f"Ticket: {args.ticket_id}")
print(f"Images totales: {len(all_images)}")
print(f"Images pertinentes: {len(relevant_images)}")
print(f"Analyse du ticket: {len(ticket_analysis)} caractères")
print(f"Images analysées: {len(image_analysis_results)}")
# Sauvegarder les résultats si demandé
if args.save_results:
save_dir = os.path.join("results", args.ticket_id)
os.makedirs(save_dir, exist_ok=True)
save_file = os.path.join(save_dir, f"tri_ticket_images_{args.ticket_id}.json")
with open(save_file, "w", encoding="utf-8") as f:
json.dump(combined_results, f, ensure_ascii=False, indent=2)
print(f"\nRésultats complets des analyses sauvegardés dans: {save_file}")
return 0
# ÉTAPE 4: Générer un rapport final si en mode 4 ou avec l'option --generate-report
if args.mode == 4 or args.generate_report:
try:
print("\nInitialisation de l'agent de génération de rapport...")
report_model = MistralLarge()
report_generator = AgentReportGenerator(report_model)
print("Agent de génération de rapport initialisé avec succès")
# Préparer les données pour le rapport
rapport_data = {
"ticket_id": args.ticket_id,
"ticket_data": ticket_data,
"ticket_analyse": ticket_analysis,
"analyse_images": combined_results["analyse_images"]
}
# Générer le rapport
print("\nGénération du rapport final...")
rapport_final = report_generator.executer(rapport_data)
print(f"Rapport généré: {len(rapport_final)} caractères")
# Ajouter le rapport final aux résultats combinés
combined_results["rapport_final"] = rapport_final
if args.show_analysis:
print("\nRapport final:")
print(rapport_final)
else:
print(f"Début du rapport: {rapport_final[:200]}...")
except Exception as e:
print(f"ERREUR: Impossible de générer le rapport: {str(e)}")
# Sauvegarder les résultats si demandé
if args.save_results:
save_dir = os.path.join("results", args.ticket_id)
os.makedirs(save_dir, exist_ok=True)
save_file = os.path.join(save_dir, f"complete_analysis_{args.ticket_id}.json")
with open(save_file, "w", encoding="utf-8") as f:
json.dump(combined_results, f, ensure_ascii=False, indent=2)
print(f"\nRésultats complets sauvegardés dans: {save_file}")
# Afficher un résumé final comme dans le workflow d'analyse d'images
print("\n=== RÉSUMÉ DE L'ANALYSE COMPLÈTE ===")
print(f"Ticket: {args.ticket_id}")
print(f"Images totales: {len(all_images)}")
print(f"Images pertinentes: {len(relevant_images)}")
print(f"Analyse du ticket: {len(ticket_analysis)} caractères")
print(f"Images analysées: {len(image_analysis_results)}")
if args.generate_report or args.mode == 4:
print(f"Rapport final généré: {len(combined_results.get('rapport_final', ''))} caractères")
return 0
def _interactive_menu(args):
"""
Affiche un menu interactif pour choisir le mode d'analyse
"""
print("\n=== MENU D'ANALYSE ===")
print("Ticket à analyser: " + args.ticket_id)
print("1. Tri d'images uniquement")
print("2. Tri d'images + Analyse du ticket")
print("3. Tri d'images + Analyse du ticket + Analyse approfondie des images")
print("4. Analyse complète avec génération de rapport")
print("5. Analyse d'une seule image")
print("0. Quitter")
while True:
try:
choice = int(input("\nVotre choix (0-5): "))
if 0 <= choice <= 5:
break
else:
print("Veuillez entrer un chiffre entre 0 et 5.")
except ValueError:
print("Veuillez entrer un chiffre valide.")
if choice == 0:
print("Au revoir!")
sys.exit(0)
if choice == 5:
# Mode analyse d'une seule image
image_path = input("Chemin de l'image à analyser: ")
if not os.path.exists(image_path):
print(f"ERREUR: L'image spécifiée n'existe pas: {image_path}")
sys.exit(1)
args.image = image_path
else:
# Configurer le mode d'analyse
args.mode = choice
# Paramètres supplémentaires
if input("Afficher les analyses détaillées? (o/n): ").lower().startswith('o'):
args.show_analysis = True
if input("Sauvegarder les résultats? (o/n): ").lower().startswith('o'):
args.save_results = True
if input("Désactiver le préfiltrage des doublons? (o/n): ").lower().startswith('o'):
args.no_dedup = True
if args.no_dedup is False:
try:
seuil = int(input("Seuil de similarité pour détecter les doublons (0-10, défaut=5): "))
if 0 <= seuil <= 10:
args.seuil = seuil
except ValueError:
print("Valeur invalide, utilisation du seuil par défaut: 5")
return args
def _charger_ticket_data(args):
"""
Charge les données du ticket à partir d'un fichier spécifié ou du fichier de rapport par défaut.
"""
ticket_data = {}
if args.ticket_file:
if not os.path.exists(args.ticket_file):
print(f"ERREUR: Le fichier de ticket spécifié n'existe pas: {args.ticket_file}")
return None
try:
with open(args.ticket_file, "r", encoding="utf-8") as f:
ticket_data = json.load(f)
print(f"Fichier de ticket chargé: {args.ticket_file}")
except json.JSONDecodeError:
print(f"ERREUR: Le fichier {args.ticket_file} n'est pas un JSON valide")
return None
else:
# Chercher le fichier de rapport du ticket
rapport_file = get_ticket_report_file(args.ticket_id, args.output_dir)
if not rapport_file:
print(f"ERREUR: Aucun fichier de rapport trouvé pour le ticket {args.ticket_id}")
return None
print(f"Fichier de rapport trouvé: {rapport_file}")
try:
with open(rapport_file, "r", encoding="utf-8") as f:
ticket_data = json.load(f)
print(f"Fichier de rapport chargé: {rapport_file}")
except json.JSONDecodeError:
print(f"ERREUR: Le fichier {rapport_file} n'est pas un JSON valide")
return None
# Vérifier que les données du ticket contiennent un code
if "code" not in ticket_data:
ticket_data["code"] = args.ticket_id
print(f"Code de ticket ajouté: {args.ticket_id}")
return ticket_data
if __name__ == "__main__":
sys.exit(main())