15:40processok

This commit is contained in:
Ladebeze66 2025-04-07 15:40:17 +02:00
parent 5ddc0fef57
commit b871048da5
16 changed files with 2222 additions and 576 deletions

View File

@ -1,65 +0,0 @@
# SpecStory Artifacts Directory
This directory is automatically created and maintained by the SpecStory extension to preserve your Cursor composer and chat history.
## What's Here?
- `.specstory/history`: Contains markdown files of your AI coding sessions
- Each file represents a separate chat or composer session
- Files are automatically updated as you work
- `.specstory/cursor_rules_backups`: Contains backups of the `.cursor/rules/derived-cursor-rules.mdc` file
- Backups are automatically created each time the `.cursor/rules/derived-cursor-rules.mdc` file is updated
- You can enable/disable the Cursor Rules feature in the SpecStory settings, it is disabled by default
## Valuable Uses
- Capture: Keep your context window up-to-date when starting new Chat/Composer sessions via @ references
- Search: For previous prompts and code snippets
- Learn: Meta-analyze your patterns and learn from your past experiences
- Derive: Keep Cursor on course with your past decisions by automatically deriving Cursor rules from your AI interactions
## Version Control
We recommend keeping this directory under version control to maintain a history of your AI interactions. However, if you prefer not to version these files, you can exclude them by adding this to your `.gitignore`:
```
.specstory
```
We recommend not keeping the `.specstory/cursor_rules_backups` directory under version control if you are already using git to version the `.cursor/rules` directory, and committing regularly. You can exclude it by adding this to your `.gitignore`:
```
.specstory/cursor_rules_backups
```
## Searching Your Codebase
When searching your codebase in Cursor, search results may include your previous AI coding interactions. To focus solely on your actual code files, you can exclude the AI interaction history from search results.
To exclude AI interaction history:
1. Open the "Find in Files" search in Cursor (Cmd/Ctrl + Shift + F)
2. Navigate to the "files to exclude" section
3. Add the following pattern:
```
.specstory/*
```
This will ensure your searches only return results from your working codebase files.
## Notes
- Auto-save only works when Cursor/sqlite flushes data to disk. This results in a small delay after the AI response is complete before SpecStory can save the history.
- Auto-save does not yet work on remote WSL workspaces.
## Settings
You can control auto-saving behavior in Cursor:
1. Open Cursor → Settings → VS Code Settings (Cmd/Ctrl + ,)
2. Search for "SpecStory"
3. Find "Auto Save" setting to enable/disable
Auto-save occurs when changes are detected in Cursor's sqlite database, or every 2 minutes as a safety net.

View File

@ -1,40 +0,0 @@
<!-- Generated by SpecStory -->
# Déplacement du fichier de workspace (2025-04-04 08:46:10)
_**User**_
ce fichier json si je le supprime il me sort du mon projet il faudrait déplacer le workspace à la racine de mon projet
---
_**Assistant**_
Pour déplacer votre fichier de workspace à la racine de votre projet, vous pouvez suivre ces étapes :
1. **Déplacez le fichier** : Déplacez le fichier `llm-ticket3.code-workspace` à la racine de votre projet. Cela signifie que vous devez le placer dans le dossier où se trouvent vos fichiers de projet principaux.
2. **Mettez à jour le chemin dans le fichier** : Une fois que vous avez déplacé le fichier, ouvrez-le et mettez à jour le chemin dans la section `folders`. Par exemple, si vous déplacez le fichier à la racine, vous pouvez le modifier comme suit :
```json
{
"folders": [
{
"path": "."
}
]
}
```
Cela indique que le dossier de votre projet est maintenant le répertoire courant.
3. **Ouvrez le workspace** : Après avoir effectué ces modifications, ouvrez le fichier `llm-ticket3.code-workspace` dans votre IDE pour vous assurer que tout fonctionne correctement.
Si vous avez besoin d'aide pour effectuer ces étapes, n'hésitez pas à demander !
---
---

View File

@ -8797,6 +8797,16 @@ Quels aspects souhaitez-vous modifier en priorité dans le comportement de vos a
---
_**User**_
probleme pour changer de branche
---
En ce qui concerne l'analyse de texte (agentjsonanalyser) il faudrait déterminer quel est précisement le fichier récupéré
---
---

View File

