llm_ticket3/.specstory/history/2025-04-07_12-42-identification-du-fichier-pour-agentjsonanalyser.md
2025-04-14 17:15:28 +02:00

64 KiB

Identification du fichier pour agentjsonanalyser (2025-04-07 14:42:16)

User

En ce qui concerne l'analyse de texte (agentjsonanalyser) il faudrait déterminer quel est précisement le fichier récupéré


Assistant

Pour déterminer quel fichier est récupéré par l'analyseur de texte agentjsonanalyser, il serait utile de vérifier comment ce module est intégré dans votre code. En général, cela pourrait impliquer de regarder les appels de fonction ou les importations dans votre projet.

Dans le fichier markdown_to_json.py, il n'y a pas de référence directe à agentjsonanalyser. Cependant, si ce module est utilisé ailleurs dans votre code, vous devriez chercher des lignes comme :

from agentjsonanalyser import ...

ou des appels de fonction qui pourraient indiquer quel fichier est analysé.

Si vous avez un fichier spécifique ou une section de code où agentjsonanalyser est utilisé, je peux vous aider à l'examiner. Sinon, vous pourriez également vérifier la documentation ou les commentaires dans votre code pour voir si des informations supplémentaires sont fournies sur le fichier analysé.

Si vous avez besoin d'aide pour une partie spécifique de votre code, n'hésitez pas à la partager !


User

peux tu déterminer quel est le fichier récupéré


Assistant

Dans le fichier agent_json_analyser.py, l'agent AgentJsonAnalyser est conçu pour analyser des tickets au format JSON. Le fichier récupéré pour l'analyse est celui qui est passé en tant que paramètre à la méthode executer, qui prend un dictionnaire ticket_data contenant les données du ticket.

Voici les points clés concernant le fichier récupéré :

  1. Format des données : Le fichier JSON doit contenir des informations structurées sur le ticket, y compris des champs comme code, name, description, messages, et potentiellement d'autres métadonnées.

  2. Méthode executer : Cette méthode est responsable de l'analyse du ticket. Elle appelle la méthode _formater_ticket_pour_analyse pour préparer les données du ticket avant de les envoyer à un modèle de langage (LLM) pour analyse.

  3. Exemple de structure de données : Le dictionnaire ticket_data pourrait ressembler à ceci :

    {
        "code": "TICKET-123",
        "name": "Problème de connexion",
        "description": "L'utilisateur ne peut pas se connecter.",
        "messages": [
            {
                "from": "utilisateur",
                "date": "2023-10-01",
                "content": "Je ne peux pas me connecter."
            }
        ],
        "metadata": {
            "priorité": "haute",
            "statut": "ouvert"
        }
    }
    

Pour résumer, le fichier récupéré est un fichier JSON contenant les informations du ticket, et il est analysé par l'agent via la méthode executer. Si vous avez besoin de plus de détails ou d'exemples spécifiques, n'hésitez pas à demander !


User

peut on déterminer quels sont les fichiers utilisés dana agent_json_analyser.py en passant en revue son code et celui de orchestror.py


Assistant

Pour déterminer quels fichiers sont utilisés dans agent_json_analyser.py et orchestrator.py, examinons les deux fichiers et les interactions entre eux.

1. Fichiers utilisés dans agent_json_analyser.py

Dans agent_json_analyser.py, l'agent AgentJsonAnalyser est conçu pour analyser des tickets JSON. Voici les points clés concernant les fichiers :

  • Fichier JSON : L'agent s'attend à recevoir un dictionnaire ticket_data qui contient les données du ticket. Ce dictionnaire est généralement chargé à partir d'un fichier JSON, comme indiqué dans la méthode executer où il utilise self.llm.interroger(prompt) pour analyser les données.

2. Fichiers utilisés dans orchestrator.py

Dans orchestrator.py, plusieurs méthodes sont responsables de la gestion des fichiers :

  • Méthode trouver_rapport : Cette méthode cherche des fichiers de rapport dans différents emplacements. Elle vérifie les fichiers JSON et Markdown pour un ticket donné. Les chemins des fichiers sont construits en fonction de l'ID du ticket et de l'emplacement d'extraction.

  • Méthode _preparer_donnees_ticket : Cette méthode essaie de charger les données du ticket à partir d'un fichier JSON, et si cela échoue, elle essaie de charger à partir d'un fichier Markdown. Les fichiers recherchés sont :

    • "{ticket_id}_rapport.json"
    • "{ticket_id}_rapport.md"
  • Méthode _extraire_donnees_de_markdown : Cette méthode extrait les données d'un fichier Markdown et les structure en un dictionnaire. Elle lit le contenu du fichier Markdown spécifié par md_path.

Conclusion

En résumé, les fichiers utilisés dans agent_json_analyser.py et orchestrator.py sont principalement des fichiers JSON et Markdown qui contiennent les données des tickets. Voici les types de fichiers et leurs emplacements :

  • Fichiers JSON : Cherchés dans les répertoires d'extraction, nommés selon le format "{ticket_id}_rapport.json".
  • Fichiers Markdown : Cherchés dans les mêmes répertoires, nommés selon le format "{ticket_id}_rapport.md".

Ces fichiers sont essentiels pour le fonctionnement de l'agent d'analyse JSON et de l'orchestrateur, car ils fournissent les données nécessaires pour l'analyse et le traitement des tickets. Si vous avez besoin d'une exploration plus approfondie ou d'exemples spécifiques, n'hésitez pas à demander !


User

il faut déterminer avec précision quel est réellment le rapport utiliser dans cet agent car s'il s'agit du markdown alors il faut revoir son nom et sa configuration pour mieux l'adapter as tu compris?


Assistant

Examinons plus précisément quel rapport est réellement utilisé par l'agent JSON.

Dans orchestrator.py, l'agent JSON est appelé via cette séquence:

  1. Dans la méthode traiter_ticket(), l'orchestrateur commence par chercher les rapports avec trouver_rapport(extraction_path, ticket_id)
  2. Ensuite, il prépare les données du ticket avec _preparer_donnees_ticket(rapports, ticket_id)
  3. Si des données sont trouvées, il appelle l'agent JSON avec self.json_agent.executer(ticket_data)

La méthode _preparer_donnees_ticket() est cruciale. Elle essaie d'abord de charger les données depuis un fichier JSON. Si cela échoue, elle essaie de charger depuis un fichier Markdown en utilisant _extraire_donnees_de_markdown().

