pour faciliter la lecture
+
+3. **Correction automatique des références à l'entreprise**:
+ - Le prompt demandait de remplacer "CBAD" par "CBAO" (la forme correcte)
+ - Cette correction a été appliquée dans le rapport généré, ce qui montre que les instructions ont été suivies
+
+4. **Métadonnées techniques complÚtes**:
+ - Le JSON contient des informations détaillées sur les modÚles utilisés pour chaque agent
+ - Les statistiques de traitement (temps, nombre d'images, etc.) sont bien présentes
+ - Le systÚme de prompt centralisé permet d'avoir une traçabilité complÚte du traitement
+
+5. **Cohérence entre agents**:
+ - Le tri des images (AgentImageSorter) a correctement identifié les images pertinentes
+ - L'analyse d'image (AgentImageAnalyser) a fourni une description détaillée de l'image pertinente
+ - Le générateur de rapport (AgentReportGenerator) a synthétisé toutes ces informations
+
+## Avantages de la centralisation des prompts
+
+1. **Cohérence entre les étapes**:
+ - Grùce à la centralisation des prompts, les instructions sont cohérentes entre les différentes étapes du traitement
+ - Les rĂ©sultats s'enchaĂźnent de maniĂšre fluide (tri â analyse â rapport)
+
+2. **Uniformité du format**:
+ - Le formatage JSON est constant et respecte les exigences spécifiées
+ - La structure des analyses et des résumés suit un modÚle cohérent
+
+3. **Maintenance simplifiée**:
+ - Toute modification future des instructions ne nécessitera qu'un changement à un seul endroit
+ - Les risques d'incohérence entre différentes parties du code sont minimisés
+
+4. **Traçabilité**:
+ - Le JSON final contient toutes les informations sur les prompts utilisés pour sa génération
+ - Cela permet de comprendre comment les résultats ont été obtenus
+
+En conclusion, la centralisation des prompts dans chaque agent a permis d'obtenir un processus de traitement cohérent et bien structuré, produisant un rapport final de qualité avec toutes les informations pertinentes pour comprendre et résoudre le problÚme client. Le rapport Markdown généré est lisible et bien organisé, facilitant l'exploitation des informations par les équipes de support.
+
+---
+
+_**User**_
+
+ok mais il me manque des informations a la fin du rapport markdown par rapport au json
+
+---
+
+_**Assistant**_
+
+Vous avez raison, il manque des informations importantes dans le rapport Markdown par rapport au JSON. Dans la section "Agents impliqués" à la fin du rapport Markdown, les modÚles utilisés pour l'agent de tri d'images et l'agent d'analyse d'images sont indiqués comme "Non spécifié", alors que ces informations sont bien présentes dans le fichier JSON.
+
+Voici les informations qui devraient ĂȘtre correctement affichĂ©es dans le rapport Markdown:
+
+### Informations manquantes ou incorrectes dans le Markdown:
+
+1. **Agent de tri d'images**:
+ - Dans le JSON: `"model": "pixtral-12b-latest", "temperature": 0.2, "top_p": 0.8, "max_tokens": 300`
+ - Dans le Markdown: "ModÚle: Non spécifié"
+
+2. **Agent d'analyse d'images**:
+ - Dans le JSON: `"model": "pixtral-12b-latest", "temperature": 0.3, "top_p": 0.9, "max_tokens": 1200`
+ - Dans le Markdown: "ModÚle: Non spécifié"
+
+Ces informations sont clairement présentes dans le JSON sous:
+```json
+"agents": {
+ "image_sorter": {
+ "model": "pixtral-12b-latest",
+ "temperature": 0.2,
+ "top_p": 0.8,
+ "max_tokens": 300
+ },
+ "image_analyser": {
+ "model": "pixtral-12b-latest",
+ "temperature": 0.3,
+ "top_p": 0.9,
+ "max_tokens": 1200
+ },
+ "report_generator": {
+ "model": "mistral-medium",
+ "temperature": 0.4,
+ "top_p": 0.9,
+ "max_tokens": 2500
+ }
+}
+```
+
+### Solution proposée:
+
+Pour corriger cette incohĂ©rence, il faudrait modifier le code qui gĂ©nĂšre le rapport Markdown pour qu'il extraie correctement ces informations du JSON. Il s'agit probablement d'un problĂšme dans la fonction qui transforme les donnĂ©es JSON en Markdown, oĂč elle n'accĂšde pas correctement aux mĂ©tadonnĂ©es des agents.
+
+La partie du code qui gĂ©nĂšre cette section devrait ĂȘtre modifiĂ©e pour extraire et afficher correctement les informations de tous les agents impliquĂ©s, avec leur modĂšle, tempĂ©rature, top_p et autres paramĂštres pertinents. Cela garantirait une reprĂ©sentation complĂšte et fidĂšle des informations dans le rapport Markdown.
+
+Voulez-vous que je vous aide Ă modifier cette partie du code pour corriger ce problĂšme?
+
+---
+
+_**User**_
+
+oui
+
+---
+
+_**Assistant**_
+
+Je vais vous aider Ă modifier le code pour corriger ce problĂšme d'affichage des informations des agents dans le rapport Markdown final. D'abord, recherchons oĂč se trouve le code qui gĂ©nĂšre le rapport Markdown Ă partir du JSON.
+
+---
+
+
+ Grep search for "rapport_final.md|markdown|json_to_markdown" âą **16** files
+
+| File | Line | Match |
+|------|------|-------|
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/test_orchestrator.py` | L124 | `# Chercher le rapport Markdown le plus récent` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/test_orchestrator_qwen.py` | L124 | `# Chercher le rapport Markdown le plus récent` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/retrieve_ticket.py` | L14 | `from utils.json_to_markdown import create_markdown_from_json` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/retrieve_ticket.py` | L23 | `parser.add_argument("--no-md", action="store_true", help="Désactiver la génération automatique du fichier Markdown")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/retrieve_ticket.py` | L101 | `# Générer automatiquement le fichier Markdown si demandé` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/retrieve_ticket.py` | L107 | `print(f"Génération du rapport Markdown...")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/retrieve_ticket.py` | L108 | `if create_markdown_from_json(json_file, md_file_path):` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/retrieve_ticket.py` | L109 | `print(f"Rapport Markdown créé: {md_file_path}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/retrieve_ticket.py` | L112 | `print("Ăchec de la gĂ©nĂ©ration du rapport Markdown")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/retrieve_ticket.py` | L121 | `print(f"Rapport Markdown: {md_file_path}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/test_orchestrator_large.py` | L124 | `# Chercher le rapport Markdown le plus récent` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/test_agents_with_models.py` | L32 | `os.makedirs("reports/markdown_reports", exist_ok=True)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L8 | `from utils.report_formatter import generate_markdown_report` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L21 | `- Markdown est utilisé uniquement comme format de présentation finale` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L23 | `- La conversion JSON->Markdown se fait uniquement à la fin du processus pour la présentation` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L121 | `Dictionnaire avec {"json": chemin_json, "markdown": chemin_md}` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L127 | `rapports: Dict[str, Optional[str]] = {"json": None, "markdown": None} if result is None else result` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L129 | `# Si on a un JSON mais pas de Markdown, générer le Markdown à partir du JSON` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L131 | `if json_path and not rapports.get("markdown"):` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L132 | `logger.info(f"Rapport JSON trouvé sans Markdown correspondant, génération du Markdown: {json_path}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L134 | `success, md_path_or_error = generate_markdown_report(json_path)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L136 | `rapports["markdown"] = md_path_or_error` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L137 | `logger.info(f"Markdown généré avec succÚs: {md_path_or_error}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L139 | `logger.warning(f"Erreur lors de la génération du Markdown: {md_path_or_error}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L336 | `# Générer le rapport Markdown à partir du JSON en utilisant report_formatter` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L337 | `success, md_path = generate_markdown_report(json_path)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L340 | `logger.info(f"Rapport Markdown généré à : {rapport_path}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L341 | `print(f" Rapport Markdown généré avec succÚs: {os.path.basename(md_path)}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L342 | `# Vérifier si le rapport Markdown contient un tableau des échanges` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L346 | `logger.info(f"Vérification du rapport Markdown: Tableau des échanges {'présent' if has_exchanges else 'absent'}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L348 | `logger.warning(f"Erreur lors de la génération du Markdown: {md_path}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L349 | `print(f" ERREUR: ProblÚme lors de la génération du rapport Markdown")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L383 | `if not rapports or (not rapports.get("json") and not rapports.get("markdown")):` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L402 | `# Fallback sur le Markdown uniquement si JSON non disponible` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L403 | `if not ticket_data and rapports.get("markdown") and rapports["markdown"] is not None:` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L405 | `# Utiliser le loader pour charger les données depuis le Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L406 | `ticket_data = self.ticket_loader.charger(rapports["markdown"])` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L407 | `logger.info(f"Données Markdown chargées depuis: {rapports['markdown']} (fallback)")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L408 | `print(f" Rapport Markdown chargé (fallback): {os.path.basename(rapports['markdown'])}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L413 | `ticket_data["metadata"]["format_source"] = "markdown"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L415 | `logger.error(f"Erreur lors du chargement du Markdown: {e}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/orchestrator.py` | L416 | `print(f" ERREUR: Impossible de charger le fichier Markdown: {e}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_ticket_analyser.py` | L16 | `Agent pour analyser les tickets (JSON ou Markdown) et en extraire les informations importantes.` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_ticket_analyser.py` | L86 | `source_format: Format source du ticket (JSON, Markdown, etc.)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_ticket_analyser.py` | L109 | `ou chemin vers un fichier de ticket (JSON ou Markdown)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_ticket_analyser.py` | L261 | `Analyse un ticket Ă partir d'un fichier (JSON ou Markdown)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/clean_html.py` | L13 | `Nettoie le contenu HTML pour le Markdown en identifiant et ignorant les parties problématiques.` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/clean_html.py` | L203 | `# Transformer les balises h1 en titres Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/clean_html.py` | L302 | `# Test avec le cas problématique de bas de page avec formatage markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/clean_html.py` | L303 | `test_cbao_markdown = """Bonjour,` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/clean_html.py` | L314 | `cleaned_markdown = clean_html(test_cbao_markdown)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/clean_html.py` | L315 | `print("\nTest avec formatage Markdown CBAO nettoyé :\n", cleaned_markdown)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L2 | `Script pour convertir les fichiers JSON de tickets en Markdown formaté.` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L3 | `Ce script prend les données JSON des tickets extraits et crée un fichier Markdown structuré.` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L18 | `def create_markdown_from_json(json_file, output_file):` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L20 | `Crée un fichier Markdown à partir d'un fichier JSON de messages.` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L24 | `output_file: Chemin du fichier Markdown à créer` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L80 | `# Commencer Ă construire le contenu Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L338 | `print(f"Rapport Markdown créé : {output_file}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L339 | `# Appeler le script markdown_to_json.py` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L340 | `subprocess.run(['python', 'utils/markdown_to_json.py', output_file, json_output_file], check=True)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L344 | `print(f"Erreur lors de l'écriture du fichier Markdown: {e}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L348 | `parser = argparse.ArgumentParser(description="Convertir les fichiers JSON de tickets en Markdown")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L352 | `parser.add_argument("--output_name", "-o", default="rapport.md", help="Nom du fichier Markdown à générer")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L379 | `if create_markdown_from_json(json_file, None):` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L380 | `print(f"Rapport Markdown créé.")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/json_to_markdown.py` | L382 | `print("Ăchec de la crĂ©ation du rapport Markdown")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/markdown_to_json.py` | L5 | `def parse_markdown(md_content):` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/markdown_to_json.py` | L128 | `def convert_markdown_to_json(md_file_path, output_file_path):` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/markdown_to_json.py` | L132 | `data = parse_markdown(md_content)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/markdown_to_json.py` | L154 | `print("Utilisation : python markdown_to_json.py ")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/markdown_to_json.py` | L160 | `convert_markdown_to_json(md_file, output_file)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L22 | `- Le rapport Markdown est généré à partir du JSON uniquement pour la présentation` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L33 | `4. Conversion et sauvegarde au format Markdown (pour présentation)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L575 | `Extrait l'objet JSON des échanges du texte du rapport et le convertit en Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L581 | `Tuple (rapport_traité, echanges_json, echanges_markdown)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L670 | `# Convertir en tableau Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L671 | `echanges_markdown = "| Date | Ămetteur | Type | Contenu | Statut |\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L672 | `echanges_markdown += "|------|---------|------|---------|--------|\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L700 | `echanges_markdown += f"| {date} | {emetteur} | {type_msg} | {contenu} | {statut} |\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L704 | `echanges_markdown += "\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L706 | `# Remplacer le JSON dans le texte par le tableau Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L709 | `rapport_traite = texte_rapport.replace(json_match.group(0), echanges_markdown)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L712 | `rapport_traite = texte_rapport.replace(json_text, echanges_markdown)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L714 | `return rapport_traite, echanges_json, echanges_markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/ticket_data_loader.py` | L53 | `class MarkdownTicketSource(TicketDataSource):` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/ticket_data_loader.py` | L54 | `"""Source de données pour les tickets au format Markdown"""` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/ticket_data_loader.py` | L57 | `"""Charge les données du ticket depuis un fichier Markdown"""` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/ticket_data_loader.py` | L62 | `# Extraire les données du contenu Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/ticket_data_loader.py` | L63 | `donnees = self._extraire_donnees_de_markdown(contenu_md)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/ticket_data_loader.py` | L70 | `donnees["metadata"]["format"] = "markdown"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/test_orchestrator_qwen.py` | L124 | `# Chercher le rapport Markdown le plus récent` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/test_orchestrator_large.py` | L124 | `# Chercher le rapport Markdown le plus récent` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L22 | `- Le rapport Markdown est généré à partir du JSON uniquement pour la présentation` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L33 | `4. Conversion et sauvegarde au format Markdown (pour présentation)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L575 | `Extrait l'objet JSON des échanges du texte du rapport et le convertit en Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L581 | `Tuple (rapport_traité, echanges_json, echanges_markdown)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L670 | `# Convertir en tableau Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L671 | `echanges_markdown = "| Date | Ămetteur | Type | Contenu | Statut |\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L672 | `echanges_markdown += "|------|---------|------|---------|--------|\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L700 | `echanges_markdown += f"| {date} | {emetteur} | {type_msg} | {contenu} | {statut} |\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L704 | `echanges_markdown += "\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L706 | `# Remplacer le JSON dans le texte par le tableau Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L709 | `rapport_traite = texte_rapport.replace(json_match.group(0), echanges_markdown)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L712 | `rapport_traite = texte_rapport.replace(json_text, echanges_markdown)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/agents/agent_report_generator.py` | L714 | `return rapport_traite, echanges_json, echanges_markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L5 | `formats de sortie (Markdown, HTML, etc.) sans utiliser de LLM.` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L16 | `def generate_markdown_report(json_path: str, output_path: Optional[str] = None) -> Tuple[bool, str]:` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L18 | `GénÚre un rapport au format Markdown à partir d'un fichier JSON.` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L22 | `output_path: Chemin de sortie pour le fichier Markdown (facultatif)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L37 | `# Générer le contenu Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L38 | `markdown_content = _generate_markdown_content(rapport_data)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L42 | `f.write(markdown_content)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L44 | `print(f"Rapport Markdown généré avec succÚs: {output_path}")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L48 | `error_message = f"Erreur lors de la génération du rapport Markdown: {str(e)}"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L52 | `def _generate_markdown_content(rapport_data: Dict) -> str:` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L54 | `GénÚre le contenu Markdown à partir des données du rapport.` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L60 | `Contenu Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L67 | `markdown = f"# Rapport d'analyse du ticket #{ticket_id}\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L68 | `markdown += f"*Généré le: {generation_date}*\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L72 | `markdown += rapport_data["resume"] + "\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L75 | `markdown += "## Chronologie des échanges\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L79 | `markdown += "| Date | Ămetteur | Type | Contenu | Statut |\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L80 | `markdown += "|------|---------|------|---------|--------|\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L109 | `markdown += f"| {date} | {emetteur} | {type_msg} | {contenu} | {statut} |\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L113 | `markdown += "\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L115 | `markdown += "*Aucun échange détecté dans le ticket.*\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L118 | `markdown += "## Analyse des images\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L124 | `markdown += "*Aucune image pertinente n'a été identifiée.*\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L131 | `markdown += f"### Image {i}: {image_name}\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L135 | `markdown += f"**Raison de la pertinence**: {reason}\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L140 | `markdown += "\nAnalyse détaillée de l'image
\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L141 | `markdown += "```\n" + analyse_detail + "\n```\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L142 | `markdown += " \n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L144 | `markdown += "*Aucune image pertinente n'a été analysée.*\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L148 | `markdown += "## Diagnostic technique\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L149 | `markdown += rapport_data["diagnostic"] + "\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L152 | `markdown += "---\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L155 | `markdown += "# Détails des analyses effectuées\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L156 | `markdown += "## Processus d'analyse\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L161 | `markdown += "### Ătape 1: Analyse du ticket\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L162 | `markdown += "L'agent d'analyse de ticket a extrait les informations suivantes du ticket d'origine:\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L163 | `markdown += "\nCliquez pour voir l'analyse complĂšte du ticket
\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L164 | `markdown += "```\n" + str(ticket_analyse) + "\n```\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L165 | `markdown += " \n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L167 | `markdown += "### Ătape 1: Analyse du ticket\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L168 | `markdown += "*Aucune analyse de ticket disponible*\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L171 | `markdown += "### Ătape 2: Tri des images\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L172 | `markdown += "L'agent de tri d'images a évalué chaque image pour déterminer sa pertinence par rapport au problÚme client:\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L177 | `markdown += "| Image | Pertinence | Raison |\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L178 | `markdown += "|-------|------------|--------|\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L186 | `markdown += f"| {image_name} | {is_relevant} | {reason} |\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L188 | `markdown += "\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L190 | `markdown += "*Aucune image n'a été triée pour ce ticket.*\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L193 | `markdown += "### Ătape 3: Analyse dĂ©taillĂ©e des images pertinentes\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L200 | `markdown += f"#### Image pertinente {i}: {image_name}\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L201 | `markdown += "\nCliquez pour voir l'analyse complĂšte de l'image
\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L202 | `markdown += "```\n" + str(analyse_detail) + "\n```\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L203 | `markdown += " \n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L205 | `markdown += "*Aucune image pertinente n'a été identifiée pour ce ticket.*\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L208 | `markdown += "### Ătape 4: GĂ©nĂ©ration du rapport de synthĂšse\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L209 | `markdown += "L'agent de génération de rapport a synthétisé toutes les analyses précédentes pour produire le rapport ci-dessus.\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L212 | `markdown += "## Informations techniques\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L218 | `markdown += "### Statistiques\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L219 | `markdown += f"- **Images analysées**: {statistiques.get('total_images', 0)}\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L220 | `markdown += f"- **Images pertinentes**: {statistiques.get('images_pertinentes', 0)}\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L223 | `markdown += f"- **Temps de génération**: {statistiques['generation_time']:.2f} secondes\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L226 | `markdown += "\n### ModÚle LLM utilisé\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L227 | `markdown += f"- **ModÚle**: {metadata.get('model', 'Non spécifié')}\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L230 | `markdown += f"- **Version**: {metadata.get('model_version', 'Non spécifiée')}\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L232 | `markdown += f"- **Température**: {metadata.get('temperature', 'Non spécifiée')}\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L233 | `markdown += f"- **Top_p**: {metadata.get('top_p', 'Non spécifié')}\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L237 | `markdown += "\n### Agents impliqués\n\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L243 | `markdown += "#### Agent d'analyse du ticket\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L246 | `markdown += f"- **ModÚle**: {json_analyser['model_info'].get('name', 'Non spécifié')}\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L250 | `markdown += "\n#### Agent de tri d'images\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L252 | `markdown += f"- **ModÚle**: {sorter.get('name', 'Non spécifié')}\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L256 | `markdown += "\n#### Agent d'analyse d'images\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L258 | `markdown += f"- **ModÚle**: {analyser.get('name', 'Non spécifié')}\n"` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L260 | `return markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L274 | `# Générer d'abord le Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L275 | `success, md_path_or_error = generate_markdown_report(json_path, None)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L280 | `# Lire le contenu Markdown` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L282 | `markdown_content = f.read()` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L289 | `# Conversion Markdown â HTML (avec gestion de l'absence de mistune)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L290 | `html_content = _simple_markdown_to_html(markdown_content)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L295 | `markdown = mistune.create_markdown(escape=False)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L296 | `html_content = markdown(markdown_content)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L342 | `def _simple_markdown_to_html(markdown_content: str) -> str:` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L344 | `Convertit un contenu Markdown en HTML de façon simplifiée.` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L347 | `markdown_content: Contenu Markdown Ă convertir` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L352 | `html = markdown_content` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L382 | `def process_report(json_path: str, output_format: str = "markdown") -> None:` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L388 | `output_format: Format de sortie (markdown ou html)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L390 | `if output_format.lower() == "markdown":` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L391 | `generate_markdown_report(json_path)` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L400 | `parser.add_argument("--format", "-f", choices=["markdown", "html"], default="markdown",` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L401 | `help="Format de sortie (markdown par défaut)")` |
+| `vscode-remote://wsl%2Bubuntu-24.04/home/fgras-ca/llm-ticket3/utils/report_formatter.py` | L406 | `if args.format == "markdown":` |
+
+
+
+---
+
+Trouvons le fichier principal qui convertit le JSON en Markdown :
+
+---
+
+Read file: utils/report_formatter.py
+
+---
+
+Maintenant, examinons la section spécifique qui gÚre l'affichage des agents dans le rapport Markdown :
+
+---
+
+Read file: utils/report_formatter.py
+
+---
+
+Maintenant, vérifions le format des informations des agents dans le fichier JSON :
+
+---
+
+Read file: output/ticket_T0101/T0101_20250404_170239/T0101_rapports/T0101/T0101_rapport_final.json
+
+---
+
+Read file: output/ticket_T0101/T0101_20250404_170239/T0101_rapports/T0101/T0101_rapport_final.json
+
+---
+
+Le problÚme est maintenant clair. Dans le code qui génÚre le rapport Markdown, à la ligne ~252 et ~258, il essaie d'accéder à `sorter.get('name', 'Non spécifié')` alors que dans le JSON, les modÚles sont stockés directement dans la propriété `model` et non dans une sous-propriété `name`.
+
+Voici comment nous allons corriger ce problĂšme :
+
+---
+
+```diff
+ markdown += "\n#### Agent de tri d'images\n"
+ sorter = agents["image_sorter"]
+- markdown += f"- **ModÚle**: {sorter.get('name', 'Non spécifié')}\n"
++ # Récupérer directement le modÚle ou via model_info selon la structure
++ if "model" in sorter:
++ markdown += f"- **ModÚle**: {sorter.get('model', 'Non spécifié')}\n"
++ markdown += f"- **Température**: {sorter.get('temperature', 'Non spécifiée')}\n"
++ markdown += f"- **Top_p**: {sorter.get('top_p', 'Non spécifié')}\n"
++ elif "model_info" in sorter:
++ markdown += f"- **ModÚle**: {sorter['model_info'].get('name', 'Non spécifié')}\n"
++ else:
++ markdown += f"- **ModÚle**: Non spécifié\n"
+
+ # Agent d'analyse d'images
+ if "image_analyser" in agents:
+ markdown += "\n#### Agent d'analyse d'images\n"
+ analyser = agents["image_analyser"]
+- markdown += f"- **ModÚle**: {analyser.get('name', 'Non spécifié')}\n"
++ # Récupérer directement le modÚle ou via model_info selon la structure
++ if "model" in analyser:
++ markdown += f"- **ModÚle**: {analyser.get('model', 'Non spécifié')}\n"
++ markdown += f"- **Température**: {analyser.get('temperature', 'Non spécifiée')}\n"
++ markdown += f"- **Top_p**: {analyser.get('top_p', 'Non spécifié')}\n"
++ elif "model_info" in analyser:
++ markdown += f"- **ModÚle**: {analyser['model_info'].get('name', 'Non spécifié')}\n"
++ else:
++ markdown += f"- **ModÚle**: Non spécifié\n"
+
+ return markdown
+```
+
+---
+
+Maintenant, vérifions que notre code fonctionne en regénérant le rapport Markdown :
+
+---
+
+```bash
+cd ~/llm-ticket3 && python utils/report_formatter.py output/ticket_T0101/T0101_20250404_170239/T0101_rapports/T0101/T0101_rapport_final.json --format markdown
+```
+
+```
+<_170239/T0101_rapports/T0101/T0101_rapport_final.json --format markdown
+Rapport Markdown généré avec succÚs: output/ticket_T0101/T0101_20250404_170239/T0101_rapports/T0101/T0101_ra
+pport_final.md
+fgras-ca@PC-DEV:~/llm-ticket3$
+```
+
+---
+
+Vérifions maintenant le contenu du rapport Markdown mis à jour pour confirmer que les informations des modÚles sont correctement affichées :
+
+---
+
+Read file: output/ticket_T0101/T0101_20250404_170239/T0101_rapports/T0101/T0101_rapport_final.md
+
+---
+
+Excellent ! Les modifications que nous avons apportées ont résolu le problÚme. Le rapport Markdown affiche maintenant correctement les informations sur les modÚles utilisés par les agents de tri d'images et d'analyse d'images :
+
+### Avant la correction :
+```
+#### Agent de tri d'images
+- **ModÚle**: Non spécifié
+
+#### Agent d'analyse d'images
+- **ModÚle**: Non spécifié
+```
+
+### AprĂšs la correction :
+```
+#### Agent de tri d'images
+- **ModĂšle**: pixtral-12b-latest
+- **Température**: 0.2
+- **Top_p**: 0.8
+
+#### Agent d'analyse d'images
+- **ModĂšle**: pixtral-12b-latest
+- **Température**: 0.3
+- **Top_p**: 0.9
+```
+
+## Résumé des modifications
+
+1. **ProblÚme identifié** : Le code cherchait la propriété `name` au lieu de `model` dans les informations des agents, ce qui conduisait à afficher "Non spécifié" pour les modÚles des agents de tri et d'analyse d'images dans le rapport Markdown.
+
+2. **Solution implémentée** : Nous avons modifié la fonction `_generate_markdown_content` dans le fichier `utils/report_formatter.py` pour :
+ - Vérifier si la propriété `model` existe directement dans l'objet agent
+ - Ajouter des informations supplémentaires (température, top_p) pour une meilleure visibilité
+ - Conserver la compatibilité avec l'ancienne structure (via model_info.name) pour éviter de casser des rapports existants
+
+3. **Résultat** : Le rapport Markdown généré affiche maintenant correctement toutes les informations sur les modÚles utilisés par chaque agent, ainsi que leurs paramÚtres de configuration.
+
+Cette correction améliore la cohérence entre les rapports JSON et Markdown, et fournit aux utilisateurs des informations complÚtes sur les modÚles utilisés dans le processus d'analyse, ce qui est important pour la traçabilité et la reproductibilité des résultats.
+
+---
+
+_**User**_
+
+je voudrai que agent_report_genrator.py génére en fin d'analyse un tableau question/réponse simple en identifiant support/client en synthétisant les questions/réponses(si celles-ci sont trop longues) en spécifiant si aucune réponse n'apparait dans le ticket
+
+---
+
+_**Assistant**_
+
+Je vais modifier l'agent_report_generator.py pour ajouter un tableau question/réponse simplifié en fin d'analyse. Cette fonctionnalité permettra de mieux visualiser les échanges entre le client et le support.
+
+---
+
+Cancelled
+
+---
+
---
diff --git a/agents/agent_image_analyser.py b/agents/agent_image_analyser.py
index 409cdbe..82d7eca 100644
--- a/agents/agent_image_analyser.py
+++ b/agents/agent_image_analyser.py
@@ -15,24 +15,31 @@ class AgentImageAnalyser(BaseAgent):
def __init__(self, llm):
super().__init__("AgentImageAnalyser", llm)
- # Configuration locale de l'agent (remplace AgentConfig)
+ # Configuration locale de l'agent
self.temperature = 0.3
self.top_p = 0.9
self.max_tokens = 1200
- self.system_prompt = """Tu es un expert en analyse d'images pour le support technique de BRG-Lab.
-Ta mission est d'analyser des captures d'écran en lien avec le contexte du ticket de support.
-
-Structure ton analyse d'image de façon factuelle:
+
+ # Centralisation des instructions d'analyse pour éviter la duplication
+ self.instructions_analyse = """
1. Description objective: Ce que montre l'image (interface, message d'erreur, code, etc.)
2. ĂlĂ©ments techniques clĂ©s: Versions, codes d'erreur, paramĂštres visibles, messages du systĂšme
-3. Relation avec le problÚme: Comment cette image se rapporte au problÚme décrit dans le ticket
+3. Relation avec le problÚme: Comment cette image se rapporte au problÚme décrit
IMPORTANT:
- Ne fais PAS d'interprétation complexe ou de diagnostic
- Ne propose PAS de solutions ou recommandations
- Reste strictement factuel et objectif dans ta description
- Concentre-toi uniquement sur ce qui est visible dans l'image
-- Ne répÚte pas les informations du ticket sauf si elles sont visibles dans l'image
+- Cite les textes exacts visibles dans l'image (messages d'erreur, etc.)
+"""
+
+ # 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.
+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."""
@@ -57,19 +64,6 @@ Ton analyse sera utilisée comme élément factuel pour un rapport technique plu
"max_tokens": self.max_tokens
}
- # Ajustements selon le type de modĂšle
- if "mistral_medium" in self.llm.__class__.__name__.lower():
- params["temperature"] += 0.05
- params["max_tokens"] = 1000
- elif "pixtral" in self.llm.__class__.__name__.lower():
- params["temperature"] -= 0.05
- elif "ollama" in self.llm.__class__.__name__.lower():
- params["temperature"] += 0.1
- params.update({
- "num_ctx": 2048,
- "repeat_penalty": 1.1,
- })
-
self.llm.configurer(**params)
def _verifier_image(self, image_path: str) -> bool:
@@ -144,6 +138,25 @@ Ton analyse sera utilisée comme élément factuel pour un rapport technique plu
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]:
"""
@@ -177,24 +190,8 @@ Ton analyse sera utilisée comme élément factuel pour un rapport technique plu
}
}
- # Créer un prompt détaillé pour l'analyse d'image avec le contexte du ticket
- prompt = f"""Analyse cette image en tenant compte du contexte suivant du ticket de support technique:
-
-CONTEXTE DU TICKET:
-{contexte}
-
-Fournis une analyse STRICTEMENT FACTUELLE de l'image avec les sections suivantes:
-1. Description objective: Ce que montre concrĂštement l'image
-2. ĂlĂ©ments techniques visibles: Messages d'erreur exacts, versions, configurations, paramĂštres
-3. Relation avec le problÚme: Comment cette image se rapporte au problÚme décrit
-
-IMPORTANT:
-- NE fais PAS d'interprétation ou de diagnostic
-- NE propose PAS de solutions
-- Reste strictement factuel dans ta description
-- Décris UNIQUEMENT ce qui est visible dans l'image
-- Cite les textes exacts visibles dans l'image (messages d'erreur, etc.)
-"""
+ # 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")
@@ -208,26 +205,8 @@ IMPORTANT:
logger.warning(f"La méthode interroger_avec_image n'existe pas, utilisation du fallback pour {image_name}")
img_base64 = self._encoder_image_base64(image_path)
if img_base64:
- prompt_base64 = f"""Analyse cette image:
-{img_base64}
-
-En tenant compte du contexte suivant du ticket de support technique:
-
-CONTEXTE DU TICKET:
-{contexte}
-
-Fournis une analyse STRICTEMENT FACTUELLE de l'image avec les sections suivantes:
-1. Description objective: Ce que montre concrĂštement l'image
-2. ĂlĂ©ments techniques visibles: Messages d'erreur exacts, versions, configurations, paramĂštres
-3. Relation avec le problÚme: Comment cette image se rapporte au problÚme décrit
-
-IMPORTANT:
-- NE fais PAS d'interprétation ou de diagnostic
-- NE propose PAS de solutions
-- Reste strictement factuel dans ta description
-- Décris UNIQUEMENT ce qui est visible dans l'image
-- Cite les textes exacts visibles dans l'image (messages d'erreur, etc.)
-"""
+ # 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:
diff --git a/agents/agent_image_sorter.py b/agents/agent_image_sorter.py
index 9eb00d5..23125d0 100644
--- a/agents/agent_image_sorter.py
+++ b/agents/agent_image_sorter.py
@@ -15,13 +15,13 @@ class AgentImageSorter(BaseAgent):
def __init__(self, llm):
super().__init__("AgentImageSorter", llm)
- # Configuration locale de l'agent (remplace AgentConfig)
+ # Configuration locale de l'agent
self.temperature = 0.2
self.top_p = 0.8
self.max_tokens = 300
- self.system_prompt = """Tu es un expert en tri d'images pour le support technique de BRG_Lab pour la société CBAO.
-Ta mission est de déterminer si une image est pertinente pour le support technique de logiciels.
-
+
+ # 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
@@ -36,11 +36,21 @@ Images NON PERTINENTES (réponds "non" ou "non pertinent"):
- 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"."""
+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()
@@ -63,19 +73,6 @@ Analyse d'abord ce que montre l'image, puis réponds par "oui"/"pertinent" ou "n
"max_tokens": self.max_tokens
}
- # Ajustements selon le type de modĂšle
- if "mistral_medium" in self.llm.__class__.__name__.lower():
- params["temperature"] += 0.05
- params["max_tokens"] = 1000
- elif "pixtral" in self.llm.__class__.__name__.lower():
- params["temperature"] -= 0.05
- elif "ollama" in self.llm.__class__.__name__.lower():
- params["temperature"] += 0.1
- params.update({
- "num_ctx": 2048,
- "repeat_penalty": 1.1,
- })
-
self.llm.configurer(**params)
def _verifier_image(self, image_path: str) -> bool:
@@ -150,6 +147,22 @@ Analyse d'abord ce que montre l'image, puis réponds par "oui"/"pertinent" ou "n
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]:
"""
@@ -186,9 +199,8 @@ Analyse d'abord ce que montre l'image, puis réponds par "oui"/"pertinent" ou "n
# Utiliser une référence au fichier image que le modÚle peut comprendre
try:
- # Préparation du prompt
- prompt = f"""Est-ce une image pertinente pour un ticket de support technique?
-Réponds simplement par 'oui' ou 'non' suivi d'une brÚve explication."""
+ # 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"):
@@ -199,11 +211,7 @@ Réponds simplement par 'oui' ou 'non' suivi d'une brÚve explication."""
logger.warning(f"La méthode interroger_avec_image n'existe pas, utilisation du fallback pour {image_name}")
img_base64 = self._encoder_image_base64(image_path)
if img_base64:
- prompt_base64 = f"""Analyse cette image:
-{img_base64}
-
-Est-ce une image pertinente pour un ticket de support technique?
-Réponds simplement par 'oui' ou 'non' suivi d'une brÚve explication."""
+ 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"
diff --git a/agents/agent_json_analyser.py b/agents/agent_json_analyser.py
deleted file mode 100644
index b533894..0000000
--- a/agents/agent_json_analyser.py
+++ /dev/null
@@ -1,160 +0,0 @@
-from .base_agent import BaseAgent
-from typing import Dict, Any
-import logging
-import json
-
-logger = logging.getLogger("AgentJSONAnalyser")
-
-class AgentJsonAnalyser(BaseAgent):
- """
- Agent pour analyser les tickets JSON et en extraire les informations importantes.
- """
- def __init__(self, llm):
- super().__init__("AgentJsonAnalyser", llm)
-
- # Configuration locale de l'agent (remplace AgentConfig)
- self.temperature = 0.1 # Besoin d'analyse trÚs précise
- self.top_p = 0.8
- self.max_tokens = 1500
- self.system_prompt = """Tu es un expert en analyse de tickets pour le support informatique de BRG_Lab pour la société CBAO.
-Ton rĂŽle est d'extraire et d'analyser les informations importantes des tickets JSON.
-Organise ta réponse avec les sections suivantes:
-1. Résumé du problÚme
-2. Informations techniques essentielles (logiciels, versions, etc.)
-3. Contexte client (urgence, impact)
-4. Pistes d'analyse suggérées
-
-Sois précis, factuel et synthétique dans ton analyse."""
-
- # Appliquer la configuration au LLM
- self._appliquer_config_locale()
-
- logger.info("AgentJsonAnalyser initialisé")
-
- def _appliquer_config_locale(self) -> None:
- """
- Applique la configuration locale au modĂšle LLM.
- """
- # Appliquer le prompt systĂšme
- if hasattr(self.llm, "prompt_system"):
- self.llm.prompt_system = self.system_prompt
-
- # Appliquer les paramĂštres
- if hasattr(self.llm, "configurer"):
- params = {
- "temperature": self.temperature,
- "top_p": self.top_p,
- "max_tokens": self.max_tokens
- }
-
- # Ajustements selon le type de modĂšle
- if "mistral_medium" in self.llm.__class__.__name__.lower():
- params["temperature"] += 0.05
- params["max_tokens"] = 1000
- elif "pixtral" in self.llm.__class__.__name__.lower():
- params["temperature"] -= 0.05
- elif "ollama" in self.llm.__class__.__name__.lower():
- params["temperature"] += 0.1
- params.update({
- "num_ctx": 2048,
- "repeat_penalty": 1.1,
- })
-
- self.llm.configurer(**params)
-
- def executer(self, ticket_data: Dict) -> str:
- """
- Analyse un ticket JSON pour en extraire les informations pertinentes
-
- Args:
- ticket_data: Dictionnaire contenant les données du ticket à analyser
-
- Returns:
- Réponse formatée contenant l'analyse du ticket
- """
- logger.info(f"Analyse du ticket: {ticket_data.get('code', 'Inconnu')}")
- print(f"AgentJsonAnalyser: Analyse du ticket {ticket_data.get('code', 'Inconnu')}")
-
- # Préparer le ticket pour l'analyse
- ticket_formate = self._formater_ticket_pour_analyse(ticket_data)
-
- # Créer le prompt pour l'analyse
- prompt = f"""Analyse ce ticket de support technique et fournis une synthÚse structurée:
-
-{ticket_formate}
-
-Réponds de maniÚre factuelle, en te basant uniquement sur les informations fournies."""
-
- try:
- logger.info("Interrogation du LLM")
- response = self.llm.interroger(prompt)
- logger.info(f"Réponse reçue: {len(response)} caractÚres")
- print(f" Analyse terminée: {len(response)} caractÚres")
- except Exception as e:
- error_message = f"Erreur lors de l'analyse du ticket: {str(e)}"
- logger.error(error_message)
- response = f"ERREUR: {error_message}"
- print(f" ERREUR: {error_message}")
-
- # Enregistrer l'historique avec le prompt complet pour la traçabilité
- self.ajouter_historique("analyse_ticket",
- {
- "ticket_id": ticket_data.get("code", "Inconnu"),
- "prompt": prompt,
- "temperature": self.temperature,
- "top_p": self.top_p,
- "max_tokens": self.max_tokens,
- "timestamp": self._get_timestamp()
- },
- response)
-
- return response
-
- def _formater_ticket_pour_analyse(self, ticket_data: Dict) -> str:
- """
- Formate les données du ticket pour l'analyse LLM
-
- Args:
- ticket_data: Les données du ticket
-
- Returns:
- Représentation textuelle formatée du ticket
- """
- # Initialiser avec les informations de base
- info = f"## TICKET {ticket_data.get('code', 'Inconnu')}: {ticket_data.get('name', 'Sans titre')}\n\n"
-
- # Ajouter la description
- description = ticket_data.get('description', '')
- if description:
- info += f"## DESCRIPTION\n{description}\n\n"
-
- # Ajouter les informations du ticket
- info += "## INFORMATIONS DU TICKET\n"
- for key, value in ticket_data.items():
- if key not in ['code', 'name', 'description', 'messages', 'metadata'] and value:
- info += f"- {key}: {value}\n"
- info += "\n"
-
- # Ajouter les messages (conversations)
- messages = ticket_data.get('messages', [])
- if messages:
- info += "## ĂCHANGES ET MESSAGES\n"
- for i, msg in enumerate(messages):
- sender = msg.get('from', 'Inconnu')
- date = msg.get('date', 'Date inconnue')
- content = msg.get('content', '')
- info += f"### Message {i+1} - De: {sender} - Date: {date}\n{content}\n\n"
-
- # Ajouter les métadonnées techniques si présentes
- metadata = ticket_data.get('metadata', {})
- if metadata:
- info += "## MĂTADONNĂES TECHNIQUES\n"
- info += json.dumps(metadata, indent=2, ensure_ascii=False)
- info += "\n"
-
- return info
-
- def _get_timestamp(self) -> str:
- """Retourne un timestamp au format YYYYMMDD_HHMMSS"""
- from datetime import datetime
- return datetime.now().strftime("%Y%m%d_%H%M%S")
\ No newline at end of file
diff --git a/agents/agent_report_generator.py b/agents/agent_report_generator.py
index 8d4fc9b..c3d9520 100644
--- a/agents/agent_report_generator.py
+++ b/agents/agent_report_generator.py
@@ -2,7 +2,7 @@ import json
import os
from .base_agent import BaseAgent
from datetime import datetime
-from typing import Dict, Any, Tuple, Optional
+from typing import Dict, Any, Tuple, Optional, List
import logging
import traceback
import re
@@ -36,13 +36,13 @@ class AgentReportGenerator(BaseAgent):
def __init__(self, llm):
super().__init__("AgentReportGenerator", llm)
- # Configuration locale de l'agent (remplace AgentConfig)
+ # Configuration locale de l'agent
self.temperature = 0.4 # Génération de rapport factuelle mais bien structurée
self.top_p = 0.9
self.max_tokens = 2500
- self.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG-Lab pour la société CBAO.
-Ta mission est de synthétiser les analyses (ticket et images) en un rapport structuré et exploitable.
-
+
+ # Centralisation des exigences de format JSON
+ self.exigences_json = """
EXIGENCE ABSOLUE - GĂNĂRATION DE DONNĂES EN FORMAT JSON:
- Tu DOIS IMPĂRATIVEMENT inclure dans ta rĂ©ponse un objet JSON structurĂ© pour les Ă©changes client/support
- Le format de chaque Ă©change dans le JSON DOIT ĂȘtre:
@@ -63,29 +63,71 @@ EXIGENCE ABSOLUE - GĂNĂRATION DE DONNĂES EN FORMAT JSON:
- Si une question n'a pas de réponse, assure-toi de le noter clairement
- Toute mention de "CBAD" doit ĂȘtre remplacĂ©e par "CBAO" qui est le nom correct de la sociĂ©tĂ©
- Tu dois synthétiser au mieux les échanges (le plus court et clair possible)
+"""
+ # Centralisation des instructions de formatage
+ self.instructions_format = """
IMPORTANT POUR LE FORMAT:
- Le JSON doit ĂȘtre valide et parsable
- Utilise ```json et ``` pour délimiter le bloc JSON
- Ne modifie pas la structure des clés ("chronologie_echanges", "date", "emetteur", "type", "contenu")
- Assure-toi que les accolades et crochets sont correctement équilibrés
+"""
+ # Centralisation de la structure du rapport
+ self.structure_rapport = """
Structure ton rapport:
1. Résumé exécutif: SynthÚse du problÚme initial (nom de la demande + description)
2. Chronologie des échanges: Objet JSON avec la structure imposée ci-dessus (partie CRUCIALE)
3. Analyse des images: Ce que montrent les captures d'écran et leur pertinence
4. Diagnostic technique: Interprétation des informations techniques pertinentes
+"""
+
+ # Centralisation des exemples JSON
+ self.exemples_json = """
+EXEMPLES D'ĂCHANGES POUR RĂFĂRENCE:
+
+Exemple 1:
+```json
+{
+ "chronologie_echanges": [
+ {"date": "2023-01-15", "emetteur": "CLIENT", "type": "Question", "contenu": "Je n'arrive pas Ă me connecter Ă l'application"},
+ {"date": "2023-01-16", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Avez-vous essayé de réinitialiser votre mot de passe?"}
+ ]
+}
+```
+
+Exemple 2:
+```json
+{
+ "chronologie_echanges": [
+ {"date": "2023-02-10", "emetteur": "CLIENT", "type": "Information technique", "contenu": "Version de l'application: 2.3.1"},
+ {"date": "2023-02-11", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Cette version contient un bug connu, veuillez mettre à jour"}
+ ]
+}
+```
+
+N'oublie pas que le format EXACT est important. Utilise TOUJOURS la clé "chronologie_echanges" comme clé principale.
+"""
+
+ # Construction du prompt systÚme final avec des blocs de texte littéraux pour éviter les problÚmes d'accolades
+ self.system_prompt = f"""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é et exploitable.
+
+{self.exigences_json}
+{self.instructions_format}
+{self.structure_rapport}
Reste factuel et précis dans ton analyse.
Les données d'échanges client/support sont l'élément le plus important du rapport.
Tu DOIS inclure le JSON des échanges dans ta réponse exactement au format:
```json
-{
+{{
"chronologie_echanges": [
- {"date": "...", "emetteur": "CLIENT", "type": "Question", "contenu": "..."},
- {"date": "...", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "..."}
+ {{"date": "...", "emetteur": "CLIENT", "type": "Question", "contenu": "..."}},
+ {{"date": "...", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "..."}}
]
-}
+}}
```"""
# Appliquer la configuration au LLM
@@ -111,32 +153,7 @@ Tu DOIS inclure le JSON des échanges dans ta réponse exactement au format:
# Ajout des exemples dans le prompt systĂšme pour tous les modĂšles
if not "EXEMPLES D'ĂCHANGES" in self.llm.prompt_system:
- exemple_json = """
-EXEMPLES D'ĂCHANGES POUR RĂFĂRENCE:
-
-Exemple 1:
-```json
-{
- "chronologie_echanges": [
- {"date": "2023-01-15", "emetteur": "CLIENT", "type": "Question", "contenu": "Je n'arrive pas Ă me connecter Ă l'application"},
- {"date": "2023-01-16", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Avez-vous essayé de réinitialiser votre mot de passe?"}
- ]
-}
-```
-
-Exemple 2:
-```json
-{
- "chronologie_echanges": [
- {"date": "2023-02-10", "emetteur": "CLIENT", "type": "Information technique", "contenu": "Version de l'application: 2.3.1"},
- {"date": "2023-02-11", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Cette version contient un bug connu, veuillez mettre à jour"}
- ]
-}
-```
-
-N'oublie pas que le format EXACT est important. Utilise TOUJOURS la clé "chronologie_echanges" comme clé principale.
-"""
- self.llm.prompt_system += exemple_json
+ self.llm.prompt_system += self.exemples_json
logger.info("Exemples JSON ajoutés au prompt systÚme")
self.llm.configurer(**params)
@@ -144,7 +161,189 @@ N'oublie pas que le format EXACT est important. Utilise TOUJOURS la clé "chrono
else:
logger.warning("Le modÚle LLM ne supporte pas la méthode configurer()")
- def executer(self, rapport_data: Dict, rapport_dir: str) -> Tuple[Optional[str], Optional[str]]:
+ def _generer_prompt_instructions(self) -> str:
+ """
+ GénÚre les instructions pour la génération du rapport
+
+ Returns:
+ Instructions formatées
+ """
+ return f"""
+## INSTRUCTIONS POUR LA GĂNĂRATION DU RAPPORT
+
+1. Résume d'abord le problÚme principal du ticket en quelques phrases.
+
+2. GĂNĂRER OBLIGATOIREMENT LE JSON DES ĂCHANGES CLIENT/SUPPORT:
+ - Les données d'échanges sont l'élément le plus important du rapport
+ - Utilise EXACTEMENT la structure suivante, sans la modifier:
+ ```json
+ {{
+ "chronologie_echanges": [
+ {{"date": "date1", "emetteur": "CLIENT", "type": "Question", "contenu": "contenu de la question"}},
+ {{"date": "date2", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "contenu de la réponse"}}
+ ]
+ }}
+ ```
+ - La clĂ© principale DOIT ĂȘtre "chronologie_echanges"
+ - N'ajoute pas de commentaires ou de texte dans le JSON
+ - Assure-toi que le JSON est valide et correspond EXACTEMENT au format demandé
+ - Entoure le JSON avec ```json et ``` pour faciliter l'extraction
+
+3. AprÚs le JSON, analyse les images pertinentes et leur contribution à la compréhension du problÚme.
+
+4. Termine par une analyse technique des causes probables du problĂšme.
+
+IMPORTANT: Le JSON des Ă©changes client/support est OBLIGATOIRE et doit ĂȘtre parfaitement formatĂ©.
+"""
+
+ def _generer_exemple_json(self) -> str:
+ """
+ GénÚre un exemple JSON pour le prompt
+
+ Returns:
+ Exemple JSON formaté
+ """
+ return """
+EXEMPLE EXACT DU FORMAT JSON ATTENDU:
+```json
+{
+ "chronologie_echanges": [
+ {"date": "2023-05-10", "emetteur": "CLIENT", "type": "Question", "contenu": "L'application affiche une erreur lors de la connexion"},
+ {"date": "2023-05-11", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Pouvez-vous préciser le message d'erreur?"},
+ {"date": "2023-05-12", "emetteur": "CLIENT", "type": "Information technique", "contenu": "Message: Erreur de connexion au serveur"}
+ ]
+}
+```
+"""
+
+ def _formater_prompt_pour_rapport(self, ticket_analyse, images_analyses, ticket_id):
+ """
+ Formate le prompt pour la génération du rapport
+
+ Args:
+ ticket_analyse: Analyse du ticket
+ images_analyses: Liste des analyses d'images, format [{image_name, analyse}, ...]
+ ticket_id: ID du ticket
+
+ Returns:
+ Prompt formaté pour le LLM
+ """
+ num_images = len(images_analyses)
+ logger.info(f"Formatage du prompt avec {num_images} analyses d'images")
+
+ # Inclure une vérification des données reçues
+ prompt = f"""GénÚre un rapport technique complet pour le ticket #{ticket_id}, en te basant sur les analyses suivantes.
+
+## VĂRIFICATION DES DONNĂES REĂUES
+Je vais d'abord vérifier que j'ai bien reçu les données d'analyses:
+- Analyse du ticket : {"PRĂSENTE" if ticket_analyse else "MANQUANTE"}
+- Analyses d'images : {"PRĂSENTES (" + str(num_images) + " images)" if num_images > 0 else "MANQUANTES"}
+
+## ANALYSE DU TICKET
+{ticket_analyse}
+
+## ANALYSES DES IMAGES ({num_images} images analysées)
+"""
+
+ # Ajouter l'analyse de chaque image
+ for i, img_analyse in enumerate(images_analyses, 1):
+ image_name = img_analyse.get("image_name", f"Image {i}")
+ analyse = img_analyse.get("analyse", "Analyse non disponible")
+ prompt += f"\n### IMAGE {i}: {image_name}\n{analyse}\n"
+ logger.info(f"Ajout de l'analyse de l'image {image_name} au prompt ({len(str(analyse))} caractĂšres)")
+
+ # Instructions claires pour tous les modĂšles
+ prompt += self._generer_prompt_instructions()
+
+ # Ajouter l'exemple non formaté pour éviter les erreurs de formatage
+ prompt += self._generer_exemple_json()
+
+ logger.info(f"Prompt formaté: {len(prompt)} caractÚres au total")
+ return prompt
+
+ def _generer_tableau_questions_reponses(self, echanges: List[Dict]) -> str:
+ """
+ GénÚre un tableau question/réponse simplifié à partir des échanges
+
+ Args:
+ echanges: Liste des échanges client/support
+
+ Returns:
+ Tableau au format markdown
+ """
+ if not echanges:
+ return "Aucun échange trouvé dans ce ticket."
+
+ # Initialiser le tableau
+ tableau = "\n## Tableau récapitulatif des échanges\n\n"
+ tableau += "| Question (Client) | Réponse (Support) |\n"
+ tableau += "|------------------|-------------------|\n"
+
+ # Variables pour suivre les questions et réponses
+ question_courante = None
+ questions_sans_reponse = []
+
+ # Parcourir tous les échanges pour identifier les questions et réponses
+ for echange in echanges:
+ emetteur = echange.get("emetteur", "").lower()
+ type_msg = echange.get("type", "").lower()
+ contenu = echange.get("contenu", "")
+ date = echange.get("date", "")
+
+ # Formater le contenu (synthétiser si trop long)
+ contenu_formate = self._synthétiser_contenu(contenu, 150)
+
+ # Si c'est une question du client
+ if emetteur == "client" and (type_msg == "question" or "?" in contenu):
+ # Si une question précédente n'a pas de réponse, l'ajouter à la liste
+ if question_courante:
+ questions_sans_reponse.append(question_courante)
+
+ # Enregistrer la nouvelle question courante
+ question_courante = f"{contenu_formate} _(date: {date})_"
+
+ # Si c'est une réponse du support et qu'il y a une question en attente
+ elif emetteur == "support" and question_courante:
+ # Ajouter la paire question/réponse au tableau
+ tableau += f"| {question_courante} | {contenu_formate} _(date: {date})_ |\n"
+ question_courante = None # Réinitialiser la question courante
+
+ # Traiter toute question restante sans réponse
+ if question_courante:
+ questions_sans_reponse.append(question_courante)
+
+ # Ajouter les questions sans réponse au tableau
+ for q in questions_sans_reponse:
+ tableau += f"| {q} | **Aucune réponse du support** |\n"
+
+ # Ajouter une note si aucun échange support n'a été trouvé
+ if not any(echange.get("emetteur", "").lower() == "support" for echange in echanges):
+ tableau += "\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n"
+
+ return tableau
+
+ def _synthétiser_contenu(self, contenu: str, longueur_max: int) -> str:
+ """
+ Synthétise le contenu s'il est trop long
+
+ Args:
+ contenu: Contenu à synthétiser
+ longueur_max: Longueur maximale souhaitée
+
+ Returns:
+ Contenu synthétisé
+ """
+ if len(contenu) <= longueur_max:
+ return contenu
+
+ # Extraire les premiers caractĂšres
+ debut = contenu[:longueur_max//2].strip()
+ # Extraire les derniers caractĂšres
+ fin = contenu[-(longueur_max//2):].strip()
+
+ return f"{debut}... {fin}"
+
+ def executer(self, rapport_data: Dict, rapport_dir: str) -> Optional[str]:
"""
GénÚre un rapport à partir des analyses effectuées
@@ -156,7 +355,7 @@ N'oublie pas que le format EXACT est important. Utilise TOUJOURS la clé "chrono
rapport_dir: RĂ©pertoire oĂč sauvegarder le rapport
Returns:
- Tuple (chemin vers le rapport JSON, chemin vers le rapport Markdown)
+ Chemin vers le rapport JSON
"""
# Récupérer l'ID du ticket depuis les données
ticket_id = rapport_data.get("ticket_id", "")
@@ -274,7 +473,9 @@ N'oublie pas que le format EXACT est important. Utilise TOUJOURS la clé "chrono
if analyse_detail:
images_analyses.append({
"image_name": image_name,
- "analyse": analyse_detail
+ "image_path": image_path,
+ "analyse": analyse_detail,
+ "sorting_info": analyse_data.get("sorting", {})
})
logger.info(f"Analyse de l'image {image_name} ajoutée au rapport (longueur: {len(str(analyse_detail))} caractÚres)")
else:
@@ -284,7 +485,6 @@ N'oublie pas que le format EXACT est important. Utilise TOUJOURS la clé "chrono
# Créer le chemin du fichier de rapport JSON (sortie principale)
json_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.json")
- md_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.md")
# Formater les données pour le LLM
prompt = self._formater_prompt_pour_rapport(ticket_analyse, images_analyses, ticket_id)
@@ -293,20 +493,21 @@ N'oublie pas que le format EXACT est important. Utilise TOUJOURS la clé "chrono
logger.info("Génération du rapport avec le LLM")
print(f" Génération du rapport avec le LLM...")
+ # Debut du timing
+ start_time = datetime.now()
+
# Interroger le LLM
rapport_genere = self.llm.interroger(prompt)
+
+ # Fin du timing
+ end_time = datetime.now()
+ generation_time = (end_time - 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")
- # Traiter le JSON pour extraire la chronologie des échanges et le convertir en tableau Markdown
- rapport_traite, echanges_json, echanges_markdown = self._extraire_et_traiter_json(rapport_genere)
- if echanges_json and echanges_markdown:
- logger.info(f"Ăchanges JSON convertis en tableau Markdown: {len(str(echanges_markdown))} caractĂšres")
- print(f" Ăchanges client/support convertis du JSON vers le format Markdown")
- # Utiliser le rapport traité avec le tableau Markdown à la place du JSON
- rapport_genere = rapport_traite
- else:
- logger.warning("Aucun JSON d'échanges trouvé dans le rapport, conservation du format original")
+ # Traiter le JSON pour extraire la chronologie des échanges
+ _, echanges_json, _ = self._extraire_et_traiter_json(rapport_genere)
# Tracer l'historique avec le prompt pour la transparence
self.ajouter_historique("generation_rapport",
@@ -320,35 +521,70 @@ N'oublie pas que le format EXACT est important. Utilise TOUJOURS la clé "chrono
# Préparer les métadonnées complÚtes pour le rapport
timestamp = self._get_timestamp()
+ # Extraire le résumé et diagnostic du rapport généré (premiÚre partie et derniÚre partie)
+ resume = ""
+ diagnostic = ""
+
+ if rapport_genere:
+ # Supprimer le bloc JSON (pour isoler le texte d'analyse)
+ rapport_sans_json = re.sub(r'```json.*?```', '', rapport_genere, flags=re.DOTALL)
+
+ # Diviser le texte en paragraphes
+ paragraphes = [p.strip() for p in rapport_sans_json.split('\n\n') if p.strip()]
+
+ # Le premier paragraphe est généralement le résumé
+ if paragraphes:
+ resume = paragraphes[0]
+
+ # Les derniers paragraphes aprĂšs "Diagnostic" ou "Analyse technique"
+ # contiennent généralement le diagnostic
+ for i, p in enumerate(paragraphes):
+ if any(marker in p.lower() for marker in ["diagnostic", "analyse technique", "conclusion"]):
+ diagnostic = '\n\n'.join(paragraphes[i:])
+ break
+
# Préparer le JSON complet du rapport (format principal)
rapport_data_complet = {
"ticket_id": ticket_id,
"timestamp": timestamp,
- "rapport_genere": rapport_genere,
- "ticket_analyse": ticket_analyse,
- "images_analyses": images_analyses,
+ "rapport_complet": rapport_genere, # Texte complet généré par le LLM
+ "ticket_analyse": ticket_analyse, # Analyse du ticket d'origine
+ "images_analyses": images_analyses, # Analyses des images
+ "chronologie_echanges": echanges_json.get("chronologie_echanges", []) if echanges_json else [],
+ "resume": resume, # Résumé extrait du rapport généré
+ "diagnostic": diagnostic, # Diagnostic technique extrait du rapport
"statistiques": {
"total_images": total_images,
"images_pertinentes": images_pertinentes,
- "analyses_generees": len(images_analyses)
+ "analyses_generees": len(images_analyses),
+ "generation_time": generation_time
+ },
+ "prompt": {
+ "systeme": self.system_prompt,
+ "utilisateur": prompt
}
}
# Ajouter les métadonnées pour la traçabilité
metadata = {
"timestamp": timestamp,
+ "generation_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"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,
- "agents": agents_info
+ "agents": agents_info,
+ "generation_time": generation_time,
+ "duree_traitement": str(getattr(self.llm, "dureeTraitement", "N/A"))
}
rapport_data_complet["metadata"] = metadata
- # S'assurer que les clés nécessaires pour le markdown sont présentes
- if "ticket_analyse" not in rapport_data_complet:
- rapport_data_complet["ticket_analyse"] = ticket_analyse
+ # Ajouter le tableau questions/réponses dans les métadonnées
+ if echanges_json and "chronologie_echanges" in echanges_json:
+ tableau_qr = self._generer_tableau_questions_reponses(echanges_json["chronologie_echanges"])
+ rapport_data_complet["tableau_questions_reponses"] = tableau_qr
# ĂTAPE 1: Sauvegarder le rapport au format JSON (FORMAT PRINCIPAL)
with open(json_path, "w", encoding="utf-8") as f:
@@ -357,25 +593,15 @@ N'oublie pas que le format EXACT est important. Utilise TOUJOURS la clé "chrono
logger.info(f"Rapport JSON (format principal) sauvegardé: {json_path}")
print(f" Rapport JSON sauvegardé: {json_path}")
- # ĂTAPE 2: GĂ©nĂ©rer et sauvegarder le rapport au format Markdown (pour prĂ©sentation)
- markdown_content = self._generer_markdown_depuis_json(rapport_data_complet)
-
- with open(md_path, "w", encoding="utf-8") as f:
- f.write(markdown_content)
-
- logger.info(f"Rapport Markdown (pour présentation) sauvegardé: {md_path}")
- print(f" Rapport Markdown sauvegardé: {md_path}")
-
- logger.info(f"Taille du rapport Markdown: {len(markdown_content)} caractĂšres")
-
- # Retourner les chemins des deux fichiers (JSON en premier, Markdown en second)
- return json_path, md_path
+ # Retourner le chemin du fichier JSON
+ return json_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
+ return None
def _collecter_info_agents(self, rapport_data: Dict) -> Dict:
"""
@@ -432,279 +658,6 @@ N'oublie pas que le format EXACT est important. Utilise TOUJOURS la clé "chrono
return agents_info
- def _generer_markdown_depuis_json(self, rapport_data: Dict) -> str:
- """
- GénÚre un rapport Markdown directement à partir des données JSON
-
- Args:
- rapport_data: Données JSON complÚtes du rapport
- Format attendu:
- - ticket_id: ID du ticket
- - metadata: Métadonnées (timestamp, modÚle, etc.)
- - rapport_genere: Texte du rapport généré par le LLM
- - ticket_analyse: Analyse du ticket
- - images_analyses: Liste des analyses d'images (format privilégié)
- OU
- - analyse_images: Dictionnaire des analyses d'images (format alternatif)
-
- Returns:
- Contenu Markdown du rapport
- """
- ticket_id = rapport_data.get("ticket_id", "")
- timestamp = rapport_data.get("metadata", {}).get("timestamp", self._get_timestamp())
-
- logger.info(f"Génération du rapport Markdown pour le ticket {ticket_id}")
-
- # Contenu de base du rapport (partie générée par le LLM)
- rapport_contenu = rapport_data.get("rapport_genere", "")
-
- # EntĂȘte du document
- markdown = f"# Rapport d'analyse du ticket #{ticket_id}\n\n"
- markdown += f"*Généré le: {timestamp}*\n\n"
-
- # Ajouter le rapport principal généré par le LLM
- markdown += rapport_contenu + "\n\n"
-
- # Section séparatrice pour les détails d'analyse
- markdown += "---\n\n"
- markdown += "# Détails des analyses effectuées\n\n"
-
- # Ajouter un résumé du processus d'analyse complet
- markdown += "## Processus d'analyse\n\n"
-
- # 1. Analyse de ticket
- ticket_analyse = rapport_data.get("ticket_analyse", "")
- if not ticket_analyse and "analyse_json" in rapport_data:
- ticket_analyse = rapport_data.get("analyse_json", "")
-
- if ticket_analyse:
- markdown += "### Ătape 1: Analyse du ticket\n\n"
- markdown += "L'agent d'analyse de ticket a extrait les informations suivantes du ticket d'origine:\n\n"
- markdown += "\nCliquez pour voir l'analyse complĂšte du ticket
\n\n"
- markdown += "```\n" + ticket_analyse + "\n```\n\n"
- markdown += " \n\n"
- logger.info(f"Analyse du ticket ajoutée au rapport Markdown ({len(str(ticket_analyse))} caractÚres)")
- else:
- logger.warning("Aucune analyse de ticket disponible pour le rapport Markdown")
-
- # 2. Tri des images
- markdown += "### Ătape 2: Tri des images\n\n"
- markdown += "L'agent de tri d'images a évalué chaque image pour déterminer sa pertinence par rapport au problÚme client:\n\n"
-
- # Vérifier quelle structure de données est disponible pour les images
- has_analyse_images = "analyse_images" in rapport_data and rapport_data["analyse_images"]
- has_images_analyses = "images_analyses" in rapport_data and isinstance(rapport_data["images_analyses"], list) and rapport_data["images_analyses"]
-
- logger.info(f"Structure des données d'images: analyse_images={has_analyse_images}, images_analyses={has_images_analyses}")
-
- analyse_images_data = {}
- if has_analyse_images:
- analyse_images_data = rapport_data["analyse_images"]
-
- if analyse_images_data:
- # Créer un tableau pour le tri des images
- markdown += "| Image | Pertinence | Raison |\n"
- markdown += "|-------|------------|--------|\n"
-
- for image_path, analyse_data in analyse_images_data.items():
- image_name = os.path.basename(image_path)
-
- # Information de tri
- is_relevant = "Non"
- reason = "Non spécifiée"
-
- if "sorting" in analyse_data:
- sorting_data = analyse_data["sorting"]
- if isinstance(sorting_data, dict):
- is_relevant = "Oui" if sorting_data.get("is_relevant", False) else "Non"
- reason = sorting_data.get("reason", "Non spécifiée")
-
- markdown += f"| {image_name} | {is_relevant} | {reason} |\n"
-
- markdown += "\n"
- logger.info(f"Tableau de tri des images ajouté au rapport Markdown ({len(analyse_images_data)} images)")
- elif has_images_analyses and rapport_data["images_analyses"]:
- # Si nous avons les analyses d'images mais pas les données de tri, créer un tableau simplifié
- markdown += "| Image | Pertinence |\n"
- markdown += "|-------|------------|\n"
-
- for img_data in rapport_data["images_analyses"]:
- image_name = img_data.get("image_name", "Image inconnue")
- markdown += f"| {image_name} | Oui |\n"
-
- markdown += "\n"
- logger.info(f"Tableau de tri simplifié ajouté au rapport Markdown ({len(rapport_data['images_analyses'])} images)")
- else:
- markdown += "*Aucune image n'a été trouvée ou analysée.*\n\n"
- logger.warning("Aucune analyse d'images disponible pour le tableau de tri")
-
- # 3. Analyse des images pertinentes
- markdown += "### Ătape 3: Analyse dĂ©taillĂ©e des images pertinentes\n\n"
-
- # Traiter directement les images_analyses du rapport_data si disponible
- images_pertinentes = 0
-
- # D'abord essayer d'utiliser la liste images_analyses qui est déjà traitée
- if has_images_analyses:
- images_list = rapport_data["images_analyses"]
- for i, img_data in enumerate(images_list, 1):
- images_pertinentes += 1
- image_name = img_data.get("image_name", f"Image {i}")
- analyse_detail = img_data.get("analyse", "Analyse non disponible")
-
- markdown += f"#### Image pertinente {images_pertinentes}: {image_name}\n\n"
- markdown += "\nCliquez pour voir l'analyse complĂšte de l'image
\n\n"
- markdown += "```\n" + analyse_detail + "\n```\n\n"
- markdown += " \n\n"
-
- logger.info(f"Analyse de l'image {image_name} ajoutée au rapport Markdown (from images_analyses)")
- # Sinon, traiter les données brutes d'analyse_images
- elif has_analyse_images:
- analyse_images_data = rapport_data["analyse_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)
-
- if is_relevant:
- images_pertinentes += 1
- image_name = os.path.basename(image_path)
-
- # Récupérer l'analyse détaillée avec gestion des différents formats possibles
- analyse_detail = "Analyse non disponible"
- if "analysis" in analyse_data and analyse_data["analysis"]:
- if isinstance(analyse_data["analysis"], dict):
- if "analyse" in analyse_data["analysis"]:
- analyse_detail = analyse_data["analysis"]["analyse"]
- logger.info(f"Analyse de l'image {image_name} récupérée via analyse_data['analysis']['analyse']")
- elif "error" in analyse_data["analysis"] and not analyse_data["analysis"].get("error", True):
- analyse_detail = str(analyse_data["analysis"])
- logger.info(f"Analyse de l'image {image_name} récupérée via str(analyse_data['analysis'])")
- else:
- analyse_detail = json.dumps(analyse_data["analysis"], ensure_ascii=False, indent=2)
- logger.info(f"Analyse de l'image {image_name} récupérée via json.dumps")
- elif isinstance(analyse_data["analysis"], str):
- analyse_detail = analyse_data["analysis"]
- logger.info(f"Analyse de l'image {image_name} récupérée directement (string)")
- else:
- logger.warning(f"Aucune analyse disponible pour l'image pertinente {image_name}")
-
- markdown += f"#### Image pertinente {images_pertinentes}: {image_name}\n\n"
- markdown += "\nCliquez pour voir l'analyse complĂšte de l'image
\n\n"
- markdown += "```\n" + analyse_detail + "\n```\n\n"
- markdown += " \n\n"
-
- if images_pertinentes == 0:
- markdown += "*Aucune image pertinente n'a été identifiée pour ce ticket.*\n\n"
- logger.warning("Aucune image pertinente identifiée pour l'analyse détaillée")
- else:
- logger.info(f"{images_pertinentes} images pertinentes ajoutées au rapport Markdown")
-
- # 4. SynthĂšse (rapport final)
- markdown += "### Ătape 4: GĂ©nĂ©ration du rapport de synthĂšse\n\n"
- markdown += "L'agent de génération de rapport a synthétisé toutes les analyses précédentes pour produire le rapport ci-dessus.\n\n"
-
- # Informations techniques
- markdown += "## Informations techniques\n\n"
-
- # Statistiques d'analyse
- markdown += "### Statistiques\n\n"
-
- total_images = 0
- if has_analyse_images:
- total_images = len(analyse_images_data)
- elif "statistiques" in rapport_data and "total_images" in rapport_data["statistiques"]:
- total_images = rapport_data["statistiques"]["total_images"]
-
- markdown += f"- **Images analysées**: {total_images}\n"
- markdown += f"- **Images pertinentes**: {images_pertinentes}\n\n"
-
- logger.info(f"Rapport Markdown généré ({len(markdown)} caractÚres)")
- return markdown
-
- def _formater_prompt_pour_rapport(self, ticket_analyse, images_analyses, ticket_id):
- """
- Formate le prompt pour la génération du rapport
-
- Args:
- ticket_analyse: Analyse du ticket
- images_analyses: Liste des analyses d'images, format [{image_name, analyse}, ...]
- ticket_id: ID du ticket
-
- Returns:
- Prompt formaté pour le LLM
- """
- num_images = len(images_analyses)
- logger.info(f"Formatage du prompt avec {num_images} analyses d'images")
-
- # Inclure une vérification des données reçues
- prompt = f"""GénÚre un rapport technique complet pour le ticket #{ticket_id}, en te basant sur les analyses suivantes.
-
-## VĂRIFICATION DES DONNĂES REĂUES
-Je vais d'abord vérifier que j'ai bien reçu les données d'analyses:
-- Analyse du ticket : {"PRĂSENTE" if ticket_analyse else "MANQUANTE"}
-- Analyses d'images : {"PRĂSENTES (" + str(num_images) + " images)" if num_images > 0 else "MANQUANTES"}
-
-## ANALYSE DU TICKET
-{ticket_analyse}
-
-## ANALYSES DES IMAGES ({num_images} images analysées)
-"""
-
- # Ajouter l'analyse de chaque image
- for i, img_analyse in enumerate(images_analyses, 1):
- image_name = img_analyse.get("image_name", f"Image {i}")
- analyse = img_analyse.get("analyse", "Analyse non disponible")
- prompt += f"\n### IMAGE {i}: {image_name}\n{analyse}\n"
- logger.info(f"Ajout de l'analyse de l'image {image_name} au prompt ({len(str(analyse))} caractĂšres)")
-
- # Instructions claires pour tous les modĂšles
- prompt += f"""
-## INSTRUCTIONS POUR LA GĂNĂRATION DU RAPPORT
-
-1. Résume d'abord le problÚme principal du ticket en quelques phrases.
-
-2. GĂNĂRER OBLIGATOIREMENT LE JSON DES ĂCHANGES CLIENT/SUPPORT:
- - Les données d'échanges sont l'élément le plus important du rapport
- - Utilise EXACTEMENT la structure suivante, sans la modifier:
- ```json
- {{
- "chronologie_echanges": [
- {{"date": "date1", "emetteur": "CLIENT", "type": "Question", "contenu": "contenu de la question"}},
- {{"date": "date2", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "contenu de la réponse"}}
- ]
- }}
- ```
- - La clĂ© principale DOIT ĂȘtre "chronologie_echanges"
- - N'ajoute pas de commentaires ou de texte dans le JSON
- - Assure-toi que le JSON est valide et correspond EXACTEMENT au format demandé
- - Entoure le JSON avec ```json et ``` pour faciliter l'extraction
-
-3. AprÚs le JSON, analyse les images pertinentes et leur contribution à la compréhension du problÚme.
-
-4. Termine par une analyse technique des causes probables du problĂšme.
-
-IMPORTANT: Le JSON des Ă©changes client/support est OBLIGATOIRE et doit ĂȘtre parfaitement formatĂ©.
-"""
-
- # Ajouter l'exemple non formaté (sans f-string) pour éviter les erreurs de formatage
- prompt += """
-EXEMPLE EXACT DU FORMAT JSON ATTENDU:
-```json
-{
- "chronologie_echanges": [
- {"date": "2023-05-10", "emetteur": "CLIENT", "type": "Question", "contenu": "L'application affiche une erreur lors de la connexion"},
- {"date": "2023-05-11", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Pouvez-vous préciser le message d'erreur?"},
- {"date": "2023-05-12", "emetteur": "CLIENT", "type": "Information technique", "contenu": "Message: Erreur de connexion au serveur"}
- ]
-}
-```
-"""
-
- logger.info(f"Prompt formaté: {len(prompt)} caractÚres au total")
- return prompt
-
def _extraire_et_traiter_json(self, texte_rapport):
"""
Extrait l'objet JSON des échanges du texte du rapport et le convertit en Markdown
@@ -837,6 +790,10 @@ EXEMPLE EXACT DU FORMAT JSON ATTENDU:
# Ajouter une note si aucune réponse du support n'a été trouvée
if not any(echange.get("emetteur", "").lower() == "support" for echange in echanges_json["chronologie_echanges"]):
echanges_markdown += "\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n\n"
+
+ # Ajouter un tableau questions/réponses simplifié
+ tableau_qr = self._generer_tableau_questions_reponses(echanges_json["chronologie_echanges"])
+ echanges_markdown += f"\n{tableau_qr}\n"
# Remplacer le JSON dans le texte par le tableau Markdown
# Si le JSON était entouré de backticks, remplacer tout le bloc
diff --git a/agents/agent_report_generator.py.backup b/agents/agent_report_generator.py.backup
deleted file mode 100644
index 3600781..0000000
--- a/agents/agent_report_generator.py.backup
+++ /dev/null
@@ -1,623 +0,0 @@
-import json
-import os
-from .base_agent import BaseAgent
-from datetime import datetime
-from typing import Dict, Any, Tuple, Optional
-import logging
-import traceback
-
-logger = logging.getLogger("AgentReportGenerator")
-
-class AgentReportGenerator(BaseAgent):
- """
- Agent pour générer un rapport complet à partir des analyses de ticket et d'images.
-
- Cet agent prend en entrée :
- - L'analyse du ticket
- - Les analyses des images pertinentes
- - Les métadonnées associées
-
- Format de données attendu:
- - JSON est le format principal de données en entrée et en sortie
- - Le rapport Markdown est généré à partir du JSON uniquement pour la présentation
-
- Structure des données d'analyse d'images:
- - Deux structures possibles sont supportées:
- 1. Liste d'objets: rapport_data["images_analyses"] = [{image_name, analyse}, ...]
- 2. Dictionnaire: rapport_data["analyse_images"] = {chemin_image: {sorting: {...}, analysis: {...}}, ...}
-
- Flux de traitement:
- 1. Préparation des données d'entrée
- 2. Génération du rapport avec le LLM
- 3. Sauvegarde au format JSON (format principal)
- 4. Conversion et sauvegarde au format Markdown (pour présentation)
- """
- def __init__(self, llm):
- super().__init__("AgentReportGenerator", llm)
-
- # Configuration locale de l'agent (remplace AgentConfig)
- self.temperature = 0.4 # Génération de rapport factuelle mais bien structurée
- self.top_p = 0.9
- self.max_tokens = 2500
- self.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG-Lab pour la société CBAO.
-Ta mission est de synthétiser les analyses (ticket et images) en un rapport structuré et exploitable.
-
-EXIGENCE ABSOLUE - TABLEAU DES ĂCHANGES CLIENT/SUPPORT:
-- Tu DOIS IMPĂRATIVEMENT crĂ©er un TABLEAU MARKDOWN des Ă©changes client/support
-- Le format du tableau DOIT ĂȘtre:
- | Date | Ămetteur (CLIENT/SUPPORT) | Type (Question/RĂ©ponse) | Contenu |
- |------|---------------------------|-------------------------|---------|
- | date1 | CLIENT | Question | contenu... |
- | date2 | SUPPORT | Réponse | contenu... |
-- Chaque message du ticket doit apparaĂźtre dans une ligne du tableau
-- Indique clairement qui est CLIENT et qui est SUPPORT
-- Tu dois synthétiser au mieux les échanges(le plus court et clair possible) client/support(question/réponse) dans le tableau
-- TU dois spécifié si la question n'a pas de réponse
-- Le tableau DOIT ĂȘtre inclus dans la section "Chronologie des Ă©changes"
-
-Structure ton rapport:
-1. Résumé exécutif: SynthÚse du problÚme initial (nom de la demande + description)
-2. Chronologie des échanges: TABLEAU des interactions client/support (format imposé ci-dessus)
-3. Analyse des images: Ce que montrent les captures d'écran et leur pertinence
-4. Diagnostic technique: Interprétation des informations techniques pertinentes
-
-Reste factuel et précis dans ton analyse.
-Le tableau des échanges client/support est l'élément le plus important du rapport."""
-
- # 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
- }
-
- # 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, rapport_data: Dict, rapport_dir: str) -> Tuple[Optional[str], Optional[str]]:
- """
- GénÚre un rapport à partir des analyses effectuées
-
- Args:
- rapport_data: Dictionnaire contenant toutes les données analysées
- Doit contenir au moins une des clés:
- - "ticket_analyse" ou "analyse_json": Analyse du ticket
- - "analyse_images": Analyses des images (facultatif)
- rapport_dir: RĂ©pertoire oĂč sauvegarder le rapport
-
- Returns:
- Tuple (chemin vers le rapport JSON, chemin vers le rapport Markdown)
- """
- # Récupérer l'ID du ticket depuis les données
- ticket_id = rapport_data.get("ticket_id", "")
- if not ticket_id and "ticket_data" in rapport_data and isinstance(rapport_data["ticket_data"], dict):
- ticket_id = rapport_data["ticket_data"].get("code", "")
-
- if not ticket_id:
- ticket_id = os.path.basename(os.path.dirname(rapport_dir))
- if not ticket_id.startswith("T"):
- # Dernier recours, utiliser le dernier segment du chemin
- ticket_id = os.path.basename(rapport_dir)
-
- logger.info(f"Génération du rapport pour le ticket: {ticket_id}")
- print(f"AgentReportGenerator: Génération du rapport pour {ticket_id}")
-
- # Validation des données d'entrée
- logger.info("Vérification de la complétude des données d'entrée:")
- if "ticket_data" in rapport_data:
- logger.info(f" - Données de ticket présentes: {len(str(rapport_data['ticket_data']))} caractÚres")
- else:
- logger.warning(" - Données de ticket manquantes")
-
- # Vérification des analyses
- ticket_analyse_exists = False
- if "ticket_analyse" in rapport_data and rapport_data["ticket_analyse"]:
- ticket_analyse_exists = True
- logger.info(f" - Analyse du ticket présente: {len(rapport_data['ticket_analyse'])} caractÚres")
- elif "analyse_json" in rapport_data and rapport_data["analyse_json"]:
- ticket_analyse_exists = True
- logger.info(f" - Analyse JSON présente: {len(rapport_data['analyse_json'])} caractÚres")
- else:
- logger.warning(" - Analyse du ticket manquante")
-
- # Vérification des analyses d'images
- if "analyse_images" in rapport_data and rapport_data["analyse_images"]:
- n_images = len(rapport_data["analyse_images"])
- n_relevant = sum(1 for _, data in rapport_data["analyse_images"].items()
- if "sorting" in data and isinstance(data["sorting"], dict) and data["sorting"].get("is_relevant", False))
- n_analyzed = sum(1 for _, data in rapport_data["analyse_images"].items()
- if "analysis" in data and data["analysis"])
-
- logger.info(f" - Analyses d'images présentes: {n_images} images, {n_relevant} pertinentes, {n_analyzed} analysées")
- else:
- logger.warning(" - Analyses d'images manquantes")
-
- # S'assurer que le répertoire existe
- if not os.path.exists(rapport_dir):
- os.makedirs(rapport_dir)
- logger.info(f"Répertoire de rapport créé: {rapport_dir}")
-
- try:
- # Préparer les données formatées pour l'analyse
- ticket_analyse = None
-
- # Vérifier que l'analyse du ticket est disponible sous l'une des clés possibles
- if "ticket_analyse" in rapport_data and rapport_data["ticket_analyse"]:
- ticket_analyse = rapport_data["ticket_analyse"]
- logger.info("Utilisation de ticket_analyse")
- elif "analyse_json" in rapport_data and rapport_data["analyse_json"]:
- ticket_analyse = rapport_data["analyse_json"]
- logger.info("Utilisation de analyse_json en fallback")
- else:
- # 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")
- ticket_analyse = f"Analyse par défaut du ticket:\nNom: {ticket_name}\nDescription: {ticket_desc}\n(Aucune analyse détaillée n'a été fournie par l'agent d'analyse de ticket)"
-
- # Préparer les données d'analyse d'images
- images_analyses = []
- analyse_images_data = rapport_data.get("analyse_images", {})
-
- # Statistiques pour les métadonnées
- total_images = len(analyse_images_data) if analyse_images_data else 0
- images_pertinentes = 0
-
- # Collecter des informations sur les agents et LLM utilisés
- agents_info = self._collecter_info_agents(rapport_data)
-
- # Transformer les analyses d'images en liste structurée pour le prompt
- for image_path, analyse_data in analyse_images_data.items():
- image_name = os.path.basename(image_path)
-
- # 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)
- if is_relevant:
- images_pertinentes += 1
-
- # Récupérer l'analyse détaillée si elle existe et que l'image est pertinente
- analyse_detail = None
- if is_relevant:
- if "analysis" in analyse_data and analyse_data["analysis"]:
- # Vérifier différentes structures possibles de l'analyse
- if isinstance(analyse_data["analysis"], dict):
- if "analyse" in analyse_data["analysis"]:
- analyse_detail = analyse_data["analysis"]["analyse"]
- elif "error" in analyse_data["analysis"] and not analyse_data["analysis"].get("error", True):
- # Si pas d'erreur et que l'analyse est directement dans le dictionnaire
- analyse_detail = str(analyse_data["analysis"])
- else:
- # Essayer de récupérer directement le contenu du dictionnaire
- analyse_detail = json.dumps(analyse_data["analysis"], ensure_ascii=False, indent=2)
- elif isinstance(analyse_data["analysis"], str):
- # Si l'analyse est directement une chaĂźne
- analyse_detail = analyse_data["analysis"]
-
- # Si l'analyse n'a pas été trouvée mais que l'image est pertinente
- if not analyse_detail:
- analyse_detail = f"Image marquée comme pertinente. Raison: {analyse_data['sorting'].get('reason', 'Non spécifiée')}"
-
- # Ajouter l'analyse Ă la liste si elle existe
- if analyse_detail:
- images_analyses.append({
- "image_name": image_name,
- "analyse": analyse_detail
- })
- logger.info(f"Analyse de l'image {image_name} ajoutée au rapport (longueur: {len(analyse_detail)})")
- else:
- logger.warning(f"Analyse non trouvée pour l'image pertinente {image_name}")
- else:
- logger.info(f"Image {image_name} ignorée car non pertinente")
-
- # Créer le chemin du fichier de rapport JSON (sortie principale)
- json_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.json")
- md_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.md")
-
- # Formater les données pour le LLM
- prompt = self._formater_prompt_pour_rapport(ticket_analyse, images_analyses, ticket_id)
-
- # Générer le rapport avec le LLM
- logger.info("Génération du rapport avec le LLM")
- print(f" Génération du rapport avec le LLM...")
-
- # Interroger le LLM
- rapport_genere = self.llm.interroger(prompt)
- logger.info(f"Rapport généré: {len(rapport_genere)} caractÚres")
- print(f" Rapport généré: {len(rapport_genere)} caractÚres")
-
- # Tracer l'historique avec le prompt pour la transparence
- self.ajouter_historique("generation_rapport",
- {
- "ticket_id": ticket_id,
- "prompt_taille": len(prompt),
- "timestamp": self._get_timestamp()
- },
- rapport_genere)
-
- # Préparer les métadonnées complÚtes pour le rapport
- timestamp = self._get_timestamp()
-
- # Préparer le JSON complet du rapport (format principal)
- rapport_data_complet = {
- "ticket_id": ticket_id,
- "timestamp": timestamp,
- "rapport_genere": rapport_genere,
- "ticket_analyse": ticket_analyse,
- "images_analyses": images_analyses,
- "statistiques": {
- "total_images": total_images,
- "images_pertinentes": images_pertinentes,
- "analyses_generees": len(images_analyses)
- }
- }
-
- # Ajouter les métadonnées pour la traçabilité
- metadata = {
- "timestamp": timestamp,
- "model": getattr(self.llm, "modele", str(type(self.llm))),
- "temperature": self.temperature,
- "top_p": self.top_p,
- "max_tokens": self.max_tokens,
- "agents": agents_info
- }
-
- rapport_data_complet["metadata"] = metadata
-
- # S'assurer que les clés nécessaires pour le markdown sont présentes
- if "ticket_analyse" not in rapport_data_complet:
- rapport_data_complet["ticket_analyse"] = ticket_analyse
-
- # ĂTAPE 1: Sauvegarder le rapport au format JSON (FORMAT PRINCIPAL)
- with open(json_path, "w", encoding="utf-8") as f:
- json.dump(rapport_data_complet, f, ensure_ascii=False, indent=2)
-
- logger.info(f"Rapport JSON (format principal) sauvegardé: {json_path}")
- print(f" Rapport JSON sauvegardé: {json_path}")
-
- # ĂTAPE 2: GĂ©nĂ©rer et sauvegarder le rapport au format Markdown (pour prĂ©sentation)
- markdown_content = self._generer_markdown_depuis_json(rapport_data_complet)
-
- with open(md_path, "w", encoding="utf-8") as f:
- f.write(markdown_content)
-
- logger.info(f"Rapport Markdown (pour présentation) sauvegardé: {md_path}")
- print(f" Rapport Markdown sauvegardé: {md_path}")
-
- logger.info(f"Taille du rapport Markdown: {len(markdown_content)} caractĂšres")
-
- # Retourner les chemins des deux fichiers (JSON en premier, Markdown en second)
- 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)
- print(f" ERREUR: {error_message}")
- return None, None
-
- def _collecter_info_agents(self, rapport_data: Dict) -> Dict:
- """
- Collecte des informations sur les agents utilisés dans l'analyse
-
- Args:
- rapport_data: Données du rapport
-
- Returns:
- Dictionnaire contenant les informations sur les agents
- """
- agents_info = {}
-
- # Informations sur l'agent JSON Analyser
- if "analyse_json" in rapport_data:
- json_analysis = rapport_data["analyse_json"]
- # Vérifier si l'analyse JSON contient des métadonnées
- if isinstance(json_analysis, dict) and "metadata" in json_analysis:
- agents_info["json_analyser"] = json_analysis["metadata"]
-
- # Informations sur les agents d'image
- if "analyse_images" in rapport_data and rapport_data["analyse_images"]:
- # Image Sorter
- sorter_info = {}
- analyser_info = {}
-
- for img_path, img_data in rapport_data["analyse_images"].items():
- # Collecter info du sorter
- if "sorting" in img_data and isinstance(img_data["sorting"], dict) and "metadata" in img_data["sorting"]:
- if "model_info" in img_data["sorting"]["metadata"]:
- sorter_info = img_data["sorting"]["metadata"]["model_info"]
-
- # Collecter info de l'analyser
- if "analysis" in img_data and img_data["analysis"] and isinstance(img_data["analysis"], dict) and "metadata" in img_data["analysis"]:
- if "model_info" in img_data["analysis"]["metadata"]:
- analyser_info = img_data["analysis"]["metadata"]["model_info"]
-
- # Une fois qu'on a trouvé les deux, on peut sortir
- if sorter_info and analyser_info:
- break
-
- if sorter_info:
- agents_info["image_sorter"] = sorter_info
- if analyser_info:
- agents_info["image_analyser"] = analyser_info
-
- # Ajouter les informations de l'agent report generator
- agents_info["report_generator"] = {
- "model": getattr(self.llm, "modele", str(type(self.llm))),
- "temperature": self.temperature,
- "top_p": self.top_p,
- "max_tokens": self.max_tokens
- }
-
- return agents_info
-
- def _generer_markdown_depuis_json(self, rapport_data: Dict) -> str:
- """
- GénÚre un rapport Markdown directement à partir des données JSON
-
- Args:
- rapport_data: Données JSON complÚtes du rapport
- Format attendu:
- - ticket_id: ID du ticket
- - metadata: Métadonnées (timestamp, modÚle, etc.)
- - rapport_genere: Texte du rapport généré par le LLM
- - ticket_analyse: Analyse du ticket
- - images_analyses: Liste des analyses d'images (format privilégié)
- OU
- - analyse_images: Dictionnaire des analyses d'images (format alternatif)
-
- Returns:
- Contenu Markdown du rapport
- """
- ticket_id = rapport_data.get("ticket_id", "")
- timestamp = rapport_data.get("metadata", {}).get("timestamp", self._get_timestamp())
-
- logger.info(f"Génération du rapport Markdown pour le ticket {ticket_id}")
-
- # Contenu de base du rapport (partie générée par le LLM)
- rapport_contenu = rapport_data.get("rapport_genere", "")
-
- # EntĂȘte du document
- markdown = f"# Rapport d'analyse du ticket #{ticket_id}\n\n"
- markdown += f"*Généré le: {timestamp}*\n\n"
-
- # Ajouter le rapport principal généré par le LLM
- markdown += rapport_contenu + "\n\n"
-
- # Section séparatrice pour les détails d'analyse
- markdown += "---\n\n"
- markdown += "# Détails des analyses effectuées\n\n"
-
- # Ajouter un résumé du processus d'analyse complet
- markdown += "## Processus d'analyse\n\n"
-
- # 1. Analyse de ticket
- ticket_analyse = rapport_data.get("ticket_analyse", "")
- if not ticket_analyse and "analyse_json" in rapport_data:
- ticket_analyse = rapport_data.get("analyse_json", "")
-
- if ticket_analyse:
- markdown += "### Ătape 1: Analyse du ticket\n\n"
- markdown += "L'agent d'analyse de ticket a extrait les informations suivantes du ticket d'origine:\n\n"
- markdown += "\nCliquez pour voir l'analyse complĂšte du ticket
\n\n"
- markdown += "```\n" + ticket_analyse + "\n```\n\n"
- markdown += " \n\n"
- logger.info(f"Analyse du ticket ajoutée au rapport Markdown ({len(ticket_analyse)} caractÚres)")
- else:
- logger.warning("Aucune analyse de ticket disponible pour le rapport Markdown")
-
- # 2. Tri des images
- markdown += "### Ătape 2: Tri des images\n\n"
- markdown += "L'agent de tri d'images a évalué chaque image pour déterminer sa pertinence par rapport au problÚme client:\n\n"
-
- # Vérifier quelle structure de données est disponible pour les images
- has_analyse_images = "analyse_images" in rapport_data and rapport_data["analyse_images"]
- has_images_analyses = "images_analyses" in rapport_data and isinstance(rapport_data["images_analyses"], list)
-
- logger.info(f"Structure des données d'images: analyse_images={has_analyse_images}, images_analyses={has_images_analyses}")
-
- analyse_images_data = {}
- if has_analyse_images:
- analyse_images_data = rapport_data["analyse_images"]
-
- if analyse_images_data:
- # Créer un tableau pour le tri des images
- markdown += "| Image | Pertinence | Raison |\n"
- markdown += "|-------|------------|--------|\n"
-
- for image_path, analyse_data in analyse_images_data.items():
- image_name = os.path.basename(image_path)
-
- # Information de tri
- is_relevant = "Non"
- reason = "Non spécifiée"
-
- if "sorting" in analyse_data:
- sorting_data = analyse_data["sorting"]
- if isinstance(sorting_data, dict):
- is_relevant = "Oui" if sorting_data.get("is_relevant", False) else "Non"
- reason = sorting_data.get("reason", "Non spécifiée")
-
- markdown += f"| {image_name} | {is_relevant} | {reason} |\n"
-
- markdown += "\n"
- logger.info(f"Tableau de tri des images ajouté au rapport Markdown ({len(analyse_images_data)} images)")
- else:
- markdown += "*Aucune image n'a été trouvée ou analysée.*\n\n"
- logger.warning("Aucune analyse d'images disponible pour le tableau de tri")
-
- # 3. Analyse des images pertinentes
- markdown += "### Ătape 3: Analyse dĂ©taillĂ©e des images pertinentes\n\n"
-
- # Traiter directement les images_analyses du rapport_data si disponible
- images_pertinentes = 0
-
- # D'abord essayer d'utiliser la liste images_analyses qui est déjà traitée
- if has_images_analyses:
- images_list = rapport_data["images_analyses"]
- for i, img_data in enumerate(images_list, 1):
- images_pertinentes += 1
- image_name = img_data.get("image_name", f"Image {i}")
- analyse_detail = img_data.get("analyse", "Analyse non disponible")
-
- markdown += f"#### Image pertinente {images_pertinentes}: {image_name}\n\n"
- markdown += "\nCliquez pour voir l'analyse complĂšte de l'image
\n\n"
- markdown += "```\n" + analyse_detail + "\n```\n\n"
- markdown += " \n\n"
-
- logger.info(f"Analyse de l'image {image_name} ajoutée au rapport Markdown (from images_analyses)")
- # Sinon, traiter les données brutes d'analyse_images
- elif has_analyse_images:
- analyse_images_data = rapport_data["analyse_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)
-
- if is_relevant:
- images_pertinentes += 1
- image_name = os.path.basename(image_path)
-
- # Récupérer l'analyse détaillée avec gestion des différents formats possibles
- analyse_detail = "Analyse non disponible"
- if "analysis" in analyse_data and analyse_data["analysis"]:
- if isinstance(analyse_data["analysis"], dict):
- if "analyse" in analyse_data["analysis"]:
- analyse_detail = analyse_data["analysis"]["analyse"]
- logger.info(f"Analyse de l'image {image_name} récupérée via analyse_data['analysis']['analyse']")
- elif "error" in analyse_data["analysis"] and not analyse_data["analysis"].get("error", True):
- analyse_detail = str(analyse_data["analysis"])
- logger.info(f"Analyse de l'image {image_name} récupérée via str(analyse_data['analysis'])")
- else:
- analyse_detail = json.dumps(analyse_data["analysis"], ensure_ascii=False, indent=2)
- logger.info(f"Analyse de l'image {image_name} récupérée via json.dumps")
- elif isinstance(analyse_data["analysis"], str):
- analyse_detail = analyse_data["analysis"]
- logger.info(f"Analyse de l'image {image_name} récupérée directement (string)")
- else:
- logger.warning(f"Aucune analyse disponible pour l'image pertinente {image_name}")
-
- markdown += f"#### Image pertinente {images_pertinentes}: {image_name}\n\n"
- markdown += "\nCliquez pour voir l'analyse complĂšte de l'image
\n\n"
- markdown += "```\n" + analyse_detail + "\n```\n\n"
- markdown += " \n\n"
-
- if images_pertinentes == 0:
- markdown += "*Aucune image pertinente n'a été identifiée pour ce ticket.*\n\n"
- logger.warning("Aucune image pertinente identifiée pour l'analyse détaillée")
- else:
- logger.info(f"{images_pertinentes} images pertinentes ajoutées au rapport Markdown")
-
- # 4. SynthĂšse (rapport final)
- markdown += "### Ătape 4: GĂ©nĂ©ration du rapport de synthĂšse\n\n"
- markdown += "L'agent de génération de rapport a synthétisé toutes les analyses précédentes pour produire le rapport ci-dessus.\n\n"
-
- # Informations techniques
- markdown += "## Informations techniques\n\n"
-
- # Statistiques d'analyse
- markdown += "### Statistiques\n\n"
-
- total_images = 0
- if has_analyse_images:
- total_images = len(analyse_images_data)
- elif "statistiques" in rapport_data and "total_images" in rapport_data["statistiques"]:
- total_images = rapport_data["statistiques"]["total_images"]
-
- markdown += f"- **Images analysées**: {total_images}\n"
- markdown += f"- **Images pertinentes**: {images_pertinentes}\n\n"
-
- logger.info(f"Rapport Markdown généré ({len(markdown)} caractÚres)")
- return markdown
-
- def _formater_prompt_pour_rapport(self, ticket_analyse, images_analyses, ticket_id):
- """
- Formate le prompt pour la génération du rapport
-
- Args:
- ticket_analyse: Analyse du ticket
- images_analyses: Liste des analyses d'images, format [{image_name, analyse}, ...]
- ticket_id: ID du ticket
-
- Returns:
- Prompt formaté pour le LLM
- """
- num_images = len(images_analyses)
- logger.info(f"Formatage du prompt avec {num_images} analyses d'images")
-
- # Créer un prompt détaillé en s'assurant que toutes les analyses sont incluses
- prompt = f"""GénÚre un rapport technique complet pour le ticket #{ticket_id}, en te basant sur les analyses suivantes.
-
-## ANALYSE DU TICKET
-{ticket_analyse}
-
-## ANALYSES DES IMAGES ({num_images} images analysées)
-"""
-
- # Ajouter l'analyse de chaque image
- for i, img_analyse in enumerate(images_analyses, 1):
- image_name = img_analyse.get("image_name", f"Image {i}")
- analyse = img_analyse.get("analyse", "Analyse non disponible")
- prompt += f"\n### IMAGE {i}: {image_name}\n{analyse}\n"
- logger.info(f"Ajout de l'analyse de l'image {image_name} au prompt ({len(analyse)} caractĂšres)")
-
- prompt += f"""
-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é et exploitable.
-
-EXIGENCE ABSOLUE - TABLEAU DES ĂCHANGES CLIENT/SUPPORT:
-- Tu DOIS IMPĂRATIVEMENT crĂ©er un TABLEAU MARKDOWN des Ă©changes client/support
-- Le format du tableau DOIT ĂȘtre:
- | Date | Ămetteur (CLIENT/SUPPORT) | Type (Question/RĂ©ponse) | Contenu |
- |------|---------------------------|-------------------------|---------|
- | date1 | CLIENT | Question | contenu... |
- | date2 | SUPPORT | Réponse | contenu... |
-- Chaque message du ticket doit apparaĂźtre dans une ligne du tableau
-- Indique clairement qui est CLIENT et qui est SUPPORT
-- Tu dois synthétiser au mieux les échanges(le plus court et clair possible) client/support(question/réponse) dans le tableau
-- TU dois spécifié si la question n'a pas de réponse
-- Le tableau DOIT ĂȘtre inclus dans la section "Chronologie des Ă©changes"
-
-Structure ton rapport:
-1. Résumé exécutif: SynthÚse du problÚme initial (nom de la demande + description)
-2. Chronologie des échanges: TABLEAU des interactions client/support (format imposé ci-dessus)
-3. Analyse des images: Ce que montrent les captures d'écran et leur pertinence
-4. Diagnostic technique: Interprétation des informations techniques pertinentes
-
-Reste factuel et précis dans ton analyse.
-Le tableau des échanges client/support est l'élément le plus important du rapport."""
-
- logger.info(f"Prompt formaté: {len(prompt)} caractÚres au total")
- return prompt
-
- def _get_timestamp(self) -> str:
- """Retourne un timestamp au format YYYYMMDD_HHMMSS"""
- return datetime.now().strftime("%Y%m%d_%H%M%S")
\ No newline at end of file
diff --git a/agents/agent_report_generator.py.bak b/agents/agent_report_generator.py.bak
deleted file mode 100644
index 6008a01..0000000
--- a/agents/agent_report_generator.py.bak
+++ /dev/null
@@ -1,717 +0,0 @@
-import json
-import os
-from .base_agent import BaseAgent
-from datetime import datetime
-from typing import Dict, Any, Tuple, Optional
-import logging
-import traceback
-import re
-
-logger = logging.getLogger("AgentReportGenerator")
-
-class AgentReportGenerator(BaseAgent):
- """
- Agent pour générer un rapport complet à partir des analyses de ticket et d'images.
-
- Cet agent prend en entrée :
- - L'analyse du ticket
- - Les analyses des images pertinentes
- - Les métadonnées associées
-
- Format de données attendu:
- - JSON est le format principal de données en entrée et en sortie
- - Le rapport Markdown est généré à partir du JSON uniquement pour la présentation
-
- Structure des données d'analyse d'images:
- - Deux structures possibles sont supportées:
- 1. Liste d'objets: rapport_data["images_analyses"] = [{image_name, analyse}, ...]
- 2. Dictionnaire: rapport_data["analyse_images"] = {chemin_image: {sorting: {...}, analysis: {...}}, ...}
-
- Flux de traitement:
- 1. Préparation des données d'entrée
- 2. Génération du rapport avec le LLM
- 3. Sauvegarde au format JSON (format principal)
- 4. Conversion et sauvegarde au format Markdown (pour présentation)
- """
- def __init__(self, llm):
- super().__init__("AgentReportGenerator", llm)
-
- # Configuration locale de l'agent (remplace AgentConfig)
- self.temperature = 0.4 # Génération de rapport factuelle mais bien structurée
- self.top_p = 0.9
- self.max_tokens = 2500
- self.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG-Lab pour la société CBAO.
-Ta mission est de synthétiser les analyses (ticket et images) en un rapport structuré et exploitable.
-
-EXIGENCE ABSOLUE - GĂNĂRATION DE DONNĂES EN FORMAT JSON:
-- Tu DOIS IMPĂRATIVEMENT inclure dans ta rĂ©ponse un objet JSON structurĂ© pour les Ă©changes client/support
-- Le format de chaque Ă©change dans le JSON DOIT ĂȘtre:
- {
- "chronologie_echanges": [
- {
- "date": "date de l'échange",
- "emetteur": "CLIENT ou SUPPORT",
- "type": "Question ou Réponse ou Information technique",
- "contenu": "contenu synthétisé de l'échange"
- },
- ... autres échanges ...
- ]
- }
-- Chaque message du ticket doit apparaĂźtre comme un objet dans la liste
-- Indique clairement qui est CLIENT et qui est SUPPORT dans le champ "emetteur"
-- Si une question n'a pas de réponse, assure-toi de le noter clairement
-- Toute mention de "CBAD" doit ĂȘtre remplacĂ©e par "CBAO" qui est le nom correct de la sociĂ©tĂ©
-- Tu dois synthétiser au mieux les échanges (le plus court et clair possible)
-
-Structure ton rapport:
-1. Résumé exécutif: SynthÚse du problÚme initial (nom de la demande + description)
-2. Chronologie des échanges: Objet JSON avec la structure imposée ci-dessus
-3. Analyse des images: Ce que montrent les captures d'écran et leur pertinence
-4. Diagnostic technique: Interprétation des informations techniques pertinentes
-
-Reste factuel et précis dans ton analyse.
-Les données d'échanges client/support sont l'élément le plus important du rapport.
-Tu DOIS inclure le JSON des échanges dans ta réponse au format:
-```json
-{
- "chronologie_echanges": [
- {"date": "...", "emetteur": "CLIENT", "type": "Question", "contenu": "..."},
- {"date": "...", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "..."}
- ]
-}
-```"""
-
- # 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
- }
-
- # 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, rapport_data: Dict, rapport_dir: str) -> Tuple[Optional[str], Optional[str]]:
- """
- GénÚre un rapport à partir des analyses effectuées
-
- Args:
- rapport_data: Dictionnaire contenant toutes les données analysées
- Doit contenir au moins une des clés:
- - "ticket_analyse" ou "analyse_json": Analyse du ticket
- - "analyse_images": Analyses des images (facultatif)
- rapport_dir: RĂ©pertoire oĂč sauvegarder le rapport
-
- Returns:
- Tuple (chemin vers le rapport JSON, chemin vers le rapport Markdown)
- """
- # Récupérer l'ID du ticket depuis les données
- ticket_id = rapport_data.get("ticket_id", "")
- if not ticket_id and "ticket_data" in rapport_data and isinstance(rapport_data["ticket_data"], dict):
- ticket_id = rapport_data["ticket_data"].get("code", "")
-
- if not ticket_id:
- ticket_id = os.path.basename(os.path.dirname(rapport_dir))
- if not ticket_id.startswith("T"):
- # Dernier recours, utiliser le dernier segment du chemin
- ticket_id = os.path.basename(rapport_dir)
-
- logger.info(f"Génération du rapport pour le ticket: {ticket_id}")
- print(f"AgentReportGenerator: Génération du rapport pour {ticket_id}")
-
- # Validation des données d'entrée
- logger.info("Vérification de la complétude des données d'entrée:")
- if "ticket_data" in rapport_data:
- logger.info(f" - Données de ticket présentes: {len(str(rapport_data['ticket_data']))} caractÚres")
- else:
- logger.warning(" - Données de ticket manquantes")
-
- # Vérification des analyses
- ticket_analyse_exists = False
- if "ticket_analyse" in rapport_data and rapport_data["ticket_analyse"]:
- ticket_analyse_exists = True
- logger.info(f" - Analyse du ticket présente: {len(rapport_data['ticket_analyse'])} caractÚres")
- elif "analyse_json" in rapport_data and rapport_data["analyse_json"]:
- ticket_analyse_exists = True
- logger.info(f" - Analyse JSON présente: {len(rapport_data['analyse_json'])} caractÚres")
- else:
- logger.warning(" - Analyse du ticket manquante")
-
- # Vérification des analyses d'images
- if "analyse_images" in rapport_data and rapport_data["analyse_images"]:
- n_images = len(rapport_data["analyse_images"])
- n_relevant = sum(1 for _, data in rapport_data["analyse_images"].items()
- if "sorting" in data and isinstance(data["sorting"], dict) and data["sorting"].get("is_relevant", False))
- n_analyzed = sum(1 for _, data in rapport_data["analyse_images"].items()
- if "analysis" in data and data["analysis"])
-
- logger.info(f" - Analyses d'images présentes: {n_images} images, {n_relevant} pertinentes, {n_analyzed} analysées")
- else:
- logger.warning(" - Analyses d'images manquantes")
-
- # S'assurer que le répertoire existe
- if not os.path.exists(rapport_dir):
- os.makedirs(rapport_dir)
- logger.info(f"Répertoire de rapport créé: {rapport_dir}")
-
- try:
- # Préparer les données formatées pour l'analyse
- ticket_analyse = None
-
- # Vérifier que l'analyse du ticket est disponible sous l'une des clés possibles
- if "ticket_analyse" in rapport_data and rapport_data["ticket_analyse"]:
- ticket_analyse = rapport_data["ticket_analyse"]
- logger.info("Utilisation de ticket_analyse")
- elif "analyse_json" in rapport_data and rapport_data["analyse_json"]:
- ticket_analyse = rapport_data["analyse_json"]
- logger.info("Utilisation de analyse_json en fallback")
- else:
- # 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")
- ticket_analyse = f"Analyse par défaut du ticket:\nNom: {ticket_name}\nDescription: {ticket_desc}\n(Aucune analyse détaillée n'a été fournie par l'agent d'analyse de ticket)"
-
- # Préparer les données d'analyse d'images
- images_analyses = []
- analyse_images_data = rapport_data.get("analyse_images", {})
-
- # Statistiques pour les métadonnées
- total_images = len(analyse_images_data) if analyse_images_data else 0
- images_pertinentes = 0
-
- # Collecter des informations sur les agents et LLM utilisés
- agents_info = self._collecter_info_agents(rapport_data)
-
- # Transformer les analyses d'images en liste structurée pour le prompt
- for image_path, analyse_data in analyse_images_data.items():
- image_name = os.path.basename(image_path)
-
- # 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)
- if is_relevant:
- images_pertinentes += 1
-
- # Récupérer l'analyse détaillée si elle existe et que l'image est pertinente
- analyse_detail = None
- if is_relevant:
- if "analysis" in analyse_data and analyse_data["analysis"]:
- # Vérifier différentes structures possibles de l'analyse
- if isinstance(analyse_data["analysis"], dict):
- if "analyse" in analyse_data["analysis"]:
- analyse_detail = analyse_data["analysis"]["analyse"]
- elif "error" in analyse_data["analysis"] and not analyse_data["analysis"].get("error", True):
- # Si pas d'erreur et que l'analyse est directement dans le dictionnaire
- analyse_detail = str(analyse_data["analysis"])
- else:
- # Essayer de récupérer directement le contenu du dictionnaire
- analyse_detail = json.dumps(analyse_data["analysis"], ensure_ascii=False, indent=2)
- elif isinstance(analyse_data["analysis"], str):
- # Si l'analyse est directement une chaĂźne
- analyse_detail = analyse_data["analysis"]
-
- # Si l'analyse n'a pas été trouvée mais que l'image est pertinente
- if not analyse_detail:
- analyse_detail = f"Image marquée comme pertinente. Raison: {analyse_data['sorting'].get('reason', 'Non spécifiée')}"
-
- # Analyse détaillée
- if analyse_detail:
- images_analyses.append({
- "image_name": image_name,
- "analyse": analyse_detail
- })
- logger.info(f"Analyse de l'image {image_name} ajoutée au rapport (longueur: {len(str(analyse_detail))} caractÚres)")
- else:
- logger.warning(f"Analyse non trouvée pour l'image pertinente {image_name}")
- else:
- logger.info(f"Image {image_name} ignorée car non pertinente")
-
- # Créer le chemin du fichier de rapport JSON (sortie principale)
- json_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.json")
- md_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.md")
-
- # Formater les données pour le LLM
- prompt = self._formater_prompt_pour_rapport(ticket_analyse, images_analyses, ticket_id)
-
- # Générer le rapport avec le LLM
- logger.info("Génération du rapport avec le LLM")
- print(f" Génération du rapport avec le LLM...")
-
- # Interroger le LLM
- rapport_genere = self.llm.interroger(prompt)
- logger.info(f"Rapport généré: {len(rapport_genere)} caractÚres")
- print(f" Rapport généré: {len(rapport_genere)} caractÚres")
-
- # Traiter le JSON pour extraire la chronologie des échanges et le convertir en tableau Markdown
- rapport_traite, echanges_json, echanges_markdown = self._extraire_et_traiter_json(rapport_genere)
- if echanges_json and echanges_markdown:
- logger.info(f"Ăchanges JSON convertis en tableau Markdown: {len(str(echanges_markdown))} caractĂšres")
- print(f" Ăchanges client/support convertis du JSON vers le format Markdown")
- # Utiliser le rapport traité avec le tableau Markdown à la place du JSON
- rapport_genere = rapport_traite
- else:
- logger.warning("Aucun JSON d'échanges trouvé dans le rapport, conservation du format original")
-
- # Tracer l'historique avec le prompt pour la transparence
- self.ajouter_historique("generation_rapport",
- {
- "ticket_id": ticket_id,
- "prompt_taille": len(prompt),
- "timestamp": self._get_timestamp()
- },
- rapport_genere)
-
- # Préparer les métadonnées complÚtes pour le rapport
- timestamp = self._get_timestamp()
-
- # Préparer le JSON complet du rapport (format principal)
- rapport_data_complet = {
- "ticket_id": ticket_id,
- "timestamp": timestamp,
- "rapport_genere": rapport_genere,
- "ticket_analyse": ticket_analyse,
- "images_analyses": images_analyses,
- "statistiques": {
- "total_images": total_images,
- "images_pertinentes": images_pertinentes,
- "analyses_generees": len(images_analyses)
- }
- }
-
- # Ajouter les métadonnées pour la traçabilité
- metadata = {
- "timestamp": timestamp,
- "model": getattr(self.llm, "modele", str(type(self.llm))),
- "temperature": self.temperature,
- "top_p": self.top_p,
- "max_tokens": self.max_tokens,
- "agents": agents_info
- }
-
- rapport_data_complet["metadata"] = metadata
-
- # S'assurer que les clés nécessaires pour le markdown sont présentes
- if "ticket_analyse" not in rapport_data_complet:
- rapport_data_complet["ticket_analyse"] = ticket_analyse
-
- # ĂTAPE 1: Sauvegarder le rapport au format JSON (FORMAT PRINCIPAL)
- with open(json_path, "w", encoding="utf-8") as f:
- json.dump(rapport_data_complet, f, ensure_ascii=False, indent=2)
-
- logger.info(f"Rapport JSON (format principal) sauvegardé: {json_path}")
- print(f" Rapport JSON sauvegardé: {json_path}")
-
- # ĂTAPE 2: GĂ©nĂ©rer et sauvegarder le rapport au format Markdown (pour prĂ©sentation)
- markdown_content = self._generer_markdown_depuis_json(rapport_data_complet)
-
- with open(md_path, "w", encoding="utf-8") as f:
- f.write(markdown_content)
-
- logger.info(f"Rapport Markdown (pour présentation) sauvegardé: {md_path}")
- print(f" Rapport Markdown sauvegardé: {md_path}")
-
- logger.info(f"Taille du rapport Markdown: {len(markdown_content)} caractĂšres")
-
- # Retourner les chemins des deux fichiers (JSON en premier, Markdown en second)
- 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)
- print(f" ERREUR: {error_message}")
- return None, None
-
- def _collecter_info_agents(self, rapport_data: Dict) -> Dict:
- """
- Collecte des informations sur les agents utilisés dans l'analyse
-
- Args:
- rapport_data: Données du rapport
-
- Returns:
- Dictionnaire contenant les informations sur les agents
- """
- agents_info = {}
-
- # Informations sur l'agent JSON Analyser
- if "analyse_json" in rapport_data:
- json_analysis = rapport_data["analyse_json"]
- # Vérifier si l'analyse JSON contient des métadonnées
- if isinstance(json_analysis, dict) and "metadata" in json_analysis:
- agents_info["json_analyser"] = json_analysis["metadata"]
-
- # Informations sur les agents d'image
- if "analyse_images" in rapport_data and rapport_data["analyse_images"]:
- # Image Sorter
- sorter_info = {}
- analyser_info = {}
-
- for img_path, img_data in rapport_data["analyse_images"].items():
- # Collecter info du sorter
- if "sorting" in img_data and isinstance(img_data["sorting"], dict) and "metadata" in img_data["sorting"]:
- if "model_info" in img_data["sorting"]["metadata"]:
- sorter_info = img_data["sorting"]["metadata"]["model_info"]
-
- # Collecter info de l'analyser
- if "analysis" in img_data and img_data["analysis"] and isinstance(img_data["analysis"], dict) and "metadata" in img_data["analysis"]:
- if "model_info" in img_data["analysis"]["metadata"]:
- analyser_info = img_data["analysis"]["metadata"]["model_info"]
-
- # Une fois qu'on a trouvé les deux, on peut sortir
- if sorter_info and analyser_info:
- break
-
- if sorter_info:
- agents_info["image_sorter"] = sorter_info
- if analyser_info:
- agents_info["image_analyser"] = analyser_info
-
- # Ajouter les informations de l'agent report generator
- agents_info["report_generator"] = {
- "model": getattr(self.llm, "modele", str(type(self.llm))),
- "temperature": self.temperature,
- "top_p": self.top_p,
- "max_tokens": self.max_tokens
- }
-
- return agents_info
-
- def _generer_markdown_depuis_json(self, rapport_data: Dict) -> str:
- """
- GénÚre un rapport Markdown directement à partir des données JSON
-
- Args:
- rapport_data: Données JSON complÚtes du rapport
- Format attendu:
- - ticket_id: ID du ticket
- - metadata: Métadonnées (timestamp, modÚle, etc.)
- - rapport_genere: Texte du rapport généré par le LLM
- - ticket_analyse: Analyse du ticket
- - images_analyses: Liste des analyses d'images (format privilégié)
- OU
- - analyse_images: Dictionnaire des analyses d'images (format alternatif)
-
- Returns:
- Contenu Markdown du rapport
- """
- ticket_id = rapport_data.get("ticket_id", "")
- timestamp = rapport_data.get("metadata", {}).get("timestamp", self._get_timestamp())
-
- logger.info(f"Génération du rapport Markdown pour le ticket {ticket_id}")
-
- # Contenu de base du rapport (partie générée par le LLM)
- rapport_contenu = rapport_data.get("rapport_genere", "")
-
- # EntĂȘte du document
- markdown = f"# Rapport d'analyse du ticket #{ticket_id}\n\n"
- markdown += f"*Généré le: {timestamp}*\n\n"
-
- # Ajouter le rapport principal généré par le LLM
- markdown += rapport_contenu + "\n\n"
-
- # Section séparatrice pour les détails d'analyse
- markdown += "---\n\n"
- markdown += "# Détails des analyses effectuées\n\n"
-
- # Ajouter un résumé du processus d'analyse complet
- markdown += "## Processus d'analyse\n\n"
-
- # 1. Analyse de ticket
- ticket_analyse = rapport_data.get("ticket_analyse", "")
- if not ticket_analyse and "analyse_json" in rapport_data:
- ticket_analyse = rapport_data.get("analyse_json", "")
-
- if ticket_analyse:
- markdown += "### Ătape 1: Analyse du ticket\n\n"
- markdown += "L'agent d'analyse de ticket a extrait les informations suivantes du ticket d'origine:\n\n"
- markdown += "\nCliquez pour voir l'analyse complĂšte du ticket
\n\n"
- markdown += "```\n" + ticket_analyse + "\n```\n\n"
- markdown += " \n\n"
- logger.info(f"Analyse du ticket ajoutée au rapport Markdown ({len(str(ticket_analyse))} caractÚres)")
- else:
- logger.warning("Aucune analyse de ticket disponible pour le rapport Markdown")
-
- # 2. Tri des images
- markdown += "### Ătape 2: Tri des images\n\n"
- markdown += "L'agent de tri d'images a évalué chaque image pour déterminer sa pertinence par rapport au problÚme client:\n\n"
-
- # Vérifier quelle structure de données est disponible pour les images
- has_analyse_images = "analyse_images" in rapport_data and rapport_data["analyse_images"]
- has_images_analyses = "images_analyses" in rapport_data and isinstance(rapport_data["images_analyses"], list) and rapport_data["images_analyses"]
-
- logger.info(f"Structure des données d'images: analyse_images={has_analyse_images}, images_analyses={has_images_analyses}")
-
- analyse_images_data = {}
- if has_analyse_images:
- analyse_images_data = rapport_data["analyse_images"]
-
- if analyse_images_data:
- # Créer un tableau pour le tri des images
- markdown += "| Image | Pertinence | Raison |\n"
- markdown += "|-------|------------|--------|\n"
-
- for image_path, analyse_data in analyse_images_data.items():
- image_name = os.path.basename(image_path)
-
- # Information de tri
- is_relevant = "Non"
- reason = "Non spécifiée"
-
- if "sorting" in analyse_data:
- sorting_data = analyse_data["sorting"]
- if isinstance(sorting_data, dict):
- is_relevant = "Oui" if sorting_data.get("is_relevant", False) else "Non"
- reason = sorting_data.get("reason", "Non spécifiée")
-
- markdown += f"| {image_name} | {is_relevant} | {reason} |\n"
-
- markdown += "\n"
- logger.info(f"Tableau de tri des images ajouté au rapport Markdown ({len(analyse_images_data)} images)")
- elif has_images_analyses and rapport_data["images_analyses"]:
- # Si nous avons les analyses d'images mais pas les données de tri, créer un tableau simplifié
- markdown += "| Image | Pertinence |\n"
- markdown += "|-------|------------|\n"
-
- for img_data in rapport_data["images_analyses"]:
- image_name = img_data.get("image_name", "Image inconnue")
- markdown += f"| {image_name} | Oui |\n"
-
- markdown += "\n"
- logger.info(f"Tableau de tri simplifié ajouté au rapport Markdown ({len(rapport_data['images_analyses'])} images)")
- else:
- markdown += "*Aucune image n'a été trouvée ou analysée.*\n\n"
- logger.warning("Aucune analyse d'images disponible pour le tableau de tri")
-
- # 3. Analyse des images pertinentes
- markdown += "### Ătape 3: Analyse dĂ©taillĂ©e des images pertinentes\n\n"
-
- # Traiter directement les images_analyses du rapport_data si disponible
- images_pertinentes = 0
-
- # D'abord essayer d'utiliser la liste images_analyses qui est déjà traitée
- if has_images_analyses:
- images_list = rapport_data["images_analyses"]
- for i, img_data in enumerate(images_list, 1):
- images_pertinentes += 1
- image_name = img_data.get("image_name", f"Image {i}")
- analyse_detail = img_data.get("analyse", "Analyse non disponible")
-
- markdown += f"#### Image pertinente {images_pertinentes}: {image_name}\n\n"
- markdown += "\nCliquez pour voir l'analyse complĂšte de l'image
\n\n"
- markdown += "```\n" + analyse_detail + "\n```\n\n"
- markdown += " \n\n"
-
- logger.info(f"Analyse de l'image {image_name} ajoutée au rapport Markdown (from images_analyses)")
- # Sinon, traiter les données brutes d'analyse_images
- elif has_analyse_images:
- analyse_images_data = rapport_data["analyse_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)
-
- if is_relevant:
- images_pertinentes += 1
- image_name = os.path.basename(image_path)
-
- # Récupérer l'analyse détaillée avec gestion des différents formats possibles
- analyse_detail = "Analyse non disponible"
- if "analysis" in analyse_data and analyse_data["analysis"]:
- if isinstance(analyse_data["analysis"], dict):
- if "analyse" in analyse_data["analysis"]:
- analyse_detail = analyse_data["analysis"]["analyse"]
- logger.info(f"Analyse de l'image {image_name} récupérée via analyse_data['analysis']['analyse']")
- elif "error" in analyse_data["analysis"] and not analyse_data["analysis"].get("error", True):
- analyse_detail = str(analyse_data["analysis"])
- logger.info(f"Analyse de l'image {image_name} récupérée via str(analyse_data['analysis'])")
- else:
- analyse_detail = json.dumps(analyse_data["analysis"], ensure_ascii=False, indent=2)
- logger.info(f"Analyse de l'image {image_name} récupérée via json.dumps")
- elif isinstance(analyse_data["analysis"], str):
- analyse_detail = analyse_data["analysis"]
- logger.info(f"Analyse de l'image {image_name} récupérée directement (string)")
- else:
- logger.warning(f"Aucune analyse disponible pour l'image pertinente {image_name}")
-
- markdown += f"#### Image pertinente {images_pertinentes}: {image_name}\n\n"
- markdown += "\nCliquez pour voir l'analyse complĂšte de l'image
\n\n"
- markdown += "```\n" + analyse_detail + "\n```\n\n"
- markdown += " \n\n"
-
- if images_pertinentes == 0:
- markdown += "*Aucune image pertinente n'a été identifiée pour ce ticket.*\n\n"
- logger.warning("Aucune image pertinente identifiée pour l'analyse détaillée")
- else:
- logger.info(f"{images_pertinentes} images pertinentes ajoutées au rapport Markdown")
-
- # 4. SynthĂšse (rapport final)
- markdown += "### Ătape 4: GĂ©nĂ©ration du rapport de synthĂšse\n\n"
- markdown += "L'agent de génération de rapport a synthétisé toutes les analyses précédentes pour produire le rapport ci-dessus.\n\n"
-
- # Informations techniques
- markdown += "## Informations techniques\n\n"
-
- # Statistiques d'analyse
- markdown += "### Statistiques\n\n"
-
- total_images = 0
- if has_analyse_images:
- total_images = len(analyse_images_data)
- elif "statistiques" in rapport_data and "total_images" in rapport_data["statistiques"]:
- total_images = rapport_data["statistiques"]["total_images"]
-
- markdown += f"- **Images analysées**: {total_images}\n"
- markdown += f"- **Images pertinentes**: {images_pertinentes}\n\n"
-
- logger.info(f"Rapport Markdown généré ({len(markdown)} caractÚres)")
- return markdown
-
- def _formater_prompt_pour_rapport(self, ticket_analyse, images_analyses, ticket_id):
- """
- Formate le prompt pour la génération du rapport
-
- Args:
- ticket_analyse: Analyse du ticket
- images_analyses: Liste des analyses d'images, format [{image_name, analyse}, ...]
- ticket_id: ID du ticket
-
- Returns:
- Prompt formaté pour le LLM
- """
- num_images = len(images_analyses)
- logger.info(f"Formatage du prompt avec {num_images} analyses d'images")
-
- # Inclure une vérification des données reçues
- prompt = f"""GénÚre un rapport technique complet pour le ticket #{ticket_id}, en te basant sur les analyses suivantes.
-
-## VĂRIFICATION DES DONNĂES REĂUES
-Je vais d'abord vérifier que j'ai bien reçu les données d'analyses:
-- Analyse du ticket : {"PRĂSENTE" if ticket_analyse else "MANQUANTE"}
-- Analyses d'images : {"PRĂSENTES (" + str(num_images) + " images)" if num_images > 0 else "MANQUANTES"}
-
-## ANALYSE DU TICKET
-{ticket_analyse}
-
-## ANALYSES DES IMAGES ({num_images} images analysées)
-"""
-
- # Ajouter l'analyse de chaque image
- for i, img_analyse in enumerate(images_analyses, 1):
- image_name = img_analyse.get("image_name", f"Image {i}")
- analyse = img_analyse.get("analyse", "Analyse non disponible")
- prompt += f"\n### IMAGE {i}: {image_name}\n{analyse}\n"
- logger.info(f"Ajout de l'analyse de l'image {image_name} au prompt ({len(str(analyse))} caractĂšres)")
-
- # Ne pas répéter les instructions déjà présentes dans le system_prompt
- prompt += f"""
-N'oublie pas d'inclure un objet JSON structuré des échanges client/support selon le format demandé.
-Assure-toi que le JSON est valide et clairement balisé avec ```json et ``` dans ta réponse.
-"""
-
- logger.info(f"Prompt formaté: {len(prompt)} caractÚres au total")
- return prompt
-
- def _extraire_et_traiter_json(self, texte_rapport):
- """
- Extrait l'objet JSON des échanges du texte du rapport et le convertit en Markdown
-
- Args:
- texte_rapport: Texte complet du rapport généré par le LLM
-
- Returns:
- Tuple (rapport_traité, echanges_json, echanges_markdown)
- """
- # Remplacer CBAD par CBAO dans tout le rapport
- texte_rapport = texte_rapport.replace("CBAD", "CBAO")
-
- # Rechercher un objet JSON dans le texte
- json_match = re.search(r'```json\s*({.*?})\s*```', texte_rapport, re.DOTALL)
-
- if not json_match:
- logger.warning("Aucun JSON trouvé dans le rapport")
- return texte_rapport, None, None
-
- # Extraire le JSON et le parser
- json_text = json_match.group(1)
- try:
- echanges_json = json.loads(json_text)
- logger.info(f"JSON extrait avec succĂšs: {len(json_text)} caractĂšres")
-
- # Convertir en tableau Markdown
- echanges_markdown = "| Date | Ămetteur | Type | Contenu | Statut |\n"
- echanges_markdown += "|------|---------|------|---------|--------|\n"
-
- if "chronologie_echanges" in echanges_json and isinstance(echanges_json["chronologie_echanges"], list):
- # Pré-traitement pour vérifier les questions sans réponse
- questions_sans_reponse = {}
- for i, echange in enumerate(echanges_json["chronologie_echanges"]):
- if echange.get("type", "").lower() == "question" and echange.get("emetteur", "").lower() == "client":
- has_response = False
- # Vérifier si la question a une réponse
- for j in range(i+1, len(echanges_json["chronologie_echanges"])):
- next_echange = echanges_json["chronologie_echanges"][j]
- if next_echange.get("type", "").lower() == "réponse" and next_echange.get("emetteur", "").lower() == "support":
- has_response = True
- break
- questions_sans_reponse[i] = not has_response
-
- # Générer le tableau
- for i, echange in enumerate(echanges_json["chronologie_echanges"]):
- date = echange.get("date", "-")
- emetteur = echange.get("emetteur", "-")
- type_msg = echange.get("type", "-")
- contenu = echange.get("contenu", "-")
-
- # Ajouter un statut pour les questions sans réponse
- statut = ""
- if emetteur.lower() == "client" and type_msg.lower() == "question" and questions_sans_reponse.get(i, False):
- statut = "**Sans réponse**"
-
- echanges_markdown += f"| {date} | {emetteur} | {type_msg} | {contenu} | {statut} |\n"
-
- # Ajouter une note si aucune réponse du support n'a été trouvée
- if not any(echange.get("emetteur", "").lower() == "support" for echange in echanges_json["chronologie_echanges"]):
- echanges_markdown += "\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n\n"
-
- # Remplacer le JSON dans le texte par le tableau Markdown
- rapport_traite = texte_rapport.replace(json_match.group(0), echanges_markdown)
-
- return rapport_traite, echanges_json, echanges_markdown
-
- except json.JSONDecodeError as e:
- logger.error(f"Erreur lors du décodage JSON: {e}")
- return texte_rapport, None, None
-
- def _get_timestamp(self) -> str:
- """Retourne un timestamp au format YYYYMMDD_HHMMSS"""
- return datetime.now().strftime("%Y%m%d_%H%M%S")
\ No newline at end of file
diff --git a/agents/agent_report_generator.py.bak2 b/agents/agent_report_generator.py.bak2
deleted file mode 100644
index 6008a01..0000000
--- a/agents/agent_report_generator.py.bak2
+++ /dev/null
@@ -1,717 +0,0 @@
-import json
-import os
-from .base_agent import BaseAgent
-from datetime import datetime
-from typing import Dict, Any, Tuple, Optional
-import logging
-import traceback
-import re
-
-logger = logging.getLogger("AgentReportGenerator")
-
-class AgentReportGenerator(BaseAgent):
- """
- Agent pour générer un rapport complet à partir des analyses de ticket et d'images.
-
- Cet agent prend en entrée :
- - L'analyse du ticket
- - Les analyses des images pertinentes
- - Les métadonnées associées
-
- Format de données attendu:
- - JSON est le format principal de données en entrée et en sortie
- - Le rapport Markdown est généré à partir du JSON uniquement pour la présentation
-
- Structure des données d'analyse d'images:
- - Deux structures possibles sont supportées:
- 1. Liste d'objets: rapport_data["images_analyses"] = [{image_name, analyse}, ...]
- 2. Dictionnaire: rapport_data["analyse_images"] = {chemin_image: {sorting: {...}, analysis: {...}}, ...}
-
- Flux de traitement:
- 1. Préparation des données d'entrée
- 2. Génération du rapport avec le LLM
- 3. Sauvegarde au format JSON (format principal)
- 4. Conversion et sauvegarde au format Markdown (pour présentation)
- """
- def __init__(self, llm):
- super().__init__("AgentReportGenerator", llm)
-
- # Configuration locale de l'agent (remplace AgentConfig)
- self.temperature = 0.4 # Génération de rapport factuelle mais bien structurée
- self.top_p = 0.9
- self.max_tokens = 2500
- self.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG-Lab pour la société CBAO.
-Ta mission est de synthétiser les analyses (ticket et images) en un rapport structuré et exploitable.
-
-EXIGENCE ABSOLUE - GĂNĂRATION DE DONNĂES EN FORMAT JSON:
-- Tu DOIS IMPĂRATIVEMENT inclure dans ta rĂ©ponse un objet JSON structurĂ© pour les Ă©changes client/support
-- Le format de chaque Ă©change dans le JSON DOIT ĂȘtre:
- {
- "chronologie_echanges": [
- {
- "date": "date de l'échange",
- "emetteur": "CLIENT ou SUPPORT",
- "type": "Question ou Réponse ou Information technique",
- "contenu": "contenu synthétisé de l'échange"
- },
- ... autres échanges ...
- ]
- }
-- Chaque message du ticket doit apparaĂźtre comme un objet dans la liste
-- Indique clairement qui est CLIENT et qui est SUPPORT dans le champ "emetteur"
-- Si une question n'a pas de réponse, assure-toi de le noter clairement
-- Toute mention de "CBAD" doit ĂȘtre remplacĂ©e par "CBAO" qui est le nom correct de la sociĂ©tĂ©
-- Tu dois synthétiser au mieux les échanges (le plus court et clair possible)
-
-Structure ton rapport:
-1. Résumé exécutif: SynthÚse du problÚme initial (nom de la demande + description)
-2. Chronologie des échanges: Objet JSON avec la structure imposée ci-dessus
-3. Analyse des images: Ce que montrent les captures d'écran et leur pertinence
-4. Diagnostic technique: Interprétation des informations techniques pertinentes
-
-Reste factuel et précis dans ton analyse.
-Les données d'échanges client/support sont l'élément le plus important du rapport.
-Tu DOIS inclure le JSON des échanges dans ta réponse au format:
-```json
-{
- "chronologie_echanges": [
- {"date": "...", "emetteur": "CLIENT", "type": "Question", "contenu": "..."},
- {"date": "...", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "..."}
- ]
-}
-```"""
-
- # 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
- }
-
- # 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, rapport_data: Dict, rapport_dir: str) -> Tuple[Optional[str], Optional[str]]:
- """
- GénÚre un rapport à partir des analyses effectuées
-
- Args:
- rapport_data: Dictionnaire contenant toutes les données analysées
- Doit contenir au moins une des clés:
- - "ticket_analyse" ou "analyse_json": Analyse du ticket
- - "analyse_images": Analyses des images (facultatif)
- rapport_dir: RĂ©pertoire oĂč sauvegarder le rapport
-
- Returns:
- Tuple (chemin vers le rapport JSON, chemin vers le rapport Markdown)
- """
- # Récupérer l'ID du ticket depuis les données
- ticket_id = rapport_data.get("ticket_id", "")
- if not ticket_id and "ticket_data" in rapport_data and isinstance(rapport_data["ticket_data"], dict):
- ticket_id = rapport_data["ticket_data"].get("code", "")
-
- if not ticket_id:
- ticket_id = os.path.basename(os.path.dirname(rapport_dir))
- if not ticket_id.startswith("T"):
- # Dernier recours, utiliser le dernier segment du chemin
- ticket_id = os.path.basename(rapport_dir)
-
- logger.info(f"Génération du rapport pour le ticket: {ticket_id}")
- print(f"AgentReportGenerator: Génération du rapport pour {ticket_id}")
-
- # Validation des données d'entrée
- logger.info("Vérification de la complétude des données d'entrée:")
- if "ticket_data" in rapport_data:
- logger.info(f" - Données de ticket présentes: {len(str(rapport_data['ticket_data']))} caractÚres")
- else:
- logger.warning(" - Données de ticket manquantes")
-
- # Vérification des analyses
- ticket_analyse_exists = False
- if "ticket_analyse" in rapport_data and rapport_data["ticket_analyse"]:
- ticket_analyse_exists = True
- logger.info(f" - Analyse du ticket présente: {len(rapport_data['ticket_analyse'])} caractÚres")
- elif "analyse_json" in rapport_data and rapport_data["analyse_json"]:
- ticket_analyse_exists = True
- logger.info(f" - Analyse JSON présente: {len(rapport_data['analyse_json'])} caractÚres")
- else:
- logger.warning(" - Analyse du ticket manquante")
-
- # Vérification des analyses d'images
- if "analyse_images" in rapport_data and rapport_data["analyse_images"]:
- n_images = len(rapport_data["analyse_images"])
- n_relevant = sum(1 for _, data in rapport_data["analyse_images"].items()
- if "sorting" in data and isinstance(data["sorting"], dict) and data["sorting"].get("is_relevant", False))
- n_analyzed = sum(1 for _, data in rapport_data["analyse_images"].items()
- if "analysis" in data and data["analysis"])
-
- logger.info(f" - Analyses d'images présentes: {n_images} images, {n_relevant} pertinentes, {n_analyzed} analysées")
- else:
- logger.warning(" - Analyses d'images manquantes")
-
- # S'assurer que le répertoire existe
- if not os.path.exists(rapport_dir):
- os.makedirs(rapport_dir)
- logger.info(f"Répertoire de rapport créé: {rapport_dir}")
-
- try:
- # Préparer les données formatées pour l'analyse
- ticket_analyse = None
-
- # Vérifier que l'analyse du ticket est disponible sous l'une des clés possibles
- if "ticket_analyse" in rapport_data and rapport_data["ticket_analyse"]:
- ticket_analyse = rapport_data["ticket_analyse"]
- logger.info("Utilisation de ticket_analyse")
- elif "analyse_json" in rapport_data and rapport_data["analyse_json"]:
- ticket_analyse = rapport_data["analyse_json"]
- logger.info("Utilisation de analyse_json en fallback")
- else:
- # 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")
- ticket_analyse = f"Analyse par défaut du ticket:\nNom: {ticket_name}\nDescription: {ticket_desc}\n(Aucune analyse détaillée n'a été fournie par l'agent d'analyse de ticket)"
-
- # Préparer les données d'analyse d'images
- images_analyses = []
- analyse_images_data = rapport_data.get("analyse_images", {})
-
- # Statistiques pour les métadonnées
- total_images = len(analyse_images_data) if analyse_images_data else 0
- images_pertinentes = 0
-
- # Collecter des informations sur les agents et LLM utilisés
- agents_info = self._collecter_info_agents(rapport_data)
-
- # Transformer les analyses d'images en liste structurée pour le prompt
- for image_path, analyse_data in analyse_images_data.items():
- image_name = os.path.basename(image_path)
-
- # 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)
- if is_relevant:
- images_pertinentes += 1
-
- # Récupérer l'analyse détaillée si elle existe et que l'image est pertinente
- analyse_detail = None
- if is_relevant:
- if "analysis" in analyse_data and analyse_data["analysis"]:
- # Vérifier différentes structures possibles de l'analyse
- if isinstance(analyse_data["analysis"], dict):
- if "analyse" in analyse_data["analysis"]:
- analyse_detail = analyse_data["analysis"]["analyse"]
- elif "error" in analyse_data["analysis"] and not analyse_data["analysis"].get("error", True):
- # Si pas d'erreur et que l'analyse est directement dans le dictionnaire
- analyse_detail = str(analyse_data["analysis"])
- else:
- # Essayer de récupérer directement le contenu du dictionnaire
- analyse_detail = json.dumps(analyse_data["analysis"], ensure_ascii=False, indent=2)
- elif isinstance(analyse_data["analysis"], str):
- # Si l'analyse est directement une chaĂźne
- analyse_detail = analyse_data["analysis"]
-
- # Si l'analyse n'a pas été trouvée mais que l'image est pertinente
- if not analyse_detail:
- analyse_detail = f"Image marquée comme pertinente. Raison: {analyse_data['sorting'].get('reason', 'Non spécifiée')}"
-
- # Analyse détaillée
- if analyse_detail:
- images_analyses.append({
- "image_name": image_name,
- "analyse": analyse_detail
- })
- logger.info(f"Analyse de l'image {image_name} ajoutée au rapport (longueur: {len(str(analyse_detail))} caractÚres)")
- else:
- logger.warning(f"Analyse non trouvée pour l'image pertinente {image_name}")
- else:
- logger.info(f"Image {image_name} ignorée car non pertinente")
-
- # Créer le chemin du fichier de rapport JSON (sortie principale)
- json_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.json")
- md_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.md")
-
- # Formater les données pour le LLM
- prompt = self._formater_prompt_pour_rapport(ticket_analyse, images_analyses, ticket_id)
-
- # Générer le rapport avec le LLM
- logger.info("Génération du rapport avec le LLM")
- print(f" Génération du rapport avec le LLM...")
-
- # Interroger le LLM
- rapport_genere = self.llm.interroger(prompt)
- logger.info(f"Rapport généré: {len(rapport_genere)} caractÚres")
- print(f" Rapport généré: {len(rapport_genere)} caractÚres")
-
- # Traiter le JSON pour extraire la chronologie des échanges et le convertir en tableau Markdown
- rapport_traite, echanges_json, echanges_markdown = self._extraire_et_traiter_json(rapport_genere)
- if echanges_json and echanges_markdown:
- logger.info(f"Ăchanges JSON convertis en tableau Markdown: {len(str(echanges_markdown))} caractĂšres")
- print(f" Ăchanges client/support convertis du JSON vers le format Markdown")
- # Utiliser le rapport traité avec le tableau Markdown à la place du JSON
- rapport_genere = rapport_traite
- else:
- logger.warning("Aucun JSON d'échanges trouvé dans le rapport, conservation du format original")
-
- # Tracer l'historique avec le prompt pour la transparence
- self.ajouter_historique("generation_rapport",
- {
- "ticket_id": ticket_id,
- "prompt_taille": len(prompt),
- "timestamp": self._get_timestamp()
- },
- rapport_genere)
-
- # Préparer les métadonnées complÚtes pour le rapport
- timestamp = self._get_timestamp()
-
- # Préparer le JSON complet du rapport (format principal)
- rapport_data_complet = {
- "ticket_id": ticket_id,
- "timestamp": timestamp,
- "rapport_genere": rapport_genere,
- "ticket_analyse": ticket_analyse,
- "images_analyses": images_analyses,
- "statistiques": {
- "total_images": total_images,
- "images_pertinentes": images_pertinentes,
- "analyses_generees": len(images_analyses)
- }
- }
-
- # Ajouter les métadonnées pour la traçabilité
- metadata = {
- "timestamp": timestamp,
- "model": getattr(self.llm, "modele", str(type(self.llm))),
- "temperature": self.temperature,
- "top_p": self.top_p,
- "max_tokens": self.max_tokens,
- "agents": agents_info
- }
-
- rapport_data_complet["metadata"] = metadata
-
- # S'assurer que les clés nécessaires pour le markdown sont présentes
- if "ticket_analyse" not in rapport_data_complet:
- rapport_data_complet["ticket_analyse"] = ticket_analyse
-
- # ĂTAPE 1: Sauvegarder le rapport au format JSON (FORMAT PRINCIPAL)
- with open(json_path, "w", encoding="utf-8") as f:
- json.dump(rapport_data_complet, f, ensure_ascii=False, indent=2)
-
- logger.info(f"Rapport JSON (format principal) sauvegardé: {json_path}")
- print(f" Rapport JSON sauvegardé: {json_path}")
-
- # ĂTAPE 2: GĂ©nĂ©rer et sauvegarder le rapport au format Markdown (pour prĂ©sentation)
- markdown_content = self._generer_markdown_depuis_json(rapport_data_complet)
-
- with open(md_path, "w", encoding="utf-8") as f:
- f.write(markdown_content)
-
- logger.info(f"Rapport Markdown (pour présentation) sauvegardé: {md_path}")
- print(f" Rapport Markdown sauvegardé: {md_path}")
-
- logger.info(f"Taille du rapport Markdown: {len(markdown_content)} caractĂšres")
-
- # Retourner les chemins des deux fichiers (JSON en premier, Markdown en second)
- 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)
- print(f" ERREUR: {error_message}")
- return None, None
-
- def _collecter_info_agents(self, rapport_data: Dict) -> Dict:
- """
- Collecte des informations sur les agents utilisés dans l'analyse
-
- Args:
- rapport_data: Données du rapport
-
- Returns:
- Dictionnaire contenant les informations sur les agents
- """
- agents_info = {}
-
- # Informations sur l'agent JSON Analyser
- if "analyse_json" in rapport_data:
- json_analysis = rapport_data["analyse_json"]
- # Vérifier si l'analyse JSON contient des métadonnées
- if isinstance(json_analysis, dict) and "metadata" in json_analysis:
- agents_info["json_analyser"] = json_analysis["metadata"]
-
- # Informations sur les agents d'image
- if "analyse_images" in rapport_data and rapport_data["analyse_images"]:
- # Image Sorter
- sorter_info = {}
- analyser_info = {}
-
- for img_path, img_data in rapport_data["analyse_images"].items():
- # Collecter info du sorter
- if "sorting" in img_data and isinstance(img_data["sorting"], dict) and "metadata" in img_data["sorting"]:
- if "model_info" in img_data["sorting"]["metadata"]:
- sorter_info = img_data["sorting"]["metadata"]["model_info"]
-
- # Collecter info de l'analyser
- if "analysis" in img_data and img_data["analysis"] and isinstance(img_data["analysis"], dict) and "metadata" in img_data["analysis"]:
- if "model_info" in img_data["analysis"]["metadata"]:
- analyser_info = img_data["analysis"]["metadata"]["model_info"]
-
- # Une fois qu'on a trouvé les deux, on peut sortir
- if sorter_info and analyser_info:
- break
-
- if sorter_info:
- agents_info["image_sorter"] = sorter_info
- if analyser_info:
- agents_info["image_analyser"] = analyser_info
-
- # Ajouter les informations de l'agent report generator
- agents_info["report_generator"] = {
- "model": getattr(self.llm, "modele", str(type(self.llm))),
- "temperature": self.temperature,
- "top_p": self.top_p,
- "max_tokens": self.max_tokens
- }
-
- return agents_info
-
- def _generer_markdown_depuis_json(self, rapport_data: Dict) -> str:
- """
- GénÚre un rapport Markdown directement à partir des données JSON
-
- Args:
- rapport_data: Données JSON complÚtes du rapport
- Format attendu:
- - ticket_id: ID du ticket
- - metadata: Métadonnées (timestamp, modÚle, etc.)
- - rapport_genere: Texte du rapport généré par le LLM
- - ticket_analyse: Analyse du ticket
- - images_analyses: Liste des analyses d'images (format privilégié)
- OU
- - analyse_images: Dictionnaire des analyses d'images (format alternatif)
-
- Returns:
- Contenu Markdown du rapport
- """
- ticket_id = rapport_data.get("ticket_id", "")
- timestamp = rapport_data.get("metadata", {}).get("timestamp", self._get_timestamp())
-
- logger.info(f"Génération du rapport Markdown pour le ticket {ticket_id}")
-
- # Contenu de base du rapport (partie générée par le LLM)
- rapport_contenu = rapport_data.get("rapport_genere", "")
-
- # EntĂȘte du document
- markdown = f"# Rapport d'analyse du ticket #{ticket_id}\n\n"
- markdown += f"*Généré le: {timestamp}*\n\n"
-
- # Ajouter le rapport principal généré par le LLM
- markdown += rapport_contenu + "\n\n"
-
- # Section séparatrice pour les détails d'analyse
- markdown += "---\n\n"
- markdown += "# Détails des analyses effectuées\n\n"
-
- # Ajouter un résumé du processus d'analyse complet
- markdown += "## Processus d'analyse\n\n"
-
- # 1. Analyse de ticket
- ticket_analyse = rapport_data.get("ticket_analyse", "")
- if not ticket_analyse and "analyse_json" in rapport_data:
- ticket_analyse = rapport_data.get("analyse_json", "")
-
- if ticket_analyse:
- markdown += "### Ătape 1: Analyse du ticket\n\n"
- markdown += "L'agent d'analyse de ticket a extrait les informations suivantes du ticket d'origine:\n\n"
- markdown += "\nCliquez pour voir l'analyse complĂšte du ticket
\n\n"
- markdown += "```\n" + ticket_analyse + "\n```\n\n"
- markdown += " \n\n"
- logger.info(f"Analyse du ticket ajoutée au rapport Markdown ({len(str(ticket_analyse))} caractÚres)")
- else:
- logger.warning("Aucune analyse de ticket disponible pour le rapport Markdown")
-
- # 2. Tri des images
- markdown += "### Ătape 2: Tri des images\n\n"
- markdown += "L'agent de tri d'images a évalué chaque image pour déterminer sa pertinence par rapport au problÚme client:\n\n"
-
- # Vérifier quelle structure de données est disponible pour les images
- has_analyse_images = "analyse_images" in rapport_data and rapport_data["analyse_images"]
- has_images_analyses = "images_analyses" in rapport_data and isinstance(rapport_data["images_analyses"], list) and rapport_data["images_analyses"]
-
- logger.info(f"Structure des données d'images: analyse_images={has_analyse_images}, images_analyses={has_images_analyses}")
-
- analyse_images_data = {}
- if has_analyse_images:
- analyse_images_data = rapport_data["analyse_images"]
-
- if analyse_images_data:
- # Créer un tableau pour le tri des images
- markdown += "| Image | Pertinence | Raison |\n"
- markdown += "|-------|------------|--------|\n"
-
- for image_path, analyse_data in analyse_images_data.items():
- image_name = os.path.basename(image_path)
-
- # Information de tri
- is_relevant = "Non"
- reason = "Non spécifiée"
-
- if "sorting" in analyse_data:
- sorting_data = analyse_data["sorting"]
- if isinstance(sorting_data, dict):
- is_relevant = "Oui" if sorting_data.get("is_relevant", False) else "Non"
- reason = sorting_data.get("reason", "Non spécifiée")
-
- markdown += f"| {image_name} | {is_relevant} | {reason} |\n"
-
- markdown += "\n"
- logger.info(f"Tableau de tri des images ajouté au rapport Markdown ({len(analyse_images_data)} images)")
- elif has_images_analyses and rapport_data["images_analyses"]:
- # Si nous avons les analyses d'images mais pas les données de tri, créer un tableau simplifié
- markdown += "| Image | Pertinence |\n"
- markdown += "|-------|------------|\n"
-
- for img_data in rapport_data["images_analyses"]:
- image_name = img_data.get("image_name", "Image inconnue")
- markdown += f"| {image_name} | Oui |\n"
-
- markdown += "\n"
- logger.info(f"Tableau de tri simplifié ajouté au rapport Markdown ({len(rapport_data['images_analyses'])} images)")
- else:
- markdown += "*Aucune image n'a été trouvée ou analysée.*\n\n"
- logger.warning("Aucune analyse d'images disponible pour le tableau de tri")
-
- # 3. Analyse des images pertinentes
- markdown += "### Ătape 3: Analyse dĂ©taillĂ©e des images pertinentes\n\n"
-
- # Traiter directement les images_analyses du rapport_data si disponible
- images_pertinentes = 0
-
- # D'abord essayer d'utiliser la liste images_analyses qui est déjà traitée
- if has_images_analyses:
- images_list = rapport_data["images_analyses"]
- for i, img_data in enumerate(images_list, 1):
- images_pertinentes += 1
- image_name = img_data.get("image_name", f"Image {i}")
- analyse_detail = img_data.get("analyse", "Analyse non disponible")
-
- markdown += f"#### Image pertinente {images_pertinentes}: {image_name}\n\n"
- markdown += "\nCliquez pour voir l'analyse complĂšte de l'image
\n\n"
- markdown += "```\n" + analyse_detail + "\n```\n\n"
- markdown += " \n\n"
-
- logger.info(f"Analyse de l'image {image_name} ajoutée au rapport Markdown (from images_analyses)")
- # Sinon, traiter les données brutes d'analyse_images
- elif has_analyse_images:
- analyse_images_data = rapport_data["analyse_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)
-
- if is_relevant:
- images_pertinentes += 1
- image_name = os.path.basename(image_path)
-
- # Récupérer l'analyse détaillée avec gestion des différents formats possibles
- analyse_detail = "Analyse non disponible"
- if "analysis" in analyse_data and analyse_data["analysis"]:
- if isinstance(analyse_data["analysis"], dict):
- if "analyse" in analyse_data["analysis"]:
- analyse_detail = analyse_data["analysis"]["analyse"]
- logger.info(f"Analyse de l'image {image_name} récupérée via analyse_data['analysis']['analyse']")
- elif "error" in analyse_data["analysis"] and not analyse_data["analysis"].get("error", True):
- analyse_detail = str(analyse_data["analysis"])
- logger.info(f"Analyse de l'image {image_name} récupérée via str(analyse_data['analysis'])")
- else:
- analyse_detail = json.dumps(analyse_data["analysis"], ensure_ascii=False, indent=2)
- logger.info(f"Analyse de l'image {image_name} récupérée via json.dumps")
- elif isinstance(analyse_data["analysis"], str):
- analyse_detail = analyse_data["analysis"]
- logger.info(f"Analyse de l'image {image_name} récupérée directement (string)")
- else:
- logger.warning(f"Aucune analyse disponible pour l'image pertinente {image_name}")
-
- markdown += f"#### Image pertinente {images_pertinentes}: {image_name}\n\n"
- markdown += "\nCliquez pour voir l'analyse complĂšte de l'image
\n\n"
- markdown += "```\n" + analyse_detail + "\n```\n\n"
- markdown += " \n\n"
-
- if images_pertinentes == 0:
- markdown += "*Aucune image pertinente n'a été identifiée pour ce ticket.*\n\n"
- logger.warning("Aucune image pertinente identifiée pour l'analyse détaillée")
- else:
- logger.info(f"{images_pertinentes} images pertinentes ajoutées au rapport Markdown")
-
- # 4. SynthĂšse (rapport final)
- markdown += "### Ătape 4: GĂ©nĂ©ration du rapport de synthĂšse\n\n"
- markdown += "L'agent de génération de rapport a synthétisé toutes les analyses précédentes pour produire le rapport ci-dessus.\n\n"
-
- # Informations techniques
- markdown += "## Informations techniques\n\n"
-
- # Statistiques d'analyse
- markdown += "### Statistiques\n\n"
-
- total_images = 0
- if has_analyse_images:
- total_images = len(analyse_images_data)
- elif "statistiques" in rapport_data and "total_images" in rapport_data["statistiques"]:
- total_images = rapport_data["statistiques"]["total_images"]
-
- markdown += f"- **Images analysées**: {total_images}\n"
- markdown += f"- **Images pertinentes**: {images_pertinentes}\n\n"
-
- logger.info(f"Rapport Markdown généré ({len(markdown)} caractÚres)")
- return markdown
-
- def _formater_prompt_pour_rapport(self, ticket_analyse, images_analyses, ticket_id):
- """
- Formate le prompt pour la génération du rapport
-
- Args:
- ticket_analyse: Analyse du ticket
- images_analyses: Liste des analyses d'images, format [{image_name, analyse}, ...]
- ticket_id: ID du ticket
-
- Returns:
- Prompt formaté pour le LLM
- """
- num_images = len(images_analyses)
- logger.info(f"Formatage du prompt avec {num_images} analyses d'images")
-
- # Inclure une vérification des données reçues
- prompt = f"""GénÚre un rapport technique complet pour le ticket #{ticket_id}, en te basant sur les analyses suivantes.
-
-## VĂRIFICATION DES DONNĂES REĂUES
-Je vais d'abord vérifier que j'ai bien reçu les données d'analyses:
-- Analyse du ticket : {"PRĂSENTE" if ticket_analyse else "MANQUANTE"}
-- Analyses d'images : {"PRĂSENTES (" + str(num_images) + " images)" if num_images > 0 else "MANQUANTES"}
-
-## ANALYSE DU TICKET
-{ticket_analyse}
-
-## ANALYSES DES IMAGES ({num_images} images analysées)
-"""
-
- # Ajouter l'analyse de chaque image
- for i, img_analyse in enumerate(images_analyses, 1):
- image_name = img_analyse.get("image_name", f"Image {i}")
- analyse = img_analyse.get("analyse", "Analyse non disponible")
- prompt += f"\n### IMAGE {i}: {image_name}\n{analyse}\n"
- logger.info(f"Ajout de l'analyse de l'image {image_name} au prompt ({len(str(analyse))} caractĂšres)")
-
- # Ne pas répéter les instructions déjà présentes dans le system_prompt
- prompt += f"""
-N'oublie pas d'inclure un objet JSON structuré des échanges client/support selon le format demandé.
-Assure-toi que le JSON est valide et clairement balisé avec ```json et ``` dans ta réponse.
-"""
-
- logger.info(f"Prompt formaté: {len(prompt)} caractÚres au total")
- return prompt
-
- def _extraire_et_traiter_json(self, texte_rapport):
- """
- Extrait l'objet JSON des échanges du texte du rapport et le convertit en Markdown
-
- Args:
- texte_rapport: Texte complet du rapport généré par le LLM
-
- Returns:
- Tuple (rapport_traité, echanges_json, echanges_markdown)
- """
- # Remplacer CBAD par CBAO dans tout le rapport
- texte_rapport = texte_rapport.replace("CBAD", "CBAO")
-
- # Rechercher un objet JSON dans le texte
- json_match = re.search(r'```json\s*({.*?})\s*```', texte_rapport, re.DOTALL)
-
- if not json_match:
- logger.warning("Aucun JSON trouvé dans le rapport")
- return texte_rapport, None, None
-
- # Extraire le JSON et le parser
- json_text = json_match.group(1)
- try:
- echanges_json = json.loads(json_text)
- logger.info(f"JSON extrait avec succĂšs: {len(json_text)} caractĂšres")
-
- # Convertir en tableau Markdown
- echanges_markdown = "| Date | Ămetteur | Type | Contenu | Statut |\n"
- echanges_markdown += "|------|---------|------|---------|--------|\n"
-
- if "chronologie_echanges" in echanges_json and isinstance(echanges_json["chronologie_echanges"], list):
- # Pré-traitement pour vérifier les questions sans réponse
- questions_sans_reponse = {}
- for i, echange in enumerate(echanges_json["chronologie_echanges"]):
- if echange.get("type", "").lower() == "question" and echange.get("emetteur", "").lower() == "client":
- has_response = False
- # Vérifier si la question a une réponse
- for j in range(i+1, len(echanges_json["chronologie_echanges"])):
- next_echange = echanges_json["chronologie_echanges"][j]
- if next_echange.get("type", "").lower() == "réponse" and next_echange.get("emetteur", "").lower() == "support":
- has_response = True
- break
- questions_sans_reponse[i] = not has_response
-
- # Générer le tableau
- for i, echange in enumerate(echanges_json["chronologie_echanges"]):
- date = echange.get("date", "-")
- emetteur = echange.get("emetteur", "-")
- type_msg = echange.get("type", "-")
- contenu = echange.get("contenu", "-")
-
- # Ajouter un statut pour les questions sans réponse
- statut = ""
- if emetteur.lower() == "client" and type_msg.lower() == "question" and questions_sans_reponse.get(i, False):
- statut = "**Sans réponse**"
-
- echanges_markdown += f"| {date} | {emetteur} | {type_msg} | {contenu} | {statut} |\n"
-
- # Ajouter une note si aucune réponse du support n'a été trouvée
- if not any(echange.get("emetteur", "").lower() == "support" for echange in echanges_json["chronologie_echanges"]):
- echanges_markdown += "\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n\n"
-
- # Remplacer le JSON dans le texte par le tableau Markdown
- rapport_traite = texte_rapport.replace(json_match.group(0), echanges_markdown)
-
- return rapport_traite, echanges_json, echanges_markdown
-
- except json.JSONDecodeError as e:
- logger.error(f"Erreur lors du décodage JSON: {e}")
- return texte_rapport, None, None
-
- def _get_timestamp(self) -> str:
- """Retourne un timestamp au format YYYYMMDD_HHMMSS"""
- return datetime.now().strftime("%Y%m%d_%H%M%S")
\ No newline at end of file
diff --git a/agents/agent_ticket_analyser.py b/agents/agent_ticket_analyser.py
index 4f33274..4dc16cc 100644
--- a/agents/agent_ticket_analyser.py
+++ b/agents/agent_ticket_analyser.py
@@ -24,9 +24,9 @@ class AgentTicketAnalyser(BaseAgent):
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.
-
+
+ # Centralisation des objectifs d'analyse
+ self.objectifs_analyse = """
Ta mission principale:
1. Mettre en perspective le NOM DE LA DEMANDE qui contient souvent le problÚme soulevé par le client
2. Analyser la DESCRIPTION du problĂšme qui ajoute du contexte
@@ -34,14 +34,23 @@ Ta mission principale:
- Les questions posées par le client
- Les réponses fournies par le support
- Les informations techniques fournies par chaque partie
+ - Fourinir un tableau clair des questions/Réponses support/client sur deux colonnes
+"""
-Sois factuel et reste dans une démarche technique. Ton analyse sera utilisée comme contexte pour l'analyse des images pertinentes.
-
+ # Centralisation de la structure de réponse
+ self.structure_reponse = """
Structure ta réponse:
1. Analyse du problĂšme initial (nom de la demande + description)
2. Informations techniques essentielles (logiciels, versions, configurations)
3. Chronologie des échanges client/support avec identification claire des questions/réponses
"""
+
+ # Construction du prompt systĂšme
+ self.system_prompt = f"""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.
+{self.objectifs_analyse}
+Sois factuel et reste dans une démarche technique. Ton analyse sera utilisée comme contexte pour l'analyse des images pertinentes.
+{self.structure_reponse}"""
# Initialiser le loader de données
self.ticket_loader = TicketDataLoader()
@@ -67,21 +76,31 @@ Structure ta réponse:
"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 _generer_prompt_analyse(self, ticket_formate: str, source_format: str) -> str:
+ """
+ GénÚre le prompt d'analyse standardisé
+
+ Args:
+ ticket_formate: Texte du ticket formaté pour l'analyse
+ source_format: Format source du ticket (JSON, Markdown, etc.)
+
+ Returns:
+ Prompt formaté pour l'analyse du ticket
+ """
+ return f"""Analyse ce ticket de support technique et fournis une synthÚse structurée:
+
+{ticket_formate}
+
+Concentre-toi sur:
+1. L'analyse du problÚme initial décrit dans le nom de la demande et la description
+2. L'extraction des informations techniques importantes
+3. L'établissement d'une chronologie claire des échanges client/support en identifiant précisément les questions posées et les réponses fournies
+
+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."""
+
def executer(self, ticket_data: Dict[str, Any]) -> str:
"""
Analyse un ticket pour en extraire les informations pertinentes
@@ -126,17 +145,7 @@ Structure ta réponse:
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}
-
-Concentre-toi sur:
-1. L'analyse du problÚme initial décrit dans le nom de la demande et la description
-2. L'extraction des informations techniques importantes
-3. L'établissement d'une chronologie claire des échanges client/support en identifiant précisément les questions posées et les réponses fournies
-
-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."""
+ prompt = self._generer_prompt_analyse(ticket_formate, source_format)
try:
logger.info("Interrogation du LLM")
diff --git a/orchestrator.py b/orchestrator.py
index ec292f0..07af46f 100644
--- a/orchestrator.py
+++ b/orchestrator.py
@@ -3,9 +3,10 @@ import json
import logging
import time
import traceback
-from typing import List, Dict, Any, Optional, Union
+from typing import List, Dict, Any, Optional, Union, Mapping, cast
from agents.base_agent import BaseAgent
from utils.ticket_data_loader import TicketDataLoader
+from utils.report_formatter import generate_markdown_report
# Configuration du logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s',
@@ -111,20 +112,34 @@ class Orchestrator:
def trouver_rapport(self, extraction_path: str, ticket_id: str) -> Dict[str, Optional[str]]:
"""
- Cherche le rapport du ticket dans différents emplacements possibles (JSON ou MD)
+ Cherche les rapports disponibles (JSON et/ou MD) pour un ticket
Args:
- extraction_path: Chemin de l'extraction
- ticket_id: ID du ticket (ex: T0101)
+ extraction_path: Chemin vers l'extraction
+ ticket_id: ID du ticket
Returns:
- Un dictionnaire avec les chemins des fichiers JSON et MD s'ils sont trouvés
+ Dictionnaire avec {"json": chemin_json, "markdown": chemin_md}
"""
- # 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
+ # Utiliser la méthode du TicketDataLoader pour trouver les fichiers
+ result = self.ticket_loader.trouver_ticket(extraction_path, ticket_id)
+
+ # S'assurer que nous avons un dictionnaire avec la structure correcte
+ rapports: Dict[str, Optional[str]] = {"json": None, "markdown": None} if result is None else result
+
+ # Si on a un JSON mais pas de Markdown, générer le Markdown à partir du JSON
+ json_path = rapports.get("json")
+ if json_path and not rapports.get("markdown"):
+ logger.info(f"Rapport JSON trouvé sans Markdown correspondant, génération du Markdown: {json_path}")
+
+ success, md_path_or_error = generate_markdown_report(json_path)
+ if success:
+ rapports["markdown"] = md_path_or_error
+ logger.info(f"Markdown généré avec succÚs: {md_path_or_error}")
+ else:
+ logger.warning(f"Erreur lors de la génération du Markdown: {md_path_or_error}")
+
+ return rapports
def traiter_ticket(self, ticket_path: str) -> bool:
"""Traite un ticket spécifique et retourne True si le traitement a réussi"""
@@ -313,16 +328,29 @@ class Orchestrator:
os.makedirs(rapport_path, exist_ok=True)
# Générer le rapport
- json_path, md_path = self.report_generator.executer(rapport_data, rapport_path)
+ json_path = self.report_generator.executer(rapport_data, rapport_path)
- if json_path and md_path:
- logger.info(f"Rapport généré à : {rapport_path}")
- print(f" Rapport généré avec succÚs")
- print(f" - JSON: {os.path.basename(json_path)}")
- print(f" - Markdown: {os.path.basename(md_path)}")
+ if json_path:
+ logger.info(f"Rapport JSON généré à : {rapport_path}")
+ print(f" Rapport JSON généré avec succÚs: {os.path.basename(json_path)}")
+
+ # Générer le rapport Markdown à partir du JSON en utilisant report_formatter
+ success, md_path = generate_markdown_report(json_path)
+
+ if success:
+ logger.info(f"Rapport Markdown généré à : {rapport_path}")
+ print(f" Rapport Markdown généré avec succÚs: {os.path.basename(md_path)}")
+ # Vérifier si le rapport Markdown contient un tableau des échanges
+ with open(md_path, "r", encoding="utf-8") as f:
+ md_content = f.read()
+ has_exchanges = "| Date | Ămetteur |" in md_content
+ logger.info(f"Vérification du rapport Markdown: Tableau des échanges {'présent' if has_exchanges else 'absent'}")
+ else:
+ logger.warning(f"Erreur lors de la génération du Markdown: {md_path}")
+ print(f" ERREUR: ProblÚme lors de la génération du rapport Markdown")
else:
- logger.warning("Erreur lors de la génération du rapport")
- print(f" ERREUR: ProblÚme lors de la génération du rapport")
+ logger.warning("Erreur lors de la génération du rapport JSON")
+ print(f" ERREUR: ProblÚme lors de la génération du rapport JSON")
else:
logger.warning("Report Generator non disponible")
print(" Report Generator non disponible, génération de rapport ignorée")
diff --git a/output/ticket_T0101/T0101_20250404_170239/T0101_rapports/T0101/mistral_medium/T0101_rapport_final.json b/output/ticket_T0101/T0101_20250404_170239/T0101_rapports/T0101/mistral_medium/T0101_rapport_final.json
new file mode 100644
index 0000000..8941bc3
--- /dev/null
+++ b/output/ticket_T0101/T0101_20250404_170239/T0101_rapports/T0101/mistral_medium/T0101_rapport_final.json
@@ -0,0 +1,86 @@
+{
+ "ticket_id": "T0101",
+ "timestamp": "20250408_120234",
+ "rapport_complet": "RĂ©sumĂ© exĂ©cutif:\nLe client a rĂ©installĂ© le logiciel ESQ sur un nouveau serveur pour permettre le tĂ©lĂ©travail. Cependant, lors de l'activation du logiciel, il est incertain si le numĂ©ro de licence a Ă©tĂ© modifiĂ© suite Ă un achat de version rĂ©seau en 2019 par JB Lafitte ou si le problĂšme est diffĂ©rent.\n\nChronologie des Ă©changes:\n```json\n{\n \"chronologie_echanges\": [\n {\n \"date\": \"26/03/2020\",\n \"emetteur\": \"CLIENT\",\n \"type\": \"Question\",\n \"contenu\": \"Besoin d'aide pour l'activation du logiciel ESQ sur le nouveau serveur, incertain si le numĂ©ro de licence a Ă©tĂ© modifiĂ© ou non.\"\n },\n {\n \"date\": \"26/03/2020\",\n \"emetteur\": \"CLIENT\",\n \"type\": \"Information technique\",\n \"contenu\": \"RĂ©installation du logiciel ESQ sur un nouveau serveur, achat d'une version rĂ©seau en 2019 par JB Lafitte.\"\n }\n ]\n}\n```\n\nAnalyse des images:\n\nImage 1: image005.jpg\n- L'image montre une fenĂȘtre d'activation de logiciel intitulĂ©e \"Activation du logiciel\".\n- Le client doit saisir l'ID du logiciel fourni par CBAO pour activer le logiciel.\n- Si le client ne dispose pas de son ID de logiciel, il doit contacter CBAO par mail Ă l'adresse support@cbao.com ou par tĂ©lĂ©phone au 01 60 61 53 15.\n\nDiagnostic technique:\nLe client a besoin d'aide pour activer le logiciel ESQ sur le nouveau serveur. Il est possible que le numĂ©ro de licence ait Ă©tĂ© modifiĂ© suite Ă l'achat de la version rĂ©seau en 2019. Le client doit contacter le support CBAO pour obtenir l'ID de logiciel nĂ©cessaire Ă l'activation.",
+ "ticket_analyse": "1. Analyse du problĂšme initial\n - Nom de la demande: Activation Logiciel\n - Description: ProblĂšme de licence\n - ProblĂšme initial: Le client a rĂ©installĂ© le logiciel ESQ sur un autre serveur pour permettre le tĂ©lĂ©travail. Cependant, le logiciel demande une activation et le client est incertain si le numĂ©ro de licence a Ă©tĂ© modifiĂ© suite Ă un achat de version rĂ©seau en 2019 par JB Lafitte ou si le problĂšme est diffĂ©rent.\n\n2. Informations techniques essentielles\n - Logiciel: ESQ\n - Version: Non spĂ©cifiĂ©e dans les informations fournies\n - Configuration: RĂ©installation sur un nouveau serveur pour le tĂ©lĂ©travail\n - Licence: PossibilitĂ© d'un changement de numĂ©ro de licence suite Ă un achat de version rĂ©seau en 2019\n\n3. Chronologie des Ă©changes client/support\n - Message 1 - [AUTRE] De: Inconnu - Date: 26/03/2020 14:43:45\n - Question du client: Le client demande de l'aide pour l'activation du logiciel ESQ sur le nouveau serveur, Ă©tant incertain si le numĂ©ro de licence a Ă©tĂ© modifiĂ© ou non.\n - Informations techniques fournies par le client: RĂ©installation du logiciel ESQ sur un nouveau serveur, achat d'une version rĂ©seau en 2019 par JB Lafitte.\n - PiĂšces jointes: Deux images (image006.jpg et image005.jpg) montrant probablement la fenĂȘtre d'activation du logiciel.\n\nAucune rĂ©ponse du support n'est fournie dans les informations donnĂ©es.",
+ "images_analyses": [
+ {
+ "image_name": "image005.jpg",
+ "image_path": "output/ticket_T0101/T0101_20250404_170239/attachments/image005.jpg",
+ "analyse": "### Analyse d'image\n\n#### 1. Description objective\nL'image montre une fenĂȘtre d'activation de logiciel intitulĂ©e \"Activation du logiciel\". La fenĂȘtre contient un champ pour entrer l'ID du logiciel, un message d'instructions, et trois options pour l'activation du logiciel.\n\n#### 2. ĂlĂ©ments techniques clĂ©s\n- **Titre de la fenĂȘtre**: \"Activation du logiciel\"\n- **Champ d'ID du logiciel**: \"ID du logiciel\" avec un champ de texte vide\n- **Message d'instructions**:\n - \"Afin d'activer votre logiciel, veuillez saisir l'ID du logiciel fourni par CBAD.\"\n - \"Si vous ne disposez pas de votre ID de logiciel, veuillez contacter CBAD par mail Ă l'adresse suivante : support@cbad.com ou par tĂ©lĂ©phone au 01 60 61 53 15 ou en cliquant sur le bouton tĂ©lĂ©phone situĂ© en haut Ă droite de la fenĂȘtre.\"\n- **Options d'activation**:\n - \"Activer le logiciel (par internet)\"\n - \"Activer plus tard (4 jours restants)\"\n - \"Activation par tĂ©lĂ©phone\"\n\n#### 3. Relation avec le problĂšme\nL'image se rapporte au problĂšme dĂ©crit dans le ticket de support concernant l'activation du logiciel ESQ. Le message d'instructions indique que le client doit contacter CBAD pour obtenir l'ID du logiciel, ce qui pourrait ĂȘtre pertinent pour rĂ©soudre le problĂšme d'activation. Les options d'activation montrent que le logiciel permet plusieurs mĂ©thodes d'activation, y compris par internet et par tĂ©lĂ©phone, ce qui pourrait ĂȘtre utile pour le client en fonction de la situation.",
+ "sorting_info": {
+ "is_relevant": true,
+ "reason": "oui. L'image montre une fenĂȘtre d'activation de logiciel, ce qui est pertinent pour le support technique de logiciels.",
+ "raw_response": "oui. L'image montre une fenĂȘtre d'activation de logiciel, ce qui est pertinent pour le support technique de logiciels.",
+ "metadata": {
+ "image_path": "output/ticket_T0101/T0101_20250404_170239/attachments/image005.jpg",
+ "image_name": "image005.jpg",
+ "timestamp": "20250408_120217",
+ "model_info": {
+ "model": "pixtral-12b-latest",
+ "temperature": 0.2,
+ "top_p": 0.8,
+ "max_tokens": 300
+ }
+ }
+ }
+ }
+ ],
+ "chronologie_echanges": [
+ {
+ "date": "26/03/2020",
+ "emetteur": "CLIENT",
+ "type": "Question",
+ "contenu": "Besoin d'aide pour l'activation du logiciel ESQ sur le nouveau serveur, incertain si le numéro de licence a été modifié ou non."
+ },
+ {
+ "date": "26/03/2020",
+ "emetteur": "CLIENT",
+ "type": "Information technique",
+ "contenu": "Réinstallation du logiciel ESQ sur un nouveau serveur, achat d'une version réseau en 2019 par JB Lafitte."
+ }
+ ],
+ "resume": "Résumé exécutif:\nLe client a réinstallé le logiciel ESQ sur un nouveau serveur pour permettre le télétravail. Cependant, lors de l'activation du logiciel, il est incertain si le numéro de licence a été modifié suite à un achat de version réseau en 2019 par JB Lafitte ou si le problÚme est différent.",
+ "diagnostic": "Diagnostic technique:\nLe client a besoin d'aide pour activer le logiciel ESQ sur le nouveau serveur. Il est possible que le numéro de licence ait été modifié suite à l'achat de la version réseau en 2019. Le client doit contacter le support CBAO pour obtenir l'ID de logiciel nécessaire à l'activation.",
+ "statistiques": {
+ "total_images": 2,
+ "images_pertinentes": 1,
+ "analyses_generees": 1,
+ "generation_time": 12.270459
+ },
+ "prompt": {
+ "systeme": "Tu es un expert en gĂ©nĂ©ration de rapports techniques pour BRG-Lab.\nTa mission est de synthĂ©tiser les analyses (ticket et images) en un rapport structurĂ© et exploitable.\n\nIMPORTANCE DES ĂCHANGES CLIENT/SUPPORT:\n- Tu dois IMPĂRATIVEMENT prĂ©senter les Ă©changes client/support sous forme d'un TABLEAU MARKDOWN clair\n- Chaque ligne du tableau doit contenir: Date | Ămetteur | Type (Question/RĂ©ponse) | Contenu\n- Identifie clairement qui est l'Ă©metteur (CLIENT ou SUPPORT)\n- Mets en Ă©vidence les questions posĂ©es et les rĂ©ponses fournies\n\nStructure ton rapport:\n1. RĂ©sumĂ© exĂ©cutif: SynthĂšse du problĂšme initial (nom de la demande + description)\n2. Chronologie des Ă©changes: TABLEAU des interactions client/support\n3. Analyse des images: Ce que montrent les captures d'Ă©cran et leur pertinence\n4. Diagnostic technique: InterprĂ©tation des informations techniques pertinentes\n\nReste factuel et prĂ©cis. Ne fais pas d'analyses inutiles ou de recommandations non fondĂ©es.\nTon rapport doit mettre en avant la chronologie des Ă©changes et les informations techniques clĂ©s.",
+ "utilisateur": "GĂ©nĂšre un rapport technique complet pour le ticket #T0101, en te basant sur les analyses suivantes.\n\n## VĂRIFICATION DES DONNĂES REĂUES\nJe vais d'abord vĂ©rifier que j'ai bien reçu les donnĂ©es d'analyses:\n- Analyse du ticket : PRĂSENTE\n- Analyses d'images : PRĂSENTES (1 images)\n\n## ANALYSE DU TICKET\n1. Analyse du problĂšme initial\n - Nom de la demande: Activation Logiciel\n - Description: ProblĂšme de licence\n - ProblĂšme initial: Le client a rĂ©installĂ© le logiciel ESQ sur un autre serveur pour permettre le tĂ©lĂ©travail. Cependant, le logiciel demande une activation et le client est incertain si le numĂ©ro de licence a Ă©tĂ© modifiĂ© suite Ă un achat de version rĂ©seau en 2019 par JB Lafitte ou si le problĂšme est diffĂ©rent.\n\n2. Informations techniques essentielles\n - Logiciel: ESQ\n - Version: Non spĂ©cifiĂ©e dans les informations fournies\n - Configuration: RĂ©installation sur un nouveau serveur pour le tĂ©lĂ©travail\n - Licence: PossibilitĂ© d'un changement de numĂ©ro de licence suite Ă un achat de version rĂ©seau en 2019\n\n3. Chronologie des Ă©changes client/support\n - Message 1 - [AUTRE] De: Inconnu - Date: 26/03/2020 14:43:45\n - Question du client: Le client demande de l'aide pour l'activation du logiciel ESQ sur le nouveau serveur, Ă©tant incertain si le numĂ©ro de licence a Ă©tĂ© modifiĂ© ou non.\n - Informations techniques fournies par le client: RĂ©installation du logiciel ESQ sur un nouveau serveur, achat d'une version rĂ©seau en 2019 par JB Lafitte.\n - PiĂšces jointes: Deux images (image006.jpg et image005.jpg) montrant probablement la fenĂȘtre d'activation du logiciel.\n\nAucune rĂ©ponse du support n'est fournie dans les informations donnĂ©es.\n\n## ANALYSES DES IMAGES (1 images analysĂ©es)\n\n### IMAGE 1: image005.jpg\n### Analyse d'image\n\n#### 1. Description objective\nL'image montre une fenĂȘtre d'activation de logiciel intitulĂ©e \"Activation du logiciel\". La fenĂȘtre contient un champ pour entrer l'ID du logiciel, un message d'instructions, et trois options pour l'activation du logiciel.\n\n#### 2. ĂlĂ©ments techniques clĂ©s\n- **Titre de la fenĂȘtre**: \"Activation du logiciel\"\n- **Champ d'ID du logiciel**: \"ID du logiciel\" avec un champ de texte vide\n- **Message d'instructions**:\n - \"Afin d'activer votre logiciel, veuillez saisir l'ID du logiciel fourni par CBAD.\"\n - \"Si vous ne disposez pas de votre ID de logiciel, veuillez contacter CBAD par mail Ă l'adresse suivante : support@cbad.com ou par tĂ©lĂ©phone au 01 60 61 53 15 ou en cliquant sur le bouton tĂ©lĂ©phone situĂ© en haut Ă droite de la fenĂȘtre.\"\n- **Options d'activation**:\n - \"Activer le logiciel (par internet)\"\n - \"Activer plus tard (4 jours restants)\"\n - \"Activation par tĂ©lĂ©phone\"\n\n#### 3. Relation avec le problĂšme\nL'image se rapporte au problĂšme dĂ©crit dans le ticket de support concernant l'activation du logiciel ESQ. Le message d'instructions indique que le client doit contacter CBAD pour obtenir l'ID du logiciel, ce qui pourrait ĂȘtre pertinent pour rĂ©soudre le problĂšme d'activation. Les options d'activation montrent que le logiciel permet plusieurs mĂ©thodes d'activation, y compris par internet et par tĂ©lĂ©phone, ce qui pourrait ĂȘtre utile pour le client en fonction de la situation.\n\n## INSTRUCTIONS POUR LA GĂNĂRATION DU RAPPORT\n\n1. RĂ©sume d'abord le problĂšme principal du ticket en quelques phrases.\n\n2. GĂNĂRER OBLIGATOIREMENT LE JSON DES ĂCHANGES CLIENT/SUPPORT:\n - Les donnĂ©es d'Ă©changes sont l'Ă©lĂ©ment le plus important du rapport\n - Utilise EXACTEMENT la structure suivante, sans la modifier:\n ```json\n {\n \"chronologie_echanges\": [\n {\"date\": \"date1\", \"emetteur\": \"CLIENT\", \"type\": \"Question\", \"contenu\": \"contenu de la question\"},\n {\"date\": \"date2\", \"emetteur\": \"SUPPORT\", \"type\": \"RĂ©ponse\", \"contenu\": \"contenu de la rĂ©ponse\"}\n ]\n }\n ```\n - La clĂ© principale DOIT ĂȘtre \"chronologie_echanges\"\n - N'ajoute pas de commentaires ou de texte dans le JSON\n - Assure-toi que le JSON est valide et correspond EXACTEMENT au format demandĂ©\n - Entoure le JSON avec ```json et ``` pour faciliter l'extraction\n\n3. AprĂšs le JSON, analyse les images pertinentes et leur contribution Ă la comprĂ©hension du problĂšme.\n\n4. Termine par une analyse technique des causes probables du problĂšme.\n\nIMPORTANT: Le JSON des Ă©changes client/support est OBLIGATOIRE et doit ĂȘtre parfaitement formatĂ©.\n\nEXEMPLE EXACT DU FORMAT JSON ATTENDU:\n```json\n{\n \"chronologie_echanges\": [\n {\"date\": \"2023-05-10\", \"emetteur\": \"CLIENT\", \"type\": \"Question\", \"contenu\": \"L'application affiche une erreur lors de la connexion\"},\n {\"date\": \"2023-05-11\", \"emetteur\": \"SUPPORT\", \"type\": \"RĂ©ponse\", \"contenu\": \"Pouvez-vous prĂ©ciser le message d'erreur?\"},\n {\"date\": \"2023-05-12\", \"emetteur\": \"CLIENT\", \"type\": \"Information technique\", \"contenu\": \"Message: Erreur de connexion au serveur\"}\n ]\n}\n```\n"
+ },
+ "metadata": {
+ "timestamp": "20250408_120234",
+ "generation_date": "2025-04-08 12:02:34",
+ "model": "mistral-medium",
+ "model_version": "non spécifiée",
+ "temperature": 0.4,
+ "top_p": 0.9,
+ "max_tokens": 2500,
+ "agents": {
+ "image_sorter": {
+ "model": "pixtral-12b-latest",
+ "temperature": 0.2,
+ "top_p": 0.8,
+ "max_tokens": 300
+ },
+ "image_analyser": {
+ "model": "pixtral-12b-latest",
+ "temperature": 0.3,
+ "top_p": 0.9,
+ "max_tokens": 1200
+ },
+ "report_generator": {
+ "model": "mistral-medium",
+ "temperature": 0.4,
+ "top_p": 0.9,
+ "max_tokens": 2500
+ }
+ },
+ "generation_time": 12.270459,
+ "duree_traitement": "0:00:12.270189"
+ }
+}
\ No newline at end of file
diff --git a/output/ticket_T0101/T0101_20250404_170239/T0101_rapports/T0101/mistral_medium/T0101_rapport_final.md b/output/ticket_T0101/T0101_20250404_170239/T0101_rapports/T0101/mistral_medium/T0101_rapport_final.md
new file mode 100644
index 0000000..e370eec
--- /dev/null
+++ b/output/ticket_T0101/T0101_20250404_170239/T0101_rapports/T0101/mistral_medium/T0101_rapport_final.md
@@ -0,0 +1,154 @@
+# Rapport d'analyse du ticket #T0101
+
+*Généré le: 2025-04-08 12:02:34*
+
+Résumé exécutif:
+Le client a réinstallé le logiciel ESQ sur un nouveau serveur pour permettre le télétravail. Cependant, lors de l'activation du logiciel, il est incertain si le numéro de licence a été modifié suite à un achat de version réseau en 2019 par JB Lafitte ou si le problÚme est différent.
+
+## Chronologie des échanges
+
+| Date | Ămetteur | Type | Contenu | Statut |
+|------|---------|------|---------|--------|
+| 26/03/2020 | CLIENT | Question | Besoin d'aide pour l'activation du logiciel ESQ sur le nouveau serveur, incertain si le numéro de licence a été modifié ou non. | **Sans réponse** |
+| 26/03/2020 | CLIENT | Information technique | Réinstallation du logiciel ESQ sur un nouveau serveur, achat d'une version réseau en 2019 par JB Lafitte. | |
+
+**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**
+
+## Analyse des images
+
+### Image 1: image005.jpg
+
+**Raison de la pertinence**: oui. L'image montre une fenĂȘtre d'activation de logiciel, ce qui est pertinent pour le support technique de logiciels.
+
+
+Analyse détaillée de l'image
+
+```
+### Analyse d'image
+
+#### 1. Description objective
+L'image montre une fenĂȘtre d'activation de logiciel intitulĂ©e "Activation du logiciel". La fenĂȘtre contient un champ pour entrer l'ID du logiciel, un message d'instructions, et trois options pour l'activation du logiciel.
+
+#### 2. ĂlĂ©ments techniques clĂ©s
+- **Titre de la fenĂȘtre**: "Activation du logiciel"
+- **Champ d'ID du logiciel**: "ID du logiciel" avec un champ de texte vide
+- **Message d'instructions**:
+ - "Afin d'activer votre logiciel, veuillez saisir l'ID du logiciel fourni par CBAD."
+ - "Si vous ne disposez pas de votre ID de logiciel, veuillez contacter CBAD par mail Ă l'adresse suivante : support@cbad.com ou par tĂ©lĂ©phone au 01 60 61 53 15 ou en cliquant sur le bouton tĂ©lĂ©phone situĂ© en haut Ă droite de la fenĂȘtre."
+- **Options d'activation**:
+ - "Activer le logiciel (par internet)"
+ - "Activer plus tard (4 jours restants)"
+ - "Activation par téléphone"
+
+#### 3. Relation avec le problĂšme
+L'image se rapporte au problĂšme dĂ©crit dans le ticket de support concernant l'activation du logiciel ESQ. Le message d'instructions indique que le client doit contacter CBAD pour obtenir l'ID du logiciel, ce qui pourrait ĂȘtre pertinent pour rĂ©soudre le problĂšme d'activation. Les options d'activation montrent que le logiciel permet plusieurs mĂ©thodes d'activation, y compris par internet et par tĂ©lĂ©phone, ce qui pourrait ĂȘtre utile pour le client en fonction de la situation.
+```
+
+
+
+## Diagnostic technique
+
+Diagnostic technique:
+Le client a besoin d'aide pour activer le logiciel ESQ sur le nouveau serveur. Il est possible que le numéro de licence ait été modifié suite à l'achat de la version réseau en 2019. Le client doit contacter le support CBAO pour obtenir l'ID de logiciel nécessaire à l'activation.
+
+---
+
+# Détails des analyses effectuées
+
+## Processus d'analyse
+
+### Ătape 1: Analyse du ticket
+
+L'agent d'analyse de ticket a extrait les informations suivantes du ticket d'origine:
+
+
+Cliquez pour voir l'analyse complĂšte du ticket
+
+```
+1. Analyse du problĂšme initial
+ - Nom de la demande: Activation Logiciel
+ - Description: ProblĂšme de licence
+ - ProblÚme initial: Le client a réinstallé le logiciel ESQ sur un autre serveur pour permettre le télétravail. Cependant, le logiciel demande une activation et le client est incertain si le numéro de licence a été modifié suite à un achat de version réseau en 2019 par JB Lafitte ou si le problÚme est différent.
+
+2. Informations techniques essentielles
+ - Logiciel: ESQ
+ - Version: Non spécifiée dans les informations fournies
+ - Configuration: Réinstallation sur un nouveau serveur pour le télétravail
+ - Licence: Possibilité d'un changement de numéro de licence suite à un achat de version réseau en 2019
+
+3. Chronologie des échanges client/support
+ - Message 1 - [AUTRE] De: Inconnu - Date: 26/03/2020 14:43:45
+ - Question du client: Le client demande de l'aide pour l'activation du logiciel ESQ sur le nouveau serveur, étant incertain si le numéro de licence a été modifié ou non.
+ - Informations techniques fournies par le client: Réinstallation du logiciel ESQ sur un nouveau serveur, achat d'une version réseau en 2019 par JB Lafitte.
+ - PiĂšces jointes: Deux images (image006.jpg et image005.jpg) montrant probablement la fenĂȘtre d'activation du logiciel.
+
+Aucune réponse du support n'est fournie dans les informations données.
+```
+
+
+
+### Ătape 2: Tri des images
+
+L'agent de tri d'images a évalué chaque image pour déterminer sa pertinence par rapport au problÚme client:
+
+| Image | Pertinence | Raison |
+|-------|------------|--------|
+| image005.jpg | Oui | oui. L'image montre une fenĂȘtre d'activation de logiciel, ce qui est pertinent pour le support technique de logiciels. |
+
+### Ătape 3: Analyse dĂ©taillĂ©e des images pertinentes
+
+#### Image pertinente 1: image005.jpg
+
+
+Cliquez pour voir l'analyse complĂšte de l'image
+
+```
+### Analyse d'image
+
+#### 1. Description objective
+L'image montre une fenĂȘtre d'activation de logiciel intitulĂ©e "Activation du logiciel". La fenĂȘtre contient un champ pour entrer l'ID du logiciel, un message d'instructions, et trois options pour l'activation du logiciel.
+
+#### 2. ĂlĂ©ments techniques clĂ©s
+- **Titre de la fenĂȘtre**: "Activation du logiciel"
+- **Champ d'ID du logiciel**: "ID du logiciel" avec un champ de texte vide
+- **Message d'instructions**:
+ - "Afin d'activer votre logiciel, veuillez saisir l'ID du logiciel fourni par CBAD."
+ - "Si vous ne disposez pas de votre ID de logiciel, veuillez contacter CBAD par mail Ă l'adresse suivante : support@cbad.com ou par tĂ©lĂ©phone au 01 60 61 53 15 ou en cliquant sur le bouton tĂ©lĂ©phone situĂ© en haut Ă droite de la fenĂȘtre."
+- **Options d'activation**:
+ - "Activer le logiciel (par internet)"
+ - "Activer plus tard (4 jours restants)"
+ - "Activation par téléphone"
+
+#### 3. Relation avec le problĂšme
+L'image se rapporte au problĂšme dĂ©crit dans le ticket de support concernant l'activation du logiciel ESQ. Le message d'instructions indique que le client doit contacter CBAD pour obtenir l'ID du logiciel, ce qui pourrait ĂȘtre pertinent pour rĂ©soudre le problĂšme d'activation. Les options d'activation montrent que le logiciel permet plusieurs mĂ©thodes d'activation, y compris par internet et par tĂ©lĂ©phone, ce qui pourrait ĂȘtre utile pour le client en fonction de la situation.
+```
+
+
+
+### Ătape 4: GĂ©nĂ©ration du rapport de synthĂšse
+
+L'agent de génération de rapport a synthétisé toutes les analyses précédentes pour produire le rapport ci-dessus.
+
+## Informations techniques
+
+### Statistiques
+
+- **Images analysées**: 2
+- **Images pertinentes**: 1
+- **Temps de génération**: 12.27 secondes
+
+### ModÚle LLM utilisé
+
+- **ModĂšle**: mistral-medium
+- **Version**: non spécifiée
+- **Température**: 0.4
+- **Top_p**: 0.9
+
+### Agents impliqués
+
+
+#### Agent de tri d'images
+- **ModÚle**: Non spécifié
+
+#### Agent d'analyse d'images
+- **ModÚle**: Non spécifié
diff --git a/test_agents_with_models.py b/test_agents_with_models.py
index df7ff92..c21d2d0 100644
--- a/test_agents_with_models.py
+++ b/test_agents_with_models.py
@@ -1,6 +1,7 @@
+#!/usr/bin/env python3
import json
import os
-from agents.agent_json_analyser import AgentJsonAnalyser
+from agents.agent_ticket_analyser import AgentTicketAnalyser
from agents.agent_image_sorter import AgentImageSorter
from agents.agent_image_analyser import AgentImageAnalyser
from agents.agent_report_generator import AgentReportGenerator
@@ -44,11 +45,11 @@ def test_different_models():
print(f"Test avec le modĂšle {model_name}...")
# Créer l'agent avec ce modÚle
- json_agent = AgentJsonAnalyser(model)
+ json_agent = AgentTicketAnalyser(model)
- # Tester les paramÚtres appliqués
- print(f" ParamĂštres: {json_agent.config.get_params()}")
- print(f" Prompt systĂšme: {json_agent.config.get_system_prompt()[:50]}...")
+ # Afficher les paramĂštres de l'agent
+ print(f" Température: {json_agent.temperature}")
+ print(f" Prompt systĂšme: {json_agent.system_prompt[:50]}...")
# Exécuter le test
try:
@@ -62,7 +63,7 @@ def test_different_models():
results[model_name] = {
"result": result,
"success": success,
- "metadata": json_agent.historique[-1]["metadata"] if json_agent.historique else None
+ "metadata": json_agent.historique[-1]["metadata"] if json_agent.historique and json_agent.historique else {}
}
print(f" SuccĂšs: {success}")
@@ -71,14 +72,13 @@ def test_different_models():
# Générer un rapport comparatif
print("Génération du rapport comparatif...")
report_generator = AgentReportGenerator(MistralLarge())
- json_path, md_path = report_generator.executer(
+ json_path = report_generator.executer(
{"resultats_comparatifs": results},
"comparaison_modeles"
)
print(f"Rapport généré avec succÚs!")
print(f"JSON: {json_path}")
- print(f"Markdown: {md_path}")
if __name__ == "__main__":
test_different_models()
\ No newline at end of file
diff --git a/test_import.py b/test_import.py
new file mode 100644
index 0000000..5131f8f
--- /dev/null
+++ b/test_import.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+
+import agents.agent_ticket_analyser
+import agents.agent_image_sorter
+import agents.agent_image_analyser
+import agents.agent_report_generator
+
+print('Tests réussis! Tous les agents ont été importés correctement.')
diff --git a/test_orchestrator.py b/test_orchestrator.py
index 66e578f..74d5459 100644
--- a/test_orchestrator.py
+++ b/test_orchestrator.py
@@ -84,25 +84,6 @@ def test_orchestrator(ticket_id=None):
image_analyser = AgentImageAnalyser(image_analyser_llm)
report_generator = AgentReportGenerator(report_generator_llm)
- # Renforcer le system prompt du générateur de rapport pour s'assurer que le tableau est généré
- report_generator.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG-Lab.
-Ta mission est de synthétiser les analyses (ticket et images) en un rapport structuré et exploitable.
-
-IMPORTANCE DES ĂCHANGES CLIENT/SUPPORT:
-- Tu dois IMPĂRATIVEMENT prĂ©senter les Ă©changes client/support sous forme d'un TABLEAU MARKDOWN clair
-- Chaque ligne du tableau doit contenir: Date | Ămetteur | Type (Question/RĂ©ponse) | Contenu
-- Identifie clairement qui est l'émetteur (CLIENT ou SUPPORT)
-- Mets en évidence les questions posées et les réponses fournies
-
-Structure ton rapport:
-1. Résumé exécutif: SynthÚse du problÚme initial (nom de la demande + description)
-2. Chronologie des échanges: TABLEAU des interactions client/support
-3. Analyse des images: Ce que montrent les captures d'écran et leur pertinence
-4. Diagnostic technique: Interprétation des informations techniques pertinentes
-
-Reste factuel et précis. Ne fais pas d'analyses inutiles ou de recommandations non fondées.
-Ton rapport doit mettre en avant la chronologie des échanges et les informations techniques clés."""
-
print("Tous les agents ont été créés")
# Initialisation de l'orchestrateur avec les agents
diff --git a/test_orchestrator_large.py b/test_orchestrator_large.py
index f402b51..a3a07b6 100644
--- a/test_orchestrator_large.py
+++ b/test_orchestrator_large.py
@@ -83,26 +83,7 @@ def test_orchestrator(ticket_id=None):
image_sorter = AgentImageSorter(image_sorter_llm)
image_analyser = AgentImageAnalyser(image_analyser_llm)
report_generator = AgentReportGenerator(report_generator_llm)
-
- # Renforcer le system prompt du générateur de rapport pour s'assurer que le tableau est généré
- report_generator.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG-Lab.
-Ta mission est de synthétiser les analyses (ticket et images) en un rapport structuré et exploitable.
-
-IMPORTANCE DES ĂCHANGES CLIENT/SUPPORT:
-- Tu dois IMPĂRATIVEMENT prĂ©senter les Ă©changes client/support sous forme d'un TABLEAU MARKDOWN clair
-- Chaque ligne du tableau doit contenir: Date | Ămetteur | Type (Question/RĂ©ponse) | Contenu
-- Identifie clairement qui est l'émetteur (CLIENT ou SUPPORT)
-- Mets en évidence les questions posées et les réponses fournies
-
-Structure ton rapport:
-1. Résumé exécutif: SynthÚse du problÚme initial (nom de la demande + description)
-2. Chronologie des échanges: TABLEAU des interactions client/support
-3. Analyse des images: Ce que montrent les captures d'écran et leur pertinence
-4. Diagnostic technique: Interprétation des informations techniques pertinentes
-
-Reste factuel et précis. Ne fais pas d'analyses inutiles ou de recommandations non fondées.
-Ton rapport doit mettre en avant la chronologie des échanges et les informations techniques clés."""
-
+
print("Tous les agents ont été créés")
# Initialisation de l'orchestrateur avec les agents
diff --git a/test_orchestrator_qwen.py b/test_orchestrator_qwen.py
index 091c000..5d60760 100644
--- a/test_orchestrator_qwen.py
+++ b/test_orchestrator_qwen.py
@@ -84,25 +84,6 @@ def test_orchestrator(ticket_id=None):
image_analyser = AgentImageAnalyser(image_analyser_llm)
report_generator = AgentReportGenerator(report_generator_llm)
- # Renforcer le system prompt du générateur de rapport pour s'assurer que le tableau est généré
- report_generator.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG-Lab.
-Ta mission est de synthétiser les analyses (ticket et images) en un rapport structuré et exploitable.
-
-IMPORTANCE DES ĂCHANGES CLIENT/SUPPORT:
-- Tu dois IMPĂRATIVEMENT prĂ©senter les Ă©changes client/support sous forme d'un TABLEAU MARKDOWN clair
-- Chaque ligne du tableau doit contenir: Date | Ămetteur | Type (Question/RĂ©ponse) | Contenu
-- Identifie clairement qui est l'émetteur (CLIENT ou SUPPORT)
-- Mets en évidence les questions posées et les réponses fournies
-
-Structure ton rapport:
-1. Résumé exécutif: SynthÚse du problÚme initial (nom de la demande + description)
-2. Chronologie des échanges: TABLEAU des interactions client/support
-3. Analyse des images: Ce que montrent les captures d'écran et leur pertinence
-4. Diagnostic technique: Interprétation des informations techniques pertinentes
-
-Reste factuel et précis. Ne fais pas d'analyses inutiles ou de recommandations non fondées.
-Ton rapport doit mettre en avant la chronologie des échanges et les informations techniques clés."""
-
print("Tous les agents ont été créés")
# Initialisation de l'orchestrateur avec les agents
diff --git a/test_tableau_qr.py b/test_tableau_qr.py
new file mode 100644
index 0000000..b059a73
--- /dev/null
+++ b/test_tableau_qr.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+import json
+import os
+import sys
+from agents.agent_report_generator import AgentReportGenerator
+from llm_classes.ollama import Ollama # Pour avoir une instance LLM
+
+def test_tableau_qr():
+ """Test de la génération du tableau questions/réponses"""
+
+ # Créer un exemple d'échanges
+ echanges = [
+ {
+ "date": "2023-01-10",
+ "emetteur": "CLIENT",
+ "type": "Question",
+ "contenu": "Bonjour, j'ai un problĂšme avec l'activation de mon logiciel. Il me demande un code que je n'ai plus."
+ },
+ {
+ "date": "2023-01-11",
+ "emetteur": "SUPPORT",
+ "type": "Réponse",
+ "contenu": "Bonjour, pouvez-vous nous fournir votre numéro de licence qui se trouve sur votre contrat?"
+ },
+ {
+ "date": "2023-01-12",
+ "emetteur": "CLIENT",
+ "type": "Question",
+ "contenu": "J'ai regardé sur mon contrat et le numéro est BRG-12345. Mais l'application ne l'accepte pas. Y a-t-il un format particulier à respecter?"
+ },
+ {
+ "date": "2023-01-12",
+ "emetteur": "CLIENT",
+ "type": "Information technique",
+ "contenu": "Je suis sur Windows 10 version 21H2."
+ },
+ {
+ "date": "2023-01-13",
+ "emetteur": "SUPPORT",
+ "type": "Réponse",
+ "contenu": "Le format correct est BRG-xxxxx-yyyy oĂč yyyy correspond Ă l'annĂ©e de votre contrat. Essayez avec BRG-12345-2023."
+ },
+ {
+ "date": "2023-01-14",
+ "emetteur": "CLIENT",
+ "type": "Question",
+ "contenu": "Cela ne fonctionne toujours pas. Y a-t-il une autre solution?"
+ }
+ ]
+
+ # Créer une instance de l'agent
+ llm = Ollama("llama2") # Ollama est léger pour le test
+ agent = AgentReportGenerator(llm)
+
+ # Tester la méthode _generer_tableau_questions_reponses
+ tableau = agent._generer_tableau_questions_reponses(echanges)
+ print("TABLEAU QUESTIONS/RĂPONSES:")
+ print(tableau)
+
+ # Tester avec un long contenu pour voir la synthĂšse
+ long_echange = [
+ {
+ "date": "2023-01-10",
+ "emetteur": "CLIENT",
+ "type": "Question",
+ "contenu": "Bonjour, j'ai un problĂšme trĂšs complexe avec l'activation de mon logiciel. " * 10
+ },
+ {
+ "date": "2023-01-11",
+ "emetteur": "SUPPORT",
+ "type": "Réponse",
+ "contenu": "Bonjour, nous avons bien reçu votre demande et nous allons vous aider à résoudre ce problÚme. " * 10
+ }
+ ]
+
+ tableau_long = agent._generer_tableau_questions_reponses(long_echange)
+ print("\nTABLEAU AVEC CONTENU LONG (SYNTHĂTISĂ):")
+ print(tableau_long)
+
+ # Tester avec une question sans réponse
+ sans_reponse = [
+ {
+ "date": "2023-01-10",
+ "emetteur": "CLIENT",
+ "type": "Question",
+ "contenu": "Bonjour, j'ai un problĂšme avec mon logiciel. Pouvez-vous m'aider?"
+ }
+ ]
+
+ tableau_sans_reponse = agent._generer_tableau_questions_reponses(sans_reponse)
+ print("\nTABLEAU AVEC QUESTION SANS RĂPONSE:")
+ print(tableau_sans_reponse)
+
+ print("\nTest terminé avec succÚs!")
+
+if __name__ == "__main__":
+ test_tableau_qr()
\ No newline at end of file
diff --git a/utils/report_formatter.py b/utils/report_formatter.py
new file mode 100644
index 0000000..d90f53f
--- /dev/null
+++ b/utils/report_formatter.py
@@ -0,0 +1,432 @@
+#!/usr/bin/env python3
+"""
+Module pour formater les rapports à partir des fichiers JSON générés par l'AgentReportGenerator.
+
+Ce module prend en entrée un fichier JSON contenant les analyses et génÚre différents
+formats de sortie (Markdown, HTML, etc.) sans utiliser de LLM.
+"""
+
+import os
+import json
+import argparse
+import sys
+import re
+from datetime import datetime
+from typing import Dict, List, Any, Optional, Tuple
+
+def generate_markdown_report(json_path: str, output_path: Optional[str] = None) -> Tuple[bool, str]:
+ """
+ GénÚre un rapport au format Markdown à partir d'un fichier JSON.
+
+ Args:
+ json_path: Chemin vers le fichier JSON contenant les données du rapport
+ output_path: Chemin de sortie pour le fichier Markdown (facultatif)
+
+ Returns:
+ Tuple (succÚs, chemin du fichier généré ou message d'erreur)
+ """
+ try:
+ # Lire le fichier JSON
+ with open(json_path, "r", encoding="utf-8") as f:
+ rapport_data = json.load(f)
+
+ # Si le chemin de sortie n'est pas spécifié, le créer à partir du chemin d'entrée
+ if not output_path:
+ # Remplacer l'extension JSON par MD
+ output_path = os.path.splitext(json_path)[0] + ".md"
+
+ # Générer le contenu Markdown
+ markdown_content = _generate_markdown_content(rapport_data)
+
+ # Ăcrire le contenu dans le fichier de sortie
+ with open(output_path, "w", encoding="utf-8") as f:
+ f.write(markdown_content)
+
+ print(f"Rapport Markdown généré avec succÚs: {output_path}")
+ return True, output_path
+
+ except Exception as e:
+ error_message = f"Erreur lors de la génération du rapport Markdown: {str(e)}"
+ print(error_message)
+ return False, error_message
+
+def _generate_markdown_content(rapport_data: Dict) -> str:
+ """
+ GénÚre le contenu Markdown à partir des données du rapport.
+
+ Args:
+ rapport_data: Dictionnaire contenant les données du rapport
+
+ Returns:
+ Contenu Markdown
+ """
+ ticket_id = rapport_data.get("ticket_id", "")
+ timestamp = rapport_data.get("metadata", {}).get("timestamp", "")
+ generation_date = rapport_data.get("metadata", {}).get("generation_date", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
+
+ # EntĂȘte du document
+ markdown = f"# Rapport d'analyse du ticket #{ticket_id}\n\n"
+ markdown += f"*Généré le: {generation_date}*\n\n"
+
+ # 1. Résumé exécutif
+ if "resume" in rapport_data and rapport_data["resume"]:
+ markdown += rapport_data["resume"] + "\n\n"
+
+ # 2. Chronologie des échanges (tableau)
+ markdown += "## Chronologie des échanges\n\n"
+
+ if "chronologie_echanges" in rapport_data and rapport_data["chronologie_echanges"]:
+ # Créer un tableau pour les échanges
+ markdown += "| Date | Ămetteur | Type | Contenu | Statut |\n"
+ markdown += "|------|---------|------|---------|--------|\n"
+
+ # Prétraitement pour détecter les questions sans réponse
+ questions_sans_reponse = {}
+ echanges = rapport_data["chronologie_echanges"]
+
+ for i, echange in enumerate(echanges):
+ if echange.get("type", "").lower() == "question" and echange.get("emetteur", "").lower() == "client":
+ has_response = False
+ # Vérifier si la question a une réponse
+ for j in range(i+1, len(echanges)):
+ next_echange = echanges[j]
+ if next_echange.get("type", "").lower() == "réponse" and next_echange.get("emetteur", "").lower() == "support":
+ has_response = True
+ break
+ questions_sans_reponse[i] = not has_response
+
+ # Générer les lignes du tableau
+ for i, echange in enumerate(echanges):
+ date = echange.get("date", "-")
+ emetteur = echange.get("emetteur", "-")
+ type_msg = echange.get("type", "-")
+ contenu = echange.get("contenu", "-")
+
+ # Ajouter un statut pour les questions sans réponse
+ statut = ""
+ if emetteur.lower() == "client" and type_msg.lower() == "question" and questions_sans_reponse.get(i, False):
+ statut = "**Sans réponse**"
+
+ markdown += f"| {date} | {emetteur} | {type_msg} | {contenu} | {statut} |\n"
+
+ # Ajouter une note si aucune réponse du support n'a été trouvée
+ if not any(echange.get("emetteur", "").lower() == "support" for echange in echanges):
+ markdown += "\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n\n"
+ else:
+ markdown += "*Aucun échange détecté dans le ticket.*\n\n"
+
+ # 3. Analyse des images
+ markdown += "## Analyse des images\n\n"
+
+ if "images_analyses" in rapport_data and rapport_data["images_analyses"]:
+ images_list = rapport_data["images_analyses"]
+
+ if not images_list:
+ markdown += "*Aucune image pertinente n'a été identifiée.*\n\n"
+ else:
+ for i, img_data in enumerate(images_list, 1):
+ image_name = img_data.get("image_name", f"Image {i}")
+ sorting_info = img_data.get("sorting_info", {})
+ reason = sorting_info.get("reason", "Non spécifiée")
+
+ markdown += f"### Image {i}: {image_name}\n\n"
+
+ # Raison de la pertinence
+ if reason:
+ markdown += f"**Raison de la pertinence**: {reason}\n\n"
+
+ # Ajouter l'analyse détaillée dans une section dépliable
+ analyse_detail = img_data.get("analyse", "Aucune analyse disponible")
+ if analyse_detail:
+ markdown += "\nAnalyse détaillée de l'image
\n\n"
+ markdown += "```\n" + analyse_detail + "\n```\n\n"
+ markdown += " \n\n"
+ else:
+ markdown += "*Aucune image pertinente n'a été analysée.*\n\n"
+
+ # 4. Diagnostic technique
+ if "diagnostic" in rapport_data and rapport_data["diagnostic"]:
+ markdown += "## Diagnostic technique\n\n"
+ markdown += rapport_data["diagnostic"] + "\n\n"
+
+ # Tableau récapitulatif des échanges (nouveau)
+ if "tableau_questions_reponses" in rapport_data and rapport_data["tableau_questions_reponses"]:
+ markdown += rapport_data["tableau_questions_reponses"] + "\n\n"
+
+ # Section séparatrice
+ markdown += "---\n\n"
+
+ # Détails des analyses effectuées
+ markdown += "# Détails des analyses effectuées\n\n"
+ markdown += "## Processus d'analyse\n\n"
+
+ # 1. Analyse de ticket
+ ticket_analyse = rapport_data.get("ticket_analyse", "")
+ if ticket_analyse:
+ markdown += "### Ătape 1: Analyse du ticket\n\n"
+ markdown += "L'agent d'analyse de ticket a extrait les informations suivantes du ticket d'origine:\n\n"
+ markdown += "\nCliquez pour voir l'analyse complĂšte du ticket
\n\n"
+ markdown += "```\n" + str(ticket_analyse) + "\n```\n\n"
+ markdown += " \n\n"
+ else:
+ markdown += "### Ătape 1: Analyse du ticket\n\n"
+ markdown += "*Aucune analyse de ticket disponible*\n\n"
+
+ # 2. Tri des images
+ markdown += "### Ătape 2: Tri des images\n\n"
+ markdown += "L'agent de tri d'images a évalué chaque image pour déterminer sa pertinence par rapport au problÚme client:\n\n"
+
+ # Création d'un tableau récapitulatif
+ images_list = rapport_data.get("images_analyses", [])
+ if images_list:
+ markdown += "| Image | Pertinence | Raison |\n"
+ markdown += "|-------|------------|--------|\n"
+
+ for img_data in images_list:
+ image_name = img_data.get("image_name", "Image inconnue")
+ sorting_info = img_data.get("sorting_info", {})
+ is_relevant = "Oui" if sorting_info else "Oui" # Par défaut, si présent dans la liste c'est pertinent
+ reason = sorting_info.get("reason", "Non spécifiée")
+
+ markdown += f"| {image_name} | {is_relevant} | {reason} |\n"
+
+ markdown += "\n"
+ else:
+ markdown += "*Aucune image n'a été triée pour ce ticket.*\n\n"
+
+ # 3. Analyse des images
+ markdown += "### Ătape 3: Analyse dĂ©taillĂ©e des images pertinentes\n\n"
+
+ if images_list:
+ for i, img_data in enumerate(images_list, 1):
+ image_name = img_data.get("image_name", f"Image {i}")
+ analyse_detail = img_data.get("analyse", "Analyse non disponible")
+
+ markdown += f"#### Image pertinente {i}: {image_name}\n\n"
+ markdown += "\nCliquez pour voir l'analyse complĂšte de l'image
\n\n"
+ markdown += "```\n" + str(analyse_detail) + "\n```\n\n"
+ markdown += " \n\n"
+ else:
+ markdown += "*Aucune image pertinente n'a été identifiée pour ce ticket.*\n\n"
+
+ # 4. Génération du rapport
+ markdown += "### Ătape 4: GĂ©nĂ©ration du rapport de synthĂšse\n\n"
+ markdown += "L'agent de génération de rapport a synthétisé toutes les analyses précédentes pour produire le rapport ci-dessus.\n\n"
+
+ # Informations techniques et métadonnées
+ markdown += "## Informations techniques\n\n"
+
+ # Statistiques
+ statistiques = rapport_data.get("statistiques", {})
+ metadata = rapport_data.get("metadata", {})
+
+ markdown += "### Statistiques\n\n"
+ markdown += f"- **Images analysées**: {statistiques.get('total_images', 0)}\n"
+ markdown += f"- **Images pertinentes**: {statistiques.get('images_pertinentes', 0)}\n"
+
+ if "generation_time" in statistiques:
+ markdown += f"- **Temps de génération**: {statistiques['generation_time']:.2f} secondes\n"
+
+ # ModÚle utilisé
+ markdown += "\n### ModÚle LLM utilisé\n\n"
+ markdown += f"- **ModÚle**: {metadata.get('model', 'Non spécifié')}\n"
+
+ if "model_version" in metadata:
+ markdown += f"- **Version**: {metadata.get('model_version', 'Non spécifiée')}\n"
+
+ markdown += f"- **Température**: {metadata.get('temperature', 'Non spécifiée')}\n"
+ markdown += f"- **Top_p**: {metadata.get('top_p', 'Non spécifié')}\n"
+
+ # Section sur les agents utilisés
+ if "agents" in metadata:
+ markdown += "\n### Agents impliqués\n\n"
+
+ agents = metadata["agents"]
+
+ # Agent d'analyse de ticket
+ if "json_analyser" in agents:
+ markdown += "#### Agent d'analyse du ticket\n"
+ json_analyser = agents["json_analyser"]
+ if "model_info" in json_analyser:
+ markdown += f"- **ModÚle**: {json_analyser['model_info'].get('name', 'Non spécifié')}\n"
+
+ # Agent de tri d'images
+ if "image_sorter" in agents:
+ markdown += "\n#### Agent de tri d'images\n"
+ sorter = agents["image_sorter"]
+ # Récupérer directement le modÚle ou via model_info selon la structure
+ if "model" in sorter:
+ markdown += f"- **ModÚle**: {sorter.get('model', 'Non spécifié')}\n"
+ markdown += f"- **Température**: {sorter.get('temperature', 'Non spécifiée')}\n"
+ markdown += f"- **Top_p**: {sorter.get('top_p', 'Non spécifié')}\n"
+ elif "model_info" in sorter:
+ markdown += f"- **ModÚle**: {sorter['model_info'].get('name', 'Non spécifié')}\n"
+ else:
+ markdown += f"- **ModÚle**: Non spécifié\n"
+
+ # Agent d'analyse d'images
+ if "image_analyser" in agents:
+ markdown += "\n#### Agent d'analyse d'images\n"
+ analyser = agents["image_analyser"]
+ # Récupérer directement le modÚle ou via model_info selon la structure
+ if "model" in analyser:
+ markdown += f"- **ModÚle**: {analyser.get('model', 'Non spécifié')}\n"
+ markdown += f"- **Température**: {analyser.get('temperature', 'Non spécifiée')}\n"
+ markdown += f"- **Top_p**: {analyser.get('top_p', 'Non spécifié')}\n"
+ elif "model_info" in analyser:
+ markdown += f"- **ModÚle**: {analyser['model_info'].get('name', 'Non spécifié')}\n"
+ else:
+ markdown += f"- **ModÚle**: Non spécifié\n"
+
+ return markdown
+
+def generate_html_report(json_path: str, output_path: Optional[str] = None) -> Tuple[bool, str]:
+ """
+ GénÚre un rapport au format HTML à partir d'un fichier JSON.
+
+ Args:
+ json_path: Chemin vers le fichier JSON contenant les données du rapport
+ output_path: Chemin de sortie pour le fichier HTML (facultatif)
+
+ Returns:
+ Tuple (succÚs, chemin du fichier généré ou message d'erreur)
+ """
+ try:
+ # Générer d'abord le Markdown
+ success, md_path_or_error = generate_markdown_report(json_path, None)
+
+ if not success:
+ return False, md_path_or_error
+
+ # Lire le contenu Markdown
+ with open(md_path_or_error, "r", encoding="utf-8") as f:
+ markdown_content = f.read()
+
+ # Si le chemin de sortie n'est pas spécifié, le créer à partir du chemin d'entrée
+ if not output_path:
+ # Remplacer l'extension JSON par HTML
+ output_path = os.path.splitext(json_path)[0] + ".html"
+
+ # Conversion Markdown â HTML (avec gestion de l'absence de mistune)
+ html_content = _simple_markdown_to_html(markdown_content)
+
+ # Essayer d'utiliser mistune pour une meilleure conversion si disponible
+ try:
+ import mistune
+ markdown = mistune.create_markdown(escape=False)
+ html_content = markdown(markdown_content)
+ print("Conversion HTML effectuée avec mistune")
+ except ImportError:
+ print("Module mistune non disponible, utilisation de la conversion HTML simplifiée")
+
+ # Créer un HTML complet avec un peu de style
+ html_page = f"""
+
+
+
+
+ Rapport d'analyse de ticket
+
+
+
+ {html_content}
+
+"""
+
+ # Ăcrire le contenu dans le fichier de sortie
+ with open(output_path, "w", encoding="utf-8") as f:
+ f.write(html_page)
+
+ print(f"Rapport HTML généré avec succÚs: {output_path}")
+ return True, output_path
+
+ except Exception as e:
+ error_message = f"Erreur lors de la génération du rapport HTML: {str(e)}"
+ print(error_message)
+ return False, error_message
+
+def _simple_markdown_to_html(markdown_content: str) -> str:
+ """
+ Convertit un contenu Markdown en HTML de façon simplifiée.
+
+ Args:
+ markdown_content: Contenu Markdown Ă convertir
+
+ Returns:
+ Contenu HTML
+ """
+ html = markdown_content
+
+ # Titres
+ html = re.sub(r'^# (.*?)$', r'\1
', html, flags=re.MULTILINE)
+ html = re.sub(r'^## (.*?)$', r'\1
', html, flags=re.MULTILINE)
+ html = re.sub(r'^### (.*?)$', r'\1
', html, flags=re.MULTILINE)
+ html = re.sub(r'^#### (.*?)$', r'\1
', html, flags=re.MULTILINE)
+
+ # Emphase
+ html = re.sub(r'\*\*(.*?)\*\*', r'\1', html)
+ html = re.sub(r'\*(.*?)\*', r'\1', html)
+
+ # Lists
+ html = re.sub(r'^- (.*?)$', r'\1', html, flags=re.MULTILINE)
+
+ # Paragraphes
+ html = re.sub(r'([^\n])\n([^\n])', r'\1
\2', html)
+ html = re.sub(r'\n\n', r'', html)
+
+ # Tables simplifiées (sans analyser la structure)
+ html = re.sub(r'\| (.*?) \|', r'
\1 | ', html)
+
+ # Code blocks
+ html = re.sub(r'```(.*?)```', r'\1
', html, flags=re.DOTALL)
+
+ # Envelopper dans des balises paragraphe
+ html = f"{html}
"
+
+ return html
+
+def process_report(json_path: str, output_format: str = "markdown") -> None:
+ """
+ Traite un rapport dans le format spécifié.
+
+ Args:
+ json_path: Chemin vers le fichier JSON contenant les données du rapport
+ output_format: Format de sortie (markdown ou html)
+ """
+ if output_format.lower() == "markdown":
+ generate_markdown_report(json_path)
+ elif output_format.lower() == "html":
+ generate_html_report(json_path)
+ else:
+ print(f"Format non supporté: {output_format}")
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Formateur de rapports Ă partir de fichiers JSON")
+ parser.add_argument("json_path", help="Chemin vers le fichier JSON contenant les données du rapport")
+ parser.add_argument("--format", "-f", choices=["markdown", "html"], default="markdown",
+ help="Format de sortie (markdown par défaut)")
+ parser.add_argument("--output", "-o", help="Chemin de sortie pour le rapport (facultatif)")
+
+ args = parser.parse_args()
+
+ if args.format == "markdown":
+ generate_markdown_report(args.json_path, args.output)
+ elif args.format == "html":
+ generate_html_report(args.json_path, args.output)
+ else:
+ print(f"Format non supporté: {args.format}")
\ No newline at end of file