@ -0,0 +1,591 @@
<!-- Generated by SpecStory -->
# Identification du fichier pour agentjsonanalyser (2025-04-07 14:42:16)
_**User**_
En ce qui concerne l'analyse de texte (agentjsonanalyser) il faudrait déterminer quel est précisement le fichier récupéré
---
_**Assistant**_
Pour déterminer quel fichier est récupéré par l'analyseur de texte `agentjsonanalyser`, il serait utile de vérifier comment ce module est intégré dans votre code. En général, cela pourrait impliquer de regarder les appels de fonction ou les importations dans votre projet.
Dans le fichier `markdown_to_json.py`, il n'y a pas de référence directe à `agentjsonanalyser`. Cependant, si ce module est utilisé ailleurs dans votre code, vous devriez chercher des lignes comme :
```python
from agentjsonanalyser import ...
```
ou des appels de fonction qui pourraient indiquer quel fichier est analysé.
Si vous avez un fichier spécifique ou une section de code où `agentjsonanalyser` est utilisé, je peux vous aider à l'examiner. Sinon, vous pourriez également vérifier la documentation ou les commentaires dans votre code pour voir si des informations supplémentaires sont fournies sur le fichier analysé.
Si vous avez besoin d'aide pour une partie spécifique de votre code, n'hésitez pas à la partager !
---
_**User**_
peux tu déterminer quel est le fichier récupéré
---
_**Assistant**_
Dans le fichier `agent_json_analyser.py`, l'agent `AgentJsonAnalyser` est conçu pour analyser des tickets au format JSON. Le fichier récupéré pour l'analyse est celui qui est passé en tant que paramètre à la méthode `executer`, qui prend un dictionnaire `ticket_data` contenant les données du ticket.
Voici les points clés concernant le fichier récupéré :
1. **Format des données** : Le fichier JSON doit contenir des informations structurées sur le ticket, y compris des champs comme `code`, `name`, `description`, `messages`, et potentiellement d'autres métadonnées.
2. **Méthode `executer`** : Cette méthode est responsable de l'analyse du ticket. Elle appelle la méthode `_formater_ticket_pour_analyse` pour préparer les données du ticket avant de les envoyer à un modèle de langage (LLM) pour analyse.
3. **Exemple de structure de données** : Le dictionnaire `ticket_data` pourrait ressembler à ceci :
```json
{
"code": "TICKET-123",
"name": "Problème de connexion",
"description": "L'utilisateur ne peut pas se connecter.",
"messages": [
{
"from": "utilisateur",
"date": "2023-10-01",
"content": "Je ne peux pas me connecter."
}
],
"metadata": {
"priorité": "haute",
"statut": "ouvert"
}
}
```
Pour résumer, le fichier récupéré est un fichier JSON contenant les informations du ticket, et il est analysé par l'agent via la méthode `executer`. Si vous avez besoin de plus de détails ou d'exemples spécifiques, n'hésitez pas à demander !
---
_**User**_
peut on déterminer quels sont les fichiers utilisés dana agent_json_analyser.py en passant en revue son code et celui de orchestror.py
---
_**Assistant**_
Pour déterminer quels fichiers sont utilisés dans `agent_json_analyser.py` et `orchestrator.py`, examinons les deux fichiers et les interactions entre eux.
### 1. **Fichiers utilisés dans `agent_json_analyser.py`**
Dans `agent_json_analyser.py`, l'agent `AgentJsonAnalyser` est conçu pour analyser des tickets JSON. Voici les points clés concernant les fichiers :
- **Fichier JSON** : L'agent s'attend à recevoir un dictionnaire `ticket_data` qui contient les données du ticket. Ce dictionnaire est généralement chargé à partir d'un fichier JSON, comme indiqué dans la méthode `executer` où il utilise `self.llm.interroger(prompt)` pour analyser les données.
### 2. **Fichiers utilisés dans `orchestrator.py`**
Dans `orchestrator.py`, plusieurs méthodes sont responsables de la gestion des fichiers :
- **Méthode `trouver_rapport`** : Cette méthode cherche des fichiers de rapport dans différents emplacements. Elle vérifie les fichiers JSON et Markdown pour un ticket donné. Les chemins des fichiers sont construits en fonction de l'ID du ticket et de l'emplacement d'extraction.
- **Méthode `_preparer_donnees_ticket`** : Cette méthode essaie de charger les données du ticket à partir d'un fichier JSON, et si cela échoue, elle essaie de charger à partir d'un fichier Markdown. Les fichiers recherchés sont :
- `"{ticket_id}_rapport.json"`
- `"{ticket_id}_rapport.md"`
- **Méthode `_extraire_donnees_de_markdown`** : Cette méthode extrait les données d'un fichier Markdown et les structure en un dictionnaire. Elle lit le contenu du fichier Markdown spécifié par `md_path`.
### Conclusion
En résumé, les fichiers utilisés dans `agent_json_analyser.py` et `orchestrator.py` sont principalement des fichiers JSON et Markdown qui contiennent les données des tickets. Voici les types de fichiers et leurs emplacements :
- **Fichiers JSON** : Cherchés dans les répertoires d'extraction, nommés selon le format `"{ticket_id}_rapport.json"`.
- **Fichiers Markdown** : Cherchés dans les mêmes répertoires, nommés selon le format `"{ticket_id}_rapport.md"`.
Ces fichiers sont essentiels pour le fonctionnement de l'agent d'analyse JSON et de l'orchestrateur, car ils fournissent les données nécessaires pour l'analyse et le traitement des tickets. Si vous avez besoin d'une exploration plus approfondie ou d'exemples spécifiques, n'hésitez pas à demander !
---
_**User**_
il faut déterminer avec précision quel est réellment le rapport utiliser dans cet agent car s'il s'agit du markdown alors il faut revoir son nom et sa configuration pour mieux l'adapter as tu compris?
---
_**Assistant**_
Examinons plus précisément quel rapport est réellement utilisé par l'agent JSON.
Dans `orchestrator.py`, l'agent JSON est appelé via cette séquence:
1. Dans la méthode `traiter_ticket()`, l'orchestrateur commence par chercher les rapports avec `trouver_rapport(extraction_path, ticket_id)`
2. Ensuite, il prépare les données du ticket avec `_preparer_donnees_ticket(rapports, ticket_id)`
3. Si des données sont trouvées, il appelle l'agent JSON avec `self.json_agent.executer(ticket_data)`
La méthode `_preparer_donnees_ticket()` est cruciale. Elle essaie d'abord de charger les données depuis un fichier JSON. Si cela échoue, elle essaie de charger depuis un fichier Markdown en utilisant `_extraire_donnees_de_markdown()`.
En analysant le code, je constate que:
1. L'orchestrateur cherche d'abord le fichier `"{ticket_id}_rapport.json"`
2. Si ce fichier n'existe pas ou s'il y a une erreur, il essaie de charger `"{ticket_id}_rapport.md"`
3. La méthode `_extraire_donnees_de_markdown()` convertit le contenu Markdown en une structure de données similaire à celle attendue du fichier JSON
Donc, l'agent JSON peut en fait traiter des données provenant d'un fichier Markdown, mais seulement après que l'orchestrateur ait converti le contenu Markdown en une structure de données appropriée.
Si le rapport Markdown est le format principal utilisé, il faudrait renommer l'agent en quelque chose comme `AgentTicketAnalyser` au lieu de `AgentJsonAnalyser` pour refléter sa capacité à traiter des données de différentes sources, pas seulement JSON. De plus, il faudrait ajuster sa configuration pour mieux gérer les particularités des données extraites du Markdown.
Est-ce que cette analyse répond à votre question?
---
_**User**_
oui que ferais tu pour améliorer ce fonctionnement
---
_**Assistant**_
Pour améliorer ce fonctionnement, voici mes recommandations :
1. **Renommer l'agent** pour refléter sa fonction réelle :
- Renommer `AgentJsonAnalyser` en `AgentTicketAnalyser` ou `AgentRapportAnalyser`
- Mettre à jour les références dans `orchestrator.py` pour utiliser le nouveau nom
2. **Restructurer le traitement des fichiers** :
- Créer une abstraction de source de données avec des implémentations spécifiques pour JSON et Markdown
- Implémenter une classe `TicketDataLoader` avec des méthodes spécifiques pour chaque format
3. **Améliorer l'extraction des données Markdown** :
- Renforcer la robustesse de la méthode `_extraire_donnees_de_markdown()`
- Ajouter des validations pour s'assurer que tous les champs obligatoires sont présents
- Gérer correctement les formats de date et les types de données
4. **Restructurer le prompt système** :
- Adapter le prompt système pour qu'il soit plus flexible quant à la source des données
- Inclure des instructions spécifiques pour le traitement des données issues du Markdown
5. **Ajouter une détection de format** :
- Implémenter une méthode de détection automatique du format pour mieux adapter le traitement
- Ajouter des métadonnées sur la source dans les données traitées
6. **Meilleure gestion des erreurs** :
- Ajouter des logs détaillés sur le type de fichier traité et les problèmes rencontrés
- Gérer proprement les cas où la structure du Markdown ne correspond pas aux attentes
7. **Documentation claire** :
- Documenter les formats attendus pour les fichiers Markdown
- Expliquer comment l'agent interprète différentes sections du Markdown
8. **Tests spécifiques** :
- Ajouter des tests unitaires spécifiques pour chaque format de fichier
- Valider que le traitement est cohérent entre les formats JSON et Markdown
Ces améliorations permettraient d'avoir un agent plus flexible, capable de traiter efficacement les deux formats tout en maintenant une cohérence dans l'analyse produite.
---
_**User**_
tu peux procéder aux améliorations que tu viens de me donner
---
_**Assistant**_
Je vais procéder aux améliorations suggérées. Commençons par examiner la structure actuelle puis implémentons les changements nécessaires.
---
<details>
<summary>Searched codebase "agents base_agent.py" • **25** results</summary>
| File | Lines |
|------|-------|
| `agents/base_agent.py` | L1-29 |
| `agents/agent_report_generator.py` | L227-249 |
| `agents/agent_image_analyser.py` | L1-39 |
| `agents/agent_report_generator.py` | L249-282 |
| `agents/agent_json_analyser.py` | L1-32 |
| `agents/agent_image_analyser.py` | L190-221 |
| `agents/agent_image_analyser.py` | L105-143 |
| `agents/agent_report_generator.py` | L69-98 |
| `agents/agent_image_sorter.py` | L114-152 |
| `agents/agent_json_analyser.py` | L63-109 |
| `agents/agent_image_sorter.py` | L1-48 |
| `agents/agent_json_analyser.py` | L109-160 |
| `agents/agent_image_analyser.py` | L70-105 |
| `agents/agent_image_sorter.py` | L335-370 |
| `agents/agent_image_sorter.py` | L152-185 |
| `agents/agent_report_generator.py` | L98-134 |
| `agents/agent_report_generator.py` | L1-38 |
| `agents/agent_image_analyser.py` | L287-320 |
| `agents/agent_image_sorter.py` | L262-294 |
| `agents/agent_image_sorter.py` | L294-335 |
| `agents/agent_image_sorter.py` | L370-385 |
| `agents/agent_image_sorter.py` | L185-224 |
| `agents/agent_image_analyser.py` | L143-190 |
| `agents/agent_report_generator.py` | L282-311 |
| `agents/agent_report_generator.py` | L173-206 |
</details>
---
Maintenant, vérifions la structure du projet pour mieux comprendre comment les fichiers sont organisés.
---
<details>
<summary>Listed directory agents • **7** results</summary>
| Name |
|-------|
| 📄 `agent_image_analyser.py` |
| 📄 `agent_image_sorter.py` |
| 📄 `agent_json_analyser.py` |
| 📄 `agent_report_generator.py` |
| 📄 `agent_report_generator.py.backup` |
| 📄 `base_agent.py` |
| 📁 `__pycache__` |
</details>
---
Examinons plus en détail le module d'extraction des données à partir de Markdown :
---
Read file: utils/markdown_to_json.py
---
Maintenant, réalisons les améliorations suggérées. Commençons par créer un nouvel agent plus générique (renommer l'agent) et implémenter une meilleure gestion des différents formats.
1. Créons d'abord un module pour charger les données de tickets :
---
```diff
-
+ import os
+ import re
+ import json
+ import logging
+ from typing import Dict, Optional, Any, List, Union
+ from abc import ABC, abstractmethod
+
+ logger = logging.getLogger("TicketDataLoader")
+
+ class TicketDataSource(ABC):
+ """Classe abstraite pour les sources de données de tickets"""
+
+ @abstractmethod
+ def charger(self, chemin_fichier: str) -> Dict[str, Any]:
+ """Charge les données du ticket depuis un fichier source"""
+ pass
+
+ @abstractmethod
+ def get_format(self) -> str:
+ """Retourne le format de la source de données"""
+ pass
+
+ def valider_donnees(self, donnees: Dict[str, Any]) -> bool:
+ """Vérifie si les données chargées contiennent les champs obligatoires"""
+ champs_obligatoires = ["code", "name"]
+ return all(field in donnees for field in champs_obligatoires)
+
+
+ class JsonTicketSource(TicketDataSource):
+ """Source de données pour les tickets au format JSON"""
+
+ def charger(self, chemin_fichier: str) -> Dict[str, Any]:
+ """Charge les données du ticket depuis un fichier JSON"""
+ try:
+ with open(chemin_fichier, 'r', encoding='utf-8') as f:
+ donnees = json.load(f)
+
+ # Ajout de métadonnées sur la source
+ if "metadata" not in donnees:
+ donnees["metadata"] = {}
+
+ donnees["metadata"]["source_file"] = chemin_fichier
+ donnees["metadata"]["format"] = "json"
+
+ return donnees
+ except Exception as e:
+ logger.error(f"Erreur lors du chargement du fichier JSON {chemin_fichier}: {str(e)}")
+ raise ValueError(f"Impossible de charger le fichier JSON: {str(e)}")
+
+ def get_format(self) -> str:
+ return "json"
+
+
+ class MarkdownTicketSource(TicketDataSource):
+ """Source de données pour les tickets au format Markdown"""
+
+ def charger(self, chemin_fichier: str) -> Dict[str, Any]:
+ """Charge les données du ticket depuis un fichier Markdown"""
+ try:
+ with open(chemin_fichier, 'r', encoding='utf-8') as f:
+ contenu_md = f.read()
+
+ # Extraire les données du contenu Markdown
+ donnees = self._extraire_donnees_de_markdown(contenu_md)
+
+ # Ajout de métadonnées sur la source
+ if "metadata" not in donnees:
+ donnees["metadata"] = {}
+
+ donnees["metadata"]["source_file"] = chemin_fichier
+ donnees["metadata"]["format"] = "markdown"
+
+ return donnees
+ except Exception as e:
+ logger.error(f"Erreur lors du chargement du fichier Markdown {chemin_fichier}: {str(e)}")
+ raise ValueError(f"Impossible de charger le fichier Markdown: {str(e)}")
+
+ def get_format(self) -> str:
+ return "markdown"
+
+ def _extraire_donnees_de_markdown(self, contenu_md: str) -> Dict[str, Any]:
+ """Extrait les données structurées d'un contenu Markdown"""
+ donnees = {}
+
+ # Diviser le contenu en sections
+ sections = re.split(r"\n## ", contenu_md)
+
+ # Traiter chaque section
+ for section in sections:
+ if section.startswith("Informations du ticket"):
+ ticket_info = self._analyser_infos_ticket(section)
+ donnees.update(ticket_info)
+ elif section.startswith("Messages"):
+ messages = self._analyser_messages(section)
+ donnees["messages"] = messages
+ elif section.startswith("Informations sur l'extraction"):
+ extraction_info = self._analyser_infos_extraction(section)
+ donnees.update(extraction_info)
+
+ # Réorganiser les champs pour que la description soit après "name"
+ ordered_fields = ["id", "code", "name", "description"]
+ ordered_data = {}
+
+ # D'abord ajouter les champs dans l'ordre spécifié
+ for field in ordered_fields:
+ if field in donnees:
+ ordered_data[field] = donnees[field]
+
+ # Ensuite ajouter les autres champs
+ for key, value in donnees.items():
+ if key not in ordered_data:
+ ordered_data[key] = value
+
+ # S'assurer que la description est présente
+ if "description" not in ordered_data:
+ ordered_data["description"] = ""
+
+ return ordered_data
+
+ def _analyser_infos_ticket(self, section: str) -> Dict[str, Any]:
+ """Analyse la section d'informations du ticket"""
+ info = {}
+ description = []
+ capturing_description = False
+
+ lines = section.strip().split("\n")
+ i = 0
+ while i < len(lines):
+ line = lines[i]
+
+ # Si on est déjà en train de capturer la description
+ if capturing_description:
+ # Vérifie si on atteint une nouvelle section ou un nouveau champ
+ if i + 1 < len(lines) and (lines[i + 1].startswith("## ") or lines[i + 1].startswith("- **")):
+ capturing_description = False
+ info["description"] = "\n".join(description).strip()
+ else:
+ description.append(line)
+ i += 1
+ continue
+
+ # Détecte le début de la description
+ desc_match = re.match(r"- \*\*description\*\*:", line)
+ if desc_match:
+ capturing_description = True
+ i += 1 # Passe à la ligne suivante
+ continue
+
+ # Traite les autres champs normalement
+ match = re.match(r"- \*\*(.*?)\*\*: (.*)", line)
+ if match:
+ key, value = match.groups()
+ key = key.lower().replace("/", "_").replace(" ", "_")
+ info[key] = value.strip()
+
+ i += 1
+
+ # Si on finit en capturant la description, l'ajouter au dictionnaire
+ if capturing_description and description:
+ info["description"] = "\n".join(description).strip()
+ elif "description" not in info:
+ info["description"] = ""
+
+ return info
+
+ def _analyser_messages(self, section: str) -> List[Dict[str, Any]]:
+ """Analyse la section des messages"""
+ messages = []
+ current_message = {}
+ in_message = False
+
+ lines = section.strip().split("\n")
+
+ for line in lines:
+ if line.startswith("### Message"):
+ if current_message:
+ messages.append(current_message)
+ current_message = {}
+ in_message = True
+
+ elif line.startswith("**") and in_message:
+ match = re.match(r"\*\*(.*?)\*\*: (.*)", line)
+ if match:
+ key, value = match.groups()
+ key = key.lower().replace("/", "_").replace(" ", "_")
+ current_message[key] = value.strip()
+ else:
+ if in_message:
+ current_message["content"] = current_message.get("content", "") + line + "\n"
+
+ if current_message:
+ messages.append(current_message)
+
+ # Nettoyer le contenu des messages
+ for message in messages:
+ if "content" in message:
+ message["content"] = message["content"].strip()
+
+ return messages
+
+ def _analyser_infos_extraction(self, section: str) -> Dict[str, Any]:
+ """Analyse la section d'informations sur l'extraction"""
+ extraction_info = {}
+
+ lines = section.strip().split("\n")
+ for line in lines:
+ match = re.match(r"- \*\*(.*?)\*\*: (.*)", line)
+ if match:
+ key, value = match.groups()
+ key = key.lower().replace("/", "_").replace(" ", "_")
+ extraction_info[key] = value.strip()
+
+ return extraction_info
+
+
+ class TicketDataLoader:
+ """Classe pour charger les données de tickets à partir de différentes sources"""
+
+ def __init__(self):
+ self.sources = {
+ "json": JsonTicketSource(),
+ "markdown": MarkdownTicketSource()
+ }
+
+ def detecter_format(self, chemin_fichier: str) -> str:
+ """Détecte le format du fichier à partir de son extension"""
+ ext = os.path.splitext(chemin_fichier)[1].lower()
+ if ext == '.json':
+ return "json"
+ elif ext in ['.md', '.markdown']:
+ return "markdown"
+ else:
+ raise ValueError(f"Format de fichier non supporté: {ext}")
+
+ def charger(self, chemin_fichier: str, format_force: Optional[str] = None) -> Dict[str, Any]:
+ """
+ Charge les données d'un ticket à partir d'un fichier
+
+ Args:
+ chemin_fichier: Chemin du fichier à charger
+ format_force: Format à utiliser (ignore la détection automatique)
+
+ Returns:
+ Dictionnaire contenant les données du ticket
+ """
+ if not os.path.exists(chemin_fichier):
+ raise FileNotFoundError(f"Le fichier {chemin_fichier} n'existe pas")
+
+ format_fichier = format_force if format_force else self.detecter_format(chemin_fichier)
+
+ if format_fichier not in self.sources:
+ raise ValueError(f"Format non supporté: {format_fichier}")
+
+ logger.info(f"Chargement des données au format {format_fichier} depuis {chemin_fichier}")
+ donnees = self.sources[format_fichier].charger(chemin_fichier)
+
+ # Validation des données
+ if not self.sources[format_fichier].valider_donnees(donnees):
+ logger.warning(f"Les données chargées depuis {chemin_fichier} ne contiennent pas tous les champs obligatoires")
+
+ return donnees
+
+ def trouver_ticket(self, dossier: str, ticket_id: str) -> Optional[Dict[str, str]]:
+ """
+ Recherche les fichiers de ticket dans différents emplacements possibles
+
+ Args:
+ dossier: Dossier de base pour la recherche
+ ticket_id: ID du ticket (ex: T0101)
+
+ Returns:
+ Dictionnaire avec les chemins des fichiers trouvés par format
+ """
+ resultats = {"json": None, "markdown": None}
+
+ # Liste des emplacements possibles pour les rapports
+ emplacements_possibles = [
+ # 1. Dans le répertoire d'extraction directement
+ dossier,
+
+ # 2. Dans un sous-répertoire "data"
+ os.path.join(dossier, "data"),
+
+ # 3. Dans un sous-répertoire spécifique au ticket pour les rapports
+ os.path.join(dossier, f"{ticket_id}_rapports"),
+
+ # 4. Dans un sous-répertoire "rapports"
+ os.path.join(dossier, "rapports")
+ ]
+
+ # Vérifier chaque emplacement
+ for base_location in emplacements_possibles:
+ # Chercher le fichier JSON
+ json_path = os.path.join(base_location, f"{ticket_id}_rapport.json")
+ if os.path.exists(json_path):
+ logger.info(f"Rapport JSON trouvé à: {json_path}")
+ resultats["json"] = json_path
+
+ # Chercher le fichier Markdown
+ md_path = os.path.join(base_location, f"{ticket_id}_rapport.md")
+ if os.path.exists(md_path):
+ logger.info(f"Rapport Markdown trouvé à: {md_path}")
+ resultats["markdown"] = md_path
+
+ if not resultats["json"] and not resultats["markdown"]:
+ logger.warning(f"Aucun rapport trouvé pour {ticket_id} dans {dossier}")
+ return None
+
+ return resultats
```
---
---
---

View File

@ -0,0 +1,352 @@
<!-- Generated by SpecStory -->
# Optimisation des rôles des agents de support (2025-04-07 15:11:33)
_**User**_
>Tout dabord le tri d'image fonctionne. Je voudrai modifier le rôle de mon agent_ticket il doit mettre en perspective le nom de la demande ("name") qui est souvent le problème soulevé par le client. Ensuite la "description" du problème si elle est présente qui va ajouter du contexte au problème. Etablir la chronologie de la discussion client/support mettre en avant les questions/réponses qui ressortent des demandes client réponses support bien évidemment il se peut qu'il n'y ait pas de réponse défini. Il afut rester dans une démarche technique, factuelle la réponse va partir en contexte pour analyser en profondeur les images pertinentes. Ensuite le dernier agent agent_report lui va prendre les résultats du agent_ticket_analyser et agent_image_analyser son rôle est d'améliorer cette fonction d'établir clairement les questions/réponses support/client ou vice versa en un tableau markdown dans le rapport. En identifiant brièvement support et client question réponse face à face si elles existe. Il faut épurer les rôles de mes agents pour le moment ils efffectuent des tâches d'analyse et autres dont je n'ai pas besoin pour le moment. JE ne suis peut etre pas très clair
---
_**Assistant**_
Je vais examiner votre agent de génération de rapport et vous proposer des modifications basées sur vos besoins.
---
<details>
<summary>Searched codebase "agent_ticket" • **1** results</summary>
| File |
|------|
| `undefined` |
</details>
---
Read file: /home/fgras-ca/llm-ticket3/agents/agent_ticket_analyser.py
---
Read file: /home/fgras-ca/llm-ticket3/agents/agent_ticket_analyser.py
---
<details>
<summary>Searched codebase "image_analyser" • **1** results</summary>
| File |
|------|
| `undefined` |
</details>
---
Read file: /home/fgras-ca/llm-ticket3/agents/agent_image_analyser.py
---
Read file: /home/fgras-ca/llm-ticket3/agents/agent_image_analyser.py
---
Maintenant, je vais modifier les fichiers selon vos besoins. Je vais d'abord modifier le AgentTicketAnalyser pour mettre en perspective le nom de la demande, la description et établir la chronologie des discussions client/support.
---
```diff
self.max_tokens = 1500
self.system_prompt = """Tu es un expert en analyse de tickets pour le support informatique de BRG_Lab.
- Ton rôle est d'extraire et d'analyser les informations importantes des tickets, quelle que soit leur source (JSON ou Markdown).
- Organise ta réponse avec les sections suivantes:
- 1. Résumé du problème
- 2. Informations techniques essentielles (logiciels, versions, etc.)
- 3. Contexte client (urgence, impact)
- 4. Pistes d'analyse suggérées
-
- Sois précis, factuel et synthétique dans ton analyse.
- Vérifie notamment les champs suivants si disponibles :
- - Le logiciel concerné
- - La version du logiciel
- - L'environnement technique
- - La priorité ou urgence du ticket
- - Les étapes de reproduction du problème
+ Ton rôle est d'extraire et d'analyser les informations importantes des tickets.
+
+ 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
+ 3. Établir une chronologie claire des échanges client/support en identifiant précisément:
+ - Les questions posées par le client
+ - Les réponses fournies par le support
+ - Les informations techniques fournies par chaque partie
+
+ Sois factuel et reste dans une démarche technique. Ton analyse sera utilisée comme contexte pour l'analyse des images pertinentes.
+
+ 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
"""
{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()}.
"""
# Initialiser avec les informations de base
- info = f"## TICKET {ticket_data.get('code', 'Inconnu')}: {ticket_data.get('name', 'Sans titre')}\n\n"
+ ticket_name = ticket_data.get('name', 'Sans titre')
+ ticket_code = ticket_data.get('code', 'Inconnu')
+
+ info = f"## TICKET {ticket_code}: {ticket_name}\n\n"
+ info += f"## NOM DE LA DEMANDE (PROBLÈME INITIAL)\n{ticket_name}\n\n"
# Ajouter la description
description = ticket_data.get('description', '')
if description:
- info += f"## DESCRIPTION\n{description}\n\n"
+ info += f"## DESCRIPTION DU PROBLÈME\n{description}\n\n"
# Ajouter les informations du ticket (exclure certains champs spécifiques)
champs_a_exclure = ['code', 'name', 'description', 'messages', 'metadata']
- info += "## INFORMATIONS DU TICKET\n"
+ info += "## INFORMATIONS TECHNIQUES DU TICKET\n"
for key, value in ticket_data.items():
if key not in champs_a_exclure and value:
info += "\n"
- # Ajouter les messages (conversations)
+ # Ajouter les messages (conversations) avec un formatage amélioré pour distinguer client/support
messages = ticket_data.get('messages', [])
if messages:
- info += "## ÉCHANGES ET MESSAGES\n"
+ info += "## CHRONOLOGIE DES ÉCHANGES CLIENT/SUPPORT\n"
for i, msg in enumerate(messages):
# Vérifier que le message est bien un dictionnaire
date = msg.get('date', 'Date inconnue')
content = msg.get('content', '')
+
+ # Identifier si c'est client ou support
+ sender_type = "CLIENT" if "client" in sender.lower() else "SUPPORT" if "support" in sender.lower() else "AUTRE"
# Formater correctement la date si possible
pass # Garder la date d'origine en cas d'erreur
- info += f"### Message {i+1} - De: {sender} - Date: {date}\n{content}\n\n"
+ info += f"### Message {i+1} - [{sender_type}] De: {sender} - Date: {date}\n{content}\n\n"
# Ajouter les métadonnées techniques si présentes
```
---
Maintenant, je vais modifier l'agent_report_generator.py pour qu'il mette en avant la chronologie des échanges client/support dans un tableau Markdown et s'assure de bien identifier les questions/réponses :
---
```diff
self.max_tokens = 2500
self.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG_Lab.
- Ta mission est de synthétiser toutes les analyses (JSON et images) en un rapport structuré et exploitable.
+ Ta mission est de synthétiser les analyses (ticket et images) en un rapport structuré et exploitable.
- Structure ton rapport ainsi:
- 1. Résumé exécutif: Synthèse du problème et des conclusions principales
- 2. Analyse du ticket: Détails extraits du ticket client
- 3. Analyse des images: Résumé des images pertinentes et leur contribution
- 4. Diagnostic technique: Interprétation consolidée des informations
- 5. Recommandations: Actions suggérées pour résoudre le problème
-
- Chaque section doit être factuelle, précise et orientée solution.
- Inclus tous les détails techniques pertinents (versions, configurations, messages d'erreur).
- Assure une traçabilité complète entre les données sources et tes conclusions."""
+ 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."""
# Appliquer la configuration au LLM
prompt += f"""
Ton rapport doit être structuré avec les sections suivantes:
- 1. Résumé exécutif
- 2. Analyse détaillée du ticket
- 3. Analyse des images pertinentes
- 4. Diagnostic technique
- 5. Recommandations
-
- Fournir le rapport en format Markdown, avec des titres clairs et une structure cohérente.
- Assure-toi d'inclure toutes les informations techniques importantes et les liens entre les différentes analyses.
+ 1. Résumé exécutif: Synthèse concise du problème initial (reprend le nom de la demande + description)
+ 2. Chronologie des échanges: PRÉSENTE IMPÉRATIVEMENT SOUS FORME DE TABLEAU MARKDOWN, avec colonnes: Date | Émetteur (CLIENT/SUPPORT) | Type (Question/Réponse) | Contenu
+ 3. Analyse des images pertinentes: Ce que montrent les captures d'écran
+ 4. Diagnostic technique: Points clés et interprétation technique
+
+ IMPORTANT:
+ - Le tableau de chronologie doit être formaté correctement en Markdown (|---|---|---|---|)
+ - Tu dois clairement identifier qui est CLIENT et qui est SUPPORT
+ - Ne fais pas de recommandations génériques, reste factuel
+ - Utilise le format Markdown pour structurer ton rapport avec des titres clairs
+
+ Fournis un rapport concis, factuel et technique.
"""
```
---
Enfin, je vais modifier l'agent_image_analyser.py pour simplifier son rôle et le concentrer sur l'analyse factuelle des images en relation avec le contexte du ticket :
---
```diff
self.max_tokens = 1200
self.system_prompt = """Tu es un expert en analyse d'images pour le support technique de BRG_Lab.
- Ta mission est d'analyser des captures d'écran ou des images techniques en tenant compte du contexte du ticket.
-
- Pour chaque image, structure ton analyse ainsi:
- 1. Description factuelle: Ce que contient l'image (interface, message d'erreur, etc.)
- 2. Éléments techniques importants: Versions, codes d'erreur, paramètres visibles
- 3. Interprétation: Ce que cette image révèle sur le problème client
- 4. Relation avec le ticket: Comment cette image aide à résoudre le problème décrit
-
- IMPORTANT: Ne commence JAMAIS ta réponse par "Je n'ai pas accès à l'image" ou "Je ne peux pas directement visualiser l'image".
- Si tu ne peux pas analyser l'image, réponds simplement "ERREUR: Impossible d'analyser l'image".
-
- Concentre-toi sur les éléments techniques pertinents pour le support logiciel."""
+ 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:
+ 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
+
+ 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
+
+ Ton analyse sera utilisée comme élément factuel pour un rapport technique plus complet."""
# Appliquer la configuration au LLM
{contexte}
- Fournis une analyse structurée de l'image avec les sections suivantes:
- 1. Description factuelle de ce que montre l'image
- 2. Éléments techniques identifiables (messages d'erreur, versions, configurations visibles)
- 3. Interprétation de ce que cette image révèle sur le problème décrit dans le ticket
- 4. En quoi cette image est pertinente pour la résolution du problème
-
- Sois précis et factuel. Ne fais pas de suppositions non fondées.
+ 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.)
"""
{contexte}
- Fournis une analyse structurée de l'image avec les sections suivantes:
- 1. Description factuelle de ce que montre l'image
- 2. Éléments techniques identifiables (messages d'erreur, versions, configurations visibles)
- 3. Interprétation de ce que cette image révèle sur le problème décrit dans le ticket
- 4. En quoi cette image est pertinente pour la résolution du problème
-
- Sois précis et factuel. Ne fais pas de suppositions non fondées.
+ 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.)
"""
```
---
Voici un résumé des modifications apportées à vos agents :
1. **Agent Ticket Analyser** :
- Modification du prompt système pour mettre en perspective le nom de la demande et la description
- Restructuration du formatage des tickets pour identifier clairement la chronologie client/support
- Ajout d'étiquettes CLIENT/SUPPORT pour faciliter l'identification des intervenants
- Focus sur l'établissement de questions/réponses précises
2. **Agent Image Analyser** :
- Simplification du rôle pour se concentrer uniquement sur l'analyse factuelle des images
- Suppression des interprétations et recommandations
- Focus sur la description objective des éléments visibles dans l'image
- Meilleure mise en relation avec le contexte du ticket
3. **Agent Report Generator** :
- Ajout d'une présentation des échanges client/support sous forme de tableau Markdown
- Restructuration du rapport pour mettre en avant la chronologie des échanges
- Suppression des recommandations génériques non fondées
- Focus sur la factualité et la précision technique
Ces modifications permettent de :
- Mieux identifier qui est client et qui est support
- Présenter clairement les questions/réponses dans un tableau
- Rester factuel dans l'analyse des images
- Se concentrer sur les données objectives plutôt que sur des interprétations
Souhaitez-vous d'autres modifications ou clarifications sur les rôles des agents ?
---
_**User**_
comme tu peux le voir dans le rapport final toutes les demandes ne sont pas réalisés es ce que les résultats des différents agents sont bien transmis au dernier agent. De plus je souhaiterai améliorer mes rapports en ajoutant les différentes étapes à l'intérieur de mon markdown c'est à dire le tri , l'analyse du ticket, l'analyser ticket+image pertinente et enfin agent report comme actuelelment avec les réglages tels quels à la fin du fichier.
---
_**Assistant**_
Je vois le problème. Je vais améliorer votre générateur de rapport pour qu'il intègre toutes les étapes d'analyse dans le fichier markdown et s'assure que les résultats de tous les agents soient bien transmis au dernier agent.
---
Cancelled
---
---
---

120
README_TICKET_ANALYSER.md Normal file
View File

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

View File

@ -19,19 +19,22 @@ class AgentImageAnalyser(BaseAgent):
self.temperature = 0.3
self.top_p = 0.9
self.max_tokens = 1200
self.system_prompt = """Tu es un expert en analyse d'images pour le support technique de BRG_Lab.
Ta mission est d'analyser des captures d'écran ou des images techniques en tenant compte du contexte du ticket.
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.
Pour chaque image, structure ton analyse ainsi:
1. Description factuelle: Ce que contient l'image (interface, message d'erreur, etc.)
2. Éléments techniques importants: Versions, codes d'erreur, paramètres visibles
3. Interprétation: Ce que cette image révèle sur le problème client
4. Relation avec le ticket: Comment cette image aide à résoudre le problème décrit
Structure ton analyse d'image de façon factuelle:
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
IMPORTANT: Ne commence JAMAIS ta réponse par "Je n'ai pas accès à l'image" ou "Je ne peux pas directement visualiser l'image".
Si tu ne peux pas analyser l'image, réponds simplement "ERREUR: Impossible d'analyser l'image".
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
Concentre-toi sur les éléments techniques pertinents pour le support logiciel."""
Ton analyse sera utilisée comme élément factuel pour un rapport technique plus complet."""
# Appliquer la configuration au LLM
self._appliquer_config_locale()
@ -180,13 +183,17 @@ Concentre-toi sur les éléments techniques pertinents pour le support logiciel.
CONTEXTE DU TICKET:
{contexte}
Fournis une analyse structurée de l'image avec les sections suivantes:
1. Description factuelle de ce que montre l'image
2. Éléments techniques identifiables (messages d'erreur, versions, configurations visibles)
3. Interprétation de ce que cette image révèle sur le problème décrit dans le ticket
4. En quoi cette image est pertinente pour la résolution du problème
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
Sois précis et factuel. Ne fais pas de suppositions non fondées.
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.)
"""
try:
@ -209,13 +216,17 @@ En tenant compte du contexte suivant du ticket de support technique:
CONTEXTE DU TICKET:
{contexte}
Fournis une analyse structurée de l'image avec les sections suivantes:
1. Description factuelle de ce que montre l'image
2. Éléments techniques identifiables (messages d'erreur, versions, configurations visibles)
3. Interprétation de ce que cette image révèle sur le problème décrit dans le ticket
4. En quoi cette image est pertinente pour la résolution du problème
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
Sois précis et factuel. Ne fais pas de suppositions non fondées.
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.)
"""
response = self.llm.interroger(prompt_base64)

View File

@ -18,19 +18,23 @@ class AgentReportGenerator(BaseAgent):
self.temperature = 0.4 # Génération de rapport factuelle mais bien structurée
self.top_p = 0.9
self.max_tokens = 2500
self.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG_Lab.
Ta mission est de synthétiser toutes les analyses (JSON et images) en un rapport structuré et exploitable.
self.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG-Lab.
Ta mission est de synthétiser les analyses (ticket et images) en un rapport structuré et exploitable.
Structure ton rapport ainsi:
1. Résumé exécutif: Synthèse du problème et des conclusions principales
2. Analyse du ticket: Détails extraits du ticket client
3. Analyse des images: Résumé des images pertinentes et leur contribution
4. Diagnostic technique: Interprétation consolidée des informations
5. Recommandations: Actions suggérées pour résoudre le problème
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
Chaque section doit être factuelle, précise et orientée solution.
Inclus tous les détails techniques pertinents (versions, configurations, messages d'erreur).
Assure une traçabilité complète entre les données sources et tes conclusions."""
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."""
# Appliquer la configuration au LLM
self._appliquer_config_locale()
@ -107,6 +111,10 @@ Assure une traçabilité complète entre les données sources et tes conclusions
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)
@ -114,34 +122,48 @@ Assure une traçabilité complète entre les données sources et tes conclusions
for image_path, analyse_data in analyse_images_data.items():
image_name = os.path.basename(image_path)
# Récupérer l'analyse détaillée si elle existe
# 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 "analysis" in analyse_data and analyse_data["analysis"]:
if isinstance(analyse_data["analysis"], dict) and "analyse" in analyse_data["analysis"]:
analyse_detail = analyse_data["analysis"]["analyse"]
elif isinstance(analyse_data["analysis"], dict):
analyse_detail = str(analyse_data["analysis"])
# Si l'analyse n'a pas été trouvée mais que le tri indique que l'image est pertinente
if not analyse_detail and "sorting" in analyse_data and analyse_data["sorting"].get("is_relevant", False):
analyse_detail = f"Image marquée comme pertinente. Raison: {analyse_data['sorting'].get('reason', 'Non spécifiée')}"
# Ajouter l'analyse à la liste si elle existe
if analyse_detail:
images_analyses.append({
"image_name": image_name,
"analyse": analyse_detail
})
if is_relevant:
if "analysis" in analyse_data and analyse_data["analysis"]:
if isinstance(analyse_data["analysis"], dict) and "analyse" in analyse_data["analysis"]:
analyse_detail = analyse_data["analysis"]["analyse"]
elif isinstance(analyse_data["analysis"], dict):
analyse_detail = str(analyse_data["analysis"])
# Si l'analyse n'a pas été trouvée mais que 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
})
num_images = len(images_analyses)
# Créer un prompt détaillé
# Mettre à jour les métadonnées avec les statistiques
rapport_data.setdefault("metadata", {}).update({
"images_analysees": total_images,
"images_pertinentes": images_pertinentes
})
# 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)
## ANALYSES DES IMAGES ({num_images} images pertinentes sur {total_images} analysées)
"""
# Ajouter l'analyse de chaque image
@ -152,14 +174,18 @@ Assure une traçabilité complète entre les données sources et tes conclusions
prompt += f"""
Ton rapport doit être structuré avec les sections suivantes:
1. Résumé exécutif
2. Analyse détaillée du ticket
3. Analyse des images pertinentes
4. Diagnostic technique
5. Recommandations
1. Résumé exécutif: Synthèse concise du problème initial (reprend le nom de la demande + description)
2. Chronologie des échanges: PRÉSENTE IMPÉRATIVEMENT SOUS FORME DE TABLEAU MARKDOWN, avec colonnes: Date | Émetteur (CLIENT/SUPPORT) | Type (Question/Réponse) | Contenu
3. Analyse des images pertinentes: Ce que montrent les captures d'écran
4. Diagnostic technique: Points clés et interprétation technique
Fournir le rapport en format Markdown, avec des titres clairs et une structure cohérente.
Assure-toi d'inclure toutes les informations techniques importantes et les liens entre les différentes analyses.
IMPORTANT:
- Le tableau de chronologie doit être formaté correctement en Markdown (|---|---|---|---|)
- Tu dois clairement identifier qui est CLIENT et qui est SUPPORT
- Ne fais pas de recommandations génériques, reste factuel
- Utilise le format Markdown pour structurer ton rapport avec des titres clairs
Fournis un rapport concis, factuel et technique.
"""
# Appeler le LLM pour générer le rapport
@ -173,15 +199,18 @@ Assure-toi d'inclure toutes les informations techniques importantes et les liens
md_path = os.path.join(rapport_dir, f"{base_filename}.md")
# Collecter les métadonnées du rapport avec détails sur les agents et LLM utilisés
metadata = {
metadata = rapport_data.get("metadata", {})
metadata.update({
"timestamp": timestamp,
"model": getattr(self.llm, "modele", str(type(self.llm))),
"temperature": self.temperature,
"top_p": self.top_p,
"max_tokens": self.max_tokens,
"system_prompt": self.system_prompt,
"agents_info": agents_info
}
"agents_info": agents_info,
"images_analysees": total_images,
"images_pertinentes": images_pertinentes
})
# Sauvegarder le rapport au format JSON (données brutes + rapport généré)
rapport_data_complet = rapport_data.copy()
@ -307,6 +336,76 @@ Assure-toi d'inclure toutes les informations techniques importantes et les liens
# Ajouter les informations techniques (agents, LLM, paramètres)
markdown += "## Informations techniques\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 += "#### Analyse du ticket\n\n"
markdown += "<details>\n<summary>Cliquez pour voir l'analyse complète du ticket</summary>\n\n"
markdown += "```\n" + ticket_analyse + "\n```\n\n"
markdown += "</details>\n\n"
# 2. Tri des images
markdown += "#### Tri des images\n\n"
analyse_images_data = rapport_data.get("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"
# 3. Analyse des images pertinentes
markdown += "#### Analyse des images pertinentes\n\n"
images_pertinentes = 0
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
analyse_detail = "Analyse non disponible"
if "analysis" in analyse_data and analyse_data["analysis"]:
if isinstance(analyse_data["analysis"], dict) and "analyse" in analyse_data["analysis"]:
analyse_detail = analyse_data["analysis"]["analyse"]
elif isinstance(analyse_data["analysis"], dict):
analyse_detail = str(analyse_data["analysis"])
markdown += f"##### Image: {image_name}\n\n"
markdown += "<details>\n<summary>Cliquez pour voir l'analyse complète de l'image</summary>\n\n"
markdown += "```\n" + analyse_detail + "\n```\n\n"
markdown += "</details>\n\n"
if images_pertinentes == 0:
markdown += "Aucune image pertinente n'a été identifiée.\n\n"
# Ajouter les informations sur les agents utilisés
agents_info = rapport_data.get("metadata", {}).get("agents_info", {})
if agents_info:
@ -350,13 +449,19 @@ Assure-toi d'inclure toutes les informations techniques importantes et les liens
# Statistiques d'analyse
markdown += "### Statistiques\n\n"
total_images = len(analyse_images_data) if analyse_images_data else 0
markdown += f"- **Images analysées**: {total_images}\n"
markdown += f"- **Images pertinentes**: {images_pertinentes}\n\n"
# Ajouter les métadonnées supplémentaires si présentes
if "metadata" in rapport_data:
metadata = rapport_data["metadata"]
if "images_analysees" in metadata:
markdown += f"- **Images analysées**: {metadata['images_analysees']}\n"
if "images_pertinentes" in metadata:
markdown += f"- **Images pertinentes**: {metadata['images_pertinentes']}\n"
for key, value in metadata.items():
if key not in ["agents_info", "timestamp"] and not isinstance(value, dict):
markdown += f"- **{key}**: {value}\n"
return markdown
def _get_timestamp(self) -> str:

View File

@ -0,0 +1,271 @@
from .base_agent import BaseAgent
from typing import Dict, Any, Optional
import logging
import json
import os
import sys
from datetime import datetime
# Ajout du chemin des utilitaires au PATH pour pouvoir les importer
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from utils.ticket_data_loader import TicketDataLoader
logger = logging.getLogger("AgentTicketAnalyser")
class AgentTicketAnalyser(BaseAgent):
"""
Agent pour analyser les tickets (JSON ou Markdown) et en extraire les informations importantes.
Remplace l'ancien AgentJsonAnalyser avec des fonctionnalités améliorées.
"""
def __init__(self, llm):
super().__init__("AgentTicketAnalyser", llm)
# Configuration locale de l'agent
self.temperature = 0.1 # Besoin d'analyse très précise
self.top_p = 0.8
self.max_tokens = 1500
self.system_prompt = """Tu es un expert en analyse de tickets pour le support informatique de BRG-Lab.
Ton rôle est d'extraire et d'analyser les informations importantes des tickets.
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
3. Établir une chronologie claire des échanges client/support en identifiant précisément:
- Les questions posées par le client
- Les réponses fournies par le support
- Les informations techniques fournies par chaque partie
Sois factuel et reste dans une démarche technique. Ton analyse sera utilisée comme contexte pour l'analyse des images pertinentes.
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
"""
# Initialiser le loader de données
self.ticket_loader = TicketDataLoader()
# Appliquer la configuration au LLM
self._appliquer_config_locale()
logger.info("AgentTicketAnalyser initialisé")
def _appliquer_config_locale(self) -> None:
"""
Applique la configuration locale au modèle LLM.
"""
# Appliquer le prompt système
if hasattr(self.llm, "prompt_system"):
self.llm.prompt_system = self.system_prompt
# Appliquer les paramètres
if hasattr(self.llm, "configurer"):
params = {
"temperature": self.temperature,
"top_p": self.top_p,
"max_tokens": self.max_tokens
}
# Ajustements selon le type de modèle
if "mistral_medium" in self.llm.__class__.__name__.lower():
params["temperature"] += 0.05
params["max_tokens"] = 1000
elif "pixtral" in self.llm.__class__.__name__.lower():
params["temperature"] -= 0.05
elif "ollama" in self.llm.__class__.__name__.lower():
params["temperature"] += 0.1
params.update({
"num_ctx": 2048,
"repeat_penalty": 1.1,
})
self.llm.configurer(**params)
def executer(self, ticket_data: Dict[str, Any]) -> str:
"""
Analyse un ticket pour en extraire les informations pertinentes
Args:
ticket_data: Dictionnaire contenant les données du ticket à analyser
ou chemin vers un fichier de ticket (JSON ou Markdown)
Returns:
Réponse formatée contenant l'analyse du ticket
"""
# Détecter si ticket_data est un chemin de fichier ou un dictionnaire
if isinstance(ticket_data, str) and os.path.exists(ticket_data):
try:
ticket_data = self.ticket_loader.charger(ticket_data)
logger.info(f"Données chargées depuis le fichier: {ticket_data}")
except Exception as e:
error_message = f"Erreur lors du chargement du fichier: {str(e)}"
logger.error(error_message)
return f"ERREUR: {error_message}"
# Vérifier que les données sont bien un dictionnaire
if not isinstance(ticket_data, dict):
error_message = "Les données du ticket doivent être un dictionnaire ou un chemin de fichier valide"
logger.error(error_message)
return f"ERREUR: {error_message}"
ticket_code = ticket_data.get('code', 'Inconnu')
logger.info(f"Analyse du ticket: {ticket_code}")
print(f"AgentTicketAnalyser: Analyse du ticket {ticket_code}")
# Récupérer les métadonnées sur la source des données
source_format = "inconnu"
source_file = "non spécifié"
if "metadata" in ticket_data and isinstance(ticket_data["metadata"], dict):
source_format = ticket_data["metadata"].get("format", "inconnu")
source_file = ticket_data["metadata"].get("source_file", "non spécifié")
logger.info(f"Format source: {source_format}, Fichier source: {source_file}")
# Préparer le ticket pour l'analyse
ticket_formate = self._formater_ticket_pour_analyse(ticket_data)
# Créer le prompt pour l'analyse, adapté au format source
prompt = f"""Analyse ce ticket de support technique et fournis une synthèse structurée:
{ticket_formate}
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."""
try:
logger.info("Interrogation du LLM")
response = self.llm.interroger(prompt)
logger.info(f"Réponse reçue: {len(response)} caractères")
print(f" Analyse terminée: {len(response)} caractères")
except Exception as e:
error_message = f"Erreur lors de l'analyse du ticket: {str(e)}"
logger.error(error_message)
response = f"ERREUR: {error_message}"
print(f" ERREUR: {error_message}")
# Enregistrer l'historique avec le prompt complet pour la traçabilité
self.ajouter_historique("analyse_ticket",
{
"ticket_id": ticket_code,
"format_source": source_format,
"source_file": source_file,
"prompt": prompt,
"temperature": self.temperature,
"top_p": self.top_p,
"max_tokens": self.max_tokens,
"timestamp": self._get_timestamp()
},
response)
return response
def _formater_ticket_pour_analyse(self, ticket_data: Dict) -> str:
"""
Formate les données du ticket pour l'analyse LLM, avec une meilleure
gestion des différents formats et structures de données.
Args:
ticket_data: Les données du ticket
Returns:
Représentation textuelle formatée du ticket
"""
# Initialiser avec les informations de base
ticket_name = ticket_data.get('name', 'Sans titre')
ticket_code = ticket_data.get('code', 'Inconnu')
info = f"## TICKET {ticket_code}: {ticket_name}\n\n"
info += f"## NOM DE LA DEMANDE (PROBLÈME INITIAL)\n{ticket_name}\n\n"
# Ajouter la description
description = ticket_data.get('description', '')
if description:
info += f"## DESCRIPTION DU PROBLÈME\n{description}\n\n"
# Ajouter les informations du ticket (exclure certains champs spécifiques)
champs_a_exclure = ['code', 'name', 'description', 'messages', 'metadata']
info += "## INFORMATIONS TECHNIQUES DU TICKET\n"
for key, value in ticket_data.items():
if key not in champs_a_exclure and value:
# Formater les valeurs complexes si nécessaire
if isinstance(value, (dict, list)):
value = json.dumps(value, ensure_ascii=False, indent=2)
info += f"- {key}: {value}\n"
info += "\n"
# Ajouter les messages (conversations) avec un formatage amélioré pour distinguer client/support
messages = ticket_data.get('messages', [])
if messages:
info += "## CHRONOLOGIE DES ÉCHANGES CLIENT/SUPPORT\n"
for i, msg in enumerate(messages):
# Vérifier que le message est bien un dictionnaire
if not isinstance(msg, dict):
continue
sender = msg.get('from', 'Inconnu')
date = msg.get('date', 'Date inconnue')
content = msg.get('content', '')
# Identifier si c'est client ou support
sender_type = "CLIENT" if "client" in sender.lower() else "SUPPORT" if "support" in sender.lower() else "AUTRE"
# Formater correctement la date si possible
try:
if date != 'Date inconnue':
# Essayer différents formats de date
for date_format in ['%Y-%m-%d %H:%M:%S', '%Y-%m-%d', '%d/%m/%Y']:
try:
date_obj = datetime.strptime(date, date_format)
date = date_obj.strftime('%d/%m/%Y %H:%M')
break
except ValueError:
continue
except Exception:
pass # Garder la date d'origine en cas d'erreur
info += f"### Message {i+1} - [{sender_type}] De: {sender} - Date: {date}\n{content}\n\n"
# Ajouter les métadonnées techniques si présentes
metadata = ticket_data.get('metadata', {})
# Exclure certaines métadonnées internes
for key in ['source_file', 'format']:
if key in metadata:
metadata.pop(key)
if metadata:
info += "## MÉTADONNÉES TECHNIQUES\n"
for key, value in metadata.items():
if isinstance(value, (dict, list)):
value = json.dumps(value, ensure_ascii=False, indent=2)
info += f"- {key}: {value}\n"
info += "\n"
return info
def analyser_depuis_fichier(self, chemin_fichier: str) -> str:
"""
Analyse un ticket à partir d'un fichier (JSON ou Markdown)
Args:
chemin_fichier: Chemin vers le fichier à analyser
Returns:
Résultat de l'analyse
"""
try:
ticket_data = self.ticket_loader.charger(chemin_fichier)
return self.executer(ticket_data)
except Exception as e:
error_message = f"Erreur lors de l'analyse du fichier {chemin_fichier}: {str(e)}"
logger.error(error_message)
return f"ERREUR: {error_message}"
def _get_timestamp(self) -> str:
"""Retourne un timestamp au format YYYYMMDD_HHMMSS"""
return datetime.now().strftime("%Y%m%d_%H%M%S")

View File

@ -5,6 +5,7 @@ import time
import traceback
from typing import List, Dict, Any, Optional, Union
from agents.base_agent import BaseAgent
from utils.ticket_data_loader import TicketDataLoader
# Configuration du logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s',
@ -14,7 +15,7 @@ logger = logging.getLogger("Orchestrator")
class Orchestrator:
def __init__(self,
output_dir: str = "output/",
json_agent: Optional[BaseAgent] = None,
ticket_agent: Optional[BaseAgent] = None,
image_sorter: Optional[BaseAgent] = None,
image_analyser: Optional[BaseAgent] = None,
report_generator: Optional[BaseAgent] = None):
@ -22,16 +23,19 @@ class Orchestrator:
self.output_dir = output_dir
# Assignation directe des agents
self.json_agent = json_agent
self.ticket_agent = ticket_agent
self.image_sorter = image_sorter
self.image_analyser = image_analyser
self.report_generator = report_generator
# Initialisation du loader de données de ticket
self.ticket_loader = TicketDataLoader()
# Collecter et enregistrer les informations détaillées sur les agents
agents_info = self._collecter_info_agents()
logger.info(f"Orchestrator initialisé avec output_dir: {output_dir}")
logger.info(f"Agents disponibles: JSON={json_agent is not None}, ImageSorter={image_sorter is not None}, ImageAnalyser={image_analyser is not None}, ReportGenerator={report_generator is not None}")
logger.info(f"Agents disponibles: TicketAgent={ticket_agent is not None}, ImageSorter={image_sorter is not None}, ImageAnalyser={image_analyser is not None}, ReportGenerator={report_generator is not None}")
logger.info(f"Configuration des agents: {json.dumps(agents_info, indent=2)}")
def _collecter_info_agents(self) -> Dict[str, Dict[str, Any]]:
@ -40,9 +44,9 @@ class Orchestrator:
"""
agents_info = {}
# Information sur l'agent JSON
if self.json_agent:
agents_info["json_agent"] = self._get_agent_info(self.json_agent)
# Information sur l'agent Ticket
if self.ticket_agent:
agents_info["ticket_agent"] = self._get_agent_info(self.ticket_agent)
# Information sur l'agent Image Sorter
if self.image_sorter:
@ -101,41 +105,11 @@ class Orchestrator:
Returns:
Un dictionnaire avec les chemins des fichiers JSON et MD s'ils sont trouvés
"""
result: Dict[str, Optional[str]] = {"json_path": None, "md_path": None}
# Liste des emplacements possibles pour les rapports
possible_locations = [
# 1. Dans le répertoire d'extraction directement
extraction_path,
# 2. Dans un sous-répertoire "data"
os.path.join(extraction_path, "data"),
# 3. Dans un sous-répertoire spécifique au ticket pour les rapports
os.path.join(extraction_path, f"{ticket_id}_rapports"),
# 4. Dans un sous-répertoire "rapports"
os.path.join(extraction_path, "rapports")
]
# Vérifier chaque emplacement
for base_location in possible_locations:
# Chercher le fichier JSON
json_path = os.path.join(base_location, f"{ticket_id}_rapport.json")
if os.path.exists(json_path):
logger.info(f"Rapport JSON trouvé à: {json_path}")
result["json_path"] = json_path
# Chercher le fichier Markdown
md_path = os.path.join(base_location, f"{ticket_id}_rapport.md")
if os.path.exists(md_path):
logger.info(f"Rapport Markdown trouvé à: {md_path}")
result["md_path"] = md_path
if not result["json_path"] and not result["md_path"]:
logger.warning(f"Aucun rapport trouvé pour {ticket_id} dans {extraction_path}")
return result
# Utilise la nouvelle méthode de TicketDataLoader
resultats = self.ticket_loader.trouver_ticket(extraction_path, ticket_id)
if resultats is None:
return {"json": None, "markdown": None}
return resultats
def traiter_ticket(self, ticket_path: str) -> bool:
"""Traite un ticket spécifique et retourne True si le traitement a réussi"""
@ -177,25 +151,27 @@ class Orchestrator:
logger.info(f"Données du ticket chargées avec succès")
print(f" Données du ticket chargées")
# Traitement JSON avec l'agent JSON
if self.json_agent:
logger.info("Exécution de l'agent JSON")
# Traitement avec l'agent Ticket
if self.ticket_agent:
logger.info("Exécution de l'agent Ticket")
print(" Analyse du ticket en cours...")
# Log détaillé sur l'agent JSON
agent_info = self._get_agent_info(self.json_agent)
logger.info(f"Agent JSON: {json.dumps(agent_info, indent=2)}")
# Log détaillé sur l'agent Ticket
agent_info = self._get_agent_info(self.ticket_agent)
logger.info(f"Agent Ticket: {json.dumps(agent_info, indent=2)}")
json_analysis = self.json_agent.executer(ticket_data)
logger.info("Analyse JSON terminée")
ticket_analysis = self.ticket_agent.executer(ticket_data)
logger.info("Analyse du ticket terminée")
print(f" Analyse du ticket terminée: {len(ticket_analysis) if ticket_analysis else 0} caractères")
else:
logger.warning("Agent JSON non disponible")
json_analysis = None
print(" Agent JSON non disponible, analyse ignorée")
logger.warning("Agent Ticket non disponible")
ticket_analysis = None
print(" Agent Ticket non disponible, analyse ignorée")
# Traitement des images
relevant_images = []
images_analyses = {}
images_count = 0
if os.path.exists(attachments_dir):
logger.info(f"Vérification des pièces jointes dans: {attachments_dir}")
print(f" Vérification des pièces jointes...")
@ -205,33 +181,50 @@ class Orchestrator:
agent_info = self._get_agent_info(self.image_sorter)
logger.info(f"Agent Image Sorter: {json.dumps(agent_info, indent=2)}")
images_count = 0
for attachment in os.listdir(attachments_dir):
# Compter le nombre d'images
images = [f for f in os.listdir(attachments_dir)
if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif'))]
images_count = len(images)
# Tri des images
for attachment in images:
attachment_path = os.path.join(attachments_dir, attachment)
if attachment.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):
images_count += 1
if self.image_sorter:
logger.info(f"Évaluation de la pertinence de l'image: {attachment}")
print(f" Évaluation de l'image: {attachment}")
sorting_result = self.image_sorter.executer(attachment_path)
is_relevant = sorting_result.get("is_relevant", False)
reason = sorting_result.get("reason", "")
if self.image_sorter:
print(f" Analyse de l'image: {attachment}")
sorting_result = self.image_sorter.executer(attachment_path)
is_relevant = sorting_result.get("is_relevant", False)
reason = sorting_result.get("reason", "")
# Ajouter les métadonnées de tri à la liste des analyses
images_analyses[attachment_path] = {
"sorting": sorting_result,
"analysis": None # Sera rempli plus tard si pertinent
}
if is_relevant:
logger.info(f"Image pertinente identifiée: {attachment} ({reason})")
print(f" => Pertinente: {reason}")
relevant_images.append(attachment_path)
else:
logger.info(f"Image non pertinente: {attachment} ({reason})")
print(f" => Non pertinente: {reason}")
# Log détaillé du résultat
if is_relevant:
logger.info(f"Image {attachment} considérée comme pertinente")
else:
logger.warning("Image Sorter non disponible")
logger.info(f"Image {attachment} considérée comme non pertinente")
# Ajouter les métadonnées de tri à la liste des analyses
images_analyses[attachment_path] = {
"sorting": sorting_result,
"analysis": None # Sera rempli plus tard si pertinent
}
if is_relevant:
logger.info(f"Image pertinente identifiée: {attachment} ({reason})")
print(f" => Pertinente: {reason}")
relevant_images.append(attachment_path)
else:
logger.info(f"Image non pertinente: {attachment} ({reason})")
print(f" => Non pertinente: {reason}")
else:
logger.warning("Image Sorter non disponible")
# Si pas de tri, considérer toutes les images comme pertinentes
relevant_images.append(attachment_path)
images_analyses[attachment_path] = {
"sorting": {"is_relevant": True, "reason": "Auto-sélectionné (pas de tri)"},
"analysis": None
}
print(f" => Auto-sélectionné (pas de tri)")
logger.info(f"Images analysées: {images_count}, Images pertinentes: {len(relevant_images)}")
print(f" Images analysées: {images_count}, Images pertinentes: {len(relevant_images)}")
@ -240,17 +233,29 @@ class Orchestrator:
print(f" Répertoire des pièces jointes non trouvé")
# Analyse approfondie des images pertinentes
# Log détaillé sur l'agent Image Analyser
if self.image_analyser:
if relevant_images and self.image_analyser:
agent_info = self._get_agent_info(self.image_analyser)
logger.info(f"Agent Image Analyser: {json.dumps(agent_info, indent=2)}")
for image_path in relevant_images:
if self.image_analyser and json_analysis:
# S'assurer que l'analyse du ticket est disponible comme contexte
contexte_ticket = ticket_analysis if ticket_analysis else "Aucune analyse de ticket disponible"
# Analyse de chaque image pertinente
for image_path in relevant_images:
image_name = os.path.basename(image_path)
logger.info(f"Analyse approfondie de l'image: {image_name}")
print(f" Analyse approfondie de l'image: {image_name}")
analysis_result = self.image_analyser.executer(image_path, contexte=json_analysis)
# Appeler l'analyseur d'images avec le contexte du ticket
analysis_result = self.image_analyser.executer(image_path, contexte=contexte_ticket)
# Vérifier si l'analyse a réussi
if "error" in analysis_result and analysis_result["error"]:
logger.warning(f"Erreur lors de l'analyse de l'image {image_name}: {analysis_result.get('analyse', 'Erreur inconnue')}")
print(f" => ERREUR: {analysis_result.get('analyse', 'Erreur inconnue')}")
else:
logger.info(f"Analyse réussie pour l'image {image_name}")
print(f" => Analyse réussie: {len(analysis_result.get('analyse', '')) if 'analyse' in analysis_result else 0} caractères")
# Ajouter l'analyse au dictionnaire des analyses d'images
if image_path in images_analyses:
@ -263,20 +268,23 @@ class Orchestrator:
logger.info(f"Analyse complétée pour {image_name}")
# Génération du rapport final
# Préparer les données pour le rapport final
rapport_data = {
"ticket_data": ticket_data,
"ticket_id": ticket_id,
"analyse_json": json_analysis,
"ticket_analyse": ticket_analysis, # Utiliser ticket_analyse au lieu de analyse_ticket pour cohérence
"analyse_images": images_analyses,
"metadata": {
"timestamp_debut": self._get_timestamp(),
"ticket_id": ticket_id,
"images_analysees": images_count if 'images_count' in locals() else 0,
"images_analysees": images_count,
"images_pertinentes": len(relevant_images)
}
}
# Ajout de la clé alternative pour compatibilité
rapport_data["analyse_json"] = ticket_analysis
if self.report_generator:
logger.info("Génération du rapport final")
print(" Génération du rapport final")
@ -285,11 +293,21 @@ class Orchestrator:
agent_info = self._get_agent_info(self.report_generator)
logger.info(f"Agent Report Generator: {json.dumps(agent_info, indent=2)}")
# Créer le répertoire pour le rapport si nécessaire
rapport_path = os.path.join(rapports_dir, ticket_id)
os.makedirs(rapport_path, exist_ok=True)
self.report_generator.executer(rapport_data, rapport_path)
logger.info(f"Rapport généré à: {rapport_path}")
print(f" Rapport généré à: {rapport_path}")
# Générer le rapport
json_path, md_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)}")
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")
else:
logger.warning("Report Generator non disponible")
print(" Report Generator non disponible, génération de rapport ignorée")
@ -319,24 +337,28 @@ class Orchestrator:
"""
ticket_data = None
# Si aucun rapport n'est trouvé
if not rapports or (not rapports.get("json") and not rapports.get("markdown")):
logger.warning(f"Aucun rapport trouvé pour le ticket {ticket_id}")
return None
# Essayer d'abord le fichier JSON
if rapports["json_path"]:
if rapports.get("json") and rapports["json"] is not None:
try:
with open(rapports["json_path"], 'r', encoding='utf-8') as file:
ticket_data = json.load(file)
logger.info(f"Données JSON chargées depuis: {rapports['json_path']}")
print(f" Rapport JSON chargé: {os.path.basename(rapports['json_path'])}")
ticket_data = self.ticket_loader.charger(rapports["json"])
logger.info(f"Données JSON chargées depuis: {rapports['json']}")
print(f" Rapport JSON chargé: {os.path.basename(rapports['json'])}")
except Exception as e:
logger.error(f"Erreur lors du chargement du JSON: {e}")
print(f" ERREUR: Impossible de charger le fichier JSON: {e}")
# Si pas de JSON ou erreur, essayer le Markdown
if not ticket_data and rapports["md_path"]:
if not ticket_data and rapports.get("markdown") and rapports["markdown"] is not None:
try:
# Créer une structure de données à partir du contenu Markdown
ticket_data = self._extraire_donnees_de_markdown(rapports["md_path"])
logger.info(f"Données Markdown chargées depuis: {rapports['md_path']}")
print(f" Rapport Markdown chargé: {os.path.basename(rapports['md_path'])}")
# Utiliser le loader pour charger les données depuis le Markdown
ticket_data = self.ticket_loader.charger(rapports["markdown"])
logger.info(f"Données Markdown chargées depuis: {rapports['markdown']}")
print(f" Rapport Markdown chargé: {os.path.basename(rapports['markdown'])}")
except Exception as e:
logger.error(f"Erreur lors du chargement du Markdown: {e}")
print(f" ERREUR: Impossible de charger le fichier Markdown: {e}")
@ -346,136 +368,6 @@ class Orchestrator:
ticket_data["code"] = ticket_id
return ticket_data
def _extraire_donnees_de_markdown(self, md_path: str) -> Dict:
"""
Extrait les données d'un fichier Markdown et les structure
Args:
md_path: Chemin vers le fichier Markdown
Returns:
Dictionnaire structuré avec les données du ticket
"""
with open(md_path, 'r', encoding='utf-8') as file:
content = file.read()
# Initialiser la structure de données
ticket_data = {
"id": "",
"code": "",
"name": "",
"description": "",
"messages": [],
"metadata": {
"source_file": md_path,
"format": "markdown"
}
}
# Extraire le titre (première ligne)
lines = content.split('\n')
if lines and lines[0].startswith('# '):
title = lines[0].replace('# ', '')
ticket_parts = title.split(':')
if len(ticket_parts) >= 1:
ticket_data["code"] = ticket_parts[0].strip()
if len(ticket_parts) >= 2:
ticket_data["name"] = ticket_parts[1].strip()
# Extraire la description
description_section = self._extraire_section(content, "description")
if description_section:
ticket_data["description"] = description_section.strip()
# Extraire les informations du ticket
info_section = self._extraire_section(content, "Informations du ticket")
if info_section:
for line in info_section.split('\n'):
if ':' in line and line.startswith('- **'):
key = line.split('**')[1].strip()
value = line.split(':')[1].strip()
if key == "id":
ticket_data["id"] = value
elif key == "code":
ticket_data["code"] = value
# Ajouter d'autres champs au besoin
# Extraire les messages
messages_section = self._extraire_section(content, "Messages")
if messages_section:
message_blocks = messages_section.split("### Message ")
for block in message_blocks[1:]: # Ignorer le premier élément (vide)
message = {}
# Extraire les en-têtes du message
lines = block.split('\n')
for i, line in enumerate(lines):
if line.startswith('**') and ':' in line:
key = line.split('**')[1].lower()
value = line.split(':')[1].strip()
message[key] = value
# Extraire le contenu du message (tout ce qui n'est pas un en-tête)
content_start = 0
for i, line in enumerate(lines):
if i > 0 and not line.startswith('**') and line and content_start == 0:
content_start = i
break
if content_start > 0:
content_end = -1
for i in range(content_start, len(lines)):
if lines[i].startswith('**attachment_ids**') or lines[i].startswith('---'):
content_end = i
break
if content_end == -1:
message["content"] = "\n".join(lines[content_start:])
else:
message["content"] = "\n".join(lines[content_start:content_end])
# Extraire les pièces jointes
attachments = []
for line in lines:
if line.startswith('- ') and '[ID:' in line:
attachments.append(line.strip('- ').strip())
if attachments:
message["attachments"] = attachments
ticket_data["messages"].append(message)
return ticket_data
def _extraire_section(self, content: str, section_title: str) -> Optional[str]:
"""
Extrait une section du contenu Markdown
Args:
content: Contenu Markdown complet
section_title: Titre de la section à extraire
Returns:
Contenu de la section ou None si non trouvée
"""
import re
# Chercher les sections de niveau 2 (##)
pattern = r'## ' + re.escape(section_title) + r'\s*\n(.*?)(?=\n## |$)'
match = re.search(pattern, content, re.DOTALL)
if match:
return match.group(1).strip()
# Si pas trouvé, chercher les sections de niveau 3 (###)
pattern = r'### ' + re.escape(section_title) + r'\s*\n(.*?)(?=\n### |$)'
match = re.search(pattern, content, re.DOTALL)
if match:
return match.group(1).strip()
return None
def executer(self, ticket_specifique: Optional[str] = None):
"""