En analysant le code, je constate que:

  1. L'orchestrateur cherche d'abord le fichier "{ticket_id}_rapport.json"
  2. Si ce fichier n'existe pas ou s'il y a une erreur, il essaie de charger "{ticket_id}_rapport.md"
  3. La méthode _extraire_donnees_de_markdown() convertit le contenu Markdown en une structure de données similaire à celle attendue du fichier JSON

Donc, l'agent JSON peut en fait traiter des données provenant d'un fichier Markdown, mais seulement après que l'orchestrateur ait converti le contenu Markdown en une structure de données appropriée.

Si le rapport Markdown est le format principal utilisé, il faudrait renommer l'agent en quelque chose comme AgentTicketAnalyser au lieu de AgentJsonAnalyser pour refléter sa capacité à traiter des données de différentes sources, pas seulement JSON. De plus, il faudrait ajuster sa configuration pour mieux gérer les particularités des données extraites du Markdown.

Est-ce que cette analyse répond à votre question?


User

oui que ferais tu pour améliorer ce fonctionnement


Assistant

Pour améliorer ce fonctionnement, voici mes recommandations :

  1. Renommer l'agent pour refléter sa fonction réelle :

    • Renommer AgentJsonAnalyser en AgentTicketAnalyser ou AgentRapportAnalyser
    • Mettre à jour les références dans orchestrator.py pour utiliser le nouveau nom
  2. Restructurer le traitement des fichiers :

    • Créer une abstraction de source de données avec des implémentations spécifiques pour JSON et Markdown
    • Implémenter une classe TicketDataLoader avec des méthodes spécifiques pour chaque format
  3. Améliorer l'extraction des données Markdown :

    • Renforcer la robustesse de la méthode _extraire_donnees_de_markdown()
    • Ajouter des validations pour s'assurer que tous les champs obligatoires sont présents
    • Gérer correctement les formats de date et les types de données
  4. Restructurer le prompt système :

    • Adapter le prompt système pour qu'il soit plus flexible quant à la source des données
    • Inclure des instructions spécifiques pour le traitement des données issues du Markdown
  5. Ajouter une détection de format :

    • Implémenter une méthode de détection automatique du format pour mieux adapter le traitement
    • Ajouter des métadonnées sur la source dans les données traitées
  6. Meilleure gestion des erreurs :

    • Ajouter des logs détaillés sur le type de fichier traité et les problèmes rencontrés
    • Gérer proprement les cas où la structure du Markdown ne correspond pas aux attentes
  7. Documentation claire :

    • Documenter les formats attendus pour les fichiers Markdown
    • Expliquer comment l'agent interprète différentes sections du Markdown
  8. Tests spécifiques :

    • Ajouter des tests unitaires spécifiques pour chaque format de fichier
    • Valider que le traitement est cohérent entre les formats JSON et Markdown

Ces améliorations permettraient d'avoir un agent plus flexible, capable de traiter efficacement les deux formats tout en maintenant une cohérence dans l'analyse produite.


User

tu peux procéder aux améliorations que tu viens de me donner


Assistant

Je vais procéder aux améliorations suggérées. Commençons par examiner la structure actuelle puis implémentons les changements nécessaires.


Searched codebase "agents base_agent.py" • **25** results
File Lines
agents/base_agent.py L1-29
agents/agent_report_generator.py L227-249
agents/agent_image_analyser.py L1-39
agents/agent_report_generator.py L249-282
agents/agent_json_analyser.py L1-32
agents/agent_image_analyser.py L190-221
agents/agent_image_analyser.py L105-143
agents/agent_report_generator.py L69-98
agents/agent_image_sorter.py L114-152
agents/agent_json_analyser.py L63-109
agents/agent_image_sorter.py L1-48
agents/agent_json_analyser.py L109-160
agents/agent_image_analyser.py L70-105
agents/agent_image_sorter.py L335-370
agents/agent_image_sorter.py L152-185
agents/agent_report_generator.py L98-134
agents/agent_report_generator.py L1-38
agents/agent_image_analyser.py L287-320
agents/agent_image_sorter.py L262-294
agents/agent_image_sorter.py L294-335
agents/agent_image_sorter.py L370-385
agents/agent_image_sorter.py L185-224
agents/agent_image_analyser.py L143-190
agents/agent_report_generator.py L282-311
agents/agent_report_generator.py L173-206

Maintenant, vérifions la structure du projet pour mieux comprendre comment les fichiers sont organisés.


Listed directory agents • **7** results
Name
📄 agent_image_analyser.py
📄 agent_image_sorter.py
📄 agent_json_analyser.py
📄 agent_report_generator.py
📄 agent_report_generator.py.backup
📄 base_agent.py
📁 __pycache__

Examinons plus en détail le module d'extraction des données à partir de Markdown :


Read file: utils/markdown_to_json.py


