mirror of
https://github.com/Ladebeze66/llm_ticket3.git
synced 2025-12-13 12:36:50 +01:00
16:45
This commit is contained in:
parent
3190c5dd02
commit
9571d96157
@ -0,0 +1,4 @@
|
||||
Émetteur,Type,Date,Contenu,Éléments visuels
|
||||
Client,Question,Inconnu,Contenu insuffisant pour l'analyse,-
|
||||
Support,Demande de plus d'informations,Inconnu,Demande de détails supplémentaires pour aider le problème,-
|
||||
Client,Réponse,2025-04-23 16:36:57,Image fournie (image.png) de la page d'accueil par défaut de Tomcat,"Capture d'écran de la page d'accueil par défaut de Tomcat, indiquant une configuration réussie"
|
||||
|
@ -1,20 +1,22 @@
|
||||
from ..base_agent import BaseAgent
|
||||
import logging
|
||||
import os
|
||||
from typing import Dict, Any
|
||||
from typing import Dict, Any, List, Optional
|
||||
from PIL import Image
|
||||
from ..utils.pipeline_logger import sauvegarder_donnees
|
||||
from utils.translate_utils import fr_to_en, en_to_fr
|
||||
|
||||
logger = logging.getLogger("AgentImageAnalyser")
|
||||
|
||||
class AgentImageAnalyser(BaseAgent):
|
||||
"""
|
||||
Agent pour analyser les images et extraire les informations pertinentes.
|
||||
Agent for analyzing images and extracting relevant information.
|
||||
Works in English and translates to French for compatibility.
|
||||
"""
|
||||
def __init__(self, llm):
|
||||
super().__init__("AgentImageAnalyser", llm)
|
||||
|
||||
# Configuration personnalisable
|
||||
# Configurable parameters
|
||||
self.params = {
|
||||
"temperature": 0.2,
|
||||
"top_p": 0.8,
|
||||
@ -23,92 +25,103 @@ class AgentImageAnalyser(BaseAgent):
|
||||
|
||||
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")
|
||||
1. Objective Description
|
||||
Describe precisely what the image shows:
|
||||
- Software interface, menus, windows, tabs
|
||||
- Error messages, system messages, code or script
|
||||
- Software or module name/title if visible
|
||||
- Clearly distinguish the complete name of tests/modules (for example, "Methylene blue test" instead of simply "blue test")
|
||||
|
||||
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
|
||||
2. Key Technical Elements
|
||||
Identify:
|
||||
- Software versions or displayed modules
|
||||
- Visible error codes
|
||||
- Configurable parameters (text fields, sliders, dropdowns, checkboxes)
|
||||
- Values displayed or pre-filled in fields
|
||||
- Disabled, grayed out or hidden elements (often non-modifiable)
|
||||
- Active/inactive buttons
|
||||
- Reset or initialization buttons (often marked "RAZ" and not "PAZ")
|
||||
- Specify if colored elements are part of the standard interface (e.g., always red button) or if they seem to be related to the problem
|
||||
|
||||
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
|
||||
3. URLs and Links
|
||||
- Identify and explicitly copy ALL URLs visible in the image
|
||||
- Hyperlinks in blue or underlined text
|
||||
- API endpoints, server addresses
|
||||
- Format each URL on its own line for clarity: [URL] https://example.com
|
||||
- For masked/shortened URLs, clearly indicate what text is displayed
|
||||
|
||||
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
|
||||
4. Highlighted Elements
|
||||
- Look for circled, framed, highlighted or arrowed areas
|
||||
- These elements are often important for the client or support
|
||||
- Explicitly mention their content and highlighting style
|
||||
- Specifically check if error messages are visible at the bottom or top of the screen
|
||||
|
||||
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")
|
||||
5. Relationship with the Problem
|
||||
- Establish the link between visible elements and the problem described in the ticket
|
||||
- Indicate if components seem related to a misconfiguration or error
|
||||
- Specify the complete name of the module/test concerned by the problem (for example "Methylene blue test (MB)" and not just "blue test")
|
||||
- Identify if the user has access to the test screen but with errors, or if there is no access at all
|
||||
|
||||
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
|
||||
6. Potential Answers
|
||||
- Determine if the image provides elements of answer to a question asked in:
|
||||
- The ticket title
|
||||
- The problem description
|
||||
- Try to extrapolate the precise technical context by observing the interface (e.g., the "blue test" mentioned by the client clearly corresponds to "methylene blue test (MB) - NF EN 933-9")
|
||||
|
||||
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)
|
||||
7. Link with the Discussion
|
||||
- Check if the image echoes a step described in the discussion thread
|
||||
- Note correspondences (e.g., same module, same error message as previously mentioned)
|
||||
- Establish explicit connections between the vocabulary used by the client and what's visible in the interface
|
||||
|
||||
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)
|
||||
8. Broader Technical Context
|
||||
- Identify the wider context of the application (laboratory, technical tests, standardized tests)
|
||||
- Note any references to standards or norms (e.g., NF EN 933-9)
|
||||
- Mention any visible codes or identifiers that might be useful (e.g., sample numbers)
|
||||
|
||||
Important Rules:
|
||||
- Do NOT make ANY interpretation or diagnosis about possible causes
|
||||
- Do NOT propose solutions or recommendations
|
||||
- Remain strictly factual and objective, but make explicit links with terms used by the client
|
||||
- Focus only on what is visible in the image
|
||||
- Reproduce exact texts (e.g., error messages, parameter labels)
|
||||
- Pay special attention to modifiable (interactive) and non-modifiable (grayed out) elements
|
||||
- Systematically use the complete and precise name of modules and tests
|
||||
- Verify correct reading of buttons and menus (beware of confusions like PAZ/RAZ)
|
||||
- ALWAYS list URLs and links in a separate dedicated section
|
||||
"""
|
||||
)
|
||||
|
||||
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.
|
||||
You are an expert in image analysis for BRG-Lab technical support for CBAO company.
|
||||
Your mission is to analyze screenshots related to the support ticket context.
|
||||
|
||||
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.
|
||||
You must be extremely precise in your reading of interfaces and technical elements.
|
||||
Clients often use abbreviated terms (like "blue test") while the interface shows the full term ("Methylene blue test"). You must make the connection between these terms.
|
||||
|
||||
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
|
||||
Some elements in the interface may cause confusion:
|
||||
- "RAZ" buttons (reset) are sometimes difficult to read
|
||||
- Colored elements may be part of the standard interface (and not part of the problem)
|
||||
- Error messages are often at the bottom of the screen and contain crucial information
|
||||
- URLs and links must be explicitly captured and listed separately
|
||||
|
||||
Structure ton analyse d'image de façon factuelle:
|
||||
Structure your image analysis factually:
|
||||
{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.
|
||||
Your analysis will be used as a factual element for a more complete technical report and to link the client's vocabulary with the actual technical elements.
|
||||
|
||||
IMPORTANT: All responses should be in English. Translation to French will be handled separately.
|
||||
"""
|
||||
).format(
|
||||
instructions=self.instructions_analyse
|
||||
)
|
||||
|
||||
self._appliquer_config_locale()
|
||||
logger.info("AgentImageAnalyser initialisé")
|
||||
logger.info("AgentImageAnalyser initialized")
|
||||
|
||||
def _appliquer_config_locale(self) -> None:
|
||||
"""
|
||||
Applique la configuration locale au modèle LLM.
|
||||
Applies local configuration to the LLM model.
|
||||
"""
|
||||
if hasattr(self.llm, "prompt_system"):
|
||||
self.llm.prompt_system = self.system_prompt
|
||||
@ -118,7 +131,7 @@ class AgentImageAnalyser(BaseAgent):
|
||||
|
||||
def _verifier_image(self, image_path: str) -> bool:
|
||||
"""
|
||||
Vérifie si l'image existe et est accessible
|
||||
Checks if the image exists and is accessible
|
||||
"""
|
||||
try:
|
||||
if not os.path.exists(image_path) or not os.access(image_path, os.R_OK):
|
||||
@ -128,109 +141,185 @@ class AgentImageAnalyser(BaseAgent):
|
||||
width, height = img.size
|
||||
return width > 0 and height > 0
|
||||
except Exception as e:
|
||||
logger.error(f"Vérification impossible pour {image_path}: {e}")
|
||||
logger.error(f"Verification failed for {image_path}: {e}")
|
||||
return False
|
||||
|
||||
def _generer_prompt_analyse(self, contexte: str, prefix: str = "") -> str:
|
||||
def _extraire_urls(self, texte: str) -> List[str]:
|
||||
"""
|
||||
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}"""
|
||||
Extracts URLs from a text
|
||||
|
||||
def executer(self, image_path: str, contexte: str = "") -> Dict[str, Any]:
|
||||
Args:
|
||||
texte: The text to analyze
|
||||
|
||||
Returns:
|
||||
List of extracted URLs
|
||||
"""
|
||||
Analyse une image en tenant compte du contexte du ticket
|
||||
import re
|
||||
# Pattern to detect URLs (more complete than a simple http:// search)
|
||||
url_pattern = r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+'
|
||||
|
||||
# Search in the text with a broader pattern to capture context
|
||||
url_mentions = re.finditer(r'(?:URL|link|adresse|href|http)[^\n]*?(https?://[^\s\)\]\"\']+)', texte, re.IGNORECASE)
|
||||
|
||||
# List to store URLs with their context
|
||||
urls = []
|
||||
|
||||
# Add URLs extracted with the generic pattern
|
||||
for url in re.findall(url_pattern, texte):
|
||||
if url not in urls:
|
||||
urls.append(url)
|
||||
|
||||
# Add URLs extracted from the broader context
|
||||
for match in url_mentions:
|
||||
url = match.group(1)
|
||||
if url not in urls:
|
||||
urls.append(url)
|
||||
|
||||
return urls
|
||||
|
||||
def _construire_prompt(self, image_path: str, contexte: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Construit le prompt pour l'analyse d'image avec contexte.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser
|
||||
contexte: Contexte d'analyse du ticket
|
||||
|
||||
Returns:
|
||||
Prompt formaté avec instructions et contexte
|
||||
"""
|
||||
image_name = os.path.basename(image_path)
|
||||
print(f" AgentImageAnalyser: Analyse de {image_name}")
|
||||
|
||||
if not self._verifier_image(image_path):
|
||||
return self._erreur("Erreur d'accès ou image invalide", image_path)
|
||||
# Extraire le contexte du ticket (résumé en anglais)
|
||||
ticket_content_en = ""
|
||||
if isinstance(contexte, dict):
|
||||
if "response_en" in contexte:
|
||||
ticket_content_en = contexte["response_en"]
|
||||
elif "response" in contexte:
|
||||
ticket_content_en = contexte["response"]
|
||||
|
||||
# Extraire le texte OCR si disponible dans le contexte
|
||||
ocr_text = ""
|
||||
if isinstance(contexte, dict) and "ocr_text" in contexte:
|
||||
ocr_text = contexte["ocr_text"]
|
||||
elif isinstance(contexte, dict) and "ocr_info" in contexte:
|
||||
ocr_text = contexte["ocr_info"].get("texte_en", "")
|
||||
|
||||
# Construire le prompt avec instructions précises
|
||||
prompt = f"""[ENGLISH RESPONSE REQUESTED]
|
||||
|
||||
Analyze this image in the context of a technical support ticket.
|
||||
|
||||
IMAGE: {image_name}
|
||||
|
||||
"""
|
||||
|
||||
# Ajouter le texte OCR s'il est disponible
|
||||
if ocr_text and len(ocr_text.strip()) > 10:
|
||||
prompt += f"""OCR TEXT DETECTED IN IMAGE:
|
||||
{ocr_text}
|
||||
|
||||
"""
|
||||
|
||||
# Ajouter le contexte du ticket
|
||||
prompt += f"""SUPPORT TICKET CONTEXT:
|
||||
{ticket_content_en[:1500]}
|
||||
|
||||
INSTRUCTIONS:
|
||||
1. Describe what is shown in this image in detail
|
||||
2. Identify any error messages, technical information, or interface elements
|
||||
3. Explain how this image relates to the support ticket context provided
|
||||
4. Note any version numbers, status indicators, or dates visible
|
||||
5. Extract specific technical details that might help diagnose the issue
|
||||
|
||||
If the image contains text, code, or error messages, transcribe all important parts.
|
||||
Structure your analysis clearly with headers and bullet points.
|
||||
"""
|
||||
|
||||
return prompt
|
||||
|
||||
def executer(self, image_path: str, contexte: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Analyse une image dans le contexte d'un ticket de support.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser
|
||||
contexte: Contexte d'analyse du ticket (optionnel)
|
||||
|
||||
Returns:
|
||||
Dictionnaire contenant les résultats d'analyse
|
||||
"""
|
||||
image_name = os.path.basename(image_path)
|
||||
print(f" AgentImageAnalyser: Analyse de {image_name}")
|
||||
|
||||
if not image_path or not os.path.exists(image_path):
|
||||
return self._error_response(f"Image introuvable: {image_path}")
|
||||
|
||||
# Si pas de contexte, utiliser un objet vide
|
||||
if contexte is None:
|
||||
contexte = {}
|
||||
|
||||
# Extraire le ticket_id pour la sauvegarde
|
||||
ticket_id = self._extraire_ticket_id(image_path, contexte)
|
||||
|
||||
try:
|
||||
prompt = self._generer_prompt_analyse(contexte, "Analyse cette image en tenant compte du contexte suivant:")
|
||||
# Générer le prompt avec le contexte et les résultats OCR
|
||||
prompt = self._construire_prompt(image_path, contexte)
|
||||
|
||||
# Interroger le modèle avec l'image
|
||||
if hasattr(self.llm, "interroger_avec_image"):
|
||||
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)
|
||||
if img_base64:
|
||||
prompt_base64 = self._generer_prompt_analyse(contexte, f"Analyse cette image:\n{img_base64}")
|
||||
response = self.llm.interroger(prompt_base64)
|
||||
else:
|
||||
return self._erreur("Impossible d'encoder l'image en base64", image_path)
|
||||
response_en = self.llm.interroger_avec_image(image_path, prompt, english_only=True)
|
||||
else:
|
||||
return self._erreur("Le modèle ne supporte pas l'analyse d'images", image_path)
|
||||
return self._error_response("Le modèle ne supporte pas l'analyse d'images", ticket_id)
|
||||
|
||||
# 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"
|
||||
]
|
||||
|
||||
if any(phrase in response.lower() for phrase in error_phrases):
|
||||
return self._erreur("Le modèle n'a pas pu analyser l'image correctement", image_path, raw=response)
|
||||
|
||||
# Effectuer des corrections sur la réponse si nécessaire
|
||||
response = self._corriger_termes_courants(response)
|
||||
# Traduire les résultats en français pour la compatibilité
|
||||
response_fr = en_to_fr(response_en)
|
||||
|
||||
# Formater la réponse avec métadonnées
|
||||
result = {
|
||||
"analyse": response,
|
||||
"raw_response": response,
|
||||
"analyse": response_fr,
|
||||
"analyse_en": response_en,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"ticket_id": ticket_id,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"model_info": {
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
**getattr(self.llm, "params", {})
|
||||
},
|
||||
"ocr_used": "ocr_text" in contexte or "ocr_info" in contexte,
|
||||
"model": getattr(self.llm, "modele", "unknown"),
|
||||
"source_agent": self.nom
|
||||
}
|
||||
}
|
||||
|
||||
# Sauvegarder les données dans le fichier
|
||||
sauvegarder_donnees(None, "analyse_image", result, base_dir="reports", is_resultat=True)
|
||||
# Sauvegarder les résultats
|
||||
if ticket_id and ticket_id != "UNKNOWN":
|
||||
sauvegarder_donnees(ticket_id, "analyse_image",
|
||||
{"image_path": image_path, "analysis": result},
|
||||
is_resultat=True)
|
||||
|
||||
# Enregistrer l'analyse dans l'historique
|
||||
# Ajouter à l'historique
|
||||
self.ajouter_historique(
|
||||
"analyse_image",
|
||||
{"image_path": image_path, "contexte": contexte, "prompt": prompt},
|
||||
"analyse_image",
|
||||
{"image_path": image_path, "prompt": prompt},
|
||||
result
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return self._erreur(str(e), image_path)
|
||||
logger.error(f"Erreur lors de l'analyse de l'image {image_name}: {str(e)}")
|
||||
return self._error_response(f"Erreur: {str(e)}", ticket_id)
|
||||
|
||||
def _corriger_termes_courants(self, texte: str) -> str:
|
||||
"""
|
||||
Corrige certains termes couramment mal interprétés par le modèle.
|
||||
Corrects commonly misinterpreted terms by the model.
|
||||
"""
|
||||
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"
|
||||
"Essai au bleu": "Essai au bleu de méthylène",
|
||||
"Methylene blue test": "Essai au bleu de méthylène",
|
||||
"Blue test": "Essai au bleu de méthylène"
|
||||
}
|
||||
|
||||
for terme_incorrect, terme_correct in corrections.items():
|
||||
@ -240,10 +329,10 @@ Fournis une analyse STRICTEMENT FACTUELLE de l'image avec les sections suivantes
|
||||
|
||||
def _erreur(self, message: str, path: str, raw: str = "") -> Dict[str, Any]:
|
||||
"""
|
||||
Crée un dictionnaire de réponse en cas d'erreur
|
||||
Creates an error response dictionary
|
||||
"""
|
||||
return {
|
||||
"analyse": f"ERREUR: {message}",
|
||||
"analyse": f"ERROR: {message}",
|
||||
"raw_response": raw,
|
||||
"error": True,
|
||||
"metadata": {
|
||||
@ -256,6 +345,59 @@ Fournis une analyse STRICTEMENT FACTUELLE de l'image avec les sections suivantes
|
||||
}
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""Retourne un timestamp au format YYYYMMDD_HHMMSS"""
|
||||
"""Returns a timestamp in YYYYMMDD_HHMMSS format"""
|
||||
from datetime import datetime
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
def _extraire_ticket_id(self, image_path: str, contexte: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Extrait l'ID du ticket à partir du chemin de l'image ou du contexte.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image
|
||||
contexte: Contexte d'analyse du ticket
|
||||
|
||||
Returns:
|
||||
ID du ticket ou "UNKNOWN" si non trouvé
|
||||
"""
|
||||
# D'abord, chercher dans le contexte
|
||||
if isinstance(contexte, dict):
|
||||
if "metadata" in contexte and "ticket_id" in contexte["metadata"]:
|
||||
return contexte["metadata"]["ticket_id"]
|
||||
if "ticket_id" in contexte:
|
||||
return contexte["ticket_id"]
|
||||
|
||||
# Ensuite, chercher dans le chemin de l'image
|
||||
parts = image_path.split(os.path.sep)
|
||||
for part in parts:
|
||||
# Format T12345
|
||||
if part.startswith("T") and part[1:].isdigit():
|
||||
return part
|
||||
# Format ticket_T12345
|
||||
if part.startswith("ticket_T"):
|
||||
return part.replace("ticket_", "")
|
||||
|
||||
return "UNKNOWN"
|
||||
|
||||
def _error_response(self, message: str, ticket_id: str = "UNKNOWN") -> Dict[str, Any]:
|
||||
"""
|
||||
Crée une réponse d'erreur standardisée.
|
||||
|
||||
Args:
|
||||
message: Message d'erreur
|
||||
ticket_id: ID du ticket
|
||||
|
||||
Returns:
|
||||
Dictionnaire avec la réponse d'erreur formatée
|
||||
"""
|
||||
return {
|
||||
"analyse": f"ERREUR: {message}",
|
||||
"analyse_en": f"ERROR: {message}",
|
||||
"error": True,
|
||||
"metadata": {
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True,
|
||||
"ticket_id": ticket_id,
|
||||
"source_agent": self.nom
|
||||
}
|
||||
}
|
||||
@ -1,157 +1,181 @@
|
||||
import logging
|
||||
import os
|
||||
from PIL import Image
|
||||
from typing import Dict, Any, Tuple
|
||||
from typing import Dict, Any, Tuple, List
|
||||
from datetime import datetime
|
||||
|
||||
from ..base_agent import BaseAgent
|
||||
from ..utils.pipeline_logger import sauvegarder_donnees
|
||||
from utils.ocr_utils import extraire_texte_fr
|
||||
from utils.translate_utils import fr_to_en, en_to_fr, sauvegarder_ocr_traduction
|
||||
|
||||
logger = logging.getLogger("AgentImageSorter")
|
||||
|
||||
class AgentImageSorter(BaseAgent):
|
||||
"""
|
||||
Agent de tri d'image optimisé pour llama_vision.
|
||||
Réalise un OCR en français, le traduit en anglais, génère un prompt enrichi, et analyse l'image.
|
||||
Détermine si une image est pertinente pour l'analyse d'un ticket de support.
|
||||
"""
|
||||
|
||||
def __init__(self, llm):
|
||||
super().__init__("AgentImageSorter", llm)
|
||||
|
||||
self.params = {
|
||||
"temperature": 0.2,
|
||||
"temperature": 0.1,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 300
|
||||
"max_tokens": 1000
|
||||
}
|
||||
|
||||
# Collecteur de résultats pour traitement par lots
|
||||
self.resultats = []
|
||||
|
||||
# Amélioration du prompt système pour obtenir une réponse plus précise
|
||||
self.system_prompt = """You are an expert image evaluator for technical support tickets.
|
||||
|
||||
Your task is to determine if an image is relevant to a technical support issue for BRG_Lab software (CBAO enterprise).
|
||||
|
||||
Follow these guidelines when analyzing images:
|
||||
1. Technical screenshots (interfaces, error messages, logs) are RELEVANT
|
||||
2. System configuration pages are RELEVANT
|
||||
3. Personal photos, generic web pages, marketing materials are NOT RELEVANT
|
||||
4. Corrupted or unreadable images are NOT RELEVANT
|
||||
|
||||
Respond with only 'Yes' or 'No' at the beginning, followed by a brief explanation.
|
||||
DO NOT attempt to perform OCR or analyze text - focus only on the image format and content type."""
|
||||
|
||||
self._appliquer_config_locale()
|
||||
logger.info("AgentImageSorter (llama_vision) initialisé")
|
||||
|
||||
def _appliquer_config_locale(self) -> None:
|
||||
"""Applique la configuration locale au modèle."""
|
||||
if hasattr(self.llm, "prompt_system"):
|
||||
self.llm.prompt_system = self.system_prompt
|
||||
if hasattr(self.llm, "configurer"):
|
||||
self.llm.configurer(**self.params)
|
||||
|
||||
def _verifier_image(self, image_path: str) -> bool:
|
||||
"""
|
||||
Vérifie si l'image existe, est accessible et a un format valide.
|
||||
Ne réalise aucun prétraitement.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image
|
||||
|
||||
Returns:
|
||||
True si l'image est valide, False sinon
|
||||
"""
|
||||
try:
|
||||
# Vérifier que le fichier existe et est accessible
|
||||
if not os.path.exists(image_path) or not os.access(image_path, os.R_OK):
|
||||
logger.warning(f"Image inaccessible: {image_path}")
|
||||
return False
|
||||
|
||||
# Vérifier que c'est une image valide avec PIL
|
||||
with Image.open(image_path) as img:
|
||||
width, height = img.size
|
||||
return width > 0 and height > 0
|
||||
if width < 10 or height < 10:
|
||||
logger.warning(f"Image trop petite: {image_path} ({width}x{height})")
|
||||
return False
|
||||
|
||||
# Vérifier le format
|
||||
format = img.format.lower() if img.format else "unknown"
|
||||
valid_formats = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp']
|
||||
|
||||
if format.lower() not in valid_formats:
|
||||
logger.warning(f"Format d'image non supporté: {image_path} ({format})")
|
||||
return False
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Vérification impossible pour {image_path}: {e}")
|
||||
logger.error(f"Erreur lors de la vérification de l'image {image_path}: {e}")
|
||||
return False
|
||||
|
||||
def _generer_prompt(self, ocr_fr: str, ocr_en: str) -> str:
|
||||
return (
|
||||
"[ENGLISH RESPONSE REQUESTED]\n\n"
|
||||
"The following image is from a technical support ticket at CBAO "
|
||||
"for the BRG_Lab software system.\n\n"
|
||||
f"OCR detected French text:\n[FR] {ocr_fr or '—'}\n[EN] {ocr_en or '—'}\n\n"
|
||||
"Please analyze the image and determine:\n"
|
||||
"- Is it relevant for a technical support issue?\n"
|
||||
"- Answer only 'yes' or 'no', then briefly explain why in English."
|
||||
def _extraire_ticket_id_depuis_path(self, image_path: str) -> str:
|
||||
"""Extrait l'ID du ticket depuis le chemin de l'image."""
|
||||
parts = image_path.split(os.sep)
|
||||
for part in parts:
|
||||
if part.startswith("T") and part[1:].isdigit():
|
||||
return part
|
||||
if part.startswith("ticket_T") and part[8:].isdigit():
|
||||
return "T" + part[8:]
|
||||
return "UNKNOWN"
|
||||
|
||||
def _generer_prompt(self, ocr_text: str = "") -> str:
|
||||
"""Génère le prompt pour le LLM."""
|
||||
prompt = (
|
||||
"Is this image relevant for a technical support issue related to the BRG_Lab software from CBAO enterprise?\n\n"
|
||||
"Technical screenshots, error messages, configuration screens, and system logs ARE relevant.\n"
|
||||
"Personal photos, generic web pages, corrupted images, and marketing materials are NOT relevant.\n\n"
|
||||
)
|
||||
|
||||
# Ajouter le texte OCR au prompt si disponible
|
||||
if ocr_text and len(ocr_text.strip()) > 10:
|
||||
prompt += f"Text detected in the image (OCR):\n\"{ocr_text}\"\n\n"
|
||||
|
||||
prompt += "Answer only with 'Yes' or 'No' at the beginning, followed by a brief explanation."
|
||||
return prompt
|
||||
|
||||
def _analyser_reponse(self, response: str) -> Tuple[bool, str]:
|
||||
r = response.lower()
|
||||
first_line = r.split('\n')[0] if '\n' in r else r.strip()[:50]
|
||||
"""Analyse la réponse du LLM pour déterminer la pertinence de l'image."""
|
||||
response_lower = response.lower()
|
||||
|
||||
# Analyse simplifiée basée sur la première ligne
|
||||
first_line = response_lower.split('\n')[0] if '\n' in response_lower else response_lower.strip()[:50]
|
||||
|
||||
# Détection pour réponses en anglais
|
||||
if first_line.startswith("yes"):
|
||||
return True, response.strip()
|
||||
if first_line.startswith("no"):
|
||||
return False, response.strip()
|
||||
|
||||
# Fallback: analyse basée sur les mots-clés
|
||||
positive_count = sum(1 for word in ["relevant", "useful", "error message", "interface", "technical", "system"] if word in response_lower)
|
||||
negative_count = sum(1 for word in ["not relevant", "irrelevant", "unrelated", "personal", "marketing", "corrupted"] if word in response_lower)
|
||||
|
||||
return positive_count > negative_count, response.strip()
|
||||
|
||||
def sauvegarder_resultats(self) -> None:
|
||||
"""Sauvegarde tous les résultats collectés."""
|
||||
logger.info(f"Sauvegarde de {len(self.resultats)} résultats de tri d'images")
|
||||
for resultat in self.resultats:
|
||||
ticket_id = resultat.get("metadata", {}).get("ticket_id", "UNKNOWN")
|
||||
image_path = resultat.get("metadata", {}).get("image_path", "")
|
||||
|
||||
# Détection pour réponses en français (fallback)
|
||||
if first_line.startswith("oui"):
|
||||
return True, response.strip()
|
||||
if first_line.startswith("non"):
|
||||
return False, response.strip()
|
||||
if not image_path or not os.path.exists(image_path):
|
||||
continue
|
||||
|
||||
# Sauvegarde dans la structure de répertoires appropriée
|
||||
try:
|
||||
sauvegarder_donnees(ticket_id, "tri_image", resultat, base_dir="reports", is_resultat=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la sauvegarde des résultats pour {image_path}: {e}")
|
||||
|
||||
# Réinitialiser la liste après sauvegarde
|
||||
self.resultats = []
|
||||
|
||||
# Analyse basée sur mots-clés (anglais et français)
|
||||
pos_keywords = ["pertinent", "utile", "interface", "message", "diagnostic",
|
||||
"useful", "relevant", "interface", "message", "diagnostic", "helpful"]
|
||||
neg_keywords = ["inutile", "photo", "irrelevant", "hors sujet",
|
||||
"useless", "irrelevant", "unrelated", "not relevant"]
|
||||
score = sum(kw in r for kw in pos_keywords) - sum(kw in r for kw in neg_keywords)
|
||||
return score > 0, response.strip()
|
||||
|
||||
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:
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
def _extraire_ticket_id_depuis_path(self, image_path: str) -> str:
|
||||
def executer(self, image_path: str, ocr_context: str = "") -> Dict[str, Any]:
|
||||
"""
|
||||
Extrait l'ID du ticket depuis le chemin de l'image.
|
||||
Cherche d'abord dans les segments du chemin, puis examine la structure de dossiers.
|
||||
Supporte les formats comme:
|
||||
- output/ticket_T12345/T12345_date/...
|
||||
- .../T12345/...
|
||||
Filtre une image pour déterminer si elle est pertinente pour un ticket de support.
|
||||
Ne fait pas d'OCR, se concentre uniquement sur la pertinence visuelle.
|
||||
|
||||
Args:
|
||||
image_path: Chemin de l'image
|
||||
image_path: Chemin vers l'image à analyser
|
||||
ocr_context: Texte extrait de l'image par OCR (optionnel)
|
||||
|
||||
Returns:
|
||||
ID du ticket ou "unknown_ticket" si non trouvé
|
||||
Dictionnaire contenant les résultats d'analyse
|
||||
"""
|
||||
# Normaliser le chemin pour éviter les problèmes de séparateurs
|
||||
norm_path = os.path.normpath(image_path)
|
||||
parts = norm_path.split(os.sep)
|
||||
|
||||
# Première passe: chercher directement un segment "Txxxx"
|
||||
for part in parts:
|
||||
if part.startswith("T") and part[1:].isdigit():
|
||||
return part
|
||||
|
||||
# Deuxième passe: chercher dans la structure de dossiers output/ticket_Txxxx/
|
||||
for i, part in enumerate(parts):
|
||||
if part == "output" and i+1 < len(parts):
|
||||
next_part = parts[i+1]
|
||||
if next_part.startswith("ticket_T"):
|
||||
return next_part.replace("ticket_", "")
|
||||
|
||||
# Pas d'ID de ticket trouvé
|
||||
logger.warning(f"Impossible d'extraire l'ID de ticket depuis le chemin: {image_path}")
|
||||
return "unknown_ticket"
|
||||
|
||||
def executer(self, image_path: str) -> Dict[str, Any]:
|
||||
image_name = os.path.basename(image_path)
|
||||
print(f" AgentImageSorter: Évaluation de {image_name}")
|
||||
|
||||
if not self._verifier_image(image_path):
|
||||
return self._erreur("Erreur d'accès ou image invalide", image_path)
|
||||
return self._erreur("Erreur d'accès ou format d'image invalide", image_path)
|
||||
|
||||
try:
|
||||
# Pré-traitement OCR et traduction
|
||||
# Extraction du ticket ID pour la sauvegarde
|
||||
ticket_id = self._extraire_ticket_id_depuis_path(image_path)
|
||||
ocr_fr = extraire_texte_fr(image_path)
|
||||
ocr_en = fr_to_en(ocr_fr)
|
||||
ocr_en_back_fr = en_to_fr(ocr_en) if ocr_en else ""
|
||||
|
||||
# Prompt pour LlamaVision (avec contexte OCR si disponible)
|
||||
prompt = self._generer_prompt(ocr_context)
|
||||
|
||||
# Sauvegarde OCR + Traductions
|
||||
sauvegarder_ocr_traduction(image_path, ticket_id, ocr_fr, ocr_en, ocr_en_back_fr, base_dir=None)
|
||||
|
||||
# Prompt en anglais enrichi
|
||||
prompt = self._generer_prompt(ocr_fr, ocr_en)
|
||||
|
||||
# Appel au LLM
|
||||
# Utilisation directe de la vision pour l'analyse d'image
|
||||
if hasattr(self.llm, "interroger_avec_image"):
|
||||
response = self.llm.interroger_avec_image(image_path, prompt)
|
||||
else:
|
||||
@ -164,27 +188,63 @@ class AgentImageSorter(BaseAgent):
|
||||
is_relevant, reason = self._analyser_reponse(response)
|
||||
print(f" Décision: Image {image_name} {'pertinente' if is_relevant else 'non pertinente'}")
|
||||
|
||||
# Construction du résultat
|
||||
result = {
|
||||
"is_relevant": is_relevant,
|
||||
"reason": reason,
|
||||
"raw_response": response,
|
||||
"ocr_fr": ocr_fr,
|
||||
"ocr_en": ocr_en,
|
||||
"ocr_used": bool(ocr_context and len(ocr_context.strip()) > 10),
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"model_info": {
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
**getattr(self.llm, "params", {})
|
||||
**self.params
|
||||
},
|
||||
"ticket_id": ticket_id,
|
||||
"source_agent": self.nom
|
||||
}
|
||||
}
|
||||
|
||||
sauvegarder_donnees(ticket_id, "tri_image", result, base_dir=None, is_resultat=True)
|
||||
self.ajouter_historique("tri_image", {"image_path": image_path, "prompt": prompt}, result)
|
||||
|
||||
# Ajouter au collecteur de résultats
|
||||
self.resultats.append(result)
|
||||
|
||||
# Ajouter à l'historique
|
||||
self.ajouter_historique(
|
||||
"tri_image",
|
||||
{"image_path": image_path, "prompt": prompt, "ocr_context": ocr_context},
|
||||
result
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return self._erreur(str(e), image_path)
|
||||
return self._erreur(f"Erreur inattendue: {str(e)}", image_path)
|
||||
|
||||
def _erreur(self, message: str, path: str, raw: str = "") -> Dict[str, Any]:
|
||||
"""Génère un rapport d'erreur standardisé."""
|
||||
ticket_id = self._extraire_ticket_id_depuis_path(path)
|
||||
result = {
|
||||
"is_relevant": False, # Par défaut, les images avec erreur ne sont pas considérées pertinentes
|
||||
"reason": f"ERROR: {message}",
|
||||
"raw_response": raw,
|
||||
"error": True,
|
||||
"metadata": {
|
||||
"image_path": path,
|
||||
"image_name": os.path.basename(path),
|
||||
"ticket_id": ticket_id,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True,
|
||||
"source_agent": self.nom
|
||||
}
|
||||
}
|
||||
|
||||
# Ajouter au collecteur de résultats même en cas d'erreur
|
||||
self.resultats.append(result)
|
||||
|
||||
return result
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""Génère un timestamp au format YYYYMMDD_HHMMSS."""
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
@ -6,10 +6,11 @@ import json
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from ..utils.pipeline_logger import sauvegarder_donnees
|
||||
from utils.translate_utils import fr_to_en, en_to_fr
|
||||
|
||||
# Configuration du logger pour plus de détails
|
||||
# Configuration for detailed logging
|
||||
logger = logging.getLogger("AgentReportGenerator")
|
||||
logger.setLevel(logging.DEBUG) # Augmenter le niveau de logging pour le débogage
|
||||
logger.setLevel(logging.DEBUG) # Increase logging level for debugging
|
||||
|
||||
class AgentReportGenerator(BaseAgent):
|
||||
def __init__(self, llm):
|
||||
@ -21,7 +22,8 @@ class AgentReportGenerator(BaseAgent):
|
||||
"max_tokens": 8000
|
||||
}
|
||||
|
||||
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.
|
||||
# System prompt in French (preserved for French-speaking models)
|
||||
self.system_prompt_fr = """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
|
||||
@ -54,79 +56,145 @@ Règles pour le tableau d'échanges :
|
||||
|
||||
Reste strictement factuel. Ne fais aucune hypothèse. Ne suggère pas d'étapes ni d'interprétation."""
|
||||
|
||||
# System prompt in English for LlamaVision
|
||||
self.system_prompt_en = """You are a technical support expert responsible for generating a final report from the analyses of a support ticket.
|
||||
Your role is to cross-reference information from:
|
||||
- textual analysis of the customer ticket
|
||||
- detailed analyses of multiple screenshots
|
||||
|
||||
You must structure your response in a clear question/answer format, keeping all important points.
|
||||
|
||||
Never propose a solution. Do not reformulate the context.
|
||||
Your only mission is to cross-reference textual and visual data to draw clear observations, listing factual elements visible in the screenshots that support or complement the ticket text.
|
||||
|
||||
Expected report structure:
|
||||
1. General context (textual ticket summary in one sentence)
|
||||
2. Identified problems or questions (in the form of clear questions)
|
||||
3. Cross-referenced image/text summary for each question
|
||||
4. List of additional relevant observations (if applicable)
|
||||
5. Chronological exchange table
|
||||
- Include a structured table of exchanges between client and support
|
||||
- Format: Sender | Type | Date | Content | Relevant visual elements
|
||||
- Do not mention real names, use "CLIENT" and "SUPPORT"
|
||||
- Synthesize content while preserving important information
|
||||
- Preserve additional information provided by images (example: client: blue test, image analysis: Methylene blue test (MB) - NF EN 933-9 (02-2022))
|
||||
- Preserve useful links (documentation, FAQ, manual, links to web pages, etc.)
|
||||
- Associate visual elements from screenshots with corresponding exchanges
|
||||
|
||||
Rules for the exchange table:
|
||||
- TYPE can be: question, answer, information, visual complement
|
||||
- For each client exchange mentioning a problem, add visual elements from screenshots that contextualize the problem
|
||||
- For each support response, add visual elements that confirm or contradict the response
|
||||
- Do not invent any content or date
|
||||
- Use factual data from images to enrich understanding of exchanges
|
||||
|
||||
Stay strictly factual. Make no assumptions. Do not suggest steps or interpretation."""
|
||||
|
||||
self._appliquer_config_locale()
|
||||
logger.info("AgentReportGenerator initialisé")
|
||||
logger.info("AgentReportGenerator initialized")
|
||||
|
||||
def _appliquer_config_locale(self) -> None:
|
||||
"""
|
||||
Applies local configuration based on the model used (LlamaVision or other)
|
||||
"""
|
||||
if hasattr(self.llm, "prompt_system"):
|
||||
self.llm.prompt_system = self.system_prompt
|
||||
model_name = getattr(self.llm, "modele", "").lower()
|
||||
# Use English prompt for LlamaVision
|
||||
if "llama" in model_name or "vision" in model_name:
|
||||
self.llm.prompt_system = self.system_prompt_en
|
||||
logger.info("LlamaVision mode detected: using English system prompt")
|
||||
else:
|
||||
self.llm.prompt_system = self.system_prompt_fr
|
||||
logger.info("Using French system prompt")
|
||||
|
||||
if hasattr(self.llm, "configurer"):
|
||||
self.llm.configurer(**self.params)
|
||||
|
||||
def _verifier_donnees_entree(self, rapport_data: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Vérifie que les données d'entrée contiennent les éléments nécessaires.
|
||||
Verifies that the input data contains the necessary elements.
|
||||
|
||||
Args:
|
||||
rapport_data: Données pour générer le rapport
|
||||
rapport_data: Data for generating the report
|
||||
|
||||
Returns:
|
||||
bool: True si les données sont valides, False sinon
|
||||
bool: True if data is valid, False otherwise
|
||||
"""
|
||||
ticket_id = rapport_data.get("ticket_id")
|
||||
if not ticket_id:
|
||||
logger.error("Erreur de validation: ticket_id manquant")
|
||||
logger.error("Validation error: missing ticket_id")
|
||||
return False
|
||||
|
||||
ticket_analyse = rapport_data.get("ticket_analyse")
|
||||
if not ticket_analyse:
|
||||
logger.error(f"Erreur de validation pour {ticket_id}: analyse de ticket manquante")
|
||||
logger.error(f"Validation error for {ticket_id}: missing ticket analysis")
|
||||
return False
|
||||
|
||||
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
|
||||
logger.warning(f"Warning for {ticket_id}: no image analysis available")
|
||||
# Continue anyway because we can generate a report without images
|
||||
|
||||
# Vérifier si au moins une image a été analysée
|
||||
# Check if at least one image has been analyzed
|
||||
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
|
||||
|
||||
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")
|
||||
logger.warning(f"Warning for {ticket_id}: {len(analyses_images)} images found but none analyzed")
|
||||
|
||||
logger.info(f"Validation pour {ticket_id}: OK, {images_analysees} images analysées sur {len(analyses_images)} images")
|
||||
logger.info(f"Validation for {ticket_id}: OK, {images_analysees} images analyzed out of {len(analyses_images)} images")
|
||||
return True
|
||||
|
||||
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}")
|
||||
ticket_id = rapport_data.get("ticket_id", "Unknown")
|
||||
print(f"AgentReportGenerator: generating report for ticket {ticket_id}")
|
||||
|
||||
try:
|
||||
# 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]}...")
|
||||
# Check and log input data for debugging
|
||||
logger.debug(f"Data received for {ticket_id}: {json.dumps(rapport_data, default=str)[:500]}...")
|
||||
|
||||
# Vérifier que les données d'entrée sont valides
|
||||
# Verify that input data is valid
|
||||
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}"
|
||||
error_msg = f"Unable to generate report: invalid input data for {ticket_id}"
|
||||
print(f"ERROR: {error_msg}")
|
||||
return f"ERROR: {error_msg}"
|
||||
|
||||
print(f"Préparation du prompt pour le ticket {ticket_id}...")
|
||||
print(f"Preparing prompt for ticket {ticket_id}...")
|
||||
prompt = self._generer_prompt(rapport_data)
|
||||
logger.debug(f"Prompt généré ({len(prompt)} caractères): {prompt[:500]}...")
|
||||
logger.debug(f"Generated prompt ({len(prompt)} characters): {prompt[:500]}...")
|
||||
|
||||
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]}...")
|
||||
# Determine if the model is LlamaVision and translate if necessary
|
||||
model_name = getattr(self.llm, "modele", "").lower()
|
||||
need_translation = "llama" in model_name or "vision" in model_name
|
||||
|
||||
if need_translation:
|
||||
# Add explicit marker and translate prompt
|
||||
prompt_en = f"[ENGLISH RESPONSE REQUESTED]\n\n{fr_to_en(prompt)}"
|
||||
logger.info(f"Translating prompt to English for LlamaVision ({len(prompt_en)} characters)")
|
||||
else:
|
||||
prompt_en = prompt
|
||||
|
||||
print(f"Analysis in progress for ticket {ticket_id}...")
|
||||
response = self.llm.interroger(prompt_en)
|
||||
|
||||
# Translate response to French if necessary
|
||||
if need_translation:
|
||||
response_fr = en_to_fr(response)
|
||||
logger.info(f"Translating response to French ({len(response_fr)} characters)")
|
||||
else:
|
||||
response_fr = response
|
||||
|
||||
print(f"Analysis completed: {len(response_fr)} characters")
|
||||
logger.debug(f"Response received ({len(response_fr)} characters): {response_fr[:500]}...")
|
||||
|
||||
# Création du résultat complet avec métadonnées
|
||||
# Create complete result with metadata
|
||||
result = {
|
||||
"prompt": prompt,
|
||||
"response": response,
|
||||
"prompt_en": prompt_en if need_translation else None,
|
||||
"response": response_fr, # French version for compatibility
|
||||
"response_en": response if need_translation else None, # Original English version
|
||||
"metadata": {
|
||||
"ticket_id": ticket_id,
|
||||
"timestamp": self._get_timestamp(),
|
||||
@ -134,40 +202,48 @@ Reste strictement factuel. Ne fais aucune hypothèse. Ne suggère pas d'étapes
|
||||
"model_info": {
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
**getattr(self.llm, "params", {})
|
||||
}
|
||||
},
|
||||
"language": "en-fr" if need_translation else "fr" # Indicates the language used
|
||||
}
|
||||
}
|
||||
|
||||
# Sauvegarder le résultat dans le pipeline
|
||||
logger.info(f"Sauvegarde du rapport final pour le ticket {ticket_id}")
|
||||
# Save the result in the pipeline
|
||||
logger.info(f"Saving final report for ticket {ticket_id}")
|
||||
|
||||
# Créer un fichier de débogage direct pour vérifier le problème de sauvegarde
|
||||
# Create direct debug file to check for save issues
|
||||
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}")
|
||||
print(f"Debug file created: {debug_path}")
|
||||
|
||||
# If we translated, also save EN/FR versions for debugging
|
||||
if need_translation:
|
||||
with open(os.path.join(debug_dir, f"rapport_debug_{ticket_id}_en.txt"), "w", encoding="utf-8") as f:
|
||||
f.write(response)
|
||||
with open(os.path.join(debug_dir, f"rapport_debug_{ticket_id}_fr.txt"), "w", encoding="utf-8") as f:
|
||||
f.write(response_fr)
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la création du fichier de débogage: {str(e)}")
|
||||
print(f"Error creating debug file: {str(e)}")
|
||||
|
||||
# Utiliser uniquement la fonction standard de sauvegarde
|
||||
# Use only the standard save function
|
||||
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}")
|
||||
print(f"Final report generated and saved for 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.error(f"Error when saving via sauvegarder_donnees: {str(e)}")
|
||||
print(f"Standard save error: {str(e)}")
|
||||
|
||||
# Sauvegarder aussi une version en texte brut pour faciliter la lecture
|
||||
# Also save a plain text version for easier reading
|
||||
try:
|
||||
# Trouver le chemin de ticket le plus récent
|
||||
# Find the most recent ticket path
|
||||
output_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../output"))
|
||||
ticket_dir = os.path.join(output_dir, f"ticket_{ticket_id}")
|
||||
|
||||
if os.path.exists(ticket_dir):
|
||||
# Trouver l'extraction la plus récente
|
||||
# Find the most recent extraction
|
||||
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)
|
||||
@ -175,91 +251,128 @@ Reste strictement factuel. Ne fais aucune hypothèse. Ne suggère pas d'étapes
|
||||
rapports_dir = os.path.join(ticket_dir, latest_extraction, f"{ticket_id}_rapports")
|
||||
pipeline_dir = os.path.join(rapports_dir, "pipeline")
|
||||
|
||||
# S'assurer que le répertoire existe
|
||||
# Ensure directory exists
|
||||
os.makedirs(pipeline_dir, exist_ok=True)
|
||||
|
||||
# Récupérer le nom du modèle
|
||||
# Get model name
|
||||
model_name = getattr(self.llm, "modele", "unknown")
|
||||
|
||||
# Sauvegarder en format texte uniquement avec le nom du modèle
|
||||
# Save in text format with the model name
|
||||
rapport_txt_path = os.path.join(pipeline_dir, f"rapport_final_{model_name}_results.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}")
|
||||
f.write(response_fr) # French version
|
||||
print(f"Text version saved in: {rapport_txt_path}")
|
||||
|
||||
# If we translated, also save the English version
|
||||
if need_translation:
|
||||
rapport_txt_en_path = os.path.join(pipeline_dir, f"rapport_final_{model_name}_results_en.txt")
|
||||
with open(rapport_txt_en_path, "w", encoding="utf-8") as f:
|
||||
f.write(f"ANALYSIS REPORT FOR TICKET {ticket_id}\n")
|
||||
f.write("="*50 + "\n\n")
|
||||
f.write(response) # English version
|
||||
print(f"English text version saved in: {rapport_txt_en_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)}")
|
||||
print(f"Error saving text version: {str(e)}")
|
||||
logger.error(f"Text save error: {str(e)}")
|
||||
|
||||
# Sauvegarder aussi dans le dossier reports pour compatibilité
|
||||
# Also save in the reports folder for compatibility
|
||||
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)
|
||||
|
||||
# Récupérer le nom du modèle
|
||||
# Get model name
|
||||
model_name = getattr(self.llm, "modele", "unknown")
|
||||
|
||||
# Sauvegarder aussi en texte pour faciliter la lecture
|
||||
# Also save in text for easier reading
|
||||
rapport_txt_path = os.path.join(ticket_reports_dir, f"rapport_final_{ticket_id}_{model_name}.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)
|
||||
f.write(response_fr) # French version
|
||||
|
||||
logger.info(f"Rapport texte sauvegardé dans {rapport_txt_path}")
|
||||
print(f"Rapport également sauvegardé en texte dans {ticket_reports_dir}")
|
||||
# If we translated, also save the English version
|
||||
if need_translation:
|
||||
rapport_txt_en_path = os.path.join(ticket_reports_dir, f"rapport_final_{ticket_id}_{model_name}_en.txt")
|
||||
with open(rapport_txt_en_path, "w", encoding="utf-8") as f:
|
||||
f.write(f"ANALYSIS REPORT FOR TICKET {ticket_id}\n")
|
||||
f.write("="*50 + "\n\n")
|
||||
f.write(response) # English version
|
||||
|
||||
logger.info(f"Text report saved in {rapport_txt_path}")
|
||||
print(f"Report also saved as text in {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)}")
|
||||
logger.warning(f"Unable to save text report in reports/: {str(e)}")
|
||||
print(f"Error saving to reports/: {str(e)}")
|
||||
|
||||
# Ajouter à l'historique
|
||||
# Add to history
|
||||
self.ajouter_historique("rapport_final", {
|
||||
"ticket_id": ticket_id,
|
||||
"prompt": prompt,
|
||||
"timestamp": self._get_timestamp()
|
||||
}, response)
|
||||
}, response_fr) # French version for history
|
||||
|
||||
print(f"Traitement du rapport terminé pour le ticket {ticket_id}")
|
||||
return response
|
||||
print(f"Report processing completed for ticket {ticket_id}")
|
||||
return response_fr # Return French version for pipeline consistency
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la génération du rapport : {str(e)}")
|
||||
logger.error(f"Error generating report: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
print(f"ERREUR CRITIQUE lors de la génération du rapport: {str(e)}")
|
||||
return f"ERREUR: {str(e)}"
|
||||
print(f"CRITICAL ERROR during report generation: {str(e)}")
|
||||
return f"ERROR: {str(e)}"
|
||||
|
||||
|
||||
def _generer_prompt(self, rapport_data: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Generates the prompt for the report generator
|
||||
|
||||
Args:
|
||||
rapport_data: Report data containing ticket analysis and image analyses
|
||||
|
||||
Returns:
|
||||
str: Generated prompt
|
||||
"""
|
||||
ticket_text = rapport_data.get("ticket_analyse", "")
|
||||
image_blocs = []
|
||||
analyses_images = rapport_data.get("analyse_images", {})
|
||||
|
||||
# Ajouter des logs pour vérifier les données d'images
|
||||
logger.info(f"Nombre d'images à analyser: {len(analyses_images)}")
|
||||
# Add logs to check image data
|
||||
logger.info(f"Number of images to analyze: {len(analyses_images)}")
|
||||
|
||||
for chemin_image, analyse_obj in analyses_images.items():
|
||||
# Vérifier si l'image est pertinente
|
||||
# Check if the image is relevant
|
||||
is_relevant = analyse_obj.get("sorting", {}).get("is_relevant", False)
|
||||
|
||||
# Récupérer l'analyse si elle existe
|
||||
# Get analysis if it exists
|
||||
analyse = ""
|
||||
if "analysis" in analyse_obj and analyse_obj["analysis"]:
|
||||
analyse = analyse_obj["analysis"].get("analyse", "")
|
||||
# Check if there is an English or French version of the analysis
|
||||
if "analyse_en" in analyse_obj["analysis"]:
|
||||
# Use directly the English version if the model is LlamaVision
|
||||
model_name = getattr(self.llm, "modele", "").lower()
|
||||
if "llama" in model_name or "vision" in model_name:
|
||||
analyse = analyse_obj["analysis"].get("analyse_en", "")
|
||||
else:
|
||||
analyse = analyse_obj["analysis"].get("analyse", "")
|
||||
else:
|
||||
# Use standard analysis if no language versions
|
||||
analyse = analyse_obj["analysis"].get("analyse", "")
|
||||
|
||||
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)")
|
||||
logger.info(f"Adding analysis of image {os.path.basename(chemin_image)} ({len(analyse)} characters)")
|
||||
else:
|
||||
logger.warning(f"Image {os.path.basename(chemin_image)} sans analyse")
|
||||
logger.warning(f"Image {os.path.basename(chemin_image)} without analysis")
|
||||
|
||||
bloc_images = "\n".join(image_blocs)
|
||||
|
||||
# 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")
|
||||
# Log to check data size
|
||||
logger.info(f"Size of ticket analysis: {len(ticket_text)} characters")
|
||||
logger.info(f"Size of image block: {len(bloc_images)} characters")
|
||||
|
||||
# Keep the prompt in French as output will be in French or translated from English
|
||||
prompt = (
|
||||
f"Voici les données d'analyse pour un ticket de support :\n\n"
|
||||
f"=== ANALYSE DU TICKET ===\n{ticket_text}\n\n"
|
||||
@ -276,5 +389,6 @@ Reste strictement factuel. Ne fais aucune hypothèse. Ne suggère pas d'étapes
|
||||
return prompt
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""Returns a timestamp in YYYYMMDD_HHMMSS format"""
|
||||
from datetime import datetime
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
from ..base_agent import BaseAgent
|
||||
from typing import Dict, Any
|
||||
from typing import Dict, Any, List, Union
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from loaders.ticket_data_loader import TicketDataLoader
|
||||
from ..utils.pipeline_logger import sauvegarder_donnees
|
||||
from utils.translate_utils import fr_to_en, en_to_fr
|
||||
import re
|
||||
|
||||
logger = logging.getLogger("AgentTicketAnalyser")
|
||||
|
||||
@ -19,177 +21,301 @@ class AgentTicketAnalyser(BaseAgent):
|
||||
"max_tokens": 4000
|
||||
}
|
||||
|
||||
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.
|
||||
# Instructions principales
|
||||
self.instructions = """
|
||||
You will analyze a technical support ticket from the CBAO company.
|
||||
|
||||
Ta mission principale :
|
||||
The ticket consists of an initial message from the client and subsequent exchanges between the client and technical support.
|
||||
|
||||
1. Identifier le client et le contexte du ticket (demande "name" et "description")
|
||||
- Récupère le nom de l'auteur si présent
|
||||
- Indique si un `user_id` est disponible
|
||||
- Conserve uniquement les informations d'identification utiles (pas d'adresse ou signature de mail inutile)
|
||||
Follow these guidelines:
|
||||
|
||||
2. Mettre en perspective le `name` du ticket
|
||||
- Il peut contenir une ou plusieurs questions implicites
|
||||
- Reformule ces questions de façon explicite
|
||||
1. Overview:
|
||||
- Summarize the main issue reported in the ticket
|
||||
- Identify the product or service concerned
|
||||
|
||||
3. Analyser la `description`
|
||||
- Elle fournit souvent le vrai point d'entrée technique
|
||||
- Repère les formulations interrogatives ou les demandes spécifiques
|
||||
- Identifie si cette partie complète ou précise les questions du nom
|
||||
2. Detailed Analysis:
|
||||
- Analyze each message chronologically
|
||||
- Extract key information, error messages, and details about the problem
|
||||
- Note any attached images or files mentioned
|
||||
|
||||
4. Structurer le fil de discussion
|
||||
- Conserve uniquement les échanges pertinents
|
||||
-Conserve les questions soulevés par "name" ou "description"
|
||||
- CONSERVE ABSOLUMENT les références documentation, FAQ, liens utiles et manuels
|
||||
- Identifie clairement chaque intervenant (client / support)
|
||||
- Classe les informations par ordre chronologique avec date et rôle
|
||||
3. Links and Technical Details:
|
||||
- Identify ALL links (URLs) mentioned in the ticket
|
||||
- Extract technical terminology, specific error codes, or reference numbers
|
||||
- If a URL appears in the ticket, always include it in your summary
|
||||
- Note system configurations or version information
|
||||
|
||||
5. Préparer la transmission à l'agent suivant
|
||||
- Préserve tous les éléments utiles à l'analyse d'image : modules cités, options évoquées, comportements décrits
|
||||
- Mentionne si des images sont attachées au ticket
|
||||
4. Conversation Flow:
|
||||
- Identify questions asked by support and client responses
|
||||
- Highlight information requests that remain unanswered
|
||||
- Note any action items or next steps mentioned
|
||||
|
||||
Structure ta réponse :
|
||||
5. Resolution:
|
||||
- Determine if the issue was resolved
|
||||
- Summarize the solution if provided
|
||||
- Identify if the ticket was escalated or needed additional input
|
||||
|
||||
1. Résumé du contexte
|
||||
- Client (nom, email si disponible)
|
||||
- Sujet du ticket reformulé en une ou plusieurs questions
|
||||
- Description technique synthétique
|
||||
IMPORTANT: Your analysis should:
|
||||
- Be factual and objective
|
||||
- Extract ALL URLs and links
|
||||
- Avoid speculation or technical diagnosis
|
||||
- Be structured chronologically
|
||||
- Focus on the exchange of information
|
||||
- Distinguish clearly between client and support statements
|
||||
"""
|
||||
|
||||
2. Informations techniques détectées
|
||||
- Logiciels/modules mentionnés
|
||||
- Paramètres évoqués
|
||||
- Fonctionnalités impactées
|
||||
- Conditions spécifiques (multi-laboratoire, utilisateur non valide, etc.)
|
||||
# Prompt système
|
||||
self.system_prompt = f"""
|
||||
You are an expert in support ticket analysis at CBAO, tasked with extracting and organizing information from BRG-Lab technical support tickets.
|
||||
|
||||
3. Fil de discussion (filtrée, nettoyée, classée)
|
||||
- Intervenant (Client/Support)
|
||||
- Date et contenu de chaque échange
|
||||
- Résumés techniques
|
||||
- INCLURE TOUS les liens documentaires (manuel, FAQ, documentation technique)
|
||||
Your goal is to produce a clear, factual summary of the support interaction that will help the technical team understand:
|
||||
- The issue reported
|
||||
- Information exchanged
|
||||
- Current status
|
||||
- Any URLs or technical details that need attention
|
||||
|
||||
4. Éléments liés à l'analyse visuelle
|
||||
- Nombre d'images attachées
|
||||
- Références aux interfaces ou options à visualiser
|
||||
- Points à vérifier dans les captures (listes incomplètes, cases à cocher, utilisateurs grisés, etc.)
|
||||
Specifically regarding URLs and technical details:
|
||||
- You MUST preserve ALL links (URLs) that appear in the ticket
|
||||
- Format each URL on its own line, prefixed with [URL] for visibility
|
||||
- Flag any technical parameters, configuration details, or version information
|
||||
|
||||
IMPORTANT :
|
||||
- Ne propose aucune solution ni interprétation
|
||||
- Ne génère pas de tableau
|
||||
- Reste strictement factuel en te basant uniquement sur les informations fournies
|
||||
- Ne reformule pas les messages, conserve les formulations exactes sauf nettoyage de forme"""
|
||||
{self.instructions}
|
||||
|
||||
Your output will be used by the technical team to understand the ticket context, so factual accuracy is essential.
|
||||
|
||||
IMPORTANT: All responses should be in English. Translation to French will be handled separately.
|
||||
"""
|
||||
|
||||
self.ticket_loader = TicketDataLoader()
|
||||
self._appliquer_config_locale()
|
||||
logger.info("AgentTicketAnalyser initialisé")
|
||||
|
||||
def _appliquer_config_locale(self) -> None:
|
||||
"""
|
||||
Configure le LLM avec les paramètres spécifiques à cet agent
|
||||
"""
|
||||
if hasattr(self.llm, "prompt_system"):
|
||||
self.llm.prompt_system = self.system_prompt
|
||||
|
||||
if hasattr(self.llm, "configurer"):
|
||||
self.llm.configurer(**self.params)
|
||||
def executer(self, ticket_data: Dict[str, Any]) -> str:
|
||||
if isinstance(ticket_data, str) and os.path.exists(ticket_data):
|
||||
ticket_data = self.ticket_loader.charger(ticket_data)
|
||||
|
||||
if not isinstance(ticket_data, dict):
|
||||
logger.error("Les données du ticket ne sont pas valides")
|
||||
return "ERREUR: Format de ticket invalide"
|
||||
def _extraire_urls(self, texte: str) -> List[str]:
|
||||
"""
|
||||
Extrait les URLs d'un texte
|
||||
|
||||
Args:
|
||||
texte: Le texte à analyser
|
||||
|
||||
Returns:
|
||||
Liste des URLs extraites
|
||||
"""
|
||||
# Motif pour détecter les URLs (plus complet qu'une simple recherche http://)
|
||||
url_pattern = r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+'
|
||||
|
||||
# Chercher dans le texte avec un motif plus large pour capturer le contexte
|
||||
url_mentions = re.finditer(r'(?:URL|link|adresse|href|http)[^\n]*?(https?://[^\s\)\]\"\']+)', texte, re.IGNORECASE)
|
||||
|
||||
# Liste pour stocker les URLs avec leur contexte
|
||||
urls = []
|
||||
|
||||
# Ajouter les URLs extraites avec le motif générique
|
||||
for url in re.findall(url_pattern, texte):
|
||||
if url not in urls:
|
||||
urls.append(url)
|
||||
|
||||
# Ajouter les URLs extraites du contexte plus large
|
||||
for match in url_mentions:
|
||||
url = match.group(1)
|
||||
if url not in urls:
|
||||
urls.append(url)
|
||||
|
||||
return urls
|
||||
|
||||
ticket_code = ticket_data.get("code", "Inconnu")
|
||||
logger.info(f"Analyse du ticket {ticket_code}")
|
||||
print(f"AgentTicketAnalyser: analyse du ticket {ticket_code}")
|
||||
|
||||
prompt = self._generer_prompt(ticket_data)
|
||||
|
||||
try:
|
||||
response = self.llm.interroger(prompt)
|
||||
logger.info("Analyse du ticket terminée avec succès")
|
||||
print(f" Analyse terminée: {len(response)} caractères")
|
||||
|
||||
result = {
|
||||
"prompt": prompt,
|
||||
"response": response,
|
||||
def executer(self, ticket_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Analyse un ticket de support et extrait les informations importantes
|
||||
|
||||
Args:
|
||||
ticket_data: Données du ticket à analyser
|
||||
|
||||
Returns:
|
||||
Dictionnaire contenant les résultats d'analyse
|
||||
"""
|
||||
# Récupérer le ticket_id correctement, avec vérification
|
||||
ticket_id = ticket_data.get("ticket_id", "")
|
||||
if not ticket_id or ticket_id == "UNKNOWN":
|
||||
# Tentative d'extraction depuis le chemin du fichier JSON si disponible
|
||||
if "file_path" in ticket_data:
|
||||
file_path = ticket_data["file_path"]
|
||||
parts = file_path.split(os.path.sep)
|
||||
for part in parts:
|
||||
if part.startswith("T") and len(part) >= 2 and part[1:].isdigit():
|
||||
ticket_id = part
|
||||
break
|
||||
if part.startswith("ticket_T"):
|
||||
ticket_id = part.replace("ticket_", "")
|
||||
break
|
||||
|
||||
# Si toujours pas de ticket_id valide, utiliser UNKNOWN
|
||||
if not ticket_id:
|
||||
ticket_id = "UNKNOWN"
|
||||
|
||||
ticket_content = ticket_data.get("content", "")
|
||||
|
||||
print(f" AgentTicketAnalyser: Analyse du ticket {ticket_id}")
|
||||
|
||||
if not ticket_content or len(ticket_content) < 10:
|
||||
logger.warning(f"Contenu du ticket {ticket_id} vide ou trop court")
|
||||
return {
|
||||
"response": "Contenu du ticket insuffisant pour analyse",
|
||||
"response_en": "Ticket content insufficient for analysis",
|
||||
"error": True,
|
||||
"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
|
||||
"ticket_id": ticket_id
|
||||
}
|
||||
}
|
||||
sauvegarder_donnees(ticket_code, "analyse_ticket", result, base_dir="reports", is_resultat=True)
|
||||
|
||||
|
||||
try:
|
||||
# Traduire le contenu en anglais pour le LLM si nécessaire
|
||||
ticket_content_en = fr_to_en(ticket_content)
|
||||
|
||||
# Générer le prompt d'analyse
|
||||
prompt = self._generer_prompt({"ticket_id": ticket_id, "content": ticket_content_en})
|
||||
|
||||
# Analyser avec le LLM
|
||||
response_en = self.llm.interroger(prompt)
|
||||
|
||||
# Extraire les URLs de la réponse
|
||||
urls = self._extraire_urls(response_en)
|
||||
|
||||
# Ajouter un marqueur pour indiquer le début et la fin de l'analyse en anglais
|
||||
response_en_marked = "<!-- ENGLISH ANALYSIS START -->\n\n" + response_en + "\n\n<!-- ENGLISH ANALYSIS END -->"
|
||||
|
||||
# Traduire la réponse en français pour la cohérence du pipeline
|
||||
response_fr = en_to_fr(response_en)
|
||||
|
||||
# Ajouter un marqueur pour indiquer le début et la fin de la traduction
|
||||
response_fr_marked = "<!-- FRENCH TRANSLATION START -->\n\n" + response_fr + "\n\n<!-- FRENCH TRANSLATION END -->"
|
||||
|
||||
# Formater la réponse
|
||||
result = {
|
||||
"prompt": ticket_content,
|
||||
"prompt_en": ticket_content_en,
|
||||
"response": response_fr,
|
||||
"response_en": response_en,
|
||||
"urls_extracted": urls,
|
||||
"metadata": {
|
||||
"timestamp": self._get_timestamp(),
|
||||
"source_agent": self.nom,
|
||||
"ticket_id": ticket_id,
|
||||
"model_info": {
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
**self.params
|
||||
},
|
||||
"language": "en-fr",
|
||||
"translation_markers": True
|
||||
}
|
||||
}
|
||||
|
||||
# Sauvegarder les données
|
||||
sauvegarder_donnees(ticket_id, "analyse_ticket", result, is_resultat=True)
|
||||
|
||||
# Ajouter à l'historique
|
||||
self.ajouter_historique(
|
||||
"analyse_ticket",
|
||||
{"ticket_id": ticket_id, "prompt": prompt},
|
||||
result
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
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
|
||||
logger.error(f"Erreur lors de l'analyse du ticket {ticket_id}: {str(e)}")
|
||||
return {
|
||||
"response": f"Erreur lors de l'analyse du ticket: {str(e)}",
|
||||
"response_en": f"Error analyzing ticket: {str(e)}",
|
||||
"error": True,
|
||||
"metadata": {
|
||||
"timestamp": self._get_timestamp(),
|
||||
"source_agent": self.nom,
|
||||
"ticket_id": ticket_id,
|
||||
"error": True
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
"""
|
||||
Génère un prompt pour l'analyse du ticket
|
||||
|
||||
Args:
|
||||
ticket_data: Données du ticket
|
||||
|
||||
Returns:
|
||||
Prompt formaté pour le LLM
|
||||
"""
|
||||
ticket_id = ticket_data.get("ticket_id", "UNKNOWN")
|
||||
content = ticket_data.get("content", "")
|
||||
|
||||
# Ajout d'instructions spécifiques pour la capture des URLs
|
||||
prompt = f"""[ENGLISH RESPONSE REQUESTED]
|
||||
|
||||
texte = f"### TICKET {ticket_code}\n\n"
|
||||
### TICKET {ticket_id}
|
||||
|
||||
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"
|
||||
{content}
|
||||
|
||||
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()
|
||||
Analyze this support ticket and provide:
|
||||
1. A chronological summary of the exchanges
|
||||
2. Extraction of all important technical details
|
||||
3. Clear identification of ALL URLs mentioned (prefix each with "[URL]")
|
||||
4. Analysis of whether the issue was resolved
|
||||
|
||||
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"
|
||||
|
||||
return texte
|
||||
Present your analysis in a clear, concise format that would be helpful for a technical support team.
|
||||
Focus on FACTS only, avoid interpretation or diagnosis.
|
||||
"""
|
||||
return prompt
|
||||
|
||||
def _formater_date(self, date_str: str) -> str:
|
||||
if not date_str:
|
||||
return "date inconnue"
|
||||
"""
|
||||
Reformate une date pour l'uniformisation
|
||||
(Cette méthode peut être adaptée selon le format des dates dans les tickets)
|
||||
|
||||
Args:
|
||||
date_str: Chaîne de date à formater
|
||||
|
||||
Returns:
|
||||
Date formatée
|
||||
"""
|
||||
# Formats possibles:
|
||||
# - DD/MM/YYYY HH:MM
|
||||
# - YYYY-MM-DD HH:MM:SS
|
||||
# On uniformise en YYYY-MM-DD HH:MM
|
||||
|
||||
try:
|
||||
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
|
||||
# Adapter cette partie selon les formats de date rencontrés
|
||||
if "/" in date_str:
|
||||
# Format DD/MM/YYYY
|
||||
parts = date_str.split(" ")
|
||||
date_parts = parts[0].split("/")
|
||||
time_part = parts[1] if len(parts) > 1 else "00:00"
|
||||
return f"{date_parts[2]}-{date_parts[1]}-{date_parts[0]} {time_part}"
|
||||
else:
|
||||
# Format YYYY-MM-DD
|
||||
if " " in date_str:
|
||||
date_part, time_part = date_str.split(" ", 1)
|
||||
time_part = time_part.split(".", 1)[0] # Enlever les millisecondes
|
||||
return f"{date_part} {time_part}"
|
||||
return date_str
|
||||
except Exception:
|
||||
# En cas d'erreur, retourner la date d'origine
|
||||
return date_str
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""
|
||||
Génère un timestamp au format YYYYMMDD_HHMMSS
|
||||
|
||||
Returns:
|
||||
Timestamp formaté
|
||||
"""
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
@ -1,340 +0,0 @@
|
||||
from .base_agent import BaseAgent
|
||||
from typing import Any, Dict
|
||||
import logging
|
||||
import os
|
||||
from PIL import Image
|
||||
import base64
|
||||
import io
|
||||
|
||||
logger = logging.getLogger("AgentImageAnalyser")
|
||||
|
||||
class AgentImageAnalyser(BaseAgent):
|
||||
"""
|
||||
Agent pour analyser les images et extraire les informations pertinentes.
|
||||
"""
|
||||
def __init__(self, llm):
|
||||
super().__init__("AgentImageAnalyser", llm)
|
||||
|
||||
# Configuration locale de l'agent
|
||||
self.temperature = 0.2
|
||||
self.top_p = 0.9
|
||||
self.max_tokens = 3000
|
||||
|
||||
# Centralisation des instructions d'analyse pour éviter la duplication
|
||||
self.instructions_analyse = """
|
||||
1. Description objective
|
||||
Décris précisément ce que montre l'image :
|
||||
- Interface logicielle, menus, fenêtres, onglets
|
||||
- Messages d'erreur, messages système, code ou script
|
||||
- Nom ou titre du logiciel ou du module si visible
|
||||
|
||||
2. Éléments techniques clés
|
||||
Identifie :
|
||||
- Versions logicielles ou modules affichés
|
||||
- Codes d'erreur visibles
|
||||
- Paramètres configurables (champs de texte, sliders, dropdowns, cases à cocher)
|
||||
- Valeurs affichées ou préremplies dans les champs
|
||||
- Éléments désactivés, grisés ou masqués (souvent non modifiables)
|
||||
- Boutons actifs/inactifs
|
||||
|
||||
3. Éléments mis en évidence
|
||||
- Recherche les zones entourées, encadrées, surlignées ou fléchées
|
||||
- Ces éléments sont souvent importants pour le client ou le support
|
||||
- Mentionne explicitement leur contenu et leur style de mise en valeur
|
||||
|
||||
4. Relation avec le problème
|
||||
- Établis le lien entre les éléments visibles et le problème décrit dans le ticket
|
||||
- Indique si des composants semblent liés à une mauvaise configuration ou une erreur
|
||||
|
||||
5. Réponses potentielles
|
||||
- Détermine si l'image apporte des éléments de réponse à une question posée dans :
|
||||
- Le titre du ticket
|
||||
- La description du problème
|
||||
|
||||
6. Lien avec la discussion
|
||||
- Vérifie si l'image fait écho à une étape décrite dans le fil de discussion
|
||||
- Note les correspondances (ex: même module, même message d'erreur que précédemment mentionné)
|
||||
|
||||
Règles importantes :
|
||||
- Ne fais AUCUNE interprétation ni diagnostic
|
||||
- Ne propose PAS de solution ou recommandation
|
||||
- Reste strictement factuel et objectif
|
||||
- Concentre-toi uniquement sur ce qui est visible dans l'image
|
||||
- Reproduis les textes exacts(ex : messages d'erreur, libellés de paramètres)
|
||||
- Prête une attention particulière aux éléments modifiables (interactifs) et non modifiables (grisés)
|
||||
"""
|
||||
|
||||
# Prompt système construit à partir des instructions centralisées
|
||||
self.system_prompt = f"""Tu es un expert en analyse d'images pour le support technique de BRG-Lab pour la société CBAO.
|
||||
Ta mission est d'analyser des captures d'écran en lien avec le contexte du ticket de support.
|
||||
|
||||
Structure ton analyse d'image de façon factuelle:
|
||||
{self.instructions_analyse}
|
||||
|
||||
Ton analyse sera utilisée comme élément factuel pour un rapport technique plus complet."""
|
||||
|
||||
# Appliquer la configuration au LLM
|
||||
self._appliquer_config_locale()
|
||||
|
||||
logger.info("AgentImageAnalyser initialisé")
|
||||
|
||||
def _appliquer_config_locale(self) -> None:
|
||||
"""
|
||||
Applique la configuration locale au modèle LLM.
|
||||
"""
|
||||
# Appliquer le prompt système
|
||||
if hasattr(self.llm, "prompt_system"):
|
||||
self.llm.prompt_system = self.system_prompt
|
||||
|
||||
# Appliquer les paramètres
|
||||
if hasattr(self.llm, "configurer"):
|
||||
params = {
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
|
||||
self.llm.configurer(**params)
|
||||
|
||||
def _verifier_image(self, image_path: str) -> bool:
|
||||
"""
|
||||
Vérifie si l'image existe et est accessible
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image
|
||||
|
||||
Returns:
|
||||
True si l'image existe et est accessible, False sinon
|
||||
"""
|
||||
try:
|
||||
# Vérifier que le fichier existe
|
||||
if not os.path.exists(image_path):
|
||||
logger.error(f"L'image n'existe pas: {image_path}")
|
||||
return False
|
||||
|
||||
# Vérifier que le fichier est accessible en lecture
|
||||
if not os.access(image_path, os.R_OK):
|
||||
logger.error(f"L'image n'est pas accessible en lecture: {image_path}")
|
||||
return False
|
||||
|
||||
# Vérifier que le fichier peut être ouvert comme une image
|
||||
with Image.open(image_path) as img:
|
||||
# Vérifier les dimensions de l'image
|
||||
width, height = img.size
|
||||
if width <= 0 or height <= 0:
|
||||
logger.error(f"Dimensions d'image invalides: {width}x{height}")
|
||||
return False
|
||||
|
||||
logger.info(f"Image vérifiée avec succès: {image_path} ({width}x{height})")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la vérification de l'image {image_path}: {str(e)}")
|
||||
return False
|
||||
|
||||
def _encoder_image_base64(self, image_path: str) -> str:
|
||||
"""
|
||||
Encode l'image en base64 pour l'inclure directement dans le prompt
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image
|
||||
|
||||
Returns:
|
||||
Chaîne de caractères au format data URI avec l'image encodée en base64
|
||||
"""
|
||||
try:
|
||||
# Ouvrir l'image et la redimensionner si trop grande
|
||||
with Image.open(image_path) as img:
|
||||
# Redimensionner l'image si elle est trop grande (max 800x800)
|
||||
max_size = 800
|
||||
if img.width > max_size or img.height > max_size:
|
||||
img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
|
||||
|
||||
# Convertir en RGB si nécessaire (pour les formats comme PNG)
|
||||
if img.mode != "RGB":
|
||||
img = img.convert("RGB")
|
||||
|
||||
# Sauvegarder l'image en JPEG dans un buffer mémoire
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format="JPEG", quality=85)
|
||||
buffer.seek(0)
|
||||
|
||||
# Encoder en base64
|
||||
img_base64 = base64.b64encode(buffer.read()).decode("utf-8")
|
||||
|
||||
# Construire le data URI
|
||||
data_uri = f"data:image/jpeg;base64,{img_base64}"
|
||||
|
||||
return data_uri
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'encodage de l'image {image_path}: {str(e)}")
|
||||
return ""
|
||||
|
||||
def _generer_prompt_analyse(self, contexte: str, prefix: str = "") -> str:
|
||||
"""
|
||||
Génère le prompt d'analyse d'image en utilisant les instructions centralisées
|
||||
|
||||
Args:
|
||||
contexte: Contexte du ticket à inclure dans le prompt
|
||||
prefix: Préfixe optionnel (pour inclure l'image en base64 par exemple)
|
||||
|
||||
Returns:
|
||||
Prompt formaté pour l'analyse d'image
|
||||
"""
|
||||
return f"""{prefix}
|
||||
|
||||
CONTEXTE DU TICKET:
|
||||
{contexte}
|
||||
|
||||
Fournis une analyse STRICTEMENT FACTUELLE de l'image avec les sections suivantes:
|
||||
{self.instructions_analyse}"""
|
||||
|
||||
def executer(self, image_path: str, contexte: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Analyse une image en tenant compte du contexte du ticket
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser
|
||||
contexte: Contexte du ticket (résultat de l'analyse JSON)
|
||||
|
||||
Returns:
|
||||
Dictionnaire contenant l'analyse détaillée de l'image et les métadonnées d'exécution
|
||||
"""
|
||||
image_name = os.path.basename(image_path)
|
||||
logger.info(f"Analyse de l'image: {image_name} avec contexte")
|
||||
print(f" AgentImageAnalyser: Analyse de {image_name}")
|
||||
|
||||
# Vérifier que l'image existe et est accessible
|
||||
if not self._verifier_image(image_path):
|
||||
error_message = f"L'image n'est pas accessible ou n'est pas valide: {image_name}"
|
||||
logger.error(error_message)
|
||||
print(f" ERREUR: {error_message}")
|
||||
|
||||
return {
|
||||
"analyse": f"ERREUR: {error_message}. Veuillez vérifier que l'image existe et est valide.",
|
||||
"error": True,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True
|
||||
}
|
||||
}
|
||||
|
||||
# Générer le prompt d'analyse avec les instructions centralisées
|
||||
prompt = self._generer_prompt_analyse(contexte, "Analyse cette image en tenant compte du contexte suivant:")
|
||||
|
||||
try:
|
||||
logger.info("Envoi de la requête au LLM")
|
||||
|
||||
# Utiliser la méthode interroger_avec_image au lieu de interroger
|
||||
if hasattr(self.llm, "interroger_avec_image"):
|
||||
logger.info(f"Utilisation de la méthode interroger_avec_image pour {image_name}")
|
||||
response = self.llm.interroger_avec_image(image_path, prompt)
|
||||
else:
|
||||
# Fallback vers la méthode standard avec base64 si interroger_avec_image n'existe pas
|
||||
logger.warning(f"La méthode interroger_avec_image n'existe pas, utilisation du fallback pour {image_name}")
|
||||
img_base64 = self._encoder_image_base64(image_path)
|
||||
if img_base64:
|
||||
# Utiliser le même générateur de prompt avec l'image en base64
|
||||
prompt_base64 = self._generer_prompt_analyse(contexte, f"Analyse cette image:\n{img_base64}")
|
||||
|
||||
response = self.llm.interroger(prompt_base64)
|
||||
else:
|
||||
error_message = "Impossible d'encoder l'image en base64"
|
||||
logger.error(f"Erreur d'analyse pour {image_name}: {error_message}")
|
||||
print(f" ERREUR: {error_message}")
|
||||
|
||||
# Retourner un résultat d'erreur explicite
|
||||
return {
|
||||
"analyse": f"ERREUR: {error_message}. Veuillez vérifier que l'image est dans un format standard.",
|
||||
"error": True,
|
||||
"raw_response": "",
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True
|
||||
}
|
||||
}
|
||||
|
||||
# Vérifier si la réponse contient des indications que le modèle ne peut pas analyser l'image
|
||||
error_phrases = [
|
||||
"je ne peux pas directement visualiser",
|
||||
"je n'ai pas accès à l'image",
|
||||
"je ne peux pas voir l'image",
|
||||
"sans accès direct à l'image",
|
||||
"je n'ai pas la possibilité de voir",
|
||||
"je ne peux pas accéder directement",
|
||||
"erreur: impossible d'analyser l'image"
|
||||
]
|
||||
|
||||
# Vérifier si une des phrases d'erreur est présente dans la réponse
|
||||
if any(phrase in response.lower() for phrase in error_phrases):
|
||||
logger.warning(f"Le modèle indique qu'il ne peut pas analyser l'image: {image_name}")
|
||||
error_message = "Le modèle n'a pas pu analyser l'image correctement"
|
||||
logger.error(f"Erreur d'analyse pour {image_name}: {error_message}")
|
||||
print(f" ERREUR: {error_message}")
|
||||
|
||||
# Retourner un résultat d'erreur explicite
|
||||
return {
|
||||
"analyse": f"ERREUR: {error_message}. Veuillez vérifier que le modèle a accès à l'image ou utiliser un modèle différent.",
|
||||
"error": True,
|
||||
"raw_response": response,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(f"Réponse reçue pour l'image {image_name}: {response[:100]}...")
|
||||
|
||||
# Créer un dictionnaire de résultat avec l'analyse et les métadonnées
|
||||
result = {
|
||||
"analyse": response,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"model_info": {
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Enregistrer l'analyse dans l'historique avec contexte et prompt
|
||||
self.ajouter_historique("analyse_image",
|
||||
{
|
||||
"image_path": image_path,
|
||||
"contexte": contexte,
|
||||
"prompt": prompt
|
||||
},
|
||||
response)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
error_message = f"Erreur lors de l'analyse de l'image: {str(e)}"
|
||||
logger.error(error_message)
|
||||
print(f" ERREUR: {error_message}")
|
||||
|
||||
# Retourner un résultat par défaut en cas d'erreur
|
||||
return {
|
||||
"analyse": f"ERREUR: {error_message}",
|
||||
"error": True,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True
|
||||
}
|
||||
}
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""Retourne un timestamp au format YYYYMMDD_HHMMSS"""
|
||||
from datetime import datetime
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
@ -1,393 +0,0 @@
|
||||
from .base_agent import BaseAgent
|
||||
import logging
|
||||
import os
|
||||
from typing import Dict, Any, Tuple
|
||||
from PIL import Image
|
||||
import base64
|
||||
import io
|
||||
|
||||
logger = logging.getLogger("AgentImageSorter")
|
||||
|
||||
class AgentImageSorter(BaseAgent):
|
||||
"""
|
||||
Agent pour trier les images et identifier celles qui sont pertinentes.
|
||||
"""
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
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".
|
||||
|
||||
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}"""
|
||||
|
||||
# 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)
|
||||
|
||||
def _verifier_image(self, image_path: str) -> bool:
|
||||
"""
|
||||
Vérifie si l'image existe et est accessible
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image
|
||||
|
||||
Returns:
|
||||
True si l'image existe et est accessible, False sinon
|
||||
"""
|
||||
try:
|
||||
# Vérifier que le fichier existe
|
||||
if not os.path.exists(image_path):
|
||||
logger.error(f"L'image n'existe pas: {image_path}")
|
||||
return False
|
||||
|
||||
# Vérifier que le fichier est accessible en lecture
|
||||
if not os.access(image_path, os.R_OK):
|
||||
logger.error(f"L'image n'est pas accessible en lecture: {image_path}")
|
||||
return False
|
||||
|
||||
# Vérifier que le fichier peut être ouvert comme une image
|
||||
with Image.open(image_path) as img:
|
||||
# Vérifier les dimensions de l'image
|
||||
width, height = img.size
|
||||
if width <= 0 or height <= 0:
|
||||
logger.error(f"Dimensions d'image invalides: {width}x{height}")
|
||||
return False
|
||||
|
||||
logger.info(f"Image vérifiée avec succès: {image_path} ({width}x{height})")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la vérification de l'image {image_path}: {str(e)}")
|
||||
return False
|
||||
|
||||
def _encoder_image_base64(self, image_path: str) -> str:
|
||||
"""
|
||||
Encode l'image en base64 pour l'inclure directement dans le prompt
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image
|
||||
|
||||
Returns:
|
||||
Chaîne de caractères au format data URI avec l'image encodée en base64
|
||||
"""
|
||||
try:
|
||||
# Ouvrir l'image et la redimensionner si trop grande
|
||||
with Image.open(image_path) as img:
|
||||
# Redimensionner l'image si elle est trop grande (max 800x800)
|
||||
max_size = 800
|
||||
if img.width > max_size or img.height > max_size:
|
||||
img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
|
||||
|
||||
# Convertir en RGB si nécessaire (pour les formats comme PNG)
|
||||
if img.mode != "RGB":
|
||||
img = img.convert("RGB")
|
||||
|
||||
# Sauvegarder l'image en JPEG dans un buffer mémoire
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format="JPEG", quality=85)
|
||||
buffer.seek(0)
|
||||
|
||||
# Encoder en base64
|
||||
img_base64 = base64.b64encode(buffer.read()).decode("utf-8")
|
||||
|
||||
# Construire le data URI
|
||||
data_uri = f"data:image/jpeg;base64,{img_base64}"
|
||||
|
||||
return data_uri
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'encodage de l'image {image_path}: {str(e)}")
|
||||
return ""
|
||||
|
||||
def _generer_prompt_analyse(self, 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 executer(self, image_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Évalue si une image est pertinente pour l'analyse d'un ticket technique
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser
|
||||
|
||||
Returns:
|
||||
Dictionnaire contenant la décision de pertinence, l'analyse et les métadonnées
|
||||
"""
|
||||
image_name = os.path.basename(image_path)
|
||||
logger.info(f"Évaluation de la pertinence de l'image: {image_name}")
|
||||
print(f" AgentImageSorter: Évaluation de {image_name}")
|
||||
|
||||
# Vérifier que l'image existe et est accessible
|
||||
if not self._verifier_image(image_path):
|
||||
error_message = f"L'image n'est pas accessible ou n'est pas valide: {image_name}"
|
||||
logger.error(error_message)
|
||||
print(f" ERREUR: {error_message}")
|
||||
|
||||
return {
|
||||
"is_relevant": False,
|
||||
"reason": f"Erreur d'accès: {error_message}",
|
||||
"raw_response": "",
|
||||
"error": True,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True
|
||||
}
|
||||
}
|
||||
|
||||
# Utiliser une référence au fichier image que le modèle peut comprendre
|
||||
try:
|
||||
# Préparation du prompt 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)
|
||||
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 {
|
||||
"is_relevant": False,
|
||||
"reason": f"Erreur d'analyse: {error_message}",
|
||||
"raw_response": "",
|
||||
"error": True,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True
|
||||
}
|
||||
}
|
||||
|
||||
# Vérifier si la réponse contient des indications que le modèle ne peut pas analyser l'image
|
||||
error_phrases = [
|
||||
"je ne peux pas directement visualiser",
|
||||
"je n'ai pas accès à l'image",
|
||||
"je ne peux pas voir l'image",
|
||||
"sans accès direct à l'image",
|
||||
"je n'ai pas la possibilité de voir",
|
||||
"je ne peux pas accéder directement",
|
||||
"erreur: impossible d'analyser l'image"
|
||||
]
|
||||
|
||||
# Vérifier si une des phrases d'erreur est présente dans la réponse
|
||||
if any(phrase in response.lower() for phrase in error_phrases):
|
||||
logger.warning(f"Le modèle indique qu'il ne peut pas analyser l'image: {image_name}")
|
||||
error_message = "Le modèle n'a pas pu analyser l'image correctement"
|
||||
logger.error(f"Erreur d'analyse pour {image_name}: {error_message}")
|
||||
print(f" ERREUR: {error_message}")
|
||||
|
||||
# Retourner un résultat d'erreur explicite
|
||||
return {
|
||||
"is_relevant": False,
|
||||
"reason": f"Erreur d'analyse: {error_message}",
|
||||
"raw_response": response,
|
||||
"error": True,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True
|
||||
}
|
||||
}
|
||||
|
||||
# Analyse de la réponse pour déterminer la pertinence
|
||||
is_relevant, reason = self._analyser_reponse(response)
|
||||
|
||||
logger.info(f"Image {image_name} considérée comme {'pertinente' if is_relevant else 'non pertinente'}")
|
||||
print(f" Décision: Image {image_name} {'pertinente' if is_relevant else 'non pertinente'}")
|
||||
|
||||
# Préparer le résultat
|
||||
result = {
|
||||
"is_relevant": is_relevant,
|
||||
"reason": reason,
|
||||
"raw_response": response,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"model_info": {
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Enregistrer la décision et le raisonnement dans l'historique
|
||||
self.ajouter_historique("tri_image",
|
||||
{
|
||||
"image_path": image_path,
|
||||
"prompt": prompt
|
||||
},
|
||||
{
|
||||
"response": response,
|
||||
"is_relevant": is_relevant,
|
||||
"reason": reason
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'analyse de l'image {image_name}: {str(e)}")
|
||||
print(f" ERREUR: Impossible d'analyser l'image {image_name}")
|
||||
|
||||
# Retourner un résultat par défaut en cas d'erreur
|
||||
return {
|
||||
"is_relevant": False, # Par défaut, considérer non pertinent en cas d'erreur
|
||||
"reason": f"Erreur d'analyse: {str(e)}",
|
||||
"raw_response": "",
|
||||
"error": True,
|
||||
"metadata": {
|
||||
"image_path": image_path,
|
||||
"image_name": image_name,
|
||||
"timestamp": self._get_timestamp(),
|
||||
"error": True
|
||||
}
|
||||
}
|
||||
|
||||
def _analyser_reponse(self, response: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Analyse la réponse du LLM pour déterminer la pertinence et extraire le raisonnement
|
||||
|
||||
Args:
|
||||
response: Réponse brute du LLM
|
||||
|
||||
Returns:
|
||||
Tuple (is_relevant, reason) contenant la décision et le raisonnement
|
||||
"""
|
||||
# Convertir en minuscule pour faciliter la comparaison
|
||||
response_lower = response.lower()
|
||||
|
||||
# Détection directe des réponses négatives en début de texte
|
||||
first_line = response_lower.split('\n')[0] if '\n' in response_lower else response_lower[:50]
|
||||
starts_with_non = first_line.strip().startswith("non") or first_line.strip().startswith("non.")
|
||||
|
||||
# Détection explicite d'une réponse négative au début de la réponse
|
||||
explicit_negative = starts_with_non or any(neg_start in first_line for neg_start in ["non pertinent", "pas pertinent"])
|
||||
|
||||
# Détection explicite d'une réponse positive au début de la réponse
|
||||
explicit_positive = first_line.strip().startswith("oui") or first_line.strip().startswith("pertinent")
|
||||
|
||||
# Si une réponse explicite est détectée, l'utiliser directement
|
||||
if explicit_negative:
|
||||
is_relevant = False
|
||||
elif explicit_positive:
|
||||
is_relevant = True
|
||||
else:
|
||||
# Sinon, utiliser l'analyse par mots-clés
|
||||
# Mots clés positifs forts
|
||||
positive_keywords = ["oui", "pertinent", "pertinente", "utile", "important", "relevante",
|
||||
"capture d'écran", "message d'erreur", "interface logicielle",
|
||||
"configuration", "technique", "diagnostic"]
|
||||
|
||||
# Mots clés négatifs forts
|
||||
negative_keywords = ["non", "pas pertinent", "non pertinente", "inutile", "irrelevant",
|
||||
"photo personnelle", "marketing", "sans rapport", "hors sujet",
|
||||
"décorative", "logo"]
|
||||
|
||||
# Compter les occurrences de mots clés
|
||||
positive_count = sum(1 for kw in positive_keywords if kw in response_lower)
|
||||
negative_count = sum(1 for kw in negative_keywords if kw in response_lower)
|
||||
|
||||
# Heuristique de décision basée sur la prépondérance des mots clés
|
||||
is_relevant = positive_count > negative_count
|
||||
|
||||
# Extraire le raisonnement (les dernières phrases de la réponse)
|
||||
lines = response.split('\n')
|
||||
reason_lines = []
|
||||
for line in reversed(lines):
|
||||
if line.strip():
|
||||
reason_lines.insert(0, line.strip())
|
||||
if len(reason_lines) >= 2: # Prendre les 2 dernières lignes non vides
|
||||
break
|
||||
|
||||
reason = " ".join(reason_lines) if reason_lines else "Décision basée sur l'analyse des mots-clés"
|
||||
|
||||
# Log détaillé de l'analyse
|
||||
logger.debug(f"Analyse de la réponse: \n - Réponse brute: {response[:100]}...\n"
|
||||
f" - Commence par 'non': {starts_with_non}\n"
|
||||
f" - Détection explicite négative: {explicit_negative}\n"
|
||||
f" - Détection explicite positive: {explicit_positive}\n"
|
||||
f" - Décision finale: {'pertinente' if is_relevant else 'non pertinente'}\n"
|
||||
f" - Raison: {reason}")
|
||||
|
||||
return is_relevant, reason
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""Retourne un timestamp au format YYYYMMDD_HHMMSS"""
|
||||
from datetime import datetime
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
@ -1,368 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
from .base_agent import BaseAgent
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Tuple, Optional, List
|
||||
import logging
|
||||
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
|
||||
|
||||
logger = logging.getLogger("AgentReportGenerator")
|
||||
|
||||
class AgentReportGenerator(BaseAgent):
|
||||
"""
|
||||
Agent pour générer un rapport synthétique à partir des analyses de ticket et d'images.
|
||||
"""
|
||||
def __init__(self, llm):
|
||||
super().__init__("AgentReportGenerator", llm)
|
||||
|
||||
# Configuration locale de l'agent
|
||||
self.temperature = 0.2
|
||||
self.top_p = 0.9
|
||||
self.max_tokens = 10000
|
||||
|
||||
# Prompt système principal
|
||||
self.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG-Lab pour la société CBAO.
|
||||
Ta mission est de synthétiser les analyses (ticket et images) en un rapport structuré.
|
||||
|
||||
EXIGENCE ABSOLUE - Ton rapport DOIT inclure dans l'ordre:
|
||||
1. Un résumé du problème initial (nom de la demande + description)
|
||||
2. Une analyse détaillée des images pertinentes en lien avec le problème
|
||||
3. Une synthèse globale des analyses d'images
|
||||
4. Une reconstitution du fil de discussion client/support
|
||||
5. Un tableau JSON de chronologie des échanges avec cette structure:
|
||||
```json
|
||||
{
|
||||
"chronologie_echanges": [
|
||||
{"date": "date exacte", "emetteur": "CLIENT ou SUPPORT", "type": "Question ou Réponse", "contenu": "contenu synthétisé"}
|
||||
]
|
||||
}
|
||||
```
|
||||
6. Un diagnostic technique des causes probables
|
||||
|
||||
MÉTHODE D'ANALYSE (ÉTAPES OBLIGATOIRES):
|
||||
1. ANALYSE TOUTES les images AVANT de créer le tableau des échanges
|
||||
2. Concentre-toi sur les éléments mis en évidence (encadrés/surlignés) dans chaque image
|
||||
3. Réalise une SYNTHÈSE TRANSVERSALE en expliquant comment les images se complètent
|
||||
4. Remets les images en ordre chronologique selon le fil de discussion
|
||||
5. CONSERVE TOUS les liens documentaires, FAQ et références techniques
|
||||
6. Ajoute une entrée "Complément visuel" dans le tableau des échanges"""
|
||||
|
||||
# Version du prompt pour la traçabilité
|
||||
self.prompt_version = "v3.2"
|
||||
|
||||
# Appliquer la configuration au LLM
|
||||
self._appliquer_config_locale()
|
||||
|
||||
logger.info("AgentReportGenerator initialisé")
|
||||
|
||||
def _appliquer_config_locale(self) -> None:
|
||||
"""
|
||||
Applique la configuration locale au modèle LLM.
|
||||
"""
|
||||
# Appliquer le prompt système
|
||||
if hasattr(self.llm, "prompt_system"):
|
||||
self.llm.prompt_system = self.system_prompt
|
||||
|
||||
# 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)
|
||||
logger.info(f"Configuration appliquée au modèle: {str(params)}")
|
||||
|
||||
def _formater_prompt_pour_rapport(self, ticket_analyse: str, images_analyses: List[Dict]) -> str:
|
||||
"""
|
||||
Formate le prompt pour la génération du rapport
|
||||
"""
|
||||
num_images = len(images_analyses)
|
||||
logger.info(f"Formatage du prompt avec {num_images} analyses d'images")
|
||||
|
||||
# Construire la section d'analyse du ticket
|
||||
prompt = f"""Génère un rapport technique complet, en te basant sur les analyses suivantes.
|
||||
|
||||
## ANALYSE DU TICKET
|
||||
{ticket_analyse}
|
||||
"""
|
||||
|
||||
# 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"
|
||||
|
||||
# Instructions pour le rapport
|
||||
prompt += """
|
||||
## INSTRUCTIONS POUR LE RAPPORT
|
||||
|
||||
STRUCTURE OBLIGATOIRE ET ORDRE À SUIVRE:
|
||||
1. Titre principal (# Rapport d'analyse: Nom du ticket)
|
||||
2. Résumé du problème (## Résumé du problème)
|
||||
3. Analyse des images (## Analyse des images) - CRUCIAL: FAIRE CETTE SECTION AVANT LE TABLEAU
|
||||
4. Synthèse globale des analyses d'images (## 3.1 Synthèse globale des analyses d'images)
|
||||
5. Fil de discussion (## Fil de discussion)
|
||||
6. Tableau questions/réponses (## Tableau questions/réponses)
|
||||
7. Diagnostic technique (## Diagnostic technique)
|
||||
|
||||
MÉTHODE POUR ANALYSER LES IMAGES:
|
||||
- Pour chaque image, concentre-toi prioritairement sur:
|
||||
* Les éléments mis en évidence (zones encadrées, surlignées)
|
||||
* La relation avec le problème décrit
|
||||
* Le lien avec le fil de discussion
|
||||
|
||||
SYNTHÈSE GLOBALE DES IMAGES (SECTION CRUCIALE):
|
||||
- Titre à utiliser OBLIGATOIREMENT: ## 3.1 Synthèse globale des analyses d'images
|
||||
- Premier sous-titre à utiliser OBLIGATOIREMENT: _Analyse transversale des captures d'écran_
|
||||
- Structure cette section avec les sous-parties:
|
||||
* Points communs et complémentaires entre les images
|
||||
* Corrélation entre les éléments et le problème global
|
||||
* Confirmation visuelle des informations du support
|
||||
- Montre comment les images se complètent pour illustrer le processus complet
|
||||
- Cette synthèse transversale servira de base pour le "Complément visuel"
|
||||
|
||||
POUR LE TABLEAU QUESTIONS/RÉPONSES:
|
||||
- Tu DOIS créer et inclure un tableau JSON structuré comme ceci:
|
||||
```json
|
||||
{
|
||||
"chronologie_echanges": [
|
||||
{"date": "date demande", "emetteur": "CLIENT", "type": "Question", "contenu": "Texte exact du problème initial extrait du ticket"},
|
||||
{"date": "date exacte", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "réponse avec TOUS les liens documentaires"},
|
||||
{"date": "date analyse", "emetteur": "SUPPORT", "type": "Complément visuel", "contenu": "synthèse unifiée de TOUTES les images"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
DIRECTIVES ESSENTIELLES:
|
||||
- COMMENCE ABSOLUMENT par une entrée CLIENT avec les questions du NOM et de la DESCRIPTION du ticket
|
||||
- Si le premier message chronologique est une réponse du SUPPORT qui cite la question, extrais la question citée pour l'ajouter comme première entrée CLIENT
|
||||
- CONSERVE ABSOLUMENT TOUS les liens vers la documentation, FAQ, manuels et références techniques
|
||||
- Ajoute UNE SEULE entrée "Complément visuel" qui synthétise l'apport global des images
|
||||
- Cette entrée doit montrer comment les images confirment/illustrent le processus complet
|
||||
- Formulation recommandée: "L'analyse des captures d'écran confirme visuellement le processus: (1)..., (2)..., (3)... Ces interfaces complémentaires illustrent..."
|
||||
- Évite de traiter les images séparément dans le tableau; présente une vision unifiée
|
||||
- Identifie clairement chaque intervenant (CLIENT ou SUPPORT)
|
||||
"""
|
||||
|
||||
return prompt
|
||||
|
||||
def executer(self, rapport_data: Dict, rapport_dir: str) -> Tuple[Optional[str], Optional[str]]:
|
||||
"""
|
||||
Génère un rapport à partir des analyses effectuées
|
||||
"""
|
||||
try:
|
||||
# 1. PRÉPARATION
|
||||
ticket_id = self._extraire_ticket_id(rapport_data, rapport_dir)
|
||||
logger.info(f"Génération du rapport pour le ticket: {ticket_id}")
|
||||
print(f"AgentReportGenerator: Génération du rapport pour {ticket_id}")
|
||||
|
||||
# Créer le répertoire de sortie si nécessaire
|
||||
os.makedirs(rapport_dir, exist_ok=True)
|
||||
|
||||
# 2. EXTRACTION DES DONNÉES
|
||||
ticket_analyse = self._extraire_analyse_ticket(rapport_data)
|
||||
images_analyses = self._extraire_analyses_images(rapport_data)
|
||||
|
||||
# 3. COLLECTE DES INFORMATIONS SUR LES AGENTS (via le nouveau module)
|
||||
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
|
||||
prompt = self._formater_prompt_pour_rapport(ticket_analyse, images_analyses)
|
||||
|
||||
logger.info("Génération du rapport avec le LLM")
|
||||
print(f" Génération du rapport avec le LLM...")
|
||||
|
||||
# Mesurer le temps d'exécution
|
||||
start_time = datetime.now()
|
||||
rapport_genere = self.llm.interroger(prompt)
|
||||
generation_time = (datetime.now() - start_time).total_seconds()
|
||||
|
||||
logger.info(f"Rapport généré: {len(rapport_genere)} caractères")
|
||||
print(f" Rapport généré: {len(rapport_genere)} caractères")
|
||||
|
||||
# 5. EXTRACTION DES DONNÉES DU RAPPORT
|
||||
# Utiliser l'utilitaire de report_utils.py pour extraire les données JSON
|
||||
rapport_traite, echanges_json, _ = extraire_et_traiter_json(rapport_genere)
|
||||
|
||||
# Vérifier que echanges_json n'est pas None pour éviter l'erreur de type
|
||||
if echanges_json is None:
|
||||
echanges_json = {"chronologie_echanges": []}
|
||||
logger.warning("Aucun échange JSON extrait du rapport, création d'une structure vide")
|
||||
|
||||
# Extraire les sections textuelles (résumé, diagnostic)
|
||||
resume, analyse_images, diagnostic = extraire_sections_texte(rapport_genere)
|
||||
|
||||
# 6. CRÉATION DU RAPPORT JSON
|
||||
# Préparer les métadonnées de l'agent
|
||||
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
|
||||
}
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
# 7. SAUVEGARDE DU RAPPORT JSON
|
||||
json_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.json")
|
||||
|
||||
with open(json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(rapport_json, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"Rapport JSON sauvegardé: {json_path}")
|
||||
print(f" Rapport JSON sauvegardé: {json_path}")
|
||||
|
||||
# 8. GÉNÉRATION DU RAPPORT MARKDOWN
|
||||
md_path = generer_rapport_markdown(json_path)
|
||||
|
||||
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")
|
||||
|
||||
return json_path, md_path
|
||||
|
||||
except Exception as e:
|
||||
error_message = f"Erreur lors de la génération du rapport: {str(e)}"
|
||||
logger.error(error_message)
|
||||
logger.error(traceback.format_exc())
|
||||
print(f" ERREUR: {error_message}")
|
||||
return None, None
|
||||
|
||||
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", "")
|
||||
|
||||
# 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)
|
||||
|
||||
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():
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
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")
|
||||
|
||||
return images_analyses
|
||||
|
||||
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
|
||||
|
||||
# Extraire l'analyse selon le format des données
|
||||
analysis = analyse_data["analysis"]
|
||||
|
||||
# Structure type 1: {"analyse": "texte"}
|
||||
if isinstance(analysis, dict) and "analyse" in analysis:
|
||||
return analysis["analyse"]
|
||||
|
||||
# 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
|
||||
@ -1,609 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
from .base_agent import BaseAgent
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Tuple, Optional, List
|
||||
import logging
|
||||
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
|
||||
|
||||
logger = logging.getLogger("AgentReportGeneratorQwen")
|
||||
|
||||
class AgentReportGeneratorQwen(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)
|
||||
|
||||
# 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
|
||||
|
||||
# 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é.
|
||||
|
||||
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
|
||||
|
||||
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"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
IMPORTANT: La structure JSON correcte est la partie la plus critique!"""
|
||||
|
||||
# Version du prompt pour la traçabilité
|
||||
self.prompt_version = "qwen-v1.1"
|
||||
|
||||
# Flag pour indiquer si on doit utiliser l'approche en 2 étapes
|
||||
self.use_two_step_approach = True
|
||||
|
||||
# Appliquer la configuration au LLM
|
||||
self._appliquer_config_locale()
|
||||
|
||||
logger.info("AgentReportGeneratorQwen 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)}")
|
||||
|
||||
def _formater_prompt_pour_rapport_etape1(self, ticket_analyse: str, images_analyses: List[Dict]) -> str:
|
||||
"""
|
||||
Formate le prompt pour la première étape: résumé, analyse d'images et synthèse
|
||||
"""
|
||||
num_images = len(images_analyses)
|
||||
logger.info(f"Formatage du prompt étape 1 avec {num_images} analyses d'images")
|
||||
|
||||
# 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.
|
||||
|
||||
## ANALYSE DU TICKET
|
||||
{ticket_analyse}
|
||||
"""
|
||||
|
||||
# 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"
|
||||
|
||||
# Instructions pour le rapport
|
||||
prompt += """
|
||||
## INSTRUCTIONS POUR LE RAPPORT (ÉTAPE 1)
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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}")
|
||||
|
||||
# Créer le répertoire de sortie si nécessaire
|
||||
os.makedirs(rapport_dir, exist_ok=True)
|
||||
|
||||
# 2. EXTRACTION DES DONNÉES
|
||||
ticket_analyse = self._extraire_analyse_ticket(rapport_data)
|
||||
images_analyses = self._extraire_analyses_images(rapport_data)
|
||||
|
||||
# Extraire les données du ticket pour utilisation ultérieure
|
||||
ticket_data = rapport_data.get("ticket_data", {})
|
||||
|
||||
# 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]}"
|
||||
}
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
# 7. SAUVEGARDE DU RAPPORT JSON
|
||||
json_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.json")
|
||||
|
||||
with open(json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(rapport_json, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"Rapport JSON sauvegardé: {json_path}")
|
||||
print(f" Rapport JSON sauvegardé: {json_path}")
|
||||
|
||||
# 8. GÉNÉRATION DU RAPPORT MARKDOWN
|
||||
md_path = generer_rapport_markdown(json_path)
|
||||
|
||||
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")
|
||||
|
||||
return json_path, md_path
|
||||
|
||||
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(traceback.format_exc())
|
||||
print(f" ERREUR: {error_message}")
|
||||
return None, None
|
||||
|
||||
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", "")
|
||||
|
||||
# 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)
|
||||
|
||||
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():
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
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")
|
||||
|
||||
return images_analyses
|
||||
|
||||
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
|
||||
|
||||
# Extraire l'analyse selon le format des données
|
||||
analysis = analyse_data["analysis"]
|
||||
|
||||
# Structure type 1: {"analyse": "texte"}
|
||||
if isinstance(analysis, dict) and "analyse" in analysis:
|
||||
return analysis["analyse"]
|
||||
|
||||
# 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
|
||||
@ -1,301 +0,0 @@
|
||||
from .base_agent import BaseAgent
|
||||
from typing import Dict, Any, Optional
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# 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.
|
||||
|
||||
Ta mission principale :
|
||||
|
||||
1. Identifier le client et le contexte du ticket (demande "name" et "description")
|
||||
- Récupère le nom de l'auteur si présent
|
||||
- Indique si un `user_id` est disponible
|
||||
- Conserve uniquement les informations d'identification utiles (pas d'adresse ou signature de mail inutile)
|
||||
|
||||
2. Mettre en perspective le `name` du ticket
|
||||
- Il peut contenir une ou plusieurs questions implicites
|
||||
- Reformule ces questions de façon explicite
|
||||
|
||||
3. Analyser la `description`
|
||||
- Elle fournit souvent le vrai point d'entrée technique
|
||||
- Repère les formulations interrogatives ou les demandes spécifiques
|
||||
- Identifie si cette partie complète ou précise les questions du nom
|
||||
|
||||
4. Structurer le fil de discussion
|
||||
- Conserve uniquement les échanges pertinents
|
||||
-Conserve les questions soulevés par "name" ou "description"
|
||||
- CONSERVE ABSOLUMENT les références documentation, FAQ, liens utiles et manuels
|
||||
- Identifie clairement chaque intervenant (client / support)
|
||||
- Classe les informations par ordre chronologique avec date et rôle
|
||||
|
||||
5. Préparer la transmission à l'agent suivant
|
||||
- Préserve tous les éléments utiles à l'analyse d'image : modules cités, options évoquées, comportements décrits
|
||||
- Mentionne si des images sont attachées au ticket
|
||||
|
||||
Structure ta réponse :
|
||||
|
||||
1. Résumé du contexte
|
||||
- Client (nom, email si disponible)
|
||||
- Sujet du ticket reformulé en une ou plusieurs questions
|
||||
- Description technique synthétique
|
||||
|
||||
2. Informations techniques détectées
|
||||
- Logiciels/modules mentionnés
|
||||
- Paramètres évoqués
|
||||
- Fonctionnalités impactées
|
||||
- Conditions spécifiques (multi-laboratoire, utilisateur non valide, etc.)
|
||||
|
||||
3. Fil de discussion (filtrée, nettoyée, classée)
|
||||
- Intervenant (Client/Support)
|
||||
- Date et contenu de chaque échange
|
||||
- Résumés techniques
|
||||
- INCLURE TOUS les liens documentaires (manuel, FAQ, documentation technique)
|
||||
|
||||
4. Éléments liés à l'analyse visuelle
|
||||
- Nombre d'images attachées
|
||||
- Références aux interfaces ou options à visualiser
|
||||
- Points à vérifier dans les captures (listes incomplètes, cases à cocher, utilisateurs grisés, etc.)
|
||||
|
||||
IMPORTANT :
|
||||
- Ne propose aucune solution ni interprétation
|
||||
- Ne génère pas de tableau
|
||||
- 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)
|
||||
|
||||
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}"
|
||||
|
||||
# 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}"
|
||||
|
||||
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"""
|
||||
|
||||
try:
|
||||
logger.info("Interrogation du LLM")
|
||||
response = self.llm.interroger(prompt)
|
||||
logger.info(f"Réponse reçue: {len(response)} caractères")
|
||||
print(f" Analyse terminée: {len(response)} caractères")
|
||||
except Exception as e:
|
||||
error_message = f"Erreur lors de l'analyse du ticket: {str(e)}"
|
||||
logger.error(error_message)
|
||||
response = f"ERREUR: {error_message}"
|
||||
print(f" ERREUR: {error_message}")
|
||||
|
||||
# Enregistrer l'historique avec le prompt complet pour la traçabilité
|
||||
self.ajouter_historique("analyse_ticket",
|
||||
{
|
||||
"ticket_id": ticket_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)
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
ticket_data: Les données du ticket
|
||||
|
||||
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')
|
||||
|
||||
info = f"## TICKET {ticket_code}: {ticket_name}\n\n"
|
||||
info += f"## NOM DE LA DEMANDE (PROBLÈME INITIAL)\n{ticket_name}\n\n"
|
||||
|
||||
# Ajouter la description
|
||||
description = ticket_data.get('description', '')
|
||||
if description:
|
||||
info += f"## DESCRIPTION DU PROBLÈME\n{description}\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"
|
||||
|
||||
# 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
|
||||
"""
|
||||
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}"
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""Retourne un timestamp au format YYYYMMDD_HHMMSS"""
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
@ -226,9 +226,34 @@ def sauvegarder_donnees(ticket_id: Optional[str] = None, step_name: str = "", da
|
||||
if is_resultat and data_list:
|
||||
# Extraire le nom du modèle LLM des métadonnées du premier élément
|
||||
first_item = data_list[0]
|
||||
llm_name = first_item.get("metadata", {}).get("model_info", {}).get("model", "unknown_model")
|
||||
llm_name = first_item.get("metadata", {}).get("model_info", {}).get("model", "")
|
||||
|
||||
# Si le modèle est vide ou 'unknown', vérifier s'il existe déjà un fichier pour cette étape
|
||||
# afin d'éviter de créer des doublons avec "unknown_model"
|
||||
if not llm_name or llm_name.lower() == "unknown":
|
||||
# Rechercher un fichier existant pour la même étape
|
||||
existing_files = [f for f in os.listdir(pipeline_dir)
|
||||
if f.startswith(f"{step_name}_") and f.endswith("_results.json")]
|
||||
|
||||
if existing_files:
|
||||
# Utiliser le même nom de modèle que le fichier existant
|
||||
for file in existing_files:
|
||||
if "unknown_model" not in file:
|
||||
# Extraire le nom du modèle du fichier existant
|
||||
parts = file.split('_')
|
||||
if len(parts) > 2:
|
||||
# Reconstruire le nom du modèle (tout ce qui est entre step_name_ et _results.json)
|
||||
model_part = '_'.join(parts[1:-1])
|
||||
llm_name = model_part
|
||||
print(f"Utilisation du nom de modèle existant: {llm_name}")
|
||||
break
|
||||
|
||||
# Si toujours vide ou "unknown", utiliser un nom générique
|
||||
if not llm_name or llm_name.lower() == "unknown":
|
||||
llm_name = "unknown_model"
|
||||
|
||||
# Nettoyer le nom du modèle pour éviter les caractères problématiques dans le nom de fichier
|
||||
safe_llm_name = llm_name.lower().replace("/", "_").replace(" ", "_")
|
||||
safe_llm_name = llm_name.lower().replace("/", "_").replace(" ", "_").replace(":", "_")
|
||||
file_name = f"{step_name}_{safe_llm_name}_results.json"
|
||||
else:
|
||||
file_name = f"{step_name}.json"
|
||||
@ -236,53 +261,76 @@ def sauvegarder_donnees(ticket_id: Optional[str] = None, step_name: str = "", da
|
||||
file_path = os.path.join(pipeline_dir, file_name)
|
||||
|
||||
try:
|
||||
# Charger les données existantes si le fichier existe déjà
|
||||
existing_data = []
|
||||
# Vérifier si le fichier existe déjà pour éviter de créer des doublons
|
||||
if os.path.exists(file_path):
|
||||
print(f"Le fichier {file_path} existe déjà, mise à jour des données")
|
||||
|
||||
# Charger les données existantes
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
file_content = f.read().strip()
|
||||
if file_content: # Vérifier que le fichier n'est pas vide
|
||||
existing_data = json.loads(file_content)
|
||||
# Si les données existantes ne sont pas une liste, les convertir en liste
|
||||
if not isinstance(existing_data, list):
|
||||
existing_data = [existing_data]
|
||||
except json.JSONDecodeError:
|
||||
print(f"Le fichier existant {file_path} n'est pas un JSON valide, création d'un nouveau fichier")
|
||||
existing_data = json.loads(file_content) if file_content else []
|
||||
|
||||
# Si les données existantes ne sont pas une liste, les convertir
|
||||
if not isinstance(existing_data, list):
|
||||
existing_data = [existing_data]
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la lecture des données existantes: {e}")
|
||||
existing_data = []
|
||||
|
||||
# Gérer l'ajout de données et la déduplication
|
||||
updated_data = existing_data.copy()
|
||||
|
||||
# Gérer les entrées existantes pour éviter les doublons
|
||||
if step_name == "rapport_final":
|
||||
# Pour le rapport final, toujours remplacer complètement
|
||||
updated_data = data_list
|
||||
else:
|
||||
# Pour les analyses d'images, vérifier si l'image a déjà été analysée
|
||||
processed_paths = set()
|
||||
existing_data = []
|
||||
|
||||
# Fusionner les nouvelles données avec les données existantes
|
||||
combined_data = existing_data.copy()
|
||||
|
||||
# Pour chaque nouvel élément, vérifier s'il existe déjà un élément similaire
|
||||
for new_item in data_list:
|
||||
# Vérifier si un élément similaire existe déjà
|
||||
is_duplicate = False
|
||||
|
||||
# Filtrer les entrées existantes pour éviter les doublons d'images
|
||||
for item in data_list:
|
||||
image_path = item.get("metadata", {}).get("image_path", "")
|
||||
if image_path:
|
||||
processed_paths.add(image_path)
|
||||
# Supprimer les entrées existantes pour cette image
|
||||
updated_data = [entry for entry in updated_data
|
||||
if entry.get("metadata", {}).get("image_path", "") != image_path]
|
||||
# Ajouter la nouvelle entrée
|
||||
updated_data.append(item)
|
||||
else:
|
||||
# Si pas de chemin d'image, ajouter simplement
|
||||
updated_data.append(item)
|
||||
# Pour la déduplication, on compare certains champs clés
|
||||
if isinstance(new_item, dict):
|
||||
for idx, existing_item in enumerate(existing_data):
|
||||
if isinstance(existing_item, dict):
|
||||
# Comparer les métadonnées pour détecter les doublons
|
||||
new_meta = new_item.get("metadata", {})
|
||||
existing_meta = existing_item.get("metadata", {})
|
||||
|
||||
# Si les deux éléments ont des chemins d'image et que ces chemins sont identiques,
|
||||
# ou s'ils ont le même ticket_id et timestamp, on considère que c'est un doublon
|
||||
if (new_meta.get("image_path") and existing_meta.get("image_path") and
|
||||
new_meta["image_path"] == existing_meta["image_path"]):
|
||||
is_duplicate = True
|
||||
# Mettre à jour l'élément existant avec le nouveau
|
||||
combined_data[idx] = new_item
|
||||
break
|
||||
|
||||
# Pour les rapports finaux, comparer par ticket_id
|
||||
elif (step_name == "rapport_final" and
|
||||
new_meta.get("ticket_id") and existing_meta.get("ticket_id") and
|
||||
new_meta["ticket_id"] == existing_meta["ticket_id"] and
|
||||
new_meta.get("source_agent") == existing_meta.get("source_agent")):
|
||||
is_duplicate = True
|
||||
# Mettre à jour l'élément existant avec le nouveau
|
||||
combined_data[idx] = new_item
|
||||
break
|
||||
|
||||
# Si ce n'est pas un doublon, l'ajouter aux données combinées
|
||||
if not is_duplicate:
|
||||
combined_data.append(new_item)
|
||||
|
||||
# Si on n'a qu'un seul élément à la fin, le sortir de la liste pour garder
|
||||
# la même structure que précédemment
|
||||
final_data = combined_data[0] if len(combined_data) == 1 else combined_data
|
||||
|
||||
# Sauvegarder les données combinées
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
json.dump(updated_data, f, indent=2, ensure_ascii=False)
|
||||
print(f"Données sauvegardées dans {file_path} ({len(updated_data)} entrées)")
|
||||
json.dump(final_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# Générer une version texte pour tous les fichiers JSON
|
||||
generer_version_texte(updated_data, ticket_id, step_name, file_path)
|
||||
print(f"Données sauvegardées dans {file_path} ({len(combined_data)} entrées)")
|
||||
|
||||
# Générer également une version texte pour faciliter la lecture
|
||||
generer_version_texte(final_data, ticket_id, step_name, file_path)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la sauvegarde des données: {e}")
|
||||
print(f"Erreur lors de la sauvegarde des données dans {file_path}: {e}")
|
||||
File diff suppressed because one or more lines are too long
33
debug/rapport_debug_T11143_en.txt
Normal file
33
debug/rapport_debug_T11143_en.txt
Normal file
@ -0,0 +1,33 @@
|
||||
**Cross Report**
|
||||
|
||||
**Summary**
|
||||
The support ticket analysis reveals insufficient content for a thorough analysis, but the provided image suggests that the issue might be related to Tomcat setup or configuration.
|
||||
|
||||
**Chronological Table of Exchanges**
|
||||
|
||||
| Transmitter | Type | Date | Contents | Visual elements |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Customer | Question | Unknown | Insufficient content for analysis | - |
|
||||
| Support | Request for more information | Unknown | Request for additional details to assist with the issue | - |
|
||||
| Customer | Response | 2025-04-23 16:36:57 | Provided image (image.png) of Tomcat default home page | Screenshot of Tomcat default home page, indicating successful setup |
|
||||
|
||||
**Image Analysis**
|
||||
|
||||
The provided image is a screenshot of the Tomcat default home page, which indicates that Tomcat has been set up successfully. The page displays technical information such as:
|
||||
|
||||
* Tomcat version: 7
|
||||
* `catalina_home` rental: `/usr/share/tomcat7`
|
||||
* `catalina_base` rental: `/var/lib/tomcat7`
|
||||
* Reference to `/etc/tomcat7/tomcat-users.xml`
|
||||
|
||||
**Technical Context**
|
||||
|
||||
The image suggests that the issue might be related to Tomcat setup or configuration. The fact that the default Tomcat home page is displayed indicates that Tomcat is running, but there may be issues with specific applications or configurations.
|
||||
|
||||
**Recommendations**
|
||||
|
||||
* Request additional information from the customer to clarify the issue.
|
||||
* Verify the Tomcat configuration and setup to ensure it matches the expected settings.
|
||||
* Consider installing suggested packages (Tomcat 7 Docs, Tomcat 7 Example, Tomcat 7 Admin) to provide additional functionality.
|
||||
|
||||
Note: The table only includes one exchange between the customer and support, as there is insufficient information to create a more detailed chronological table.
|
||||
33
debug/rapport_debug_T11143_fr.txt
Normal file
33
debug/rapport_debug_T11143_fr.txt
Normal file
@ -0,0 +1,33 @@
|
||||
** Rapport croisé **
|
||||
|
||||
**Résumé**
|
||||
L'analyse des billets de support révèle un contenu insuffisant pour une analyse approfondie, mais l'image fournie suggère que le problème pourrait être lié à la configuration ou à la configuration de Tomcat.
|
||||
|
||||
** Tableau chronologique des échanges **
|
||||
|
||||
| Émetteur | Type | Date | Contenu | Éléments visuels |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Client | Question | Inconnu | Contenu insuffisant pour l'analyse | - |
|
||||
| Support | Demande de plus d'informations | Inconnu | Demande de détails supplémentaires pour aider le problème | - |
|
||||
| Client | Réponse | 2025-04-23 16:36:57 | Image fournie (image.png) de la page d'accueil par défaut de Tomcat | Capture d'écran de la page d'accueil par défaut de Tomcat, indiquant une configuration réussie |
|
||||
|
||||
** Analyse d'image **
|
||||
|
||||
L'image fournie est une capture d'écran de la page d'accueil par défaut de Tomcat, ce qui indique que Tomcat a été configuré avec succès. La page affiche des informations techniques telles que:
|
||||
|
||||
* Version Tomcat: 7
|
||||
* `Catalina_Home` Rental:` / usr / share / tomcat7`
|
||||
* `Catalina_Base` Rental:` / var / lib / tomcat7`
|
||||
* Référence à `/ etc / tomcat7 / tomcat-users.xml`
|
||||
|
||||
** Contexte technique **
|
||||
|
||||
L'image suggère que le problème pourrait être lié à la configuration ou à la configuration de Tomcat. Le fait que la page d'accueil de Tomcat par défaut s'affiche indique que Tomcat est en cours d'exécution, mais il peut y avoir des problèmes avec des applications ou des configurations spécifiques.
|
||||
|
||||
** Recommandations **
|
||||
|
||||
* Demandez des informations supplémentaires au client pour clarifier le problème.
|
||||
* Vérifiez la configuration et la configuration de Tomcat pour vous assurer qu'elle correspond aux paramètres attendus.
|
||||
* Envisagez d'installer des packages suggérés (Tomcat 7 Docs, Tomcat 7 Exemple, Tomcat 7 Admin) pour fournir des fonctionnalités supplémentaires.
|
||||
|
||||
Remarque: Le tableau ne comprend qu'un seul échange entre le client et le support, car il n'y a pas suffisamment d'informations pour créer un tableau chronologique plus détaillé.
|
||||
25
debug_ocr/ocr_image.png.txt
Normal file
25
debug_ocr/ocr_image.png.txt
Normal file
@ -0,0 +1,25 @@
|
||||
OCR Langue: eng
|
||||
Langue détectée: en
|
||||
--------------------------------------------------
|
||||
(© Ocumam B\o
|
||||
|
||||
¢2@6
|
||||
©) Oem Gap Pim Otam
|
||||
|
||||
lt works !
|
||||
|
||||
If you're seeing this page via a web browser, it means you've setup Tomcat successfully. Congratulations!
|
||||
|
||||
This is the default Tomcat home page. It can be found on the local filesystem at: /var/Lib/tomcat7/webapps/ROOT/ index.html
|
||||
|
||||
Tomcat? veterans might be pleased to learn that this system instance of Tomcat is installed with CATALINA_HOME in /usr/share/tomcat7 and CATALINA_BASE in /var/lib/tomcat7, following the rules from /usr/share/doc/tomcat?-common/ RUNNING. txt .gz.
|
||||
|
||||
You might consider installing the following packages, if you haven't already done so:
|
||||
|
||||
tomcat?7-docs: This package installs a web application that allows to browse the Tomcat 7 documentation locally. Once imstalled, you can access it by clicking here.
|
||||
|
||||
tomcat7-examples: This package installs a web application that allows to access the Tomcat 7 Servlet and JSP examples. Once installed, you can access it by clicking here.
|
||||
|
||||
tomecat7-admin: This package installs two web applications that can help managing this Tomcat instance. Once installed, you can access the manager webapp and the host-manager webapp.
|
||||
|
||||
NOTE: For security reasons, using the manager webapp is restricted to users with role "manager-gui"_ The host-manager webapp is restricted to users with role "admin-gui". Users are defined in /etc/tomcat7/tomcat-users.»xml_
|
||||
6
debug_ocr/ocr_image_145435.png.txt
Normal file
6
debug_ocr/ocr_image_145435.png.txt
Normal file
@ -0,0 +1,6 @@
|
||||
OCR Langue: fra
|
||||
Langue détectée: es
|
||||
--------------------------------------------------
|
||||
Mlnpoisbiañe
|
||||
|
||||
2 0 PL EURE 0
|
||||
BIN
debug_ocr/optimized_image.png
Normal file
BIN
debug_ocr/optimized_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 251 KiB |
BIN
debug_ocr/optimized_image_145435.png
Normal file
BIN
debug_ocr/optimized_image_145435.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 199 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 93 KiB |
BIN
debug_ocr/standard_image.png
Normal file
BIN
debug_ocr/standard_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 202 KiB |
BIN
debug_ocr/standard_image_145435.png
Normal file
BIN
debug_ocr/standard_image_145435.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
@ -1,62 +1,143 @@
|
||||
from .base_llm import BaseLLM
|
||||
import requests
|
||||
import base64
|
||||
"""
|
||||
Module for Llama Vision support.
|
||||
Optimized for English-only mode, eliminating intermediate translations.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
import base64
|
||||
import requests
|
||||
from typing import Dict, Any, Optional, List, Union
|
||||
from PIL import Image
|
||||
import io
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, Any
|
||||
|
||||
from .base_llm import BaseLLM
|
||||
|
||||
logger = logging.getLogger("LlamaVision")
|
||||
|
||||
class LlamaVision(BaseLLM):
|
||||
"""
|
||||
Classe optimisée pour interagir avec l'API Llama Vision.
|
||||
Interface class with Llama Vision model via its API.
|
||||
Optimized to work exclusively in English.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, modele: str = "llama3.2-vision:90b-instruct-q8_0"):
|
||||
super().__init__(modele)
|
||||
self.api_url = "http://217.182.105.173:11434/api/generate"
|
||||
|
||||
# Timeout de requête augmenté pour les images volumineuses
|
||||
self.request_timeout = 300 # 5 minutes
|
||||
|
||||
# Configuration standard via la méthode héritée
|
||||
# Default configuration optimized for English
|
||||
self.configurer(
|
||||
temperature=0.1,
|
||||
temperature=0.2,
|
||||
top_p=0.8,
|
||||
max_tokens=1024,
|
||||
max_tokens=4000
|
||||
)
|
||||
|
||||
|
||||
# Request timeout in seconds
|
||||
self.request_timeout = 600
|
||||
|
||||
# Default system prompt (in English)
|
||||
self.prompt_system = """You are a helpful AI assistant with vision capabilities.
|
||||
You are interacting with images provided by the user.
|
||||
Respond in English unless specifically instructed otherwise."""
|
||||
|
||||
logger.info(f"Initializing LlamaVision with model {modele} (English-only mode)")
|
||||
|
||||
def urlBase(self) -> str:
|
||||
"""
|
||||
Retourne l'URL de base de l'API Ollama.
|
||||
Returns the base URL of the Ollama API.
|
||||
"""
|
||||
return "http://217.182.105.173:11434/"
|
||||
|
||||
def cleAPI(self) -> str:
|
||||
"""
|
||||
Ollama ne nécessite pas de clé API par défaut.
|
||||
Ollama doesn't require an API key by default.
|
||||
"""
|
||||
return ""
|
||||
|
||||
def urlFonction(self) -> str:
|
||||
"""
|
||||
Retourne l'URL spécifique à Ollama pour générer une réponse.
|
||||
Returns the specific Ollama URL for generating a response.
|
||||
"""
|
||||
return "api/generate"
|
||||
|
||||
|
||||
def _encoder_image_base64(self, chemin_image: str) -> str:
|
||||
"""
|
||||
Encodes an image in base64 for the API.
|
||||
|
||||
Args:
|
||||
chemin_image: Path to the image to encode
|
||||
|
||||
Returns:
|
||||
Base64 encoded image or empty string in case of error
|
||||
"""
|
||||
try:
|
||||
# Check image size and reduce if too large
|
||||
with Image.open(chemin_image) as img:
|
||||
# If the image is too large, resize it
|
||||
max_dim = 800 # Maximum dimension
|
||||
width, height = img.size
|
||||
|
||||
if width > max_dim or height > max_dim:
|
||||
# Calculate ratio to maintain proportions
|
||||
ratio = min(max_dim / width, max_dim / height)
|
||||
new_width = int(width * ratio)
|
||||
new_height = int(height * ratio)
|
||||
|
||||
# Resize image
|
||||
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
|
||||
# Convert to RGB if necessary (for formats like PNG with alpha channel)
|
||||
if img.mode in ("RGBA", "LA", "P"):
|
||||
# Create a white background and compose the image on top to handle transparency
|
||||
background = Image.new("RGB", img.size, (255, 255, 255))
|
||||
if img.mode == "P":
|
||||
img = img.convert("RGBA")
|
||||
background.paste(img, mask=img.split()[3] if img.mode == "RGBA" else None)
|
||||
img = background
|
||||
elif img.mode != "RGB":
|
||||
img = img.convert("RGB")
|
||||
|
||||
# Temporarily save the resized image
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format="JPEG", quality=85)
|
||||
buffer.seek(0)
|
||||
|
||||
# Encode in base64
|
||||
encoded = base64.b64encode(buffer.read()).decode("utf-8")
|
||||
return encoded
|
||||
except Exception as e:
|
||||
logger.error(f"Base64 encoding error for {chemin_image}: {e}")
|
||||
try:
|
||||
# Second attempt with a simpler approach
|
||||
with Image.open(chemin_image) as img:
|
||||
# Convert directly to RGB regardless of the image
|
||||
img = img.convert("RGB")
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format="JPEG", quality=75)
|
||||
buffer.seek(0)
|
||||
encoded = base64.b64encode(buffer.read()).decode("utf-8")
|
||||
return encoded
|
||||
except Exception as e2:
|
||||
logger.error(f"Second error during image optimization: {str(e2)}")
|
||||
# Last resort: encode the original image without optimization
|
||||
with open(chemin_image, "rb") as image_file:
|
||||
encoded = base64.b64encode(image_file.read()).decode("utf-8")
|
||||
return encoded
|
||||
|
||||
def _preparer_contenu(self, question: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Prépare le contenu de la requête spécifique pour Ollama avec le modèle Llama Vision.
|
||||
Prepares the request content specific to Ollama.
|
||||
"""
|
||||
contenu = {
|
||||
return {
|
||||
"model": self.modele,
|
||||
"prompt": question,
|
||||
"options": {
|
||||
"temperature": self.params["temperature"],
|
||||
"top_p": self.params["top_p"],
|
||||
"temperature": self.params.get("temperature", 0.2),
|
||||
"top_p": self.params.get("top_p", 0.8),
|
||||
"num_predict": self.params.get("max_tokens", 1024),
|
||||
"stop": self.params.get("stop", []),
|
||||
# Paramètres Ollama spécifiques avec valeurs par défaut
|
||||
"top_k": 30,
|
||||
"num_ctx": 1024,
|
||||
"repeat_penalty": 1.1,
|
||||
@ -70,156 +151,84 @@ class LlamaVision(BaseLLM):
|
||||
},
|
||||
"stream": False
|
||||
}
|
||||
return contenu
|
||||
|
||||
|
||||
def _traiter_reponse(self, reponse: requests.Response) -> str:
|
||||
"""
|
||||
Traite et retourne la réponse fournie par Ollama.
|
||||
Processes and returns the response provided by Ollama.
|
||||
"""
|
||||
data = reponse.json()
|
||||
return data.get("response", "")
|
||||
|
||||
def _encoder_image_base64(self, image_path: str) -> str:
|
||||
|
||||
def interroger_avec_image(self, image_path: str, question: str, english_only: bool = True) -> str:
|
||||
"""
|
||||
Encode une image en base64, avec optimisation de la taille si nécessaire.
|
||||
"""
|
||||
try:
|
||||
# Vérifier la taille de l'image et la réduire si trop grande
|
||||
with Image.open(image_path) as img:
|
||||
# Si l'image est trop grande, la redimensionner
|
||||
max_dim = 800 # Dimension maximale
|
||||
width, height = img.size
|
||||
|
||||
if width > max_dim or height > max_dim:
|
||||
# Calculer le ratio pour conserver les proportions
|
||||
ratio = min(max_dim / width, max_dim / height)
|
||||
new_width = int(width * ratio)
|
||||
new_height = int(height * ratio)
|
||||
|
||||
# Redimensionner l'image
|
||||
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
|
||||
# Convertir en RGB si nécessaire (pour les formats comme PNG avec canal alpha)
|
||||
if img.mode in ("RGBA", "LA", "P"):
|
||||
# Créer un fond blanc et composer l'image dessus pour gérer la transparence
|
||||
background = Image.new("RGB", img.size, (255, 255, 255))
|
||||
if img.mode == "P":
|
||||
img = img.convert("RGBA")
|
||||
background.paste(img, mask=img.split()[3] if img.mode == "RGBA" else None)
|
||||
img = background
|
||||
elif img.mode != "RGB":
|
||||
img = img.convert("RGB")
|
||||
|
||||
# Sauvegarder temporairement l'image redimensionnée
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format="JPEG", quality=85)
|
||||
buffer.seek(0)
|
||||
|
||||
# Encoder en base64
|
||||
encoded = base64.b64encode(buffer.read()).decode("utf-8")
|
||||
return encoded
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de l'optimisation de l'image: {str(e)}")
|
||||
try:
|
||||
# Seconde tentative avec une approche plus simple
|
||||
with Image.open(image_path) as img:
|
||||
# Convertir directement en RGB quelle que soit l'image
|
||||
img = img.convert("RGB")
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format="JPEG", quality=75)
|
||||
buffer.seek(0)
|
||||
encoded = base64.b64encode(buffer.read()).decode("utf-8")
|
||||
return encoded
|
||||
except Exception as e2:
|
||||
print(f"Deuxième erreur lors de l'optimisation de l'image: {str(e2)}")
|
||||
# Dernier recours: encoder l'image originale sans optimisation
|
||||
with open(image_path, "rb") as image_file:
|
||||
encoded = base64.b64encode(image_file.read()).decode("utf-8")
|
||||
return encoded
|
||||
|
||||
def _optimiser_prompt(self, question: str) -> str:
|
||||
"""
|
||||
Optimise le prompt pour des réponses plus rapides.
|
||||
Ne modifie pas la langue ou le contenu de la question.
|
||||
"""
|
||||
# On retourne la question telle quelle sans imposer de format
|
||||
return question
|
||||
|
||||
def interroger_avec_image(self, image_path: str, question: str) -> str:
|
||||
"""
|
||||
Interroge le modèle Llama Vision avec une image et une question via Ollama.
|
||||
Optimisé pour éviter les timeouts.
|
||||
Sends a multimodal request (image + text) to the API.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image à analyser
|
||||
question: Question ou instructions pour l'analyse
|
||||
image_path: Path to the image
|
||||
question: The prompt to send
|
||||
english_only: If True, forces the response in English
|
||||
|
||||
Returns:
|
||||
Réponse du modèle à la question
|
||||
Model response
|
||||
"""
|
||||
url = self.urlBase() + self.urlFonction()
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
|
||||
# Check that the image exists
|
||||
if not os.path.exists(image_path):
|
||||
logger.error(f"Image does not exist: {image_path}")
|
||||
return f"Error: image {image_path} does not exist."
|
||||
|
||||
try:
|
||||
# Encoder l'image en base64 avec optimisation de taille
|
||||
# Encode image in base64
|
||||
image_b64 = self._encoder_image_base64(image_path)
|
||||
|
||||
# Optimiser la question pour des réponses plus courtes et plus rapides
|
||||
optimised_question = self._optimiser_prompt(question)
|
||||
# Ensure the question starts with an instruction to respond in English
|
||||
if english_only and not question.lower().startswith("[english"):
|
||||
question = "[ENGLISH RESPONSE REQUESTED]\n\n" + question
|
||||
|
||||
# Préparer le prompt avec le format spécial pour Ollama multimodal
|
||||
# Special format for Ollama multimodal
|
||||
prompt = f"""<image>
|
||||
{image_b64}
|
||||
</image>
|
||||
|
||||
{optimised_question}"""
|
||||
|
||||
contenu = {
|
||||
"model": self.modele,
|
||||
"prompt": prompt,
|
||||
"options": {
|
||||
"temperature": self.params["temperature"],
|
||||
"top_p": self.params["top_p"],
|
||||
"num_predict": self.params.get("max_tokens", 1024),
|
||||
"stop": self.params.get("stop", []),
|
||||
# Paramètres Ollama spécifiques
|
||||
"top_k": 30,
|
||||
"num_ctx": 1024,
|
||||
"repeat_penalty": 1.1,
|
||||
"repeat_last_n": 64,
|
||||
"mirostat": 0,
|
||||
"mirostat_eta": 0.1,
|
||||
"mirostat_tau": 5,
|
||||
"keep_alive": int(timedelta(minutes=2).total_seconds()),
|
||||
"min_p": 0,
|
||||
"seed": 0,
|
||||
},
|
||||
"stream": False
|
||||
}
|
||||
|
||||
self.heureDepart = datetime.now()
|
||||
response = requests.post(url=url, headers=headers, json=contenu, timeout=self.request_timeout)
|
||||
self.heureFin = datetime.now()
|
||||
{question}"""
|
||||
|
||||
# Prepare the request using the base method
|
||||
contenu = self._preparer_contenu(prompt)
|
||||
|
||||
self.heureDepart = datetime.now()
|
||||
|
||||
# Send request
|
||||
response = requests.post(
|
||||
url=url,
|
||||
headers=headers,
|
||||
json=contenu,
|
||||
timeout=self.request_timeout
|
||||
)
|
||||
|
||||
self.heureFin = datetime.now()
|
||||
if self.heureDepart is not None:
|
||||
self.dureeTraitement = self.heureFin - self.heureDepart
|
||||
else:
|
||||
self.dureeTraitement = timedelta(0)
|
||||
|
||||
|
||||
# Response verification
|
||||
if response.status_code in [200, 201]:
|
||||
self.reponseErreur = False
|
||||
return self._traiter_reponse(response)
|
||||
else:
|
||||
self.reponseErreur = True
|
||||
return f"Erreur API ({response.status_code}): {response.text}"
|
||||
|
||||
return f"LlamaVision API Error ({response.status_code}): {response.text}"
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
self.heureFin = datetime.now()
|
||||
if self.heureDepart is not None:
|
||||
self.dureeTraitement = self.heureFin - self.heureDepart
|
||||
else:
|
||||
self.dureeTraitement = timedelta(0)
|
||||
self.reponseErreur = True
|
||||
return "Timeout lors de l'appel à l'API. L'analyse de l'image a pris trop de temps."
|
||||
return "Timeout during API call. Image analysis took too long."
|
||||
|
||||
except Exception as e:
|
||||
self.heureFin = datetime.now()
|
||||
@ -228,16 +237,4 @@ class LlamaVision(BaseLLM):
|
||||
else:
|
||||
self.dureeTraitement = timedelta(0)
|
||||
self.reponseErreur = True
|
||||
return f"Erreur lors de l'analyse de l'image: {str(e)}"
|
||||
|
||||
def configurer(self, **kwargs):
|
||||
"""
|
||||
Mise à jour des paramètres du modèle.
|
||||
Utilise la méthode de BaseLLM pour mettre à jour les paramètres.
|
||||
"""
|
||||
# Si request_timeout est spécifié, le mettre à jour séparément
|
||||
if "request_timeout" in kwargs and isinstance(kwargs["request_timeout"], int):
|
||||
self.request_timeout = kwargs.pop("request_timeout")
|
||||
|
||||
# Utiliser la méthode de la classe parente pour mettre à jour les paramètres standards
|
||||
super().configurer(**kwargs)
|
||||
return f"Multimodal communication error: {str(e)}"
|
||||
@ -26,13 +26,19 @@ def parse_arguments():
|
||||
|
||||
parser.add_argument("ticket_id", help="ID du ticket à analyser (ex: T1234)")
|
||||
parser.add_argument("--output_dir", default="output", help="Répertoire de sortie (par défaut:)")
|
||||
#Options de configuration
|
||||
|
||||
# Options de configuration
|
||||
parser.add_argument("--no-dedup", action="store_true", help="Désactiver le préfiltrage des doublons d'images")
|
||||
parser.add_argument("--seuil", type=int, default=5, help="Seuil de similarité pour la déduplication (0-10)")
|
||||
parser.add_argument("--save-results", action="store_true", help="Sauvegarder les résultats intermédiaires")
|
||||
parser.add_argument("--debug", action="store_true", help="Activer le mode debug")
|
||||
parser.add_argument("--reports-dir", default="reports", help="Répertoire de suvegarde des rapports")
|
||||
# Etapes falcutatives
|
||||
|
||||
# Options OCR et traduction
|
||||
parser.add_argument("--no-ocr", action="store_true", help="Désactiver l'OCR sur les images")
|
||||
parser.add_argument("--no-english", action="store_true", help="Utiliser la langue originale au lieu de l'anglais")
|
||||
|
||||
# Etapes facultatives
|
||||
parser.add_argument("--skip-ticket-analysis", action="store_true", help="Sauter l'analyse du ticket")
|
||||
parser.add_argument("--skip-image-sorting", action="store_true", help="Sauter le tri d'images")
|
||||
parser.add_argument("--skip-image-analysis", action="store_true", help="Sauter l'analyse des images")
|
||||
@ -55,7 +61,9 @@ def main():
|
||||
"dedup_threshold": args.seuil,
|
||||
"save_results": args.save_results,
|
||||
"debug_mode": args.debug,
|
||||
"reports_dir": args.reports_dir
|
||||
"reports_dir": args.reports_dir,
|
||||
"ocr_enabled": not args.no_ocr,
|
||||
"english_only": not args.no_english
|
||||
}
|
||||
|
||||
logger.info(f"Configuration: {json.dumps(config, indent=2)}")
|
||||
|
||||
@ -3,11 +3,13 @@ import json
|
||||
import logging
|
||||
import time
|
||||
import traceback
|
||||
from typing import List, Dict, Any, Optional
|
||||
from typing import List, Dict, Any, Optional, Tuple
|
||||
|
||||
from agents.base_agent import BaseAgent
|
||||
from loaders.ticket_data_loader import TicketDataLoader
|
||||
from utils.image_dedup import filtrer_images_uniques
|
||||
from utils.ocr_utils import extraire_texte
|
||||
from utils.translate_utils import fr_to_en, en_to_fr, sauvegarder_ocr_traduction
|
||||
|
||||
# Configuration du logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
@ -38,7 +40,9 @@ class OrchestratorLlamaVision:
|
||||
"dedup_threshold": 5,
|
||||
"save_results": True,
|
||||
"debug_mode": False,
|
||||
"reports_dir": "reports"
|
||||
"reports_dir": "reports",
|
||||
"ocr_enabled": True,
|
||||
"english_only": True
|
||||
}
|
||||
|
||||
if config:
|
||||
@ -78,6 +82,17 @@ class OrchestratorLlamaVision:
|
||||
if not ticket_data:
|
||||
return
|
||||
|
||||
# Ajouter le chemin du fichier JSON au ticket_data pour faciliter l'extraction du ticket_id
|
||||
if json_path:
|
||||
ticket_data["file_path"] = json_path
|
||||
|
||||
# Traduire le contenu du ticket en anglais avant l'analyse
|
||||
if self.config.get("english_only") and ticket_data.get("content"):
|
||||
logger.info(f"Traduction du contenu du ticket {ticket_id} en anglais")
|
||||
ticket_data["content_original"] = ticket_data["content"]
|
||||
ticket_data["content"] = fr_to_en(ticket_data["content"])
|
||||
logger.info(f"Traduction terminée: {len(ticket_data['content'])} caractères")
|
||||
|
||||
ticket_analysis = None
|
||||
if self.ticket_agent:
|
||||
try:
|
||||
@ -88,11 +103,41 @@ class OrchestratorLlamaVision:
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
images_analyses, relevant_images = {}, []
|
||||
ocr_results = {}
|
||||
|
||||
if os.path.exists(attachments_dir):
|
||||
images = self._lister_images(attachments_dir)
|
||||
if self.config.get("dedup_enabled", True):
|
||||
images = filtrer_images_uniques(images, seuil_hamming=self.config["dedup_threshold"], ticket_id=ticket_id)
|
||||
|
||||
# Réaliser l'OCR sur toutes les images avant le tri
|
||||
if self.config.get("ocr_enabled", True):
|
||||
logger.info(f"Traitement OCR de {len(images)} images")
|
||||
for img in images:
|
||||
try:
|
||||
ocr_fr, langue = extraire_texte(img, lang="auto")
|
||||
|
||||
# Traduire le texte extrait en anglais pour une meilleure analyse
|
||||
ocr_en = fr_to_en(ocr_fr) if ocr_fr else ""
|
||||
|
||||
# Traduire à nouveau en français pour vérification (optionnel)
|
||||
ocr_en_back_fr = en_to_fr(ocr_en) if ocr_en else ""
|
||||
|
||||
# Sauvegarder les résultats OCR
|
||||
sauvegarder_ocr_traduction(img, ticket_id, ocr_fr, ocr_en, ocr_en_back_fr, base_dir=rapport_dir)
|
||||
|
||||
# Stocker le résultat de l'OCR pour utilisation ultérieure
|
||||
ocr_results[img] = {
|
||||
"texte_fr": ocr_fr,
|
||||
"texte_en": ocr_en,
|
||||
"langue_detectee": langue
|
||||
}
|
||||
|
||||
logger.info(f"OCR terminé pour {os.path.basename(img)}: {len(ocr_fr)} caractères ({langue})")
|
||||
except Exception as e:
|
||||
logger.warning(f"Erreur OCR pour {os.path.basename(img)}: {e}")
|
||||
ocr_results[img] = {"texte_fr": "", "texte_en": "", "langue_detectee": "unknown"}
|
||||
|
||||
# Traiter toutes les images avec l'agent de tri
|
||||
if self.image_sorter:
|
||||
logger.info(f"Traitement de {len(images)} images uniques avec l'agent de tri")
|
||||
@ -100,7 +145,9 @@ class OrchestratorLlamaVision:
|
||||
# Analyser toutes les images
|
||||
for img in images:
|
||||
try:
|
||||
result_sort = self.image_sorter.executer(img)
|
||||
# Inclure l'OCR avec le chemin de l'image pour aider au tri
|
||||
ocr_context = ocr_results.get(img, {"texte_en": ""}).get("texte_en", "")
|
||||
result_sort = self.image_sorter.executer(img, ocr_context=ocr_context)
|
||||
is_relevant = result_sort.get("is_relevant", True)
|
||||
|
||||
if is_relevant:
|
||||
@ -108,7 +155,8 @@ class OrchestratorLlamaVision:
|
||||
|
||||
images_analyses[img] = {
|
||||
"sorting": result_sort or {"is_relevant": True},
|
||||
"analysis": None
|
||||
"analysis": None,
|
||||
"ocr": ocr_results.get(img, {})
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"Erreur tri image {os.path.basename(img)}: {e}")
|
||||
@ -132,14 +180,19 @@ class OrchestratorLlamaVision:
|
||||
for img in images:
|
||||
images_analyses[img] = {
|
||||
"sorting": {"is_relevant": True},
|
||||
"analysis": None
|
||||
"analysis": None,
|
||||
"ocr": ocr_results.get(img, {})
|
||||
}
|
||||
|
||||
# Analyser les images pertinentes avec l'agent d'analyse d'images
|
||||
if self.image_analyser and ticket_analysis:
|
||||
for img in relevant_images:
|
||||
try:
|
||||
result = self.image_analyser.executer(img, contexte=ticket_analysis)
|
||||
# Intégrer les résultats de l'OCR dans le contexte
|
||||
ocr_info = ocr_results.get(img, {})
|
||||
contexte_enrichi = self._enrichir_contexte(ticket_analysis, ocr_info)
|
||||
|
||||
result = self.image_analyser.executer(img, contexte=contexte_enrichi)
|
||||
images_analyses[img]["analysis"] = result
|
||||
except Exception as e:
|
||||
logger.warning(f"Erreur analyse image {os.path.basename(img)}: {e}")
|
||||
@ -181,6 +234,35 @@ class OrchestratorLlamaVision:
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
logger.info(f"Traitement terminé pour le ticket {ticket_id}")
|
||||
|
||||
def _enrichir_contexte(self, ticket_analysis: Dict[str, Any], ocr_info: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Enrichit le contexte de l'analyse du ticket avec les informations OCR
|
||||
|
||||
Args:
|
||||
ticket_analysis: Résultat de l'analyse du ticket
|
||||
ocr_info: Informations OCR d'une image
|
||||
|
||||
Returns:
|
||||
Contexte enrichi
|
||||
"""
|
||||
if not isinstance(ticket_analysis, dict):
|
||||
return ticket_analysis
|
||||
|
||||
# Créer une copie pour ne pas modifier l'original
|
||||
contexte_enrichi = ticket_analysis.copy()
|
||||
|
||||
# Ajouter les informations OCR
|
||||
if ocr_info:
|
||||
contexte_enrichi["ocr_info"] = ocr_info
|
||||
|
||||
# Utiliser la version anglaise du texte OCR pour l'analyse
|
||||
if self.config.get("english_only") and ocr_info.get("texte_en"):
|
||||
contexte_enrichi["ocr_text"] = ocr_info["texte_en"]
|
||||
else:
|
||||
contexte_enrichi["ocr_text"] = ocr_info.get("texte_fr", "")
|
||||
|
||||
return contexte_enrichi
|
||||
|
||||
def _charger_ticket(self, json_path: Optional[str]) -> Optional[Dict[str, Any]]:
|
||||
if not json_path:
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"translation_en_back_fr": "",
|
||||
"metadata": {
|
||||
"ticket_id": "T11143",
|
||||
"timestamp": "20250423_142039",
|
||||
"timestamp": "20250423_163703",
|
||||
"source_module": "ocr_utils + translate_utils",
|
||||
"lang_detected": "fr"
|
||||
}
|
||||
@ -5,7 +5,7 @@
|
||||
"translation_en_back_fr": "",
|
||||
"metadata": {
|
||||
"ticket_id": "T11143",
|
||||
"timestamp": "20250423_142058",
|
||||
"timestamp": "20250423_163703",
|
||||
"source_module": "ocr_utils + translate_utils",
|
||||
"lang_detected": "fr"
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
{
|
||||
"image_name": "image.png",
|
||||
"ocr_fr": "(© Ocumam B\\o\n\n¢2@6\n©) Oem Gap Pim Otam\n\nlt works !\n\nIf you're seeing this page via a web browser, it means you've setup Tomcat successfully. Congratulations!\n\nThis is the default Tomcat home page. It can be found on the local filesystem at: /var/Lib/tomcat7/webapps/ROOT/ index.html\n\nTomcat? veterans might be pleased to learn that this system instance of Tomcat is installed with CATALINA_HOME in /usr/share/tomcat7 and CATALINA_BASE in /var/lib/tomcat7, following the rules from /usr/share/doc/tomcat?-common/ RUNNING. txt .gz.\n\nYou might consider installing the following packages, if you haven't already done so:\n\ntomcat?7-docs: This package installs a web application that allows to browse the Tomcat 7 documentation locally. Once imstalled, you can access it by clicking here.\n\ntomcat7-examples: This package installs a web application that allows to access the Tomcat 7 Servlet and JSP examples. Once installed, you can access it by clicking here.\n\ntomecat7-admin: This package installs two web applications that can help managing this Tomcat instance. Once installed, you can access the manager webapp and the host-manager webapp.\n\nNOTE: For security reasons, using the manager webapp is restricted to users with role \"manager-gui\"_ The host-manager webapp is restricted to users with role \"admin-gui\". Users are defined in /etc/tomcat7/tomcat-users.»xml_",
|
||||
"translation_en": "(© OCUMAM B \\ O\n\n¢ 2@6\n©) OEM GAP PIM OTAM\n\nLt Works!\n\nIf you are seeing this page via a web browser, it means you've setup tomcat successfully. Congratulations!\n\nThis is the Default Tomcat Home Page. It can be found on the local Filesystem at:/var/lib/tomcat7/webapps/root/index.html\n\nTomcat? veterans might be pleased to read this system instance of tomcat is installed with catalina_home in/usr/share/tomcat7 and catalina_base in/var/lib/tomcat7, following the rules from/usr/share/doc/tomcat? -common/Running. txt .gz.\n\nYou might consider installing the following packages, if you have alreni done so:\n\nTomcat? 7-Docs: This Package Installes A Web Application that Allows to Browse the Tomcat 7 Locally documentation. Once Imstalled, you can access it by clicking here.\n\nTomcat7-Example: This Package Installes A Web Application that Allows to Access the Tomcat 7 Servlet and JSP Examples. Once Installed, you can access it by clicking here.\n\nTomecat7-Admin: this package installations Two Web applications that can help managing this tomcat instance. Once Installed, you can access the Manager Webapp and the Host-Manager Webapp.\n\nNote: For Security Reasons, Using the Manager Webapp is restricted to users with role \"manager-gui\" _ the host-manager webapp is restricted to users with role \"admin-gui\". USERS are defined in /etc/tomcat7/tomcat-users.c",
|
||||
"translation_en_back_fr": "(© ocumam b \\ o\n\n¢ 2 @ 6\n©) OEM GAP PIM OTAM\n\nLT fonctionne!\n\nSi vous voyez cette page via un navigateur Web, cela signifie que vous avez configuré Tomcat avec succès. Félicitations!\n\nIl s'agit de la page d'accueil par défaut de Tomcat. Il peut être trouvé sur le système de fichiers local à: /var/lib/tomcat7/webapps/root/index.html\n\nMatou? Les anciens combattants pourraient être heureux de lire cette instance système de Tomcat est installé avec Catalina_Home dans / USR / Share / Tomcat7 et Catalina_Base dans / var / lib / tomcat7, en suivant les règles de / usr / share / doc / tomcat? -Common / Running. txt .gz.\n\nVous pourriez envisager d'installer les packages suivants, si vous avez fait Alreni:\n\nMatou? 7-DOCS: Ce package installe une application Web qui permet de parcourir la documentation Tomcat 7 localement. Une fois iMStalled, vous pouvez y accéder en cliquant ici.\n\nTomcat7-Exemple: Ce package installe une application Web qui permet d'accéder aux exemples de servlet Tomcat 7 et JSP. Une fois installé, vous pouvez y accéder en cliquant ici.\n\nTomecat7-admin: Ce package installe deux applications Web qui peuvent aider à gérer cette instance Tomcat. Une fois installé, vous pouvez accéder au Manager WebApp et au Host-Manager WebApp.\n\nRemarque: Pour des raisons de sécurité, l'utilisation du gestionnaire WebApp est limitée aux utilisateurs avec un rôle \"Manager-Gui\" _ Le manager host-manager est limité aux utilisateurs avec le rôle \"Admin-Gui\". Les utilisateurs sont définis dans /etc/tomcat7/tomcat-users.c",
|
||||
"metadata": {
|
||||
"ticket_id": "T11143",
|
||||
"timestamp": "20250423_163700",
|
||||
"source_module": "ocr_utils + translate_utils",
|
||||
"lang_detected": "fr"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
{
|
||||
"image_name": "image_145435.png",
|
||||
"ocr_fr": "Mlnpoisbiañe\n\n2 0 PL EURE 0",
|
||||
"translation_en": "Mlnpoisbiañe\n\n2 0 Pl Eure 0",
|
||||
"translation_en_back_fr": "Mlnpoisbiañe\n\n2 0 Pl eure 0",
|
||||
"metadata": {
|
||||
"ticket_id": "T11143",
|
||||
"timestamp": "20250423_163701",
|
||||
"source_module": "ocr_utils + translate_utils",
|
||||
"lang_detected": "fr"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
image.png
|
||||
[FR] (© Ocumam B\o
|
||||
|
||||
¢2@6
|
||||
©) Oem Gap Pim Otam
|
||||
|
||||
lt works !
|
||||
|
||||
If you're seeing this page via a web browser, it means you've setup Tomcat successfully. Congratulations!
|
||||
|
||||
This is the default Tomcat home page. It can be found on the local filesystem at: /var/Lib/tomcat7/webapps/ROOT/ index.html
|
||||
|
||||
Tomcat? veterans might be pleased to learn that this system instance of Tomcat is installed with CATALINA_HOME in /usr/share/tomcat7 and CATALINA_BASE in /var/lib/tomcat7, following the rules from /usr/share/doc/tomcat?-common/ RUNNING. txt .gz.
|
||||
|
||||
You might consider installing the following packages, if you haven't already done so:
|
||||
|
||||
tomcat?7-docs: This package installs a web application that allows to browse the Tomcat 7 documentation locally. Once imstalled, you can access it by clicking here.
|
||||
|
||||
tomcat7-examples: This package installs a web application that allows to access the Tomcat 7 Servlet and JSP examples. Once installed, you can access it by clicking here.
|
||||
|
||||
tomecat7-admin: This package installs two web applications that can help managing this Tomcat instance. Once installed, you can access the manager webapp and the host-manager webapp.
|
||||
|
||||
NOTE: For security reasons, using the manager webapp is restricted to users with role "manager-gui"_ The host-manager webapp is restricted to users with role "admin-gui". Users are defined in /etc/tomcat7/tomcat-users.»xml_
|
||||
[EN] (© OCUMAM B \ O
|
||||
|
||||
¢ 2@6
|
||||
©) OEM GAP PIM OTAM
|
||||
|
||||
Lt Works!
|
||||
|
||||
If you are seeing this page via a web browser, it means you've setup tomcat successfully. Congratulations!
|
||||
|
||||
This is the Default Tomcat Home Page. It can be found on the local Filesystem at:/var/lib/tomcat7/webapps/root/index.html
|
||||
|
||||
Tomcat? veterans might be pleased to read this system instance of tomcat is installed with catalina_home in/usr/share/tomcat7 and catalina_base in/var/lib/tomcat7, following the rules from/usr/share/doc/tomcat? -common/Running. txt .gz.
|
||||
|
||||
You might consider installing the following packages, if you have alreni done so:
|
||||
|
||||
Tomcat? 7-Docs: This Package Installes A Web Application that Allows to Browse the Tomcat 7 Locally documentation. Once Imstalled, you can access it by clicking here.
|
||||
|
||||
Tomcat7-Example: This Package Installes A Web Application that Allows to Access the Tomcat 7 Servlet and JSP Examples. Once Installed, you can access it by clicking here.
|
||||
|
||||
Tomecat7-Admin: this package installations Two Web applications that can help managing this tomcat instance. Once Installed, you can access the Manager Webapp and the Host-Manager Webapp.
|
||||
|
||||
Note: For Security Reasons, Using the Manager Webapp is restricted to users with role "manager-gui" _ the host-manager webapp is restricted to users with role "admin-gui". USERS are defined in /etc/tomcat7/tomcat-users.c
|
||||
[EN→FR] (© ocumam b \ o
|
||||
|
||||
¢ 2 @ 6
|
||||
©) OEM GAP PIM OTAM
|
||||
|
||||
LT fonctionne!
|
||||
|
||||
Si vous voyez cette page via un navigateur Web, cela signifie que vous avez configuré Tomcat avec succès. Félicitations!
|
||||
|
||||
Il s'agit de la page d'accueil par défaut de Tomcat. Il peut être trouvé sur le système de fichiers local à: /var/lib/tomcat7/webapps/root/index.html
|
||||
|
||||
Matou? Les anciens combattants pourraient être heureux de lire cette instance système de Tomcat est installé avec Catalina_Home dans / USR / Share / Tomcat7 et Catalina_Base dans / var / lib / tomcat7, en suivant les règles de / usr / share / doc / tomcat? -Common / Running. txt .gz.
|
||||
|
||||
Vous pourriez envisager d'installer les packages suivants, si vous avez fait Alreni:
|
||||
|
||||
Matou? 7-DOCS: Ce package installe une application Web qui permet de parcourir la documentation Tomcat 7 localement. Une fois iMStalled, vous pouvez y accéder en cliquant ici.
|
||||
|
||||
Tomcat7-Exemple: Ce package installe une application Web qui permet d'accéder aux exemples de servlet Tomcat 7 et JSP. Une fois installé, vous pouvez y accéder en cliquant ici.
|
||||
|
||||
Tomecat7-admin: Ce package installe deux applications Web qui peuvent aider à gérer cette instance Tomcat. Une fois installé, vous pouvez accéder au Manager WebApp et au Host-Manager WebApp.
|
||||
|
||||
Remarque: Pour des raisons de sécurité, l'utilisation du gestionnaire WebApp est limitée aux utilisateurs avec un rôle "Manager-Gui" _ Le manager host-manager est limité aux utilisateurs avec le rôle "Admin-Gui". Les utilisateurs sont définis dans /etc/tomcat7/tomcat-users.c
|
||||
|
||||
image_145435.png
|
||||
[FR] Mlnpoisbiañe
|
||||
|
||||
2 0 PL EURE 0
|
||||
[EN] Mlnpoisbiañe
|
||||
|
||||
2 0 Pl Eure 0
|
||||
[EN→FR] Mlnpoisbiañe
|
||||
|
||||
2 0 Pl eure 0
|
||||
|
||||
543d7da1b54c29ff43ce5712d1a9aa4962ed21795c4e943fcb8cb84fd4d7465a.jpg
|
||||
[FR] _
|
||||
[EN] _
|
||||
[EN→FR] _
|
||||
|
||||
a20f7697fd5e1d1fca3296c6d01228220e0e112c46b4440cc938f74d10934e98.gif
|
||||
[FR] _
|
||||
[EN] _
|
||||
[EN→FR] _
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
{
|
||||
"image_path": "output/ticket_T11143/T11143_20250422_084617/attachments/image.png",
|
||||
"analysis": {
|
||||
"analyse": "** Analyse d'image **\n\nL'image fournie semble être une capture d'écran d'une page Web affichant la page d'accueil de Tomcat par défaut. La page indique que Tomcat a été configuré avec succès.\n\n** Description détaillée **\n\n* Le haut de la page affiche le texte \"Si vous voyez cette page via un navigateur Web, cela signifie que vous configurez Tomcat avec succès. Félicitations!\"\n* Ci-dessous de ce message, il existe une section intitulée \"Les vétérans Tomcat pourraient être heureux de lire\" qui fournit des informations sur l'instance système de Tomcat, y compris les emplacements de `Catalina_home` et` Catalina_base`.\n* La page suggère également d'installer des packages supplémentaires:\n\t+ Tomcat 7 Docs: permet de parcourir la documentation locale\n\t+ Tomcat 7 Exemple: donne accès aux exemples de servlet et de JSP\n\t+ Tomcat 7 Admin: installe deux applications Web pour la gestion de l'instance Tomcat (Manager WebApp et Host-Manager WebApp)\n* Une note au bas de la page mentionne les restrictions de sécurité pour l'utilisation du Manager WebApp et Host-Manager WebApp, faisant référence aux rôles utilisateur définis dans `/ etc / tomcat7 / tomcat-users.xml`.\n\n** Messages d'erreur, informations techniques ou éléments d'interface **\n\n* Aucun message d'erreur n'est visible sur cette page.\n* Les informations techniques suivantes sont présentes:\n\t+ Version Tomcat: 7\n\t+ `Catalina_Home` Emplacement:` / usr / share / tomcat7`\n\t+ `Catalina_Base` Emplacement:` / var / lib / tomcat7`\n\t+ Référence à `/ etc / tomcat7 / tomcat- users.xml` pour les rôles utilisateur\n\n** Relation avec le contexte du ticket de support **\n\nLe contexte du ticket de support est insuffisant pour l'analyse, mais cette image suggère que le problème pourrait être lié à la configuration ou à la configuration de Tomcat. Le fait que la page d'accueil de Tomcat par défaut s'affiche indique que Tomcat est en cours d'exécution, mais il peut y avoir des problèmes avec des applications ou des configurations spécifiques.\n\n** Numéros de version, indicateurs d'état ou dates **\n\n* Version Tomcat: 7\n* Aucun indicateur ou datte d'état n'est visible sur cette page.\n\n** Détails techniques extraits **\n\n* `Catalina_Home` Emplacement:` / usr / share / tomcat7`\n* `Catalina_Base` Emplacement:` / var / lib / tomcat7`\n* Référence à `/ etc / tomcat7 / tomcat-users.xml` pour les rôles utilisateur\n* Packages suggérés pour l'installation:\n\t+ Tomcat 7 Docs\n\t+ Tomcat 7 Exemple\n\t+ Tomcat 7 Admin",
|
||||
"analyse_en": "**Image Analysis**\n\nThe provided image appears to be a screenshot of a web page displaying the default Tomcat home page. The page indicates that Tomcat has been set up successfully.\n\n**Detailed Description**\n\n* The top of the page displays the text \"If you are seeing this page via a web browser, it means you've setup tomcat successfully. Congratulations!\"\n* Below this message, there is a section titled \"Tomcat veterans might be pleased to read\" that provides information about the system instance of Tomcat, including the locations of `catalina_home` and `catalina_base`.\n* The page also suggests installing additional packages:\n\t+ Tomcat 7 Docs: allows browsing local documentation\n\t+ Tomcat 7 Example: provides access to Servlet and JSP examples\n\t+ Tomcat 7 Admin: installs two web applications for managing the Tomcat instance (Manager Webapp and Host-Manager Webapp)\n* A note at the bottom of the page mentions security restrictions for using the Manager Webapp and Host-Manager Webapp, referencing user roles defined in `/etc/tomcat7/tomcat-users.xml`.\n\n**Error Messages, Technical Information, or Interface Elements**\n\n* No error messages are visible on this page.\n* The following technical information is present:\n\t+ Tomcat version: 7\n\t+ `catalina_home` location: `/usr/share/tomcat7`\n\t+ `catalina_base` location: `/var/lib/tomcat7`\n\t+ Reference to `/etc/tomcat7/tomcat-users.xml` for user roles\n\n**Relation to Support Ticket Context**\n\nThe support ticket context is insufficient for analysis, but this image suggests that the issue might be related to Tomcat setup or configuration. The fact that the default Tomcat home page is displayed indicates that Tomcat is running, but there may be issues with specific applications or configurations.\n\n**Version Numbers, Status Indicators, or Dates**\n\n* Tomcat version: 7\n* No status indicators or dates are visible on this page.\n\n**Extracted Technical Details**\n\n* `catalina_home` location: `/usr/share/tomcat7`\n* `catalina_base` location: `/var/lib/tomcat7`\n* Reference to `/etc/tomcat7/tomcat-users.xml` for user roles\n* Suggested packages for installation:\n\t+ Tomcat 7 Docs\n\t+ Tomcat 7 Example\n\t+ Tomcat 7 Admin",
|
||||
"metadata": {
|
||||
"image_path": "output/ticket_T11143/T11143_20250422_084617/attachments/image.png",
|
||||
"image_name": "image.png",
|
||||
"ticket_id": "T11143",
|
||||
"timestamp": "20250423_163937",
|
||||
"ocr_used": true,
|
||||
"model": "llama3.2-vision:90b-instruct-q8_0",
|
||||
"source_agent": "AgentImageAnalyser"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
RÉSULTATS DE L'ANALYSE ANALYSE_IMAGE - TICKET T11143
|
||||
================================================================================
|
||||
|
||||
** Analyse d'image **
|
||||
|
||||
L'image fournie semble être une capture d'écran d'une page Web affichant la page d'accueil de Tomcat par défaut. La page indique que Tomcat a été configuré avec succès.
|
||||
|
||||
** Description détaillée **
|
||||
|
||||
* Le haut de la page affiche le texte "Si vous voyez cette page via un navigateur Web, cela signifie que vous configurez Tomcat avec succès. Félicitations!"
|
||||
* Ci-dessous de ce message, il existe une section intitulée "Les vétérans Tomcat pourraient être heureux de lire" qui fournit des informations sur l'instance système de Tomcat, y compris les emplacements de `Catalina_home` et` Catalina_base`.
|
||||
* La page suggère également d'installer des packages supplémentaires:
|
||||
+ Tomcat 7 Docs: permet de parcourir la documentation locale
|
||||
+ Tomcat 7 Exemple: donne accès aux exemples de servlet et de JSP
|
||||
+ Tomcat 7 Admin: installe deux applications Web pour la gestion de l'instance Tomcat (Manager WebApp et Host-Manager WebApp)
|
||||
* Une note au bas de la page mentionne les restrictions de sécurité pour l'utilisation du Manager WebApp et Host-Manager WebApp, faisant référence aux rôles utilisateur définis dans `/ etc / tomcat7 / tomcat-users.xml`.
|
||||
|
||||
** Messages d'erreur, informations techniques ou éléments d'interface **
|
||||
|
||||
* Aucun message d'erreur n'est visible sur cette page.
|
||||
* Les informations techniques suivantes sont présentes:
|
||||
+ Version Tomcat: 7
|
||||
+ `Catalina_Home` Emplacement:` / usr / share / tomcat7`
|
||||
+ `Catalina_Base` Emplacement:` / var / lib / tomcat7`
|
||||
+ Référence à `/ etc / tomcat7 / tomcat- users.xml` pour les rôles utilisateur
|
||||
|
||||
** Relation avec le contexte du ticket de support **
|
||||
|
||||
Le contexte du ticket de support est insuffisant pour l'analyse, mais cette image suggère que le problème pourrait être lié à la configuration ou à la configuration de Tomcat. Le fait que la page d'accueil de Tomcat par défaut s'affiche indique que Tomcat est en cours d'exécution, mais il peut y avoir des problèmes avec des applications ou des configurations spécifiques.
|
||||
|
||||
** Numéros de version, indicateurs d'état ou dates **
|
||||
|
||||
* Version Tomcat: 7
|
||||
* Aucun indicateur ou datte d'état n'est visible sur cette page.
|
||||
|
||||
** Détails techniques extraits **
|
||||
|
||||
* `Catalina_Home` Emplacement:` / usr / share / tomcat7`
|
||||
* `Catalina_Base` Emplacement:` / var / lib / tomcat7`
|
||||
* Référence à `/ etc / tomcat7 / tomcat-users.xml` pour les rôles utilisateur
|
||||
* Packages suggérés pour l'installation:
|
||||
+ Tomcat 7 Docs
|
||||
+ Tomcat 7 Exemple
|
||||
+ Tomcat 7 Admin
|
||||
|
||||
|
||||
================================================================================
|
||||
Fichier original: analyse_image_image_pixtral-large-latest_results.json
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"image_name": "image.png",
|
||||
"ocr_fr": "Apache Tomcat x +\n\nCG A ‘3 zkl.brg-lab.com\n\n@ Andre 7 Demo 7% Devmat @ Base modèle\n\nIt works !\n\nIf you're seeing this page via a web browser, it means you've setup Tomcat successfully. Congratulations!\n\nThis is the default Tomcat home page. It can be found on the local filesystem at: /var/lib/tomcat7/webapps/ROOT/index.html\n\nTomcat}? veterans might be pleased to learn that this system instance of Tomcat is installed with CATALINA_HOME in /usr/share/tomcat7 and CATALINA BASE in /var/lib/tomcat7, following the rules from /usr/share/doc/tomcat7-common/RUNNING. txt. gz.\nYou might consider installing the following packages, if you haven't already done so:\n\ntomcat7-docs: This package installs a web application that allows to browse the Tomcat 7 documentation locally. Once installed, you can access it by clicking here.\n\ntomcat7-examples: This package installs a web application that allows to access the Tomcat 7 Servlet and JSP examples. Once installed, you can access it by clicking here.\n\ntomcat7-admin: This package installs two web applications that can help managing this Tomcat instance. Once installed, you can access the manager webapp and the host-manager webapp.\n\nNOTE: For security reasons, using the manager webapp is restricted to users with role \"“manager-gui\". The host-manager webapp is restricted to users with role \"admin-gui\". Users are defined in /etc/tomcat7/tomcat-users.xml.",
|
||||
"translation_en": "Apache Tomcat x +\n\nCG A ‘3 zkl.brg-lab.com\n\n@ Andre 7 Demo 7% Devmat @ model base\n\nIt works!\n\nIf you are seeing this page via a web browser, it means you've setup tomcat successfully. Congratulations!\n\nThis is the Default Tomcat Home Page. It can be found on the local Filesystem at: /var/lib/tomcat7/webapps/root/index.html\n\nTomcat}? veterans might be pleased to read this system instance of tomcat is installed with catalina_home in/usr/tomcat7 and catalina base in/var/lib/tomcat7, following the rules from/usr/share/doc/tomcat7-common/Running. TXT. Gz.\nYou might consider installing the following packages, if you have alreni done so:\n\nTomcat7-Docs: This Package Installes A Web Application that Allows to Browse the Tomcat 7 Locally documentation. Once Installed, you can access it by clicking here.\n\nTomcat7-Example: This Package Installes A Web Application that Allows to Access the Tomcat 7 Servlet and JSP Examples. Once Installed, you can access it by clicking here.\n\nTomcat7-Admin: This Package Installes Two Web Applications that can help managing this tomcat instance. Once Installed, you can access the Manager Webapp and the Host-Manager Webapp.\n\nNote: For Security Reasons, Using the Manager Webapp is restricted to users with Role \"Manager-Gui\". The Host-Manager Webapp is restricted to users with role \"admin-guui\". USERS are defined in /etc/tomcat7/tomcat-users.xml.",
|
||||
"translation_en_back_fr": "Apache Tomcat x +\n\nCG a ‘3 zkl.brg-lab.com\n\n@ Andre 7 Demo 7% Devmat @ Model Base\n\nÇa marche!\n\nSi vous voyez cette page via un navigateur Web, cela signifie que vous avez configuré Tomcat avec succès. Félicitations!\n\nIl s'agit de la page d'accueil par défaut de Tomcat. Il peut être trouvé sur le système de fichiers local à: /var/lib/tomcat7/webapps/root/index.html\n\nMatou}? Les vétérans pourraient être heureux de lire cette instance système de Tomcat est installé avec Catalina_Home dans / USR / Tomcat7 et Catalina Base dans / var / lib / tomcat7, en suivant les règles de / usr / share / doc / tomcat7-Common / Running. SMS. GZ.\nVous pourriez envisager d'installer les packages suivants, si vous avez fait Alreni:\n\nTomcat7-Docs: Ce package installe une application Web qui permet de parcourir la documentation Tomcat 7 localement. Une fois installé, vous pouvez y accéder en cliquant ici.\n\nTomcat7-Exemple: Ce package installe une application Web qui permet d'accéder aux exemples de servlet Tomcat 7 et JSP. Une fois installé, vous pouvez y accéder en cliquant ici.\n\nTomcat7-admin: Ce package installe deux applications Web qui peuvent aider à gérer cette instance Tomcat. Une fois installé, vous pouvez accéder au Manager WebApp et au Host-Manager WebApp.\n\nRemarque: Pour des raisons de sécurité, l'utilisation du gestionnaire WebApp est limitée aux utilisateurs avec le rôle \"Manager-Gui\". Le manager host-manager est limité aux utilisateurs avec un rôle \"Admin-GUUI\". Les utilisateurs sont définis dans /etc/tomcat7/tomcat-users.xml.",
|
||||
"metadata": {
|
||||
"ticket_id": "T11143",
|
||||
"timestamp": "20250423_141944",
|
||||
"source_module": "ocr_utils + translate_utils",
|
||||
"lang_detected": "fr"
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"image_name": "image_145435.png",
|
||||
"ocr_fr": "[6] 25 giraudbrg-lobcom/BRG-LAB/PAGE_ programmeEssai/xE4AAHDVNGOAA\n\n| BRGLAS CD Béton C9 foumétew bo 4 Masse\n\nEchantillon n°2500075 réceptionné le 02/04/2025 par BOLLEE Victor - prlevii Le 02/04/2025 por BOLLEE Victor n° prétèvement : 25-6007\nMatériau Sable 0/20 - CARRIERE ADCEG\n\nNREGISTRER\n\nLMPRMER\n\nle de trouver Fadeessé IP du serveur de zk1.brg-lab.com.",
|
||||
"translation_en": "[6] 25 GIRAUDBRG-LOBCOM/BRG-LAB/PAGE_ PROGRAMESSAI/XE4AAHDVNGOAAA\n\n| Brglas CD concrete C9 Foumetew Bo 4 Mass\n\nSample n ° 2500075 received on 02/04/2025 by Bollee Victor - PRLEVII on 02/04/2025 POR BOLLEE Victor N ° PRETREMENT: 25-6007\nSand material 0/20 - CARRIERE ADCEG\n\nRegister\n\nLmprmer\n\nThe to find Fadeessé IP of the ZK1.brg-lab.com server.",
|
||||
"translation_en_back_fr": "[6] 25 Giraudbrg-Lobcom / Brg-Lab / Page_ Programessai / Xe4aahdvngoaaa\n\n| Brglas cd béton c9 foumetew bo 4 masse\n\nÉchantillon N ° 2500075 Reçu le 02/04/2025 par Bollee Victor - Prllevii le 02/04/2025 Por Bollee Victor N ° Pretection: 25-6007\nMatériau de sable 0/20 - Carriere adceg\n\nRegistre\n\nLMPRMER\n\nLe pour trouver Fadeessé IP du serveur ZK1.brg-lab.com.",
|
||||
"metadata": {
|
||||
"ticket_id": "T11143",
|
||||
"timestamp": "20250423_142010",
|
||||
"source_module": "ocr_utils + translate_utils",
|
||||
"lang_detected": "fr"
|
||||
}
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
image.png
|
||||
[FR] Apache Tomcat x +
|
||||
|
||||
CG A ‘3 zkl.brg-lab.com
|
||||
|
||||
@ Andre 7 Demo 7% Devmat @ Base modèle
|
||||
|
||||
It works !
|
||||
|
||||
If you're seeing this page via a web browser, it means you've setup Tomcat successfully. Congratulations!
|
||||
|
||||
This is the default Tomcat home page. It can be found on the local filesystem at: /var/lib/tomcat7/webapps/ROOT/index.html
|
||||
|
||||
Tomcat}? veterans might be pleased to learn that this system instance of Tomcat is installed with CATALINA_HOME in /usr/share/tomcat7 and CATALINA BASE in /var/lib/tomcat7, following the rules from /usr/share/doc/tomcat7-common/RUNNING. txt. gz.
|
||||
You might consider installing the following packages, if you haven't already done so:
|
||||
|
||||
tomcat7-docs: This package installs a web application that allows to browse the Tomcat 7 documentation locally. Once installed, you can access it by clicking here.
|
||||
|
||||
tomcat7-examples: This package installs a web application that allows to access the Tomcat 7 Servlet and JSP examples. Once installed, you can access it by clicking here.
|
||||
|
||||
tomcat7-admin: This package installs two web applications that can help managing this Tomcat instance. Once installed, you can access the manager webapp and the host-manager webapp.
|
||||
|
||||
NOTE: For security reasons, using the manager webapp is restricted to users with role "“manager-gui". The host-manager webapp is restricted to users with role "admin-gui". Users are defined in /etc/tomcat7/tomcat-users.xml.
|
||||
[EN] Apache Tomcat x +
|
||||
|
||||
CG A ‘3 zkl.brg-lab.com
|
||||
|
||||
@ Andre 7 Demo 7% Devmat @ model base
|
||||
|
||||
It works!
|
||||
|
||||
If you are seeing this page via a web browser, it means you've setup tomcat successfully. Congratulations!
|
||||
|
||||
This is the Default Tomcat Home Page. It can be found on the local Filesystem at: /var/lib/tomcat7/webapps/root/index.html
|
||||
|
||||
Tomcat}? veterans might be pleased to read this system instance of tomcat is installed with catalina_home in/usr/tomcat7 and catalina base in/var/lib/tomcat7, following the rules from/usr/share/doc/tomcat7-common/Running. TXT. Gz.
|
||||
You might consider installing the following packages, if you have alreni done so:
|
||||
|
||||
Tomcat7-Docs: This Package Installes A Web Application that Allows to Browse the Tomcat 7 Locally documentation. Once Installed, you can access it by clicking here.
|
||||
|
||||
Tomcat7-Example: This Package Installes A Web Application that Allows to Access the Tomcat 7 Servlet and JSP Examples. Once Installed, you can access it by clicking here.
|
||||
|
||||
Tomcat7-Admin: This Package Installes Two Web Applications that can help managing this tomcat instance. Once Installed, you can access the Manager Webapp and the Host-Manager Webapp.
|
||||
|
||||
Note: For Security Reasons, Using the Manager Webapp is restricted to users with Role "Manager-Gui". The Host-Manager Webapp is restricted to users with role "admin-guui". USERS are defined in /etc/tomcat7/tomcat-users.xml.
|
||||
[EN→FR] Apache Tomcat x +
|
||||
|
||||
CG a ‘3 zkl.brg-lab.com
|
||||
|
||||
@ Andre 7 Demo 7% Devmat @ Model Base
|
||||
|
||||
Ça marche!
|
||||
|
||||
Si vous voyez cette page via un navigateur Web, cela signifie que vous avez configuré Tomcat avec succès. Félicitations!
|
||||
|
||||
Il s'agit de la page d'accueil par défaut de Tomcat. Il peut être trouvé sur le système de fichiers local à: /var/lib/tomcat7/webapps/root/index.html
|
||||
|
||||
Matou}? Les vétérans pourraient être heureux de lire cette instance système de Tomcat est installé avec Catalina_Home dans / USR / Tomcat7 et Catalina Base dans / var / lib / tomcat7, en suivant les règles de / usr / share / doc / tomcat7-Common / Running. SMS. GZ.
|
||||
Vous pourriez envisager d'installer les packages suivants, si vous avez fait Alreni:
|
||||
|
||||
Tomcat7-Docs: Ce package installe une application Web qui permet de parcourir la documentation Tomcat 7 localement. Une fois installé, vous pouvez y accéder en cliquant ici.
|
||||
|
||||
Tomcat7-Exemple: Ce package installe une application Web qui permet d'accéder aux exemples de servlet Tomcat 7 et JSP. Une fois installé, vous pouvez y accéder en cliquant ici.
|
||||
|
||||
Tomcat7-admin: Ce package installe deux applications Web qui peuvent aider à gérer cette instance Tomcat. Une fois installé, vous pouvez accéder au Manager WebApp et au Host-Manager WebApp.
|
||||
|
||||
Remarque: Pour des raisons de sécurité, l'utilisation du gestionnaire WebApp est limitée aux utilisateurs avec le rôle "Manager-Gui". Le manager host-manager est limité aux utilisateurs avec un rôle "Admin-GUUI". Les utilisateurs sont définis dans /etc/tomcat7/tomcat-users.xml.
|
||||
|
||||
image_145435.png
|
||||
[FR] [6] 25 giraudbrg-lobcom/BRG-LAB/PAGE_ programmeEssai/xE4AAHDVNGOAA
|
||||
|
||||
| BRGLAS CD Béton C9 foumétew bo 4 Masse
|
||||
|
||||
Echantillon n°2500075 réceptionné le 02/04/2025 par BOLLEE Victor - prlevii Le 02/04/2025 por BOLLEE Victor n° prétèvement : 25-6007
|
||||
Matériau Sable 0/20 - CARRIERE ADCEG
|
||||
|
||||
NREGISTRER
|
||||
|
||||
LMPRMER
|
||||
|
||||
le de trouver Fadeessé IP du serveur de zk1.brg-lab.com.
|
||||
[EN] [6] 25 GIRAUDBRG-LOBCOM/BRG-LAB/PAGE_ PROGRAMESSAI/XE4AAHDVNGOAAA
|
||||
|
||||
| Brglas CD concrete C9 Foumetew Bo 4 Mass
|
||||
|
||||
Sample n ° 2500075 received on 02/04/2025 by Bollee Victor - PRLEVII on 02/04/2025 POR BOLLEE Victor N ° PRETREMENT: 25-6007
|
||||
Sand material 0/20 - CARRIERE ADCEG
|
||||
|
||||
Register
|
||||
|
||||
Lmprmer
|
||||
|
||||
The to find Fadeessé IP of the ZK1.brg-lab.com server.
|
||||
[EN→FR] [6] 25 Giraudbrg-Lobcom / Brg-Lab / Page_ Programessai / Xe4aahdvngoaaa
|
||||
|
||||
| Brglas cd béton c9 foumetew bo 4 masse
|
||||
|
||||
Échantillon N ° 2500075 Reçu le 02/04/2025 par Bollee Victor - Prllevii le 02/04/2025 Por Bollee Victor N ° Pretection: 25-6007
|
||||
Matériau de sable 0/20 - Carriere adceg
|
||||
|
||||
Registre
|
||||
|
||||
LMPRMER
|
||||
|
||||
Le pour trouver Fadeessé IP du serveur ZK1.brg-lab.com.
|
||||
|
||||
543d7da1b54c29ff43ce5712d1a9aa4962ed21795c4e943fcb8cb84fd4d7465a.jpg
|
||||
[FR] _
|
||||
[EN] _
|
||||
[EN→FR] _
|
||||
|
||||
a20f7697fd5e1d1fca3296c6d01228220e0e112c46b4440cc938f74d10934e98.gif
|
||||
[FR] _
|
||||
[EN] _
|
||||
[EN→FR] _
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
RAPPORT D'ANALYSE DU TICKET T11143
|
||||
==================================================
|
||||
|
||||
** Rapport croisé **
|
||||
|
||||
**Résumé**
|
||||
L'analyse des billets de support révèle un contenu insuffisant pour une analyse approfondie, mais l'image fournie suggère que le problème pourrait être lié à la configuration ou à la configuration de Tomcat.
|
||||
|
||||
** Tableau chronologique des échanges **
|
||||
|
||||
| Émetteur | Type | Date | Contenu | Éléments visuels |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Client | Question | Inconnu | Contenu insuffisant pour l'analyse | - |
|
||||
| Support | Demande de plus d'informations | Inconnu | Demande de détails supplémentaires pour aider le problème | - |
|
||||
| Client | Réponse | 2025-04-23 16:36:57 | Image fournie (image.png) de la page d'accueil par défaut de Tomcat | Capture d'écran de la page d'accueil par défaut de Tomcat, indiquant une configuration réussie |
|
||||
|
||||
** Analyse d'image **
|
||||
|
||||
L'image fournie est une capture d'écran de la page d'accueil par défaut de Tomcat, ce qui indique que Tomcat a été configuré avec succès. La page affiche des informations techniques telles que:
|
||||
|
||||
* Version Tomcat: 7
|
||||
* `Catalina_Home` Rental:` / usr / share / tomcat7`
|
||||
* `Catalina_Base` Rental:` / var / lib / tomcat7`
|
||||
* Référence à `/ etc / tomcat7 / tomcat-users.xml`
|
||||
|
||||
** Contexte technique **
|
||||
|
||||
L'image suggère que le problème pourrait être lié à la configuration ou à la configuration de Tomcat. Le fait que la page d'accueil de Tomcat par défaut s'affiche indique que Tomcat est en cours d'exécution, mais il peut y avoir des problèmes avec des applications ou des configurations spécifiques.
|
||||
|
||||
** Recommandations **
|
||||
|
||||
* Demandez des informations supplémentaires au client pour clarifier le problème.
|
||||
* Vérifiez la configuration et la configuration de Tomcat pour vous assurer qu'elle correspond aux paramètres attendus.
|
||||
* Envisagez d'installer des packages suggérés (Tomcat 7 Docs, Tomcat 7 Exemple, Tomcat 7 Admin) pour fournir des fonctionnalités supplémentaires.
|
||||
|
||||
Remarque: Le tableau ne comprend qu'un seul échange entre le client et le support, car il n'y a pas suffisamment d'informations pour créer un tableau chronologique plus détaillé.
|
||||
@ -0,0 +1,36 @@
|
||||
ANALYSIS REPORT FOR TICKET T11143
|
||||
==================================================
|
||||
|
||||
**Cross Report**
|
||||
|
||||
**Summary**
|
||||
The support ticket analysis reveals insufficient content for a thorough analysis, but the provided image suggests that the issue might be related to Tomcat setup or configuration.
|
||||
|
||||
**Chronological Table of Exchanges**
|
||||
|
||||
| Transmitter | Type | Date | Contents | Visual elements |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Customer | Question | Unknown | Insufficient content for analysis | - |
|
||||
| Support | Request for more information | Unknown | Request for additional details to assist with the issue | - |
|
||||
| Customer | Response | 2025-04-23 16:36:57 | Provided image (image.png) of Tomcat default home page | Screenshot of Tomcat default home page, indicating successful setup |
|
||||
|
||||
**Image Analysis**
|
||||
|
||||
The provided image is a screenshot of the Tomcat default home page, which indicates that Tomcat has been set up successfully. The page displays technical information such as:
|
||||
|
||||
* Tomcat version: 7
|
||||
* `catalina_home` rental: `/usr/share/tomcat7`
|
||||
* `catalina_base` rental: `/var/lib/tomcat7`
|
||||
* Reference to `/etc/tomcat7/tomcat-users.xml`
|
||||
|
||||
**Technical Context**
|
||||
|
||||
The image suggests that the issue might be related to Tomcat setup or configuration. The fact that the default Tomcat home page is displayed indicates that Tomcat is running, but there may be issues with specific applications or configurations.
|
||||
|
||||
**Recommendations**
|
||||
|
||||
* Request additional information from the customer to clarify the issue.
|
||||
* Verify the Tomcat configuration and setup to ensure it matches the expected settings.
|
||||
* Consider installing suggested packages (Tomcat 7 Docs, Tomcat 7 Example, Tomcat 7 Admin) to provide additional functionality.
|
||||
|
||||
Note: The table only includes one exchange between the customer and support, as there is insufficient information to create a more detailed chronological table.
|
||||
@ -0,0 +1,23 @@
|
||||
{
|
||||
"prompt": "Voici les données d'analyse pour un ticket de support :\n\n=== ANALYSE DU TICKET ===\n{'response': 'Contenu du ticket insuffisant pour analyse', 'response_en': 'Ticket content insufficient for analysis', 'error': True, 'metadata': {'timestamp': '20250423_163657', 'source_agent': 'AgentTicketAnalyser', 'ticket_id': 'T11143'}}\n\n=== ANALYSES D'IMAGES ===\n--- IMAGE : image.png ---\n**Image Analysis**\n\nThe provided image appears to be a screenshot of a web page displaying the default Tomcat home page. The page indicates that Tomcat has been set up successfully.\n\n**Detailed Description**\n\n* The top of the page displays the text \"If you are seeing this page via a web browser, it means you've setup tomcat successfully. Congratulations!\"\n* Below this message, there is a section titled \"Tomcat veterans might be pleased to read\" that provides information about the system instance of Tomcat, including the locations of `catalina_home` and `catalina_base`.\n* The page also suggests installing additional packages:\n\t+ Tomcat 7 Docs: allows browsing local documentation\n\t+ Tomcat 7 Example: provides access to Servlet and JSP examples\n\t+ Tomcat 7 Admin: installs two web applications for managing the Tomcat instance (Manager Webapp and Host-Manager Webapp)\n* A note at the bottom of the page mentions security restrictions for using the Manager Webapp and Host-Manager Webapp, referencing user roles defined in `/etc/tomcat7/tomcat-users.xml`.\n\n**Error Messages, Technical Information, or Interface Elements**\n\n* No error messages are visible on this page.\n* The following technical information is present:\n\t+ Tomcat version: 7\n\t+ `catalina_home` location: `/usr/share/tomcat7`\n\t+ `catalina_base` location: `/var/lib/tomcat7`\n\t+ Reference to `/etc/tomcat7/tomcat-users.xml` for user roles\n\n**Relation to Support Ticket Context**\n\nThe support ticket context is insufficient for analysis, but this image suggests that the issue might be related to Tomcat setup or configuration. The fact that the default Tomcat home page is displayed indicates that Tomcat is running, but there may be issues with specific applications or configurations.\n\n**Version Numbers, Status Indicators, or Dates**\n\n* Tomcat version: 7\n* No status indicators or dates are visible on this page.\n\n**Extracted Technical Details**\n\n* `catalina_home` location: `/usr/share/tomcat7`\n* `catalina_base` location: `/var/lib/tomcat7`\n* Reference to `/etc/tomcat7/tomcat-users.xml` for user roles\n* Suggested packages for installation:\n\t+ Tomcat 7 Docs\n\t+ Tomcat 7 Example\n\t+ Tomcat 7 Admin\n\n\nGénère un rapport croisé en suivant les instructions précédentes, incluant un tableau chronologique des échanges entre CLIENT et SUPPORT. Utilise le format suivant pour le tableau :\n| ÉMETTEUR | TYPE | DATE | CONTENU | ÉLÉMENTS VISUELS |\n| --- | --- | --- | --- | --- |\n| CLIENT | question | date | texte de la question | éléments pertinents des images |\n| SUPPORT | réponse | date | texte de la réponse | éléments pertinents des images |\n\nCe tableau doit synthétiser les échanges tout en intégrant les données pertinentes des images avec le maximum de contexte technique.",
|
||||
"prompt_en": "[ENGLISH RESPONSE REQUESTED]\n\nHere are the analysis data for a support ticket:\n\n=== Ticket analysis ====\n{'Response': 'Insufficient ticket content for analysis', 'Response_en': 'Insufficient content content for analysis', 'error': true, 'metadata': {'timestamp': '20250423_163657', 'source_agent': 'agenticketalyser', 'tick_id': 't11143'}}}}}}}}}}\n\n=== Image analyzes ====\n--- Image: image.png ---\n** Image Analysis **\n\nThe Provided Image Appears to Be A Screenshot of A Web Page Displaying the Default Tomcat Home Page. The page indicates that tomcat has been set up successfully.\n\n** Detaled Description **\n\n* The Top of the Page Displays The Text \"If you are seeing this page via a web browser, it means you've setup tomcat successfully. Congratulations!\"\n* Below this message, there is section titled \"Tomcat veterans might be pleased to read\" that provids about the system instance of tomcat, include the rentals of `catalina_home 'and` catalina_base`.\n* The page also suggests installing additional packages:\n\t+ Tomcat 7 Docs: Allows Browsing Local Documentation\n\t+ Tomcat 7 Example: Provids Access to Servlet and JSP Example\n\t+ Tomcat 7 Admin: Instalels Two Web Applications for Managing the Tomcat Instance (Manager Webapp and Host-Manager Webapp)\n* A Note at the Bottom of the page mentions Security Restrictions for using the Manager Webapp and Host-Manager Webapp, Referencing User Roles Defined in `/etc/Tomcat7/Tomcat -use.xm\n\n** Error messages, Technical Information, Or interface elements **\n\n* No error messages are visible on this page.\n* The Following Technical Information is present:\n\t+ Tomcat version: 7\n\t+ `catalina_home 'rental:`/usr/share/tomcat7'\n\t+ `catalina_base` rental:`/var/lib/tomcat7`\n\t+ Reference to `/etc/tomcat7/tomcat -use.xm\n\n** Relation to support Ticket Context **\n\nThe support tick context is insufficient for analysis, but this image suggests that the ISSUE Might be related to tomcat setup or configuration. The Fact that the Default Tomcat Home Page is Displayed Indicates that Tomcat is running, but there may be from specific applications or configurations.\n\n** Numbers version, status indicators, gold dates **\n\n* Tomcat version: 7\n* No status indicators or dates are visible on this page.\n\n** Extracted Technical Details **\n\n* `Catalina_home 'Rental:`/usr/Share/Tomcat7'\n* `catalina_base` rental:`/var/lib/tomcat7`\n* Reference to `/etc/tomcat7/tomcat -use.xm\n* Suggestized Packages for Installation:\n\t+ Tomcat 7 Docs\n\t+ Tomcat 7 Example\n\t+ Tomcat 7 admin\n\n\nGenerates a cross report following the previous instructions, including a chronological table of exchanges between customers and support. Use the following format for the table:\n| Transmitter | Type | Date | Contents | Visual elements |\n| --- | --- | --- | --- | --- |\n| Customer | Question | Date | Text of the question | Relevant elements of images |\n| Support | Answer | Date | Response text | Relevant elements of images |\n\nThis table must synthesize exchanges while integrating the relevant data of images with the maximum technical context.",
|
||||
"response": "** Rapport croisé **\n\n**Résumé**\nL'analyse des billets de support révèle un contenu insuffisant pour une analyse approfondie, mais l'image fournie suggère que le problème pourrait être lié à la configuration ou à la configuration de Tomcat.\n\n** Tableau chronologique des échanges **\n\n| Émetteur | Type | Date | Contenu | Éléments visuels |\n| --- | --- | --- | --- | --- |\n| Client | Question | Inconnu | Contenu insuffisant pour l'analyse | - |\n| Support | Demande de plus d'informations | Inconnu | Demande de détails supplémentaires pour aider le problème | - |\n| Client | Réponse | 2025-04-23 16:36:57 | Image fournie (image.png) de la page d'accueil par défaut de Tomcat | Capture d'écran de la page d'accueil par défaut de Tomcat, indiquant une configuration réussie |\n\n** Analyse d'image **\n\nL'image fournie est une capture d'écran de la page d'accueil par défaut de Tomcat, ce qui indique que Tomcat a été configuré avec succès. La page affiche des informations techniques telles que:\n\n* Version Tomcat: 7\n* `Catalina_Home` Rental:` / usr / share / tomcat7`\n* `Catalina_Base` Rental:` / var / lib / tomcat7`\n* Référence à `/ etc / tomcat7 / tomcat-users.xml`\n\n** Contexte technique **\n\nL'image suggère que le problème pourrait être lié à la configuration ou à la configuration de Tomcat. Le fait que la page d'accueil de Tomcat par défaut s'affiche indique que Tomcat est en cours d'exécution, mais il peut y avoir des problèmes avec des applications ou des configurations spécifiques.\n\n** Recommandations **\n\n* Demandez des informations supplémentaires au client pour clarifier le problème.\n* Vérifiez la configuration et la configuration de Tomcat pour vous assurer qu'elle correspond aux paramètres attendus.\n* Envisagez d'installer des packages suggérés (Tomcat 7 Docs, Tomcat 7 Exemple, Tomcat 7 Admin) pour fournir des fonctionnalités supplémentaires.\n\nRemarque: Le tableau ne comprend qu'un seul échange entre le client et le support, car il n'y a pas suffisamment d'informations pour créer un tableau chronologique plus détaillé.",
|
||||
"response_en": "**Cross Report**\n\n**Summary**\nThe support ticket analysis reveals insufficient content for a thorough analysis, but the provided image suggests that the issue might be related to Tomcat setup or configuration.\n\n**Chronological Table of Exchanges**\n\n| Transmitter | Type | Date | Contents | Visual elements |\n| --- | --- | --- | --- | --- |\n| Customer | Question | Unknown | Insufficient content for analysis | - |\n| Support | Request for more information | Unknown | Request for additional details to assist with the issue | - |\n| Customer | Response | 2025-04-23 16:36:57 | Provided image (image.png) of Tomcat default home page | Screenshot of Tomcat default home page, indicating successful setup |\n\n**Image Analysis**\n\nThe provided image is a screenshot of the Tomcat default home page, which indicates that Tomcat has been set up successfully. The page displays technical information such as:\n\n* Tomcat version: 7\n* `catalina_home` rental: `/usr/share/tomcat7`\n* `catalina_base` rental: `/var/lib/tomcat7`\n* Reference to `/etc/tomcat7/tomcat-users.xml`\n\n**Technical Context**\n\nThe image suggests that the issue might be related to Tomcat setup or configuration. The fact that the default Tomcat home page is displayed indicates that Tomcat is running, but there may be issues with specific applications or configurations.\n\n**Recommendations**\n\n* Request additional information from the customer to clarify the issue.\n* Verify the Tomcat configuration and setup to ensure it matches the expected settings.\n* Consider installing suggested packages (Tomcat 7 Docs, Tomcat 7 Example, Tomcat 7 Admin) to provide additional functionality.\n\nNote: The table only includes one exchange between the customer and support, as there is insufficient information to create a more detailed chronological table.",
|
||||
"metadata": {
|
||||
"ticket_id": "T11143",
|
||||
"timestamp": "20250423_164104",
|
||||
"source_agent": "AgentReportGenerator",
|
||||
"model_info": {
|
||||
"model": "llama3.2-vision:90b-instruct-q8_0",
|
||||
"temperature": 0.2,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 8000,
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 0,
|
||||
"stop": [],
|
||||
"stream": false,
|
||||
"n": 1
|
||||
},
|
||||
"language": "en-fr"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
RÉSULTATS DE L'ANALYSE RAPPORT_FINAL - TICKET T11143
|
||||
================================================================================
|
||||
|
||||
** Rapport croisé **
|
||||
|
||||
**Résumé**
|
||||
L'analyse des billets de support révèle un contenu insuffisant pour une analyse approfondie, mais l'image fournie suggère que le problème pourrait être lié à la configuration ou à la configuration de Tomcat.
|
||||
|
||||
** Tableau chronologique des échanges **
|
||||
|
||||
| Émetteur | Type | Date | Contenu | Éléments visuels |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Client | Question | Inconnu | Contenu insuffisant pour l'analyse | - |
|
||||
| Support | Demande de plus d'informations | Inconnu | Demande de détails supplémentaires pour aider le problème | - |
|
||||
| Client | Réponse | 2025-04-23 16:36:57 | Image fournie (image.png) de la page d'accueil par défaut de Tomcat | Capture d'écran de la page d'accueil par défaut de Tomcat, indiquant une configuration réussie |
|
||||
|
||||
** Analyse d'image **
|
||||
|
||||
L'image fournie est une capture d'écran de la page d'accueil par défaut de Tomcat, ce qui indique que Tomcat a été configuré avec succès. La page affiche des informations techniques telles que:
|
||||
|
||||
* Version Tomcat: 7
|
||||
* `Catalina_Home` Rental:` / usr / share / tomcat7`
|
||||
* `Catalina_Base` Rental:` / var / lib / tomcat7`
|
||||
* Référence à `/ etc / tomcat7 / tomcat-users.xml`
|
||||
|
||||
** Contexte technique **
|
||||
|
||||
L'image suggère que le problème pourrait être lié à la configuration ou à la configuration de Tomcat. Le fait que la page d'accueil de Tomcat par défaut s'affiche indique que Tomcat est en cours d'exécution, mais il peut y avoir des problèmes avec des applications ou des configurations spécifiques.
|
||||
|
||||
** Recommandations **
|
||||
|
||||
* Demandez des informations supplémentaires au client pour clarifier le problème.
|
||||
* Vérifiez la configuration et la configuration de Tomcat pour vous assurer qu'elle correspond aux paramètres attendus.
|
||||
* Envisagez d'installer des packages suggérés (Tomcat 7 Docs, Tomcat 7 Exemple, Tomcat 7 Admin) pour fournir des fonctionnalités supplémentaires.
|
||||
|
||||
Remarque: Le tableau ne comprend qu'un seul échange entre le client et le support, car il n'y a pas suffisamment d'informations pour créer un tableau chronologique plus détaillé.
|
||||
|
||||
|
||||
================================================================================
|
||||
Fichier original: rapport_final_llama3.2-vision_90b-instruct-q8_0_results.json
|
||||
@ -0,0 +1,14 @@
|
||||
{
|
||||
"is_relevant": false,
|
||||
"reason": "ERROR: Erreur d'accès ou format d'image invalide",
|
||||
"raw_response": "",
|
||||
"error": true,
|
||||
"metadata": {
|
||||
"image_path": "output/ticket_T11143/T11143_20250422_084617/attachments/a20f7697fd5e1d1fca3296c6d01228220e0e112c46b4440cc938f74d10934e98.gif",
|
||||
"image_name": "a20f7697fd5e1d1fca3296c6d01228220e0e112c46b4440cc938f74d10934e98.gif",
|
||||
"ticket_id": "T11143",
|
||||
"timestamp": "20250423_163747",
|
||||
"error": true,
|
||||
"source_agent": "AgentImageSorter"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
RÉSULTATS DE L'ANALYSE TRI_IMAGE - TICKET T11143
|
||||
================================================================================
|
||||
|
||||
|
||||
|
||||
|
||||
================================================================================
|
||||
Fichier original: tri_image_image_llama3.2-vision_90b-instruct-q8_0_results.json
|
||||
@ -1,98 +0,0 @@
|
||||
[
|
||||
{
|
||||
"is_relevant": true,
|
||||
"reason": "Yes.\n\nThis image appears to be a screenshot of the default Apache Tomcat homepage, indicating that the server is running successfully. However, this does not necessarily indicate a technical support issue. In fact, it suggests that the setup process has been completed correctly. \n\nA technical support issue would typically involve an error message or unexpected behavior, which is not present in this image. Therefore, while the image may be relevant to a technical support conversation (e.g., as proof of successful installation), it does not in itself indicate a problem requiring support.",
|
||||
"raw_response": "Yes.\n\nThis image appears to be a screenshot of the default Apache Tomcat homepage, indicating that the server is running successfully. However, this does not necessarily indicate a technical support issue. In fact, it suggests that the setup process has been completed correctly. \n\nA technical support issue would typically involve an error message or unexpected behavior, which is not present in this image. Therefore, while the image may be relevant to a technical support conversation (e.g., as proof of successful installation), it does not in itself indicate a problem requiring support.",
|
||||
"ocr_fr": "Apache Tomcat x +\n\nCG A ‘3 zkl.brg-lab.com\n\n@ Andre 7 Demo 7% Devmat @ Base modèle\n\nIt works !\n\nIf you're seeing this page via a web browser, it means you've setup Tomcat successfully. Congratulations!\n\nThis is the default Tomcat home page. It can be found on the local filesystem at: /var/lib/tomcat7/webapps/ROOT/index.html\n\nTomcat}? veterans might be pleased to learn that this system instance of Tomcat is installed with CATALINA_HOME in /usr/share/tomcat7 and CATALINA BASE in /var/lib/tomcat7, following the rules from /usr/share/doc/tomcat7-common/RUNNING. txt. gz.\nYou might consider installing the following packages, if you haven't already done so:\n\ntomcat7-docs: This package installs a web application that allows to browse the Tomcat 7 documentation locally. Once installed, you can access it by clicking here.\n\ntomcat7-examples: This package installs a web application that allows to access the Tomcat 7 Servlet and JSP examples. Once installed, you can access it by clicking here.\n\ntomcat7-admin: This package installs two web applications that can help managing this Tomcat instance. Once installed, you can access the manager webapp and the host-manager webapp.\n\nNOTE: For security reasons, using the manager webapp is restricted to users with role \"“manager-gui\". The host-manager webapp is restricted to users with role \"admin-gui\". Users are defined in /etc/tomcat7/tomcat-users.xml.",
|
||||
"ocr_en": "Apache Tomcat x +\n\nCG A ‘3 zkl.brg-lab.com\n\n@ Andre 7 Demo 7% Devmat @ model base\n\nIt works!\n\nIf you are seeing this page via a web browser, it means you've setup tomcat successfully. Congratulations!\n\nThis is the Default Tomcat Home Page. It can be found on the local Filesystem at: /var/lib/tomcat7/webapps/root/index.html\n\nTomcat}? veterans might be pleased to read this system instance of tomcat is installed with catalina_home in/usr/tomcat7 and catalina base in/var/lib/tomcat7, following the rules from/usr/share/doc/tomcat7-common/Running. TXT. Gz.\nYou might consider installing the following packages, if you have alreni done so:\n\nTomcat7-Docs: This Package Installes A Web Application that Allows to Browse the Tomcat 7 Locally documentation. Once Installed, you can access it by clicking here.\n\nTomcat7-Example: This Package Installes A Web Application that Allows to Access the Tomcat 7 Servlet and JSP Examples. Once Installed, you can access it by clicking here.\n\nTomcat7-Admin: This Package Installes Two Web Applications that can help managing this tomcat instance. Once Installed, you can access the Manager Webapp and the Host-Manager Webapp.\n\nNote: For Security Reasons, Using the Manager Webapp is restricted to users with Role \"Manager-Gui\". The Host-Manager Webapp is restricted to users with role \"admin-guui\". USERS are defined in /etc/tomcat7/tomcat-users.xml.",
|
||||
"metadata": {
|
||||
"image_path": "output/ticket_T11143/T11143_20250422_084617/attachments/image.png",
|
||||
"image_name": "image.png",
|
||||
"timestamp": "20250423_142009",
|
||||
"model_info": {
|
||||
"model": "llama3.2-vision:90b-instruct-q8_0",
|
||||
"temperature": 0.2,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 300,
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 0,
|
||||
"stop": [],
|
||||
"stream": false,
|
||||
"n": 1
|
||||
},
|
||||
"source_agent": "AgentImageSorter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"is_relevant": true,
|
||||
"reason": "Yes.\n\nThis image appears to be a screenshot from the BRG_Lab software system, showing a specific page related to concrete testing (Béton C9 Foumétew Bo 4 Masse). The text includes details about a sample received on a particular date, material information, and an error message or prompt (\"le de trouver Fadeessé IP du serveur de zk1.brg-lab.com\") that suggests a technical issue with connecting to the server. This information is relevant for a technical support issue as it provides context about the problem being experienced by the user.",
|
||||
"raw_response": "Yes.\n\nThis image appears to be a screenshot from the BRG_Lab software system, showing a specific page related to concrete testing (Béton C9 Foumétew Bo 4 Masse). The text includes details about a sample received on a particular date, material information, and an error message or prompt (\"le de trouver Fadeessé IP du serveur de zk1.brg-lab.com\") that suggests a technical issue with connecting to the server. This information is relevant for a technical support issue as it provides context about the problem being experienced by the user.",
|
||||
"ocr_fr": "[6] 25 giraudbrg-lobcom/BRG-LAB/PAGE_ programmeEssai/xE4AAHDVNGOAA\n\n| BRGLAS CD Béton C9 foumétew bo 4 Masse\n\nEchantillon n°2500075 réceptionné le 02/04/2025 par BOLLEE Victor - prlevii Le 02/04/2025 por BOLLEE Victor n° prétèvement : 25-6007\nMatériau Sable 0/20 - CARRIERE ADCEG\n\nNREGISTRER\n\nLMPRMER\n\nle de trouver Fadeessé IP du serveur de zk1.brg-lab.com.",
|
||||
"ocr_en": "[6] 25 GIRAUDBRG-LOBCOM/BRG-LAB/PAGE_ PROGRAMESSAI/XE4AAHDVNGOAAA\n\n| Brglas CD concrete C9 Foumetew Bo 4 Mass\n\nSample n ° 2500075 received on 02/04/2025 by Bollee Victor - PRLEVII on 02/04/2025 POR BOLLEE Victor N ° PRETREMENT: 25-6007\nSand material 0/20 - CARRIERE ADCEG\n\nRegister\n\nLmprmer\n\nThe to find Fadeessé IP of the ZK1.brg-lab.com server.",
|
||||
"metadata": {
|
||||
"image_path": "output/ticket_T11143/T11143_20250422_084617/attachments/image_145435.png",
|
||||
"image_name": "image_145435.png",
|
||||
"timestamp": "20250423_142039",
|
||||
"model_info": {
|
||||
"model": "llama3.2-vision:90b-instruct-q8_0",
|
||||
"temperature": 0.2,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 300,
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 0,
|
||||
"stop": [],
|
||||
"stream": false,
|
||||
"n": 1
|
||||
},
|
||||
"source_agent": "AgentImageSorter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"is_relevant": false,
|
||||
"reason": "No.\n\nThe image appears to be a jumbled collection of characters, including French text, but it does not contain any meaningful information that would be relevant to a technical support issue. The text is likely the result of an OCR (Optical Character Recognition) error or a corrupted file, rather than an actual screenshot or log from the BRG_Lab software system.",
|
||||
"raw_response": "No.\n\nThe image appears to be a jumbled collection of characters, including French text, but it does not contain any meaningful information that would be relevant to a technical support issue. The text is likely the result of an OCR (Optical Character Recognition) error or a corrupted file, rather than an actual screenshot or log from the BRG_Lab software system.",
|
||||
"ocr_fr": "",
|
||||
"ocr_en": "",
|
||||
"metadata": {
|
||||
"image_path": "output/ticket_T11143/T11143_20250422_084617/attachments/543d7da1b54c29ff43ce5712d1a9aa4962ed21795c4e943fcb8cb84fd4d7465a.jpg",
|
||||
"image_name": "543d7da1b54c29ff43ce5712d1a9aa4962ed21795c4e943fcb8cb84fd4d7465a.jpg",
|
||||
"timestamp": "20250423_142058",
|
||||
"model_info": {
|
||||
"model": "llama3.2-vision:90b-instruct-q8_0",
|
||||
"temperature": 0.2,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 300,
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 0,
|
||||
"stop": [],
|
||||
"stream": false,
|
||||
"n": 1
|
||||
},
|
||||
"source_agent": "AgentImageSorter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"is_relevant": true,
|
||||
"reason": "Yes.\n\nThe image appears to be a screenshot of an error message or a system log from the BRG_Lab software system, which is likely relevant to a technical support issue. The presence of French text and what seems to be a stack trace or debug information suggests that it may be related to a specific problem or bug that the user is experiencing with the software.",
|
||||
"raw_response": "Yes.\n\nThe image appears to be a screenshot of an error message or a system log from the BRG_Lab software system, which is likely relevant to a technical support issue. The presence of French text and what seems to be a stack trace or debug information suggests that it may be related to a specific problem or bug that the user is experiencing with the software.",
|
||||
"ocr_fr": "",
|
||||
"ocr_en": "",
|
||||
"metadata": {
|
||||
"image_path": "output/ticket_T11143/T11143_20250422_084617/attachments/a20f7697fd5e1d1fca3296c6d01228220e0e112c46b4440cc938f74d10934e98.gif",
|
||||
"image_name": "a20f7697fd5e1d1fca3296c6d01228220e0e112c46b4440cc938f74d10934e98.gif",
|
||||
"timestamp": "20250423_142115",
|
||||
"model_info": {
|
||||
"model": "llama3.2-vision:90b-instruct-q8_0",
|
||||
"temperature": 0.2,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 300,
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 0,
|
||||
"stop": [],
|
||||
"stream": false,
|
||||
"n": 1
|
||||
},
|
||||
"source_agent": "AgentImageSorter"
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -1,40 +0,0 @@
|
||||
RÉSULTATS DE L'ANALYSE TRI_IMAGE - TICKET T11143
|
||||
================================================================================
|
||||
|
||||
--- ÉLÉMENT 1 ---
|
||||
|
||||
Yes.
|
||||
|
||||
This image appears to be a screenshot of the default Apache Tomcat homepage, indicating that the server is running successfully. However, this does not necessarily indicate a technical support issue. In fact, it suggests that the setup process has been completed correctly.
|
||||
|
||||
A technical support issue would typically involve an error message or unexpected behavior, which is not present in this image. Therefore, while the image may be relevant to a technical support conversation (e.g., as proof of successful installation), it does not in itself indicate a problem requiring support.
|
||||
|
||||
----------------------------------------
|
||||
|
||||
--- ÉLÉMENT 2 ---
|
||||
|
||||
Yes.
|
||||
|
||||
This image appears to be a screenshot from the BRG_Lab software system, showing a specific page related to concrete testing (Béton C9 Foumétew Bo 4 Masse). The text includes details about a sample received on a particular date, material information, and an error message or prompt ("le de trouver Fadeessé IP du serveur de zk1.brg-lab.com") that suggests a technical issue with connecting to the server. This information is relevant for a technical support issue as it provides context about the problem being experienced by the user.
|
||||
|
||||
----------------------------------------
|
||||
|
||||
--- ÉLÉMENT 3 ---
|
||||
|
||||
No.
|
||||
|
||||
The image appears to be a jumbled collection of characters, including French text, but it does not contain any meaningful information that would be relevant to a technical support issue. The text is likely the result of an OCR (Optical Character Recognition) error or a corrupted file, rather than an actual screenshot or log from the BRG_Lab software system.
|
||||
|
||||
----------------------------------------
|
||||
|
||||
--- ÉLÉMENT 4 ---
|
||||
|
||||
Yes.
|
||||
|
||||
The image appears to be a screenshot of an error message or a system log from the BRG_Lab software system, which is likely relevant to a technical support issue. The presence of French text and what seems to be a stack trace or debug information suggests that it may be related to a specific problem or bug that the user is experiencing with the software.
|
||||
|
||||
----------------------------------------
|
||||
|
||||
|
||||
================================================================================
|
||||
Fichier original: tri_image_llama3.2-vision:90b-instruct-q8_0_results.json
|
||||
@ -0,0 +1,59 @@
|
||||
[
|
||||
{
|
||||
"is_relevant": true,
|
||||
"reason": "Yes.\n\nThis image appears to be a technical screenshot of a Tomcat server setup success page, which may be relevant for troubleshooting or configuration purposes related to the BRG_Lab software from CBAO enterprise, especially if the software uses Tomcat as part of its infrastructure. The text detected in the image provides information about the Tomcat installation and configuration, which could be useful for technical support issues.",
|
||||
"raw_response": "Yes.\n\nThis image appears to be a technical screenshot of a Tomcat server setup success page, which may be relevant for troubleshooting or configuration purposes related to the BRG_Lab software from CBAO enterprise, especially if the software uses Tomcat as part of its infrastructure. The text detected in the image provides information about the Tomcat installation and configuration, which could be useful for technical support issues.",
|
||||
"ocr_used": true,
|
||||
"metadata": {
|
||||
"image_path": "output/ticket_T11143/T11143_20250422_084617/attachments/image.png",
|
||||
"image_name": "image.png",
|
||||
"timestamp": "20250423_163722",
|
||||
"model_info": {
|
||||
"model": "llama3.2-vision:90b-instruct-q8_0",
|
||||
"temperature": 0.1,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 1000
|
||||
},
|
||||
"ticket_id": "T11143",
|
||||
"source_agent": "AgentImageSorter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"is_relevant": false,
|
||||
"reason": "No\n\nThe text detected in the image appears to be unrelated to technical support or software issues, and the content seems to be a personal or generic message rather than a technical screenshot or error message.",
|
||||
"raw_response": "No\n\nThe text detected in the image appears to be unrelated to technical support or software issues, and the content seems to be a personal or generic message rather than a technical screenshot or error message.",
|
||||
"ocr_used": true,
|
||||
"metadata": {
|
||||
"image_path": "output/ticket_T11143/T11143_20250422_084617/attachments/image_145435.png",
|
||||
"image_name": "image_145435.png",
|
||||
"timestamp": "20250423_163735",
|
||||
"model_info": {
|
||||
"model": "llama3.2-vision:90b-instruct-q8_0",
|
||||
"temperature": 0.1,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 1000
|
||||
},
|
||||
"ticket_id": "T11143",
|
||||
"source_agent": "AgentImageSorter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"is_relevant": false,
|
||||
"reason": "No\n\nThe provided text appears to be a jumbled collection of characters, likely a corrupted image file, rather than an actual image. It does not contain any visual information that could be related to a technical support issue with BRG_Lab software.",
|
||||
"raw_response": "No\n\nThe provided text appears to be a jumbled collection of characters, likely a corrupted image file, rather than an actual image. It does not contain any visual information that could be related to a technical support issue with BRG_Lab software.",
|
||||
"ocr_used": false,
|
||||
"metadata": {
|
||||
"image_path": "output/ticket_T11143/T11143_20250422_084617/attachments/543d7da1b54c29ff43ce5712d1a9aa4962ed21795c4e943fcb8cb84fd4d7465a.jpg",
|
||||
"image_name": "543d7da1b54c29ff43ce5712d1a9aa4962ed21795c4e943fcb8cb84fd4d7465a.jpg",
|
||||
"timestamp": "20250423_163747",
|
||||
"model_info": {
|
||||
"model": "llama3.2-vision:90b-instruct-q8_0",
|
||||
"temperature": 0.1,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 1000
|
||||
},
|
||||
"ticket_id": "T11143",
|
||||
"source_agent": "AgentImageSorter"
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,30 @@
|
||||
RÉSULTATS DE L'ANALYSE TRI_IMAGE - TICKET T11143
|
||||
================================================================================
|
||||
|
||||
--- ÉLÉMENT 1 ---
|
||||
|
||||
Yes.
|
||||
|
||||
This image appears to be a technical screenshot of a Tomcat server setup success page, which may be relevant for troubleshooting or configuration purposes related to the BRG_Lab software from CBAO enterprise, especially if the software uses Tomcat as part of its infrastructure. The text detected in the image provides information about the Tomcat installation and configuration, which could be useful for technical support issues.
|
||||
|
||||
----------------------------------------
|
||||
|
||||
--- ÉLÉMENT 2 ---
|
||||
|
||||
No
|
||||
|
||||
The text detected in the image appears to be unrelated to technical support or software issues, and the content seems to be a personal or generic message rather than a technical screenshot or error message.
|
||||
|
||||
----------------------------------------
|
||||
|
||||
--- ÉLÉMENT 3 ---
|
||||
|
||||
No
|
||||
|
||||
The provided text appears to be a jumbled collection of characters, likely a corrupted image file, rather than an actual image. It does not contain any visual information that could be related to a technical support issue with BRG_Lab software.
|
||||
|
||||
----------------------------------------
|
||||
|
||||
|
||||
================================================================================
|
||||
Fichier original: tri_image_llama3.2-vision_90b-instruct-q8_0_results.json
|
||||
@ -0,0 +1,36 @@
|
||||
RAPPORT D'ANALYSE DU TICKET T11143
|
||||
==================================================
|
||||
|
||||
** Rapport croisé **
|
||||
|
||||
**Résumé**
|
||||
L'analyse des billets de support révèle un contenu insuffisant pour une analyse approfondie, mais l'image fournie suggère que le problème pourrait être lié à la configuration ou à la configuration de Tomcat.
|
||||
|
||||
** Tableau chronologique des échanges **
|
||||
|
||||
| Émetteur | Type | Date | Contenu | Éléments visuels |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Client | Question | Inconnu | Contenu insuffisant pour l'analyse | - |
|
||||
| Support | Demande de plus d'informations | Inconnu | Demande de détails supplémentaires pour aider le problème | - |
|
||||
| Client | Réponse | 2025-04-23 16:36:57 | Image fournie (image.png) de la page d'accueil par défaut de Tomcat | Capture d'écran de la page d'accueil par défaut de Tomcat, indiquant une configuration réussie |
|
||||
|
||||
** Analyse d'image **
|
||||
|
||||
L'image fournie est une capture d'écran de la page d'accueil par défaut de Tomcat, ce qui indique que Tomcat a été configuré avec succès. La page affiche des informations techniques telles que:
|
||||
|
||||
* Version Tomcat: 7
|
||||
* `Catalina_Home` Rental:` / usr / share / tomcat7`
|
||||
* `Catalina_Base` Rental:` / var / lib / tomcat7`
|
||||
* Référence à `/ etc / tomcat7 / tomcat-users.xml`
|
||||
|
||||
** Contexte technique **
|
||||
|
||||
L'image suggère que le problème pourrait être lié à la configuration ou à la configuration de Tomcat. Le fait que la page d'accueil de Tomcat par défaut s'affiche indique que Tomcat est en cours d'exécution, mais il peut y avoir des problèmes avec des applications ou des configurations spécifiques.
|
||||
|
||||
** Recommandations **
|
||||
|
||||
* Demandez des informations supplémentaires au client pour clarifier le problème.
|
||||
* Vérifiez la configuration et la configuration de Tomcat pour vous assurer qu'elle correspond aux paramètres attendus.
|
||||
* Envisagez d'installer des packages suggérés (Tomcat 7 Docs, Tomcat 7 Exemple, Tomcat 7 Admin) pour fournir des fonctionnalités supplémentaires.
|
||||
|
||||
Remarque: Le tableau ne comprend qu'un seul échange entre le client et le support, car il n'y a pas suffisamment d'informations pour créer un tableau chronologique plus détaillé.
|
||||
@ -0,0 +1,36 @@
|
||||
ANALYSIS REPORT FOR TICKET T11143
|
||||
==================================================
|
||||
|
||||
**Cross Report**
|
||||
|
||||
**Summary**
|
||||
The support ticket analysis reveals insufficient content for a thorough analysis, but the provided image suggests that the issue might be related to Tomcat setup or configuration.
|
||||
|
||||
**Chronological Table of Exchanges**
|
||||
|
||||
| Transmitter | Type | Date | Contents | Visual elements |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Customer | Question | Unknown | Insufficient content for analysis | - |
|
||||
| Support | Request for more information | Unknown | Request for additional details to assist with the issue | - |
|
||||
| Customer | Response | 2025-04-23 16:36:57 | Provided image (image.png) of Tomcat default home page | Screenshot of Tomcat default home page, indicating successful setup |
|
||||
|
||||
**Image Analysis**
|
||||
|
||||
The provided image is a screenshot of the Tomcat default home page, which indicates that Tomcat has been set up successfully. The page displays technical information such as:
|
||||
|
||||
* Tomcat version: 7
|
||||
* `catalina_home` rental: `/usr/share/tomcat7`
|
||||
* `catalina_base` rental: `/var/lib/tomcat7`
|
||||
* Reference to `/etc/tomcat7/tomcat-users.xml`
|
||||
|
||||
**Technical Context**
|
||||
|
||||
The image suggests that the issue might be related to Tomcat setup or configuration. The fact that the default Tomcat home page is displayed indicates that Tomcat is running, but there may be issues with specific applications or configurations.
|
||||
|
||||
**Recommendations**
|
||||
|
||||
* Request additional information from the customer to clarify the issue.
|
||||
* Verify the Tomcat configuration and setup to ensure it matches the expected settings.
|
||||
* Consider installing suggested packages (Tomcat 7 Docs, Tomcat 7 Example, Tomcat 7 Admin) to provide additional functionality.
|
||||
|
||||
Note: The table only includes one exchange between the customer and support, as there is insufficient information to create a more detailed chronological table.
|
||||
@ -1,19 +1,23 @@
|
||||
# utils/ocr_utils.py
|
||||
|
||||
from PIL import Image, ImageEnhance
|
||||
from PIL import Image, ImageEnhance, ImageFilter
|
||||
import pytesseract
|
||||
import logging
|
||||
import os
|
||||
import io
|
||||
import numpy as np
|
||||
import cv2
|
||||
from langdetect import detect, LangDetectException
|
||||
|
||||
logger = logging.getLogger("OCR")
|
||||
|
||||
def pretraiter_image(image_path: str) -> Image.Image:
|
||||
def pretraiter_image(image_path: str, optimize_for_text: bool = True) -> Image.Image:
|
||||
"""
|
||||
Prétraite l'image pour améliorer la qualité de l'OCR.
|
||||
Prétraite l'image pour améliorer la qualité de l'OCR avec des techniques avancées.
|
||||
|
||||
Args:
|
||||
image_path: Chemin de l'image
|
||||
optimize_for_text: Si True, applique des optimisations spécifiques pour le texte
|
||||
|
||||
Returns:
|
||||
Image prétraitée
|
||||
@ -24,14 +28,35 @@ def pretraiter_image(image_path: str) -> Image.Image:
|
||||
# Convertir en niveaux de gris si l'image est en couleur
|
||||
if img.mode != 'L':
|
||||
img = img.convert('L')
|
||||
|
||||
# Conversion en array numpy pour traitement avec OpenCV
|
||||
img_np = np.array(img)
|
||||
|
||||
if optimize_for_text:
|
||||
# Appliquer une binarisation adaptative pour améliorer la lisibilité du texte
|
||||
# Cette technique s'adapte aux variations de luminosité dans l'image
|
||||
if img_np.dtype != np.uint8:
|
||||
img_np = img_np.astype(np.uint8)
|
||||
|
||||
# Utiliser OpenCV pour la binarisation adaptative
|
||||
img_np = cv2.adaptiveThreshold(
|
||||
img_np, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
cv2.THRESH_BINARY, 11, 2
|
||||
)
|
||||
|
||||
# Débruitage pour éliminer les artefacts
|
||||
img_np = cv2.fastNlMeansDenoising(img_np, None, 10, 7, 21)
|
||||
|
||||
# Reconvertir en image PIL
|
||||
img = Image.fromarray(img_np)
|
||||
|
||||
# Améliorer le contraste
|
||||
enhancer = ImageEnhance.Contrast(img)
|
||||
img = enhancer.enhance(1.5) # Facteur de contraste 1.5
|
||||
img = enhancer.enhance(2.0) # Augmenter le facteur de contraste
|
||||
|
||||
# Augmenter la netteté
|
||||
enhancer = ImageEnhance.Sharpness(img)
|
||||
img = enhancer.enhance(1.5) # Facteur de netteté 1.5
|
||||
img = enhancer.enhance(2.0) # Augmenter le facteur de netteté
|
||||
|
||||
# Agrandir l'image si elle est petite
|
||||
width, height = img.size
|
||||
@ -44,58 +69,153 @@ def pretraiter_image(image_path: str) -> Image.Image:
|
||||
return img
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du prétraitement de l'image {image_path}: {e}")
|
||||
return Image.open(image_path) # Retourner l'image originale en cas d'erreur
|
||||
# En cas d'erreur, retourner l'image originale
|
||||
try:
|
||||
return Image.open(image_path)
|
||||
except:
|
||||
# Si même l'ouverture simple échoue, retourner une image vide
|
||||
logger.critical(f"Impossible d'ouvrir l'image {image_path}")
|
||||
return Image.new('L', (100, 100), 255)
|
||||
|
||||
def detecter_langue_texte(texte: str) -> str:
|
||||
"""
|
||||
Détecte la langue principale d'un texte.
|
||||
|
||||
Args:
|
||||
texte: Texte à analyser
|
||||
|
||||
Returns:
|
||||
Code de langue ('fr', 'en', etc.) ou 'unknown' en cas d'échec
|
||||
"""
|
||||
if not texte or len(texte.strip()) < 10:
|
||||
return "unknown"
|
||||
|
||||
try:
|
||||
return detect(texte)
|
||||
except LangDetectException:
|
||||
return "unknown"
|
||||
|
||||
def extraire_texte(image_path: str, lang: str = "auto") -> tuple:
|
||||
"""
|
||||
Effectue un OCR sur une image avec détection automatique de la langue.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image
|
||||
lang: Langue pour l'OCR ('auto', 'fra', 'eng', 'fra+eng')
|
||||
|
||||
Returns:
|
||||
Tuple (texte extrait, langue détectée)
|
||||
"""
|
||||
if not os.path.exists(image_path) or not os.access(image_path, os.R_OK):
|
||||
logger.warning(f"Image inaccessible ou introuvable: {image_path}")
|
||||
return "", "unknown"
|
||||
|
||||
logger.info(f"Traitement OCR pour {image_path} (langue: {lang})")
|
||||
|
||||
# Prétraiter l'image avec différentes configurations
|
||||
img_standard = pretraiter_image(image_path, optimize_for_text=False)
|
||||
img_optimized = pretraiter_image(image_path, optimize_for_text=True)
|
||||
|
||||
# Configurer pytesseract
|
||||
config = '--psm 3 --oem 3' # Page segmentation mode: 3 (auto), OCR Engine mode: 3 (default)
|
||||
|
||||
# Déterminer la langue pour l'OCR
|
||||
ocr_lang = lang
|
||||
if lang == "auto":
|
||||
# Essayer d'extraire du texte avec plusieurs langues
|
||||
try:
|
||||
texte_fr = pytesseract.image_to_string(img_optimized, lang="fra", config=config)
|
||||
texte_en = pytesseract.image_to_string(img_optimized, lang="eng", config=config)
|
||||
texte_multi = pytesseract.image_to_string(img_optimized, lang="fra+eng", config=config)
|
||||
|
||||
# Choisir le meilleur résultat basé sur la longueur et la qualité
|
||||
results = [
|
||||
(texte_fr, "fra", len(texte_fr.strip())),
|
||||
(texte_en, "eng", len(texte_en.strip())),
|
||||
(texte_multi, "fra+eng", len(texte_multi.strip()))
|
||||
]
|
||||
|
||||
results.sort(key=lambda x: x[2], reverse=True)
|
||||
best_text, best_lang, _ = results[0]
|
||||
|
||||
# Détection secondaire basée sur le contenu
|
||||
detected_lang = detecter_langue_texte(best_text)
|
||||
if detected_lang in ["fr", "fra"]:
|
||||
ocr_lang = "fra"
|
||||
elif detected_lang in ["en", "eng"]:
|
||||
ocr_lang = "eng"
|
||||
else:
|
||||
ocr_lang = best_lang
|
||||
|
||||
logger.info(f"Langue détectée: {ocr_lang}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Détection de langue échouée: {e}, utilisation de fra+eng")
|
||||
ocr_lang = "fra+eng"
|
||||
|
||||
# Réaliser l'OCR avec la langue choisie
|
||||
try:
|
||||
# Essayer d'abord avec l'image optimisée pour le texte
|
||||
texte = pytesseract.image_to_string(img_optimized, lang=ocr_lang, config=config)
|
||||
|
||||
# Si le résultat est trop court, essayer avec l'image standard
|
||||
if len(texte.strip()) < 10:
|
||||
texte_standard = pytesseract.image_to_string(img_standard, lang=ocr_lang, config=config)
|
||||
if len(texte_standard.strip()) > len(texte.strip()):
|
||||
texte = texte_standard
|
||||
logger.info("Utilisation du résultat de l'image standard (meilleur résultat)")
|
||||
except Exception as ocr_err:
|
||||
logger.warning(f"OCR échoué: {ocr_err}, tentative avec l'image originale")
|
||||
# En cas d'échec, essayer avec l'image originale
|
||||
try:
|
||||
with Image.open(image_path) as original_img:
|
||||
texte = pytesseract.image_to_string(original_img, lang=ocr_lang, config=config)
|
||||
except Exception as e:
|
||||
logger.error(f"OCR échoué complètement: {e}")
|
||||
return "", "unknown"
|
||||
|
||||
# Nettoyer le texte
|
||||
texte = texte.strip()
|
||||
|
||||
# Détecter la langue du texte extrait pour confirmation
|
||||
detected_lang = detecter_langue_texte(texte) if texte else "unknown"
|
||||
|
||||
# Sauvegarder l'image prétraitée pour debug si OCR réussi
|
||||
if texte:
|
||||
try:
|
||||
debug_dir = "debug_ocr"
|
||||
os.makedirs(debug_dir, exist_ok=True)
|
||||
img_name = os.path.basename(image_path)
|
||||
img_optimized.save(os.path.join(debug_dir, f"optimized_{img_name}"), format="JPEG")
|
||||
img_standard.save(os.path.join(debug_dir, f"standard_{img_name}"), format="JPEG")
|
||||
|
||||
# Sauvegarder aussi le texte extrait
|
||||
with open(os.path.join(debug_dir, f"ocr_{img_name}.txt"), "w", encoding="utf-8") as f:
|
||||
f.write(f"OCR Langue: {ocr_lang}\n")
|
||||
f.write(f"Langue détectée: {detected_lang}\n")
|
||||
f.write("-" * 50 + "\n")
|
||||
f.write(texte)
|
||||
|
||||
logger.info(f"Images prétraitées et résultat OCR sauvegardés dans {debug_dir}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Impossible de sauvegarder les fichiers de débogage: {e}")
|
||||
|
||||
# Journaliser le résultat
|
||||
logger.info(f"OCR réussi [{image_path}] — {len(texte)} caractères: {texte[:100]}...")
|
||||
else:
|
||||
logger.warning(f"OCR vide (aucun texte détecté) pour {image_path}")
|
||||
|
||||
return texte, detected_lang
|
||||
|
||||
def extraire_texte_fr(image_path: str) -> str:
|
||||
"""
|
||||
Effectue un OCR sur une image en langue française.
|
||||
Retourne le texte brut extrait (chaîne vide si aucun texte détecté ou en cas d'erreur).
|
||||
Chaque appel est isolé et tracé dans les logs.
|
||||
Effectue un OCR sur une image en langue française (pour compatibilité).
|
||||
Utilise la nouvelle fonction plus avancée avec détection automatique.
|
||||
|
||||
Args:
|
||||
image_path: Chemin vers l'image
|
||||
|
||||
Returns:
|
||||
Texte extrait
|
||||
"""
|
||||
try:
|
||||
if not os.path.exists(image_path) or not os.access(image_path, os.R_OK):
|
||||
logger.warning(f"Image inaccessible ou introuvable: {image_path}")
|
||||
return ""
|
||||
|
||||
logger.info(f"Traitement OCR pour {image_path}")
|
||||
|
||||
# Configurer pytesseract
|
||||
config = '--psm 3 --oem 3' # Page segmentation mode: 3 (auto), OCR Engine mode: 3 (default)
|
||||
|
||||
# Prétraiter l'image
|
||||
img = pretraiter_image(image_path)
|
||||
logger.info(f"Image prétraitée: dimensions={img.size}, mode={img.mode}")
|
||||
|
||||
# Réaliser l'OCR avec fallback
|
||||
try:
|
||||
texte = pytesseract.image_to_string(img, lang="fra", config=config)
|
||||
except Exception as ocr_err:
|
||||
logger.warning(f"Première tentative OCR échouée: {ocr_err}, tentative avec image originale")
|
||||
# En cas d'échec, essayer avec l'image originale
|
||||
with Image.open(image_path) as original_img:
|
||||
texte = pytesseract.image_to_string(original_img, lang="fra", config=config)
|
||||
|
||||
# Nettoyer le texte
|
||||
texte = texte.strip()
|
||||
|
||||
# Sauvegarder l'image prétraitée pour debug si OCR réussi
|
||||
if texte:
|
||||
try:
|
||||
debug_dir = "debug_ocr"
|
||||
os.makedirs(debug_dir, exist_ok=True)
|
||||
img_name = os.path.basename(image_path)
|
||||
img.save(os.path.join(debug_dir, f"pretreated_{img_name}"), format="JPEG")
|
||||
logger.info(f"Image prétraitée sauvegardée dans {debug_dir}/pretreated_{img_name}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Impossible de sauvegarder l'image prétraitée: {e}")
|
||||
|
||||
# Journaliser le résultat
|
||||
if texte:
|
||||
logger.info(f"OCR réussi [{image_path}] — {len(texte)} caractères: {texte[:100]}...")
|
||||
else:
|
||||
logger.warning(f"OCR vide (aucun texte détecté) pour {image_path}")
|
||||
|
||||
return texte
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'OCR de {image_path}: {e}")
|
||||
return ""
|
||||
texte, _ = extraire_texte(image_path, lang="auto")
|
||||
return texte
|
||||
|
||||
@ -5,27 +5,162 @@ import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from typing import Optional
|
||||
from typing import Optional, Dict, Any
|
||||
import hashlib
|
||||
from functools import lru_cache
|
||||
|
||||
logger = logging.getLogger("Translate")
|
||||
|
||||
def fr_to_en(text: str) -> str:
|
||||
try:
|
||||
if not text.strip():
|
||||
return ""
|
||||
return GoogleTranslator(source="fr", target="en").translate(text)
|
||||
except Exception as e:
|
||||
logger.error(f"Traduction FR->EN échouée: {e}")
|
||||
# Cache global pour les traductions (conservé entre les appels)
|
||||
TRANSLATION_CACHE: Dict[str, str] = {}
|
||||
MAX_CACHE_SIZE = 1000 # Nombre maximal d'entrées dans le cache
|
||||
|
||||
def _get_cache_key(text: str, source: str, target: str) -> str:
|
||||
"""
|
||||
Génère une clé de cache unique pour une traduction donnée.
|
||||
|
||||
Args:
|
||||
text: Texte à traduire
|
||||
source: Langue source
|
||||
target: Langue cible
|
||||
|
||||
Returns:
|
||||
Clé de cache
|
||||
"""
|
||||
# Limiter la taille du texte pour la clé de cache
|
||||
text_snippet = text[:500] if text else ""
|
||||
hash_key = hashlib.md5(f"{text_snippet}|{source}|{target}".encode('utf-8')).hexdigest()
|
||||
return hash_key
|
||||
|
||||
def _clean_cache_if_needed() -> None:
|
||||
"""
|
||||
Nettoie le cache si sa taille dépasse la limite maximale.
|
||||
"""
|
||||
global TRANSLATION_CACHE
|
||||
if len(TRANSLATION_CACHE) > MAX_CACHE_SIZE:
|
||||
# Garder seulement 75% des entrées les plus récentes (approximativement)
|
||||
items = list(TRANSLATION_CACHE.items())
|
||||
keep_count = int(MAX_CACHE_SIZE * 0.75)
|
||||
TRANSLATION_CACHE = dict(items[-keep_count:])
|
||||
logger.info(f"Cache de traduction nettoyé : {len(TRANSLATION_CACHE)} entrées conservées")
|
||||
|
||||
def translate_text(text: str, source: str, target: str, use_cache: bool = True) -> str:
|
||||
"""
|
||||
Fonction générique de traduction avec gestion de cache.
|
||||
|
||||
Args:
|
||||
text: Texte à traduire
|
||||
source: Langue source ('fr', 'en', etc.)
|
||||
target: Langue cible ('fr', 'en', etc.)
|
||||
use_cache: Si True, utilise le cache de traduction
|
||||
|
||||
Returns:
|
||||
Texte traduit
|
||||
"""
|
||||
if not text or not text.strip():
|
||||
return ""
|
||||
|
||||
# Vérifier le cache
|
||||
if use_cache:
|
||||
cache_key = _get_cache_key(text, source, target)
|
||||
if cache_key in TRANSLATION_CACHE:
|
||||
logger.debug(f"Traduction récupérée du cache pour {source}->{target}")
|
||||
return TRANSLATION_CACHE[cache_key]
|
||||
|
||||
# Limiter la taille du texte pour éviter les problèmes avec l'API
|
||||
# Les longs textes sont découpés et traduits par morceaux
|
||||
MAX_TEXT_LENGTH = 5000
|
||||
if len(text) > MAX_TEXT_LENGTH:
|
||||
chunks = _split_text_into_chunks(text, MAX_TEXT_LENGTH)
|
||||
translated_chunks = []
|
||||
for chunk in chunks:
|
||||
translated_chunk = translate_text(chunk, source, target, use_cache)
|
||||
translated_chunks.append(translated_chunk)
|
||||
result = ' '.join(translated_chunks)
|
||||
else:
|
||||
try:
|
||||
translator = GoogleTranslator(source=source, target=target)
|
||||
result = translator.translate(text)
|
||||
except Exception as e:
|
||||
logger.error(f"Traduction {source}->{target} échouée: {e}")
|
||||
return text # Retourner le texte original en cas d'erreur
|
||||
|
||||
# Mettre en cache
|
||||
if use_cache:
|
||||
cache_key = _get_cache_key(text, source, target)
|
||||
TRANSLATION_CACHE[cache_key] = result
|
||||
_clean_cache_if_needed()
|
||||
|
||||
return result
|
||||
|
||||
def _split_text_into_chunks(text: str, max_length: int) -> list:
|
||||
"""
|
||||
Découpe un texte en morceaux plus petits en respectant les phrases.
|
||||
|
||||
Args:
|
||||
text: Texte à découper
|
||||
max_length: Longueur maximale de chaque morceau
|
||||
|
||||
Returns:
|
||||
Liste des morceaux de texte
|
||||
"""
|
||||
chunks = []
|
||||
current_chunk = ""
|
||||
|
||||
# Split by paragraphs
|
||||
paragraphs = text.split('\n')
|
||||
|
||||
for paragraph in paragraphs:
|
||||
# If paragraph is too long, split by sentences
|
||||
if len(paragraph) > max_length:
|
||||
sentences = paragraph.replace('. ', '.\n').replace('! ', '!\n').replace('? ', '?\n').split('\n')
|
||||
for sentence in sentences:
|
||||
if len(current_chunk) + len(sentence) + 1 <= max_length:
|
||||
if current_chunk:
|
||||
current_chunk += ' ' + sentence
|
||||
else:
|
||||
current_chunk = sentence
|
||||
else:
|
||||
chunks.append(current_chunk)
|
||||
current_chunk = sentence
|
||||
else:
|
||||
if len(current_chunk) + len(paragraph) + 1 <= max_length:
|
||||
if current_chunk:
|
||||
current_chunk += '\n' + paragraph
|
||||
else:
|
||||
current_chunk = paragraph
|
||||
else:
|
||||
chunks.append(current_chunk)
|
||||
current_chunk = paragraph
|
||||
|
||||
if current_chunk:
|
||||
chunks.append(current_chunk)
|
||||
|
||||
return chunks
|
||||
|
||||
def fr_to_en(text: str) -> str:
|
||||
"""
|
||||
Traduit du français vers l'anglais.
|
||||
|
||||
Args:
|
||||
text: Texte en français
|
||||
|
||||
Returns:
|
||||
Texte traduit en anglais
|
||||
"""
|
||||
return translate_text(text, "fr", "en")
|
||||
|
||||
def en_to_fr(text: str) -> str:
|
||||
try:
|
||||
if not text.strip():
|
||||
return ""
|
||||
return GoogleTranslator(source="en", target="fr").translate(text)
|
||||
except Exception as e:
|
||||
logger.error(f"Traduction EN->FR échouée: {e}")
|
||||
return ""
|
||||
"""
|
||||
Traduit de l'anglais vers le français.
|
||||
|
||||
Args:
|
||||
text: Texte en anglais
|
||||
|
||||
Returns:
|
||||
Texte traduit en français
|
||||
"""
|
||||
return translate_text(text, "en", "fr")
|
||||
|
||||
def determiner_repertoire_ticket(ticket_id: str):
|
||||
"""
|
||||
@ -134,3 +269,10 @@ def sauvegarder_ocr_traduction(
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la sauvegarde OCR+TRAD pour {image_path}: {e}")
|
||||
|
||||
# Fonction pour effacer le cache de traduction (utile pour les tests)
|
||||
def clear_translation_cache():
|
||||
"""Vide le cache de traduction."""
|
||||
global TRANSLATION_CACHE
|
||||
TRANSLATION_CACHE = {}
|
||||
logger.info("Cache de traduction vidé")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user