View File

@ -1,111 +0,0 @@
{
"ticket_data": {
"id": "113",
"code": "T0101",
"name": "ACTIVATION LOGICIEL",
"description": "Problème de licence.",
"project_name": "Demandes",
"stage_name": "Clôturé",
"user_id": "",
"partner_id_email_from": "PROVENCALE S.A, Bruno Vernet <bruno.vernet@provencale.com>",
"create_date": "26/03/2020 14:46:36",
"write_date_last_modification": "03/10/2024 13:10:50",
"date_deadline": "25/05/2020 00:00:00",
"messages": [
{
"author_id": "PROVENCALE S.A",
"date": "26/03/2020 14:43:45",
"message_type": "E-mail",
"subject": "ACTIVATION LOGICIEL",
"id": "10758",
"content": "Bonjour,\n\nAu vu de la situation liée au Coronavirus, nous avons dû passer en télétravail.\n\nPour ce faire et avoir accès aux différents logiciels nécessaires, ESQ a été réinstallé sur un autre serveur afin de pouvoir travailler en bureau à distance.\n\nDu coup le logiciel nous demande une activation mais je ne sais pas si le N° de licence a été modifié suite à un achat version réseau faite par JB Lafitte en 2019 ou si le problème est autre.\n\nCi-dessous la fenêtre au lancement du logiciel.\n\nMerci davance pour votre aide.\n\nCordialement\n\n\n- image006.jpg (image/jpeg) [ID: 31760]\n- image005.jpg (image/jpeg) [ID: 31758]\n\n---\n"
}
],
"date_d'extraction": "04/04/2025 17:02:42",
"répertoire": "output/ticket_T0101/T0101_20250404_170239"
},
"ticket_id": "T0101",
"analyse_json": "1. Résumé du problème\nLe client PROVENCALE S.A a réinstallé le logiciel ESQ sur un nouveau serveur pour permettre le télétravail de ses employés. Cependant, lors du lancement du logiciel, une activation est demandée et le client n'est pas certain si le numéro de licence a été modifié suite à un achat de version réseau en 2019.\n2. Informations techniques essentielles\nLogiciel : ESQ\nVersion : non spécifiée\nNouveau serveur installé pour le télétravail\nAchat d'une version réseau en 2019 : possible modification du numéro de licence\n3. Contexte client (urgence, impact)\nLe client a dû passer en télétravail en raison de la situation liée au Coronavirus. Le problème d'activation du logiciel ESQ sur le nouveau serveur peut avoir un impact sur la productivité des employés de PROVENCALE S.A. La date limite pour résoudre ce problème est le 25/05/2020.\n4. Pistes d'analyse suggérées\nVérifier les informations de licence pour la version réseau achetée en 2019 et comparer avec le numéro de licence actuellement utilisé. Si nécessaire, contacter le fournisseur du logiciel ESQ pour obtenir de l'aide pour l'activation ou pour obtenir un nouveau numéro de licence. Vérifier également la configuration du nouveau serveur pour s'assurer qu'il répond aux exigences système du logiciel ESQ.",
"analyse_images": {
"output/ticket_T0101/T0101_20250404_170239/attachments/image006.jpg": {
"sorting": {
"is_relevant": false,
"reason": "Non. Cette image montre le logo d'une entreprise nommée \"Provençale Carbone de Calcium\". Elle n'est pas pertinente pour le support technique de logiciels, car elle ne contient pas de captures d'écran de logiciels, de messages d'erreur, de configurations système, ou d'autres éléments techniques liés à l'informatique.",
"raw_response": "Non. Cette image montre le logo d'une entreprise nommée \"Provençale Carbone de Calcium\". Elle n'est pas pertinente pour le support technique de logiciels, car elle ne contient pas de captures d'écran de logiciels, de messages d'erreur, de configurations système, ou d'autres éléments techniques liés à l'informatique.",
"metadata": {
"image_path": "output/ticket_T0101/T0101_20250404_170239/attachments/image006.jpg",
"image_name": "image006.jpg",
"timestamp": "20250407_142051",
"model_info": {
"model": "pixtral-12b-latest",
"temperature": 0.2,
"top_p": 0.8,
"max_tokens": 300
}
}
},
"analysis": null
},
"output/ticket_T0101/T0101_20250404_170239/attachments/image005.jpg": {
"sorting": {
"is_relevant": true,
"reason": "oui. L'image montre une fenêtre d'activation de logiciel, ce qui est pertinent pour le support technique de logiciels.",
"raw_response": "oui. L'image montre une fenêtre d'activation de logiciel, ce qui est pertinent pour le support technique de logiciels.",
"metadata": {
"image_path": "output/ticket_T0101/T0101_20250404_170239/attachments/image005.jpg",
"image_name": "image005.jpg",
"timestamp": "20250407_142051",
"model_info": {
"model": "pixtral-12b-latest",
"temperature": 0.2,
"top_p": 0.8,
"max_tokens": 300
}
}
},
"analysis": {
"analyse": "### Analyse de l'image\n\n#### 1. Description factuelle de ce que montre l'image\nL'image montre une interface de fenêtre de logiciel intitulée \"Activation du logiciel\". La fenêtre contient un champ pour entrer l'ID du logiciel, un message d'instructions, et trois options pour l'activation du logiciel : par internet, par carte (4 ans restants), et par téléphone.\n\n#### 2. Éléments techniques identifiables\n- **Titre de la fenêtre** : \"Activation du logiciel\"\n- **Champ ID du logiciel** : Pré-rempli avec \"ID00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"metadata": {
"image_path": "output/ticket_T0101/T0101_20250404_170239/attachments/image005.jpg",
"image_name": "image005.jpg",
"timestamp": "20250407_142056",
"model_info": {
"model": "pixtral-12b-latest",
"temperature": 0.3,
"top_p": 0.9,
"max_tokens": 1200
}
}
}
}
},
"metadata": {
"timestamp": "20250407_142114",
"model": "mistral-medium",
"temperature": 0.4,
"top_p": 0.9,
"max_tokens": 2500,
"system_prompt": "Tu es un expert en génération de rapports techniques pour BRG_Lab.\nTa mission est de synthétiser toutes les analyses (JSON et images) en un rapport structuré et exploitable.\n\nStructure ton rapport ainsi:\n1. Résumé exécutif: Synthèse du problème et des conclusions principales\n2. Analyse du ticket: Détails extraits du ticket client\n3. Analyse des images: Résumé des images pertinentes et leur contribution\n4. Diagnostic technique: Interprétation consolidée des informations\n5. Recommandations: Actions suggérées pour résoudre le problème\n\nChaque section doit être factuelle, précise et orientée solution.\nInclus tous les détails techniques pertinents (versions, configurations, messages d'erreur).\nAssure une traçabilité complète entre les données sources et tes conclusions.",
"agents_info": {
"image_sorter": {
"model": "pixtral-12b-latest",
"temperature": 0.2,
"top_p": 0.8,
"max_tokens": 300
},
"image_analyser": {
"model": "pixtral-12b-latest",
"temperature": 0.3,
"top_p": 0.9,
"max_tokens": 1200
},
"report_generator": {
"model": "mistral-medium",
"temperature": 0.4,
"top_p": 0.9,
"max_tokens": 2500
}
}
},
"rapport_genere": "# Rapport technique pour le ticket #T0101\n\n## 1. Résumé exécutif\n\nLe client PROVENCALE S.A a récemment installé le logiciel ESQ sur un nouveau serveur pour permettre le télétravail de ses employés. Cependant, lors de l'activation du logiciel, le client n'est pas certain si le numéro de licence a été modifié suite à un achat de version réseau en 2019.\n\nLes analyses effectuées indiquent que le numéro de licence actuellement utilisé ne correspond pas à celui d'une version réseau. Il est recommandé de contacter le fournisseur du logiciel ESQ pour obtenir de l'aide pour l'activation ou pour obtenir un nouveau numéro de licence correspondant à la version réseau achetée en 2019.\n\n## 2. Analyse détaillée du ticket\n\nLe client PROVENCALE S.A a réinstallé le logiciel ESQ sur un nouveau serveur pour permettre le télétravail de ses employés. Cependant, lors du lancement du logiciel, une activation est demandée et le client n'est pas certain si le numéro de licence a été modifié suite à un achat de version réseau en 2019.\n\nLes informations techniques essentielles fournies par le client sont les suivantes :\n\n- Logiciel : ESQ\n- Version : non spécifiée\n- Nouveau serveur installé pour le télétravail\n- Achat d'une version réseau en 2019 : possible modification du numéro de licence\n\nLe client a dû passer en télétravail en raison de la situation liée au Coronavirus. Le problème d'activation du logiciel ESQ sur le nouveau serveur peut avoir un impact sur la productivité des employés de PROVENCALE S.A. La date limite pour résoudre ce problème est le 25/05/2020.\n\n## 3. Analyse des images pertinentes\n\n### 3.1 Image 1 : image005.jpg\n\n#### 3.1.1 Description factuelle\n\nL'image montre une interface de fenêtre de logiciel intitulée \"Activation du logiciel\". La fenêtre contient un champ pour entrer l'ID du logiciel, un message d'instructions, et trois options pour l'activation du logiciel : par internet, par carte (4 ans restants), et par téléphone.\n\n#### 3.1.2 Éléments techniques identifiables\n\n- **Titre de la fenêtre** : \"Activation du logiciel\"\n- **Champ ID du logiciel** : Pré-rempli avec \"ID00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}