Maintenant, réalisons les améliorations suggérées. Commençons par créer un nouvel agent plus générique (renommer l'agent) et implémenter une meilleure gestion des différents formats.

  1. Créons d'abord un module pour charger les données de tickets :

- 
+ import os
+ import re
+ import json
+ import logging
+ from typing import Dict, Optional, Any, List, Union
+ from abc import ABC, abstractmethod
+ 
+ logger = logging.getLogger("TicketDataLoader")
+ 
+ class TicketDataSource(ABC):
+     """Classe abstraite pour les sources de données de tickets"""
+     
+     @abstractmethod
+     def charger(self, chemin_fichier: str) -> Dict[str, Any]:
+         """Charge les données du ticket depuis un fichier source"""
+         pass
+     
+     @abstractmethod
+     def get_format(self) -> str:
+         """Retourne le format de la source de données"""
+         pass
+     
+     def valider_donnees(self, donnees: Dict[str, Any]) -> bool:
+         """Vérifie si les données chargées contiennent les champs obligatoires"""
+         champs_obligatoires = ["code", "name"]
+         return all(field in donnees for field in champs_obligatoires)
+ 
+ 
+ class JsonTicketSource(TicketDataSource):
+     """Source de données pour les tickets au format JSON"""
+     
+     def charger(self, chemin_fichier: str) -> Dict[str, Any]:
+         """Charge les données du ticket depuis un fichier JSON"""
+         try:
+             with open(chemin_fichier, 'r', encoding='utf-8') as f:
+                 donnees = json.load(f)
+             
+             # Ajout de métadonnées sur la source
+             if "metadata" not in donnees:
+                 donnees["metadata"] = {}
+             
+             donnees["metadata"]["source_file"] = chemin_fichier
+             donnees["metadata"]["format"] = "json"
+             
+             return donnees
+         except Exception as e:
+             logger.error(f"Erreur lors du chargement du fichier JSON {chemin_fichier}: {str(e)}")
+             raise ValueError(f"Impossible de charger le fichier JSON: {str(e)}")
+     
+     def get_format(self) -> str:
+         return "json"
+ 
+ 
+ class MarkdownTicketSource(TicketDataSource):
+     """Source de données pour les tickets au format Markdown"""
+     
+     def charger(self, chemin_fichier: str) -> Dict[str, Any]:
+         """Charge les données du ticket depuis un fichier Markdown"""
+         try:
+             with open(chemin_fichier, 'r', encoding='utf-8') as f:
+                 contenu_md = f.read()
+             
+             # Extraire les données du contenu Markdown
+             donnees = self._extraire_donnees_de_markdown(contenu_md)
+             
+             # Ajout de métadonnées sur la source
+             if "metadata" not in donnees:
+                 donnees["metadata"] = {}
+             
+             donnees["metadata"]["source_file"] = chemin_fichier
+             donnees["metadata"]["format"] = "markdown"
+             
+             return donnees
+         except Exception as e:
+             logger.error(f"Erreur lors du chargement du fichier Markdown {chemin_fichier}: {str(e)}")
+             raise ValueError(f"Impossible de charger le fichier Markdown: {str(e)}")
+     
+     def get_format(self) -> str:
+         return "markdown"
+     
+     def _extraire_donnees_de_markdown(self, contenu_md: str) -> Dict[str, Any]:
+         """Extrait les données structurées d'un contenu Markdown"""
+         donnees = {}
+         
+         # Diviser le contenu en sections
+         sections = re.split(r"\n## ", contenu_md)
+         
+         # Traiter chaque section
+         for section in sections:
+             if section.startswith("Informations du ticket"):
+                 ticket_info = self._analyser_infos_ticket(section)
+                 donnees.update(ticket_info)
+             elif section.startswith("Messages"):
+                 messages = self._analyser_messages(section)
+                 donnees["messages"] = messages
+             elif section.startswith("Informations sur l'extraction"):
+                 extraction_info = self._analyser_infos_extraction(section)
+                 donnees.update(extraction_info)
+         
+         # Réorganiser les champs pour que la description soit après "name"
+         ordered_fields = ["id", "code", "name", "description"]
+         ordered_data = {}
+         
+         # D'abord ajouter les champs dans l'ordre spécifié
+         for field in ordered_fields:
+             if field in donnees:
+                 ordered_data[field] = donnees[field]
+         
+         # Ensuite ajouter les autres champs
+         for key, value in donnees.items():
+             if key not in ordered_data:
+                 ordered_data[key] = value
+         
+         # S'assurer que la description est présente
+         if "description" not in ordered_data:
+             ordered_data["description"] = ""
+         
+         return ordered_data
+     
+     def _analyser_infos_ticket(self, section: str) -> Dict[str, Any]:
+         """Analyse la section d'informations du ticket"""
+         info = {}
+         description = []
+         capturing_description = False
+         
+         lines = section.strip().split("\n")
+         i = 0
+         while i < len(lines):
+             line = lines[i]
+             
+             # Si on est déjà en train de capturer la description
+             if capturing_description:
+                 # Vérifie si on atteint une nouvelle section ou un nouveau champ
+                 if i + 1 < len(lines) and (lines[i + 1].startswith("## ") or lines[i + 1].startswith("- **")):
+                     capturing_description = False
+                     info["description"] = "\n".join(description).strip()
+                 else:
+                     description.append(line)
+                     i += 1
+                     continue
+             
+             # Détecte le début de la description
+             desc_match = re.match(r"- \*\*description\*\*:", line)
+             if desc_match:
+                 capturing_description = True
+                 i += 1  # Passe à la ligne suivante
+                 continue
+                 
+             # Traite les autres champs normalement
+             match = re.match(r"- \*\*(.*?)\*\*: (.*)", line)
+             if match:
+                 key, value = match.groups()
+                 key = key.lower().replace("/", "_").replace(" ", "_")
+                 info[key] = value.strip()
+             
+             i += 1
+         
+         # Si on finit en capturant la description, l'ajouter au dictionnaire
+         if capturing_description and description:
+             info["description"] = "\n".join(description).strip()
+         elif "description" not in info:
+             info["description"] = ""
+         
+         return info
+     
+     def _analyser_messages(self, section: str) -> List[Dict[str, Any]]:
+         """Analyse la section des messages"""
+         messages = []
+         current_message = {}
+         in_message = False
+         
+         lines = section.strip().split("\n")
+         
+         for line in lines:
+             if line.startswith("### Message"):
+                 if current_message:
+                     messages.append(current_message)
+                     current_message = {}
+                 in_message = True
+             
+             elif line.startswith("**") and in_message:
+                 match = re.match(r"\*\*(.*?)\*\*: (.*)", line)
+                 if match:
+                     key, value = match.groups()
+                     key = key.lower().replace("/", "_").replace(" ", "_")
+                     current_message[key] = value.strip()
+             else:
+                 if in_message:
+                     current_message["content"] = current_message.get("content", "") + line + "\n"
+         
+         if current_message:
+             messages.append(current_message)
+         
+         # Nettoyer le contenu des messages
+         for message in messages:
+             if "content" in message:
+                 message["content"] = message["content"].strip()
+         
+         return messages
+     
+     def _analyser_infos_extraction(self, section: str) -> Dict[str, Any]:
+         """Analyse la section d'informations sur l'extraction"""
+         extraction_info = {}
+         
+         lines = section.strip().split("\n")
+         for line in lines:
+             match = re.match(r"- \*\*(.*?)\*\*: (.*)", line)
+             if match:
+                 key, value = match.groups()
+                 key = key.lower().replace("/", "_").replace(" ", "_")
+                 extraction_info[key] = value.strip()
+         
+         return extraction_info
+ 
+ 
+ class TicketDataLoader:
+     """Classe pour charger les données de tickets à partir de différentes sources"""
+     
+     def __init__(self):
+         self.sources = {
+             "json": JsonTicketSource(),
+             "markdown": MarkdownTicketSource()
+         }
+     
+     def detecter_format(self, chemin_fichier: str) -> str:
+         """Détecte le format du fichier à partir de son extension"""
+         ext = os.path.splitext(chemin_fichier)[1].lower()
+         if ext == '.json':
+             return "json"
+         elif ext in ['.md', '.markdown']:
+             return "markdown"
+         else:
+             raise ValueError(f"Format de fichier non supporté: {ext}")
+     
+     def charger(self, chemin_fichier: str, format_force: Optional[str] = None) -> Dict[str, Any]:
+         """
+         Charge les données d'un ticket à partir d'un fichier
+         
+         Args:
+             chemin_fichier: Chemin du fichier à charger
+             format_force: Format à utiliser (ignore la détection automatique)
+             
+         Returns:
+             Dictionnaire contenant les données du ticket
+         """
+         if not os.path.exists(chemin_fichier):
+             raise FileNotFoundError(f"Le fichier {chemin_fichier} n'existe pas")
+         
+         format_fichier = format_force if format_force else self.detecter_format(chemin_fichier)
+         
+         if format_fichier not in self.sources:
+             raise ValueError(f"Format non supporté: {format_fichier}")
+         
+         logger.info(f"Chargement des données au format {format_fichier} depuis {chemin_fichier}")
+         donnees = self.sources[format_fichier].charger(chemin_fichier)
+         
+         # Validation des données
+         if not self.sources[format_fichier].valider_donnees(donnees):
+             logger.warning(f"Les données chargées depuis {chemin_fichier} ne contiennent pas tous les champs obligatoires")
+         
+         return donnees
+     
+     def trouver_ticket(self, dossier: str, ticket_id: str) -> Optional[Dict[str, str]]:
+         """
+         Recherche les fichiers de ticket dans différents emplacements possibles
+         
+         Args:
+             dossier: Dossier de base pour la recherche
+             ticket_id: ID du ticket (ex: T0101)
+             
+         Returns:
+             Dictionnaire avec les chemins des fichiers trouvés par format
+         """
+         resultats = {"json": None, "markdown": None}
+         
+         # Liste des emplacements possibles pour les rapports
+         emplacements_possibles = [
+             # 1. Dans le répertoire d'extraction directement
+             dossier,
+             
+             # 2. Dans un sous-répertoire "data"
+             os.path.join(dossier, "data"),
+             
+             # 3. Dans un sous-répertoire spécifique au ticket pour les rapports
+             os.path.join(dossier, f"{ticket_id}_rapports"),
+             
+             # 4. Dans un sous-répertoire "rapports"
+             os.path.join(dossier, "rapports")
+         ]
+         
+         # Vérifier chaque emplacement
+         for base_location in emplacements_possibles:
+             # Chercher le fichier JSON
+             json_path = os.path.join(base_location, f"{ticket_id}_rapport.json")
+             if os.path.exists(json_path):
+                 logger.info(f"Rapport JSON trouvé à: {json_path}")
+                 resultats["json"] = json_path
+             
+             # Chercher le fichier Markdown
+             md_path = os.path.join(base_location, f"{ticket_id}_rapport.md")
+             if os.path.exists(md_path):
+                 logger.info(f"Rapport Markdown trouvé à: {md_path}")
+                 resultats["markdown"] = md_path
+         
+         if not resultats["json"] and not resultats["markdown"]:
+             logger.warning(f"Aucun rapport trouvé pour {ticket_id} dans {dossier}")
+             return None
+             
+         return resultats 

Maintenant, corrigeons les erreurs de linter dans le fichier ticket_data_loader.py :


          return donnees
      
-     def trouver_ticket(self, dossier: str, ticket_id: str) -> Optional[Dict[str, str]]:
+     def trouver_ticket(self, dossier: str, ticket_id: str) -> Optional[Dict[str, Optional[str]]]:
          """
          Recherche les fichiers de ticket dans différents emplacements possibles
              
          Returns:
-             Dictionnaire avec les chemins des fichiers trouvés par format
+             Dictionnaire avec les chemins des fichiers trouvés par format ou None si aucun fichier trouvé
          """
-         resultats = {"json": None, "markdown": None}
+         resultats: Dict[str, Optional[str]] = {"json": None, "markdown": None}
          
          # Liste des emplacements possibles pour les rapports

Maintenant, créons le nouvel agent pour remplacer AgentJsonAnalyser par AgentTicketAnalyser avec des fonctionnalités améliorées :


- 
+ 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 utils.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 = 1500
+         self.system_prompt = """Tu es un expert en analyse de tickets pour le support informatique de BRG_Lab.
+ Ton rôle est d'extraire et d'analyser les informations importantes des tickets, quelle que soit leur source (JSON ou Markdown).
+ Organise ta réponse avec les sections suivantes:
+ 1. Résumé du problème
+ 2. Informations techniques essentielles (logiciels, versions, etc.)
+ 3. Contexte client (urgence, impact)
+ 4. Pistes d'analyse suggérées
+ 
+ Sois précis, factuel et synthétique dans ton analyse.
+ Vérifie notamment les champs suivants si disponibles : 
+ - Le logiciel concerné
+ - La version du logiciel
+ - L'environnement technique
+ - La priorité ou urgence du ticket
+ - Les étapes de reproduction du problème
+ """
+         
+         # 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
+             }
+             
+             # Ajustements selon le type de modèle
+             if "mistral_medium" in self.llm.__class__.__name__.lower():
+                 params["temperature"] += 0.05
+                 params["max_tokens"] = 1000
+             elif "pixtral" in self.llm.__class__.__name__.lower():
+                 params["temperature"] -= 0.05
+             elif "ollama" in self.llm.__class__.__name__.lower():
+                 params["temperature"] += 0.1
+                 params.update({
+                     "num_ctx": 2048,
+                     "repeat_penalty": 1.1,
+                 })
+             
+             self.llm.configurer(**params)
+     
+     def executer(self, ticket_data: Dict[str, 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 de support technique et fournis une synthèse structurée:
+ 
+ {ticket_formate}
+ 
+ Ce ticket provient d'un fichier au format {source_format.upper()}.
+ Réponds de manière factuelle, en te basant uniquement sur les informations fournies."""
+         
+         try:
+             logger.info("Interrogation du LLM")
+             response = self.llm.interroger(prompt)
+             logger.info(f"Réponse reçue: {len(response)} caractères")
+             print(f"  Analyse terminée: {len(response)} caractères")
+         except Exception as e:
+             error_message = f"Erreur lors de l'analyse du ticket: {str(e)}"
+             logger.error(error_message)
+             response = f"ERREUR: {error_message}"
+             print(f"  ERREUR: {error_message}")
+         
+         # Enregistrer l'historique avec le prompt complet pour la traçabilité
+         self.ajouter_historique("analyse_ticket", 
+                                {
+                                    "ticket_id": ticket_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
+         info = f"## TICKET {ticket_data.get('code', 'Inconnu')}: {ticket_data.get('name', 'Sans titre')}\n\n"
+         
+         # Ajouter la description
+         description = ticket_data.get('description', '')
+         if description:
+             info += f"## DESCRIPTION\n{description}\n\n"
+         
+         # Ajouter les informations du ticket (exclure certains champs spécifiques)
+         champs_a_exclure = ['code', 'name', 'description', 'messages', 'metadata']
+         info += "## INFORMATIONS 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)
+         messages = ticket_data.get('messages', [])
+         if messages:
+             info += "## ÉCHANGES ET MESSAGES\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', '')
+                 
+                 # 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} - 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") 

Maintenant, nous devons mettre à jour l'orchestrateur pour utiliser le nouvel agent AgentTicketAnalyser au lieu de AgentJsonAnalyser :


  from typing import List, Dict, Any, Optional, Union
  from agents.base_agent import BaseAgent
+ from utils.ticket_data_loader import TicketDataLoader
  
  # Configuration du logging
      def __init__(self, 
                   output_dir: str = "output/", 
-                  json_agent: Optional[BaseAgent] = None,
+                  ticket_agent: Optional[BaseAgent] = None,
                   image_sorter: Optional[BaseAgent] = None,
                   image_analyser: Optional[BaseAgent] = None,
          
          # Assignation directe des agents
-         self.json_agent = json_agent
+         self.ticket_agent = ticket_agent
          self.image_sorter = image_sorter
          self.image_analyser = image_analyser
          self.report_generator = report_generator
+         
+         # Initialisation du loader de données de ticket
+         self.ticket_loader = TicketDataLoader()
          
          # Collecter et enregistrer les informations détaillées sur les agents
          agents_info = self._collecter_info_agents()
          
          logger.info(f"Orchestrator initialisé avec output_dir: {output_dir}")
-         logger.info(f"Agents disponibles: JSON={json_agent is not None}, ImageSorter={image_sorter is not None}, ImageAnalyser={image_analyser is not None}, ReportGenerator={report_generator is not None}")
+         logger.info(f"Agents disponibles: TicketAgent={ticket_agent is not None}, ImageSorter={image_sorter is not None}, ImageAnalyser={image_analyser is not None}, ReportGenerator={report_generator is not None}")
          logger.info(f"Configuration des agents: {json.dumps(agents_info, indent=2)}")
  
          agents_info = {}
          
-         # Information sur l'agent JSON
-         if self.json_agent:
-             agents_info["json_agent"] = self._get_agent_info(self.json_agent)
+         # Information sur l'agent Ticket
+         if self.ticket_agent:
+             agents_info["ticket_agent"] = self._get_agent_info(self.ticket_agent)
              
          # Information sur l'agent Image Sorter
              Un dictionnaire avec les chemins des fichiers JSON et MD s'ils sont trouvés
          """
-         result: Dict[str, Optional[str]] = {"json_path": None, "md_path": None}
-         
-         # Liste des emplacements possibles pour les rapports
-         possible_locations = [
-             # 1. Dans le répertoire d'extraction directement
-             extraction_path,
-             
-             # 2. Dans un sous-répertoire "data"
-             os.path.join(extraction_path, "data"),
-             
-             # 3. Dans un sous-répertoire spécifique au ticket pour les rapports
-             os.path.join(extraction_path, f"{ticket_id}_rapports"),
-             
-             # 4. Dans un sous-répertoire "rapports"
-             os.path.join(extraction_path, "rapports")
-         ]
-         
-         # Vérifier chaque emplacement
-         for base_location in possible_locations:
-             # Chercher le fichier JSON
-             json_path = os.path.join(base_location, f"{ticket_id}_rapport.json")
-             if os.path.exists(json_path):
-                 logger.info(f"Rapport JSON trouvé à: {json_path}")
-                 result["json_path"] = json_path
-             
-             # Chercher le fichier Markdown
-             md_path = os.path.join(base_location, f"{ticket_id}_rapport.md")
-             if os.path.exists(md_path):
-                 logger.info(f"Rapport Markdown trouvé à: {md_path}")
-                 result["md_path"] = md_path
-         
-         if not result["json_path"] and not result["md_path"]:
-             logger.warning(f"Aucun rapport trouvé pour {ticket_id} dans {extraction_path}")
-             
-         return result
+         return self.ticket_loader.trouver_ticket(extraction_path, ticket_id)
  
      def traiter_ticket(self, ticket_path: str) -> bool:
                      print(f"  Données du ticket chargées")
                      
-                     # Traitement JSON avec l'agent JSON
-                     if self.json_agent:
-                         logger.info("Exécution de l'agent JSON")
+                     # Traitement avec l'agent Ticket
+                     if self.ticket_agent:
+                         logger.info("Exécution de l'agent Ticket")
                          print("  Analyse du ticket en cours...")
                          
-                         # Log détaillé sur l'agent JSON
-                         agent_info = self._get_agent_info(self.json_agent)
-                         logger.info(f"Agent JSON: {json.dumps(agent_info, indent=2)}")
+                         # Log détaillé sur l'agent Ticket
+                         agent_info = self._get_agent_info(self.ticket_agent)
+                         logger.info(f"Agent Ticket: {json.dumps(agent_info, indent=2)}")
                          
-                         json_analysis = self.json_agent.executer(ticket_data)
-                         logger.info("Analyse JSON terminée")
+                         ticket_analysis = self.ticket_agent.executer(ticket_data)
+                         logger.info("Analyse du ticket terminée")
                      else:
-                         logger.warning("Agent JSON non disponible")
-                         json_analysis = None
-                         print("  Agent JSON non disponible, analyse ignorée")
+                         logger.warning("Agent Ticket non disponible")
+                         ticket_analysis = None
+                         print("  Agent Ticket non disponible, analyse ignorée")
  
                      # Traitement des images
  
                      # Analyse approfondie des images pertinentes
-                     # Log détaillé sur l'agent Image Analyser
                      if self.image_analyser:
                          agent_info = self._get_agent_info(self.image_analyser)
                          logger.info(f"Agent Image Analyser: {json.dumps(agent_info, indent=2)}")
                      
                      for image_path in relevant_images:
-                         if self.image_analyser and json_analysis:
+                         if self.image_analyser and ticket_analysis:
                              image_name = os.path.basename(image_path)
                              logger.info(f"Analyse approfondie de l'image: {image_name}")
                              print(f"  Analyse approfondie de l'image: {image_name}")
-                             analysis_result = self.image_analyser.executer(image_path, contexte=json_analysis)
+                             analysis_result = self.image_analyser.executer(image_path, contexte=ticket_analysis)
                              
                              # Ajouter l'analyse au dictionnaire des analyses d'images
                          "ticket_data": ticket_data,
                          "ticket_id": ticket_id,
-                         "analyse_json": json_analysis,
+                         "analyse_ticket": ticket_analysis,
                          "analyse_images": images_analyses,
                          "metadata": {
          ticket_data = None
          
+         # Si aucun rapport n'est trouvé
+         if not rapports or (not rapports.get("json") and not rapports.get("markdown")):
+             logger.warning(f"Aucun rapport trouvé pour le ticket {ticket_id}")
+             return None
+         
          # Essayer d'abord le fichier JSON
-         if rapports["json_path"]:
+         if rapports.get("json"):
              try:
-                 with open(rapports["json_path"], 'r', encoding='utf-8') as file:
-                     ticket_data = json.load(file)
-                 logger.info(f"Données JSON chargées depuis: {rapports['json_path']}")
-                 print(f"  Rapport JSON chargé: {os.path.basename(rapports['json_path'])}")
+                 ticket_data = self.ticket_loader.charger(rapports["json"])
+                 logger.info(f"Données JSON chargées depuis: {rapports['json']}")
+                 print(f"  Rapport JSON chargé: {os.path.basename(rapports['json'])}")
              except Exception as e:
                  logger.error(f"Erreur lors du chargement du JSON: {e}")
                  print(f"  ERREUR: Impossible de charger le fichier JSON: {e}")
          
          # Si pas de JSON ou erreur, essayer le Markdown
-         if not ticket_data and rapports["md_path"]:
+         if not ticket_data and rapports.get("markdown"):
              try:
-                 # Créer une structure de données à partir du contenu Markdown
-                 ticket_data = self._extraire_donnees_de_markdown(rapports["md_path"])
-                 logger.info(f"Données Markdown chargées depuis: {rapports['md_path']}")
-                 print(f"  Rapport Markdown chargé: {os.path.basename(rapports['md_path'])}")
+                 # Utiliser le loader pour charger les données depuis le Markdown
+                 ticket_data = self.ticket_loader.charger(rapports["markdown"])
+                 logger.info(f"Données Markdown chargées depuis: {rapports['markdown']}")
+                 print(f"  Rapport Markdown chargé: {os.path.basename(rapports['markdown'])}")
              except Exception as e:
                  logger.error(f"Erreur lors du chargement du Markdown: {e}")
          
          return ticket_data
-         
-     def _extraire_donnees_de_markdown(self, md_path: str) -> Dict:
-         """
-         Extrait les données d'un fichier Markdown et les structure
-         
-         Args:
-             md_path: Chemin vers le fichier Markdown
-             
-         Returns:
-             Dictionnaire structuré avec les données du ticket
-         """
-         with open(md_path, 'r', encoding='utf-8') as file:
-             content = file.read()
-         
-         # Initialiser la structure de données
-         ticket_data = {
-             "id": "",
-             "code": "",
-             "name": "",
-             "description": "",
-             "messages": [],
-             "metadata": {
-                 "source_file": md_path,
-                 "format": "markdown"
-             }
-         }
-         
-         # Extraire le titre (première ligne)
-         lines = content.split('\n')
-         if lines and lines[0].startswith('# '):
-             title = lines[0].replace('# ', '')
-             ticket_parts = title.split(':')
-             if len(ticket_parts) >= 1:
-                 ticket_data["code"] = ticket_parts[0].strip()
-             if len(ticket_parts) >= 2:
-                 ticket_data["name"] = ticket_parts[1].strip()
-         
-         # Extraire la description
-         description_section = self._extraire_section(content, "description")
-         if description_section:
-             ticket_data["description"] = description_section.strip()
-         
-         # Extraire les informations du ticket
-         info_section = self._extraire_section(content, "Informations du ticket")
-         if info_section:
-             for line in info_section.split('\n'):
-                 if ':' in line and line.startswith('- **'):
-                     key = line.split('**')[1].strip()
-                     value = line.split(':')[1].strip()
-                     if key == "id":
-                         ticket_data["id"] = value
-                     elif key == "code":
-                         ticket_data["code"] = value
-                     # Ajouter d'autres champs au besoin
-         
-         # Extraire les messages
-         messages_section = self._extraire_section(content, "Messages")
-         if messages_section:
-             message_blocks = messages_section.split("### Message ")
-             for block in message_blocks[1:]:  # Ignorer le premier élément (vide)
-                 message = {}
-                 
-                 # Extraire les en-têtes du message
-                 lines = block.split('\n')
-                 for i, line in enumerate(lines):
-                     if line.startswith('**') and ':' in line:
-                         key = line.split('**')[1].lower()
-                         value = line.split(':')[1].strip()
-                         message[key] = value
-                     
-                 # Extraire le contenu du message (tout ce qui n'est pas un en-tête)
-                 content_start = 0
-                 for i, line in enumerate(lines):
-                     if i > 0 and not line.startswith('**') and line and content_start == 0:
-                         content_start = i
-                         break
-                 
-                 if content_start > 0:
-                     content_end = -1
-                     for i in range(content_start, len(lines)):
-                         if lines[i].startswith('**attachment_ids**') or lines[i].startswith('---'):
-                             content_end = i
-                             break
-                     
-                     if content_end == -1:
-                         message["content"] = "\n".join(lines[content_start:])
-                     else:
-                         message["content"] = "\n".join(lines[content_start:content_end])
-                 
-                 # Extraire les pièces jointes
-                 attachments = []
-                 for line in lines:
-                     if line.startswith('- ') and '[ID:' in line:
-                         attachments.append(line.strip('- ').strip())
-                 
-                 if attachments:
-                     message["attachments"] = attachments
-                 
-                 ticket_data["messages"].append(message)
-         
-         return ticket_data
-     
-     def _extraire_section(self, content: str, section_title: str) -> Optional[str]:
-         """
-         Extrait une section du contenu Markdown
-         
-         Args:
-             content: Contenu Markdown complet
-             section_title: Titre de la section à extraire
-             
-         Returns:
-             Contenu de la section ou None si non trouvée
-         """
-         import re
-         
-         # Chercher les sections de niveau 2 (##)
-         pattern = r'## ' + re.escape(section_title) + r'\s*\n(.*?)(?=\n## |$)'
-         match = re.search(pattern, content, re.DOTALL)
-         
-         if match:
-             return match.group(1).strip()
-         
-         # Si pas trouvé, chercher les sections de niveau 3 (###)
-         pattern = r'### ' + re.escape(section_title) + r'\s*\n(.*?)(?=\n### |$)'
-         match = re.search(pattern, content, re.DOTALL)
-         
-         if match:
-             return match.group(1).strip()
-             
-         return None
  
      def executer(self, ticket_specifique: Optional[str] = None):

Corrigeons les erreurs de linter dans orchestrator.py :


              Un dictionnaire avec les chemins des fichiers JSON et MD s'ils sont trouvés
          """
-         return self.ticket_loader.trouver_ticket(extraction_path, ticket_id)
+         # Utilise la nouvelle méthode de TicketDataLoader
+         resultats = self.ticket_loader.trouver_ticket(extraction_path, ticket_id)
+         if resultats is None:
+             return {"json": None, "markdown": None}
+         return resultats
  
      def traiter_ticket(self, ticket_path: str) -> bool:
          
          # Essayer d'abord le fichier JSON
-         if rapports.get("json"):
+         if rapports.get("json") and rapports["json"] is not None:
              try:
                  ticket_data = self.ticket_loader.charger(rapports["json"])
          
          # Si pas de JSON ou erreur, essayer le Markdown
-         if not ticket_data and rapports.get("markdown"):
+         if not ticket_data and rapports.get("markdown") and rapports["markdown"] is not None:
              try:
                  # Utiliser le loader pour charger les données depuis le Markdown

Enfin, créons un script de test pour vérifier le bon fonctionnement de notre nouveau système :


- 
+ #!/usr/bin/env python3
+ """
+ Script de test pour l'analyse de tickets avec le nouvel AgentTicketAnalyser
+ """
+ 
+ import os
+ import sys
+ import logging
+ import json
+ import argparse
+ from datetime import datetime
+ 
+ # Configuration du logging
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
+ logger = logging.getLogger("test_ticket_analyse")
+ 
+ # Import du nouvel agent et du loader de données
+ from utils.ticket_data_loader import TicketDataLoader
+ try:
+     from agents.agent_ticket_analyser import AgentTicketAnalyser
+ except ImportError:
+     logger.error("AgentTicketAnalyser non trouvé. Utilisez-vous l'ancien agent?")
+     try:
+         from agents.agent_json_analyser import AgentJsonAnalyser as AgentTicketAnalyser
+         logger.warning("Utilisation de l'ancien AgentJsonAnalyser en fallback")
+     except ImportError:
+         logger.error("Aucun agent d'analyse de tickets trouvé!")
+         sys.exit(1)
+ 
+ # Import du LLM (à adapter selon votre implémentation)
+ try:
+     from llm.mistral_medium import MistralMedium
+     LLM_CLASS = MistralMedium
+ except ImportError:
+     try:
+         from llm.openai_gpt import OpenAIGPT
+         LLM_CLASS = OpenAIGPT
+     except ImportError:
+         logger.error("Aucun LLM trouvé! Adaptez ce script à votre implémentation.")
+         
+         # Créer une classe factice pour les tests sans LLM
+         class MockLLM:
+             def __init__(self):
+                 self.prompt_system = ""
+                 
+             def configurer(self, **kwargs):
+                 print(f"[MockLLM] Configuration: {kwargs}")
+                 
+             def interroger(self, prompt):
+                 print(f"[MockLLM] Interrogation avec prompt de {len(prompt)} caractères")
+                 return "Ceci est une réponse simulée du LLM pour les tests."
+                 
+         LLM_CLASS = MockLLM
+         logger.warning("Utilisation d'un LLM factice pour les tests")
+ 
+ 
+ def creer_agent():
+     """Crée et retourne une instance de l'agent d'analyse de tickets"""
+     try:
+         llm = LLM_CLASS()
+         return AgentTicketAnalyser(llm)
+     except Exception as e:
+         logger.error(f"Erreur lors de la création de l'agent: {e}")
+         return None
+ 
+ 
+ def tester_analyse_fichier(fichier, afficher_resultat=True):
+     """
+     Teste l'analyse d'un fichier de ticket
+     
+     Args:
+         fichier: Chemin vers le fichier de ticket à analyser
+         afficher_resultat: Si True, affiche le résultat de l'analyse
+         
+     Returns:
+         True si l'analyse a réussi, False sinon
+     """
+     if not os.path.exists(fichier):
+         logger.error(f"Le fichier {fichier} n'existe pas")
+         return False
+         
+     try:
+         agent = creer_agent()
+         if not agent:
+             return False
+             
+         logger.info(f"Analyse du fichier: {fichier}")
+         print(f"\nAnalyse du fichier: {fichier}")
+         
+         start_time = datetime.now()
+         resultat = agent.analyser_depuis_fichier(fichier)
+         duree = (datetime.now() - start_time).total_seconds()
+         
+         logger.info(f"Analyse terminée en {duree:.2f} secondes")
+         print(f"Analyse terminée en {duree:.2f} secondes")
+         
+         if afficher_resultat:
+             print("\n" + "="*80)
+             print("RÉSULTAT DE L'ANALYSE:")
+             print("="*80)
+             print(resultat)
+             print("="*80 + "\n")
+             
+         return True
+     except Exception as e:
+         logger.error(f"Erreur lors de l'analyse du fichier {fichier}: {e}")
+         import traceback
+         traceback.print_exc()
+         return False
+ 
+ 
+ def tester_recherche_ticket(dossier, ticket_id):
+     """
+     Teste la recherche de fichiers de ticket
+     
+     Args:
+         dossier: Dossier de base pour la recherche
+         ticket_id: ID du ticket à rechercher
+         
+     Returns:
+         True si au moins un fichier a été trouvé, False sinon
+     """
+     try:
+         loader = TicketDataLoader()
+         resultats = loader.trouver_ticket(dossier, ticket_id)
+         
+         print(f"\nRecherche de fichiers pour le ticket {ticket_id} dans {dossier}:")
+         if resultats is None or (not resultats.get("json") and not resultats.get("markdown")):
+             print("Aucun fichier trouvé")
+             return False
+             
+         if resultats.get("json"):
+             print(f"  - JSON: {resultats['json']}")
+             
+         if resultats.get("markdown"):
+             print(f"  - Markdown: {resultats['markdown']}")
+             
+         return True
+     except Exception as e:
+         logger.error(f"Erreur lors de la recherche de fichiers: {e}")
+         return False
+ 
+ 
+ def main():
+     """Fonction principale"""
+     parser = argparse.ArgumentParser(description="Test de l'analyse de tickets")
+     parser.add_argument("--file", "-f", help="Chemin vers un fichier de ticket à analyser")
+     parser.add_argument("--search", "-s", help="Rechercher un ticket par ID", metavar="TICKET_ID")
+     parser.add_argument("--dir", "-d", help="Dossier pour la recherche de tickets", default="output")
+     parser.add_argument("--quiet", "-q", action="store_true", help="Ne pas afficher le résultat de l'analyse")
+     args = parser.parse_args()
+     
+     if args.file:
+         tester_analyse_fichier(args.file, not args.quiet)
+     elif args.search:
+         tester_recherche_ticket(args.dir, args.search)
+     else:
+         parser.print_help()
+ 
+ 
+ if __name__ == "__main__":
+     main() 

Maintenant, créons un document README pour expliquer les changements effectués :


- 
+ # Améliorations du système d'analyse de tickets
+ 
+ ## Contexte
+ 
+ Le système d'analyse de tickets a été amélioré pour mieux gérer différents formats de données, en particulier les formats JSON et Markdown. Ce document explique les changements apportés et comment utiliser le nouveau système.
+ 
+ ## Changements principaux
+ 
+ 1. **Remplacement de `AgentJsonAnalyser` par `AgentTicketAnalyser`**
+    - Le nouvel agent est plus flexible et peut traiter des données provenant de différentes sources
+    - Il utilise un prompt système amélioré qui inclut des instructions spécifiques pour l'analyse des tickets
+    - Il intègre une meilleure gestion des métadonnées sur la source des données
+ 
+ 2. **Création d'une classe `TicketDataLoader`**
+    - Abstraction pour charger les données de tickets depuis différentes sources
+    - Implémentations spécifiques pour les formats JSON et Markdown
+    - Validation et normalisation des données chargées
+    - Gestion unifiée de la recherche de fichiers de tickets
+ 
+ 3. **Mise à jour de l'orchestrateur**
+    - Adaptation pour utiliser le nouvel agent et le loader de données
+    - Simplification des méthodes de traitement des données
+    - Suppression du code redondant pour l'extraction des données Markdown
+ 
+ 4. **Ajout d'un script de test**
+    - `test_ticket_analyse.py` permet de tester le système d'analyse de tickets
+    - Possibilité de tester l'analyse d'un fichier spécifique
+    - Possibilité de tester la recherche de fichiers de tickets
+ 
+ ## Comment utiliser le nouveau système
+ 
+ ### Dans les scripts existants
+ 
+ Remplacez les références à `AgentJsonAnalyser` par `AgentTicketAnalyser` :
+ 
+ ```python
+ # Ancien code
+ from agents.agent_json_analyser import AgentJsonAnalyser
+ agent = AgentJsonAnalyser(llm)
+ 
+ # Nouveau code
+ from agents.agent_ticket_analyser import AgentTicketAnalyser
+ agent = AgentTicketAnalyser(llm)
+ ```
+ 
+ ### Analyser un fichier directement
+ 
+ Le nouvel agent peut analyser un fichier directement sans avoir à charger les données au préalable :
+ 
+ ```python
+ from agents.agent_ticket_analyser import AgentTicketAnalyser
+ 
+ agent = AgentTicketAnalyser(llm)
+ resultat = agent.analyser_depuis_fichier("chemin/vers/ticket.json")
+ # ou
+ resultat = agent.analyser_depuis_fichier("chemin/vers/ticket.md")
+ ```
+ 
+ ### Charger des données de ticket avec le loader
+ 
+ ```python
+ from utils.ticket_data_loader import TicketDataLoader
+ 
+ loader = TicketDataLoader()
+ 
+ # Charger un fichier JSON
+ donnees_json = loader.charger("chemin/vers/ticket.json")
+ 
+ # Charger un fichier Markdown
+ donnees_md = loader.charger("chemin/vers/ticket.md")
+ 
+ # Détecter automatiquement le format
+ donnees = loader.charger("chemin/vers/ticket.ext")
+ 
+ # Rechercher des fichiers de ticket
+ resultats = loader.trouver_ticket("dossier/extraction", "T0101")
+ if resultats.get("json"):
+     print(f"Fichier JSON trouvé: {resultats['json']}")
+ if resultats.get("markdown"):
+     print(f"Fichier Markdown trouvé: {resultats['markdown']}")
+ ```
+ 
+ ## Exécuter les tests
+ 
+ ```bash
+ # Tester l'analyse d'un fichier
+ python test_ticket_analyse.py --file chemin/vers/ticket.json
+ 
+ # Rechercher un ticket par ID
+ python test_ticket_analyse.py --search T0101 --dir dossier/extraction
+ 
+ # Afficher l'aide
+ python test_ticket_analyse.py --help
+ ```
+ 
+ ## Compatibilité avec l'ancien système
+ 
+ Si vous avez encore des scripts qui utilisent l'ancien `AgentJsonAnalyser`, ceux-ci devraient continuer à fonctionner avec le nouvel agent, car l'interface de la méthode `executer()` est restée compatible.
+ 
+ ## Structure des métadonnées
+ 
+ Le nouveau système ajoute des métadonnées sur la source des données, ce qui peut être utile pour le débogage et l'analyse :
+ 
+ ```json
+ {
+   "metadata": {
+     "source_file": "chemin/vers/fichier.ext",
+     "format": "json|markdown",
+     "autres_metadonnees": "..."
+   }
+ }
+ ```
+ 
+ ## Prochaines améliorations possibles
+ 
+ 1. Ajouter le support pour d'autres formats de données (CSV, XML, etc.)
+ 2. Améliorer la validation des données chargées
+ 3. Ajouter des tests unitaires pour chaque composant
+ 4. Implémenter une détection plus avancée des formats de date
+ 5. Ajouter une option pour normaliser les noms de champs entre différents formats 

Récapitulons les améliorations que nous avons apportées au système :

  1. Nous avons créé un nouveau module ticket_data_loader.py qui fournit une abstraction pour charger des données de tickets depuis différentes sources (JSON et Markdown).

  2. Nous avons remplacé AgentJsonAnalyser par un nouvel agent AgentTicketAnalyser qui est plus flexible et peut traiter les tickets indépendamment de leur format d'origine.

  3. Nous avons mis à jour l'orchestrateur pour utiliser le nouvel agent et le nouveau loader de données, ce qui simplifie le code et élimine la duplication.

  4. Nous avons créé un script de test test_ticket_analyse.py pour tester facilement le nouveau système.

  5. Nous avons documenté les changements dans un fichier README pour faciliter la transition.

Ces améliorations permettent au système de mieux gérer les différents formats de données de tickets et rendent le code plus modulaire, réutilisable et facile à maintenir. Le système est également plus robuste grâce à une meilleure gestion des erreurs et une validation des données plus complète.