View File

@ -1,62 +0,0 @@
# Rapport d'analyse du ticket #T0101
*Généré le: 20250407_142114*
# Rapport technique pour le ticket #T0101
## 1. Résumé exécutif
Le client PROVENCALE S.A a récemment installé le logiciel ESQ sur un nouveau serveur pour permettre le télétravail de ses employés. Cependant, lors de l'activation du logiciel, le client n'est pas certain si le numéro de licence a été modifié suite à un achat de version réseau en 2019.
Les analyses effectuées indiquent que le numéro de licence actuellement utilisé ne correspond pas à celui d'une version réseau. Il est recommandé de contacter le fournisseur du logiciel ESQ pour obtenir de l'aide pour l'activation ou pour obtenir un nouveau numéro de licence correspondant à la version réseau achetée en 2019.
## 2. Analyse détaillée du ticket
Le client PROVENCALE S.A a réinstallé le logiciel ESQ sur un nouveau serveur pour permettre le télétravail de ses employés. Cependant, lors du lancement du logiciel, une activation est demandée et le client n'est pas certain si le numéro de licence a été modifié suite à un achat de version réseau en 2019.
Les informations techniques essentielles fournies par le client sont les suivantes :
- Logiciel : ESQ
- Version : non spécifiée
- Nouveau serveur installé pour le télétravail
- Achat d'une version réseau en 2019 : possible modification du numéro de licence
Le client a dû passer en télétravail en raison de la situation liée au Coronavirus. Le problème d'activation du logiciel ESQ sur le nouveau serveur peut avoir un impact sur la productivité des employés de PROVENCALE S.A. La date limite pour résoudre ce problème est le 25/05/2020.
## 3. Analyse des images pertinentes
### 3.1 Image 1 : image005.jpg
#### 3.1.1 Description factuelle
L'image montre une interface de fenêtre de logiciel intitulée "Activation du logiciel". La fenêtre contient un champ pour entrer l'ID du logiciel, un message d'instructions, et trois options pour l'activation du logiciel : par internet, par carte (4 ans restants), et par téléphone.
#### 3.1.2 Éléments techniques identifiables
- **Titre de la fenêtre** : "Activation du logiciel"
- **Champ ID du logiciel** : Pré-rempli avec "ID00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
## Informations techniques
### Agents et modèles utilisés
#### Agent de tri d'images
- **Modèle**: pixtral-12b-latest
- **Température**: 0.2
- **Top-p**: 0.8
- **Max tokens**: 300
#### Agent d'analyse d'images
- **Modèle**: pixtral-12b-latest
- **Température**: 0.3
- **Top-p**: 0.9
- **Max tokens**: 1200
#### Agent de génération de rapport
- **Modèle**: mistral-medium
- **Température**: 0.4
- **Top-p**: 0.9
- **Max tokens**: 2500
### Statistiques

View File

@ -0,0 +1,117 @@
{
"ticket_data": {
"id": "113",
"code": "T0101",
"name": "ACTIVATION LOGICIEL",
"description": "Problème de licence.",
"project_name": "Demandes",
"stage_name": "Clôturé",
"user_id": "",
"partner_id_email_from": "PROVENCALE S.A, Bruno Vernet <bruno.vernet@provencale.com>",
"create_date": "26/03/2020 14:46:36",
"write_date_last_modification": "03/10/2024 13:10:50",
"date_deadline": "25/05/2020 00:00:00",
"messages": [
{
"author_id": "PROVENCALE S.A",
"date": "26/03/2020 14:43:45",
"message_type": "E-mail",
"subject": "ACTIVATION LOGICIEL",
"id": "10758",
"content": "Bonjour,\n\nAu vu de la situation liée au Coronavirus, nous avons dû passer en télétravail.\n\nPour ce faire et avoir accès aux différents logiciels nécessaires, ESQ a été réinstallé sur un autre serveur afin de pouvoir travailler en bureau à distance.\n\nDu coup le logiciel nous demande une activation mais je ne sais pas si le N° de licence a été modifié suite à un achat version réseau faite par JB Lafitte en 2019 ou si le problème est autre.\n\nCi-dessous la fenêtre au lancement du logiciel.\n\nMerci davance pour votre aide.\n\nCordialement\n\n\n- image006.jpg (image/jpeg) [ID: 31760]\n- image005.jpg (image/jpeg) [ID: 31758]\n\n---\n"
}
],
"date_d'extraction": "04/04/2025 17:02:42",
"répertoire": "output/ticket_T0101/T0101_20250404_170239",
"metadata": {}
},
"ticket_id": "T0101",
"ticket_analyse": "1. Analyse du problème initial\n Le nom de la demande indique \"ACTIVATION LOGICIEL\", suggérant que le client est confronté à un problème d'activation d'un logiciel. La description du problème mentionne spécifiquement un \"Problème de licence\".\n\n2. Informations techniques essentielles\n - Logiciel : ESQ\n - Version : Non spécifiée dans les informations fournies\n - Configuration : Le logiciel a été réinstallé sur un autre serveur pour permettre le travail à distance. Une version réseau a été achetée en 2019 par JB Lafitte.\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 suite à sa réinstallation sur un autre serveur. Il se demande si le numéro de licence a été modifié suite à l'achat d'une version réseau en 2019.\n - Informations techniques fournies par le client : Le client a joint deux images (image006.jpg et image005.jpg) montrant probablement la fenêtre d'activation du logiciel.\n - Réponse du support : Aucune réponse n'est incluse dans les informations fournies.",
"analyse_images": {
"output/ticket_T0101/T0101_20250404_170239/attachments/image006.jpg": {
"sorting": {
"is_relevant": false,
"reason": "Non. Cette image montre le logo d'une entreprise nommée \"Provençale Carbone de Calcium\". Elle n'est pas pertinente pour le support technique de logiciels, car elle ne montre pas de captures d'écran de logiciels, de messages d'erreur, de configurations système, ou d'autres éléments techniques liés à l'informatique.",
"raw_response": "Non. Cette image montre le logo d'une entreprise nommée \"Provençale Carbone de Calcium\". Elle n'est pas pertinente pour le support technique de logiciels, car elle ne montre pas de captures d'écran de logiciels, de messages d'erreur, de configurations système, ou d'autres éléments techniques liés à l'informatique.",
"metadata": {
"image_path": "output/ticket_T0101/T0101_20250404_170239/attachments/image006.jpg",
"image_name": "image006.jpg",
"timestamp": "20250407_153622",
"model_info": {
"model": "pixtral-12b-latest",
"temperature": 0.2,
"top_p": 0.8,
"max_tokens": 300
}
}
},
"analysis": null
},
"output/ticket_T0101/T0101_20250404_170239/attachments/image005.jpg": {
"sorting": {
"is_relevant": true,
"reason": "oui. L'image montre une fenêtre d'activation de logiciel, ce qui est pertinent pour le support technique de logiciels.",
"raw_response": "oui. L'image montre une fenêtre d'activation de logiciel, ce qui est pertinent pour le support technique de logiciels.",
"metadata": {
"image_path": "output/ticket_T0101/T0101_20250404_170239/attachments/image005.jpg",
"image_name": "image005.jpg",
"timestamp": "20250407_153623",
"model_info": {
"model": "pixtral-12b-latest",
"temperature": 0.2,
"top_p": 0.8,
"max_tokens": 300
}
}
},
"analysis": {
"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 activer le logiciel.\n\n#### 2. Éléments techniques clés:\n- **Titre de la fenêtre**: \"Activation du logiciel\"\n- **Champ d'ID du logiciel**: Un champ vide pour entrer l'ID du logiciel.\n- **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 à support@cbad.com ou par téléphone au 04 68 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. La fenêtre d'activation montre que le logiciel nécessite un ID de licence pour être activé, ce qui est en ligne avec le problème de licence mentionné dans le ticket. Le message d'instructions indique que le client doit contacter CBAD pour obtenir l'ID de licence, ce qui pourrait être la source du problème d'activation.",
"metadata": {
"image_path": "output/ticket_T0101/T0101_20250404_170239/attachments/image005.jpg",
"image_name": "image005.jpg",
"timestamp": "20250407_153627",
"model_info": {
"model": "pixtral-12b-latest",
"temperature": 0.3,
"top_p": 0.9,
"max_tokens": 1200
}
}
}
}
},
"metadata": {
"timestamp_debut": "20250407_153627",
"ticket_id": "T0101",
"images_analysees": 2,
"images_pertinentes": 1,
"timestamp": "20250407_153643",
"model": "mistral-medium",
"temperature": 0.4,
"top_p": 0.9,
"max_tokens": 2500,
"system_prompt": "Tu es un expert en génération de rapports techniques pour BRG-Lab.\nTa mission est de synthétiser 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.",
"agents_info": {
"image_sorter": {
"model": "pixtral-12b-latest",
"temperature": 0.2,
"top_p": 0.8,
"max_tokens": 300
},
"image_analyser": {
"model": "pixtral-12b-latest",
"temperature": 0.3,
"top_p": 0.9,
"max_tokens": 1200
},
"report_generator": {
"model": "mistral-medium",
"temperature": 0.4,
"top_p": 0.9,
"max_tokens": 2500
}
}
},
"analyse_json": "1. Analyse du problème initial\n Le nom de la demande indique \"ACTIVATION LOGICIEL\", suggérant que le client est confronté à un problème d'activation d'un logiciel. La description du problème mentionne spécifiquement un \"Problème de licence\".\n\n2. Informations techniques essentielles\n - Logiciel : ESQ\n - Version : Non spécifiée dans les informations fournies\n - Configuration : Le logiciel a été réinstallé sur un autre serveur pour permettre le travail à distance. Une version réseau a été achetée en 2019 par JB Lafitte.\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 suite à sa réinstallation sur un autre serveur. Il se demande si le numéro de licence a été modifié suite à l'achat d'une version réseau en 2019.\n - Informations techniques fournies par le client : Le client a joint deux images (image006.jpg et image005.jpg) montrant probablement la fenêtre d'activation du logiciel.\n - Réponse du support : Aucune réponse n'est incluse dans les informations fournies.",
"rapport_genere": "**Rapport technique pour le ticket #T0101**\n\n1. **Résumé exécutif**\n - Nom de la demande : ACTIVATION LOGICIEL\n - Description : Le client rencontre un problème de licence lors de l'activation du logiciel ESQ après sa réinstallation sur un autre serveur.\n\n2. **Chronologie des échanges**\n\n| Date | Émetteur | Type | Contenu |\n| --- | --- | --- | --- |\n| 26/03/2020 14:43:45 | CLIENT | Question | Demande d'aide pour l'activation du logiciel ESQ suite à sa réinstallation sur un autre serveur. Se demande si le numéro de licence a été modifié suite à l'achat d'une version réseau en 2019. |\n| - | SUPPORT | Réponse | Aucune réponse incluse dans les informations fournies. |\n\n3. **Analyse des images pertinentes**\n\n#### IMAGE 1: image005.jpg\n\n- **Description objective** : Cette 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 activer le logiciel.\n- **Éléments techniques clés** :\n - Titre de la fenêtre : \"Activation du logiciel\"\n - Champ d'ID du logiciel : Un champ vide pour entrer l'ID du logiciel.\n - 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 à support@cbad.com ou par téléphone au 04 68 61 53 15 ou en cliquant sur le bouton téléphone situé en haut à droite de la fenêtre.\"\n - Options d'activation : \"Activer le logiciel (par internet)\", \"Activer plus tard (4 jours restants)\", \"Activation par téléphone\"\n\n4. **Diagnostic technique**\n - Logiciel : ESQ\n - Version : Non spécifiée\n - Configuration : Le logiciel a été réinstallé sur un autre serveur pour permettre le travail à distance. Une version réseau a été achetée en 2019 par JB Lafitte.\n - Problème : Le client ne parvient pas à activer le logiciel ESQ après sa réinstallation sur un autre serveur, probablement en raison d'un problème de licence ou d'ID de logiciel. L'image jointe montre la fenêtre d'activation du logiciel, indiquant que l'ID de logiciel doit être saisi pour activer le logiciel. Le client doit contacter CBAD pour obtenir l'ID de licence, comme indiqué dans le message d'instructions."
}

View File

@ -0,0 +1,146 @@
# Rapport d'analyse du ticket #T0101
*Généré le: 20250407_153643*
**Rapport technique pour le ticket #T0101**
1. **Résumé exécutif**
- Nom de la demande : ACTIVATION LOGICIEL
- Description : Le client rencontre un problème de licence lors de l'activation du logiciel ESQ après sa réinstallation sur un autre serveur.
2. **Chronologie des échanges**
| Date | Émetteur | Type | Contenu |
| --- | --- | --- | --- |
| 26/03/2020 14:43:45 | CLIENT | Question | Demande d'aide pour l'activation du logiciel ESQ suite à sa réinstallation sur un autre serveur. Se demande si le numéro de licence a été modifié suite à l'achat d'une version réseau en 2019. |
| - | SUPPORT | Réponse | Aucune réponse incluse dans les informations fournies. |
3. **Analyse des images pertinentes**
#### IMAGE 1: image005.jpg
- **Description objective** : Cette 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 activer le logiciel.
- **Éléments techniques clés** :
- Titre de la fenêtre : "Activation du logiciel"
- Champ d'ID du logiciel : Un champ vide pour entrer l'ID du logiciel.
- 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 à support@cbad.com ou par téléphone au 04 68 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"
4. **Diagnostic technique**
- Logiciel : ESQ
- Version : Non spécifiée
- Configuration : Le logiciel a été réinstallé sur un autre serveur pour permettre le travail à distance. Une version réseau a été achetée en 2019 par JB Lafitte.
- Problème : Le client ne parvient pas à activer le logiciel ESQ après sa réinstallation sur un autre serveur, probablement en raison d'un problème de licence ou d'ID de logiciel. L'image jointe montre la fenêtre d'activation du logiciel, indiquant que l'ID de logiciel doit être saisi pour activer le logiciel. Le client doit contacter CBAD pour obtenir l'ID de licence, comme indiqué dans le message d'instructions.
## Informations techniques
### Processus d'analyse
#### Analyse du ticket
<details>
<summary>Cliquez pour voir l'analyse complète du ticket</summary>
```
1. Analyse du problème initial
Le nom de la demande indique "ACTIVATION LOGICIEL", suggérant que le client est confronté à un problème d'activation d'un logiciel. La description du problème mentionne spécifiquement un "Problème de licence".
2. Informations techniques essentielles
- Logiciel : ESQ
- Version : Non spécifiée dans les informations fournies
- Configuration : Le logiciel a été réinstallé sur un autre serveur pour permettre le travail à distance. Une version réseau a été achetée en 2019 par JB Lafitte.
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 suite à sa réinstallation sur un autre serveur. Il se demande si le numéro de licence a été modifié suite à l'achat d'une version réseau en 2019.
- Informations techniques fournies par le client : Le client a joint deux images (image006.jpg et image005.jpg) montrant probablement la fenêtre d'activation du logiciel.
- Réponse du support : Aucune réponse n'est incluse dans les informations fournies.
```
</details>
#### Tri des images
| Image | Pertinence | Raison |
|-------|------------|--------|
| image006.jpg | Non | Non. Cette image montre le logo d'une entreprise nommée "Provençale Carbone de Calcium". Elle n'est pas pertinente pour le support technique de logiciels, car elle ne montre pas de captures d'écran de logiciels, de messages d'erreur, de configurations système, ou d'autres éléments techniques liés à l'informatique. |
| image005.jpg | Oui | oui. L'image montre une fenêtre d'activation de logiciel, ce qui est pertinent pour le support technique de logiciels. |
#### Analyse des images pertinentes
##### Image: image005.jpg
<details>
<summary>Cliquez pour voir l'analyse complète de l'image</summary>
```
### 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 activer le logiciel.
#### 2. Éléments techniques clés:
- **Titre de la fenêtre**: "Activation du logiciel"
- **Champ d'ID du logiciel**: Un champ vide pour entrer l'ID du logiciel.
- **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 à support@cbad.com ou par téléphone au 04 68 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. La fenêtre d'activation montre que le logiciel nécessite un ID de licence pour être activé, ce qui est en ligne avec le problème de licence mentionné dans le ticket. Le message d'instructions indique que le client doit contacter CBAD pour obtenir l'ID de licence, ce qui pourrait être la source du problème d'activation.
```
</details>
### Agents et modèles utilisés
#### Agent de tri d'images
- **Modèle**: pixtral-12b-latest
- **Température**: 0.2
- **Top-p**: 0.8
- **Max tokens**: 300
#### Agent d'analyse d'images
- **Modèle**: pixtral-12b-latest
- **Température**: 0.3
- **Top-p**: 0.9
- **Max tokens**: 1200
#### Agent de génération de rapport
- **Modèle**: mistral-medium
- **Température**: 0.4
- **Top-p**: 0.9
- **Max tokens**: 2500
### Statistiques
- **Images analysées**: 2
- **Images pertinentes**: 1
- **timestamp_debut**: 20250407_153627
- **ticket_id**: T0101
- **images_analysees**: 2
- **images_pertinentes**: 1
- **model**: mistral-medium
- **temperature**: 0.4
- **top_p**: 0.9
- **max_tokens**: 2500
- **system_prompt**: Tu es un expert en génération de rapports techniques pour BRG-Lab.
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.

View File

@ -1,5 +1,5 @@
from orchestrator import Orchestrator
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
@ -65,7 +65,7 @@ def test_orchestrator(ticket_id=None):
# Création des agents
print("Création des agents...")
json_agent = AgentJsonAnalyser(json_llm)
ticket_agent = AgentTicketAnalyser(json_llm)
image_sorter = AgentImageSorter(image_sorter_llm)
image_analyser = AgentImageAnalyser(image_analyser_llm)
report_generator = AgentReportGenerator(report_generator_llm)
@ -78,7 +78,7 @@ def test_orchestrator(ticket_id=None):
orchestrator = Orchestrator(
output_dir="output/",
json_agent=json_agent,
ticket_agent=ticket_agent,
image_sorter=image_sorter,
image_analyser=image_analyser,
report_generator=report_generator

309
utils/ticket_data_loader.py Normal file
View File

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