mirror of
https://github.com/Ladebeze66/llm_ticket3.git
synced 2025-12-15 22:06:50 +01:00
11:34
This commit is contained in:
parent
501d9506b6
commit
da64fb1131
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,577 @@
|
||||
<!-- Generated by SpecStory -->
|
||||
|
||||
# Comparaison des formats Ollama et Mistral (2025-04-08 11:12:20)
|
||||
|
||||
_**User**_
|
||||
|
||||
j'ai une disparité lorsque j'utilise ollama ou mistral le tableau question/réponse ne se fait pas avec ollama je voudrai avoir des réglages équivalent que ce soit avec mistral ou ollama dans mon agent_report_generator.py il faudrait déterminer d'où vient cette différence de format des résultats
|
||||
|
||||
---
|
||||
|
||||
_**Assistant**_
|
||||
|
||||
Read file: agents/agent_report_generator.py
|
||||
|
||||
---
|
||||
|
||||
Read file: agents/agent_report_generator.py
|
||||
|
||||
---
|
||||
|
||||
```diff
|
||||
texte_rapport = texte_rapport.replace("CBAD", "CBAO")
|
||||
|
||||
- # Rechercher un objet JSON dans le texte
|
||||
- json_match = re.search(r'```json\s*({.*?})\s*```', texte_rapport, re.DOTALL)
|
||||
-
|
||||
- if not json_match:
|
||||
+ # Patterns de recherche plus variés pour s'adapter aux différents modèles
|
||||
+ patterns = [
|
||||
+ r'```json\s*({.*?})\s*```', # Pattern standard avec backticks triples
|
||||
+ r'```\s*({.*?"chronologie_echanges".*?})\s*```', # Pattern sans spécifier json mais avec le contenu attendu
|
||||
+ r'{[\s\n]*"chronologie_echanges"[\s\n]*:[\s\n]*\[.*?\][\s\n]*}', # Pattern sans backticks
|
||||
+ r'<json>(.*?)</json>' # Pattern alternatif avec balises xml
|
||||
+ ]
|
||||
+
|
||||
+ # Essayer chaque pattern
|
||||
+ json_text = None
|
||||
+ for pattern in patterns:
|
||||
+ json_match = re.search(pattern, texte_rapport, re.DOTALL)
|
||||
+ if json_match:
|
||||
+ json_text = json_match.group(1).strip()
|
||||
+ logger.info(f"JSON trouvé avec le pattern: {pattern[:20]}...")
|
||||
+ break
|
||||
+
|
||||
+ # Si aucun pattern n'a fonctionné, tenter une approche plus agressive pour extraire le JSON
|
||||
+ if not json_text:
|
||||
+ # Chercher des indices de début de JSON dans le texte
|
||||
+ potential_starts = [
|
||||
+ texte_rapport.find('{"chronologie_echanges"'),
|
||||
+ texte_rapport.find('{\n "chronologie_echanges"'),
|
||||
+ texte_rapport.find('{ "chronologie_echanges"')
|
||||
+ ]
|
||||
+
|
||||
+ # Filtrer les indices valides (non -1)
|
||||
+ valid_starts = [idx for idx in potential_starts if idx != -1]
|
||||
+
|
||||
+ if valid_starts:
|
||||
+ # Prendre l'indice le plus petit (premier dans le texte)
|
||||
+ start_idx = min(valid_starts)
|
||||
+ # Chercher la fin du JSON (accolade fermante suivie d'une nouvelle ligne ou de la fin du texte)
|
||||
+ json_extract = texte_rapport[start_idx:]
|
||||
+ # Compter les accolades pour trouver la fermeture du JSON
|
||||
+ open_braces = 0
|
||||
+ close_idx = -1
|
||||
+
|
||||
+ for i, char in enumerate(json_extract):
|
||||
+ if char == '{':
|
||||
+ open_braces += 1
|
||||
+ elif char == '}':
|
||||
+ open_braces -= 1
|
||||
+ if open_braces == 0:
|
||||
+ close_idx = i
|
||||
+ break
|
||||
+
|
||||
+ if close_idx != -1:
|
||||
+ json_text = json_extract[:close_idx + 1]
|
||||
+ logger.info(f"JSON extrait par analyse d'accolades: {len(json_text)} caractères")
|
||||
+
|
||||
+ if not json_text:
|
||||
logger.warning("Aucun JSON trouvé dans le rapport")
|
||||
return texte_rapport, None, None
|
||||
|
||||
- # Extraire le JSON et le parser
|
||||
- json_text = json_match.group(1)
|
||||
- try:
|
||||
+ # Nettoyage supplémentaire du JSON
|
||||
+ # Enlever caractères non imprimables ou indésirables qui pourraient être ajoutés par certains modèles
|
||||
+ json_text = re.sub(r'[\x00-\x1F\x7F]', '', json_text)
|
||||
+
|
||||
+ try:
|
||||
+ # Vérifier que le texte commence par { et se termine par }
|
||||
+ if not (json_text.startswith('{') and json_text.endswith('}')):
|
||||
+ logger.warning(f"Format JSON incorrect, tentative de correction. Texte: {json_text[:50]}...")
|
||||
+ # Chercher les délimiteurs du JSON
|
||||
+ start = json_text.find('{')
|
||||
+ end = json_text.rfind('}')
|
||||
+ if start != -1 and end != -1 and start < end:
|
||||
+ json_text = json_text[start:end+1]
|
||||
+
|
||||
echanges_json = json.loads(json_text)
|
||||
logger.info(f"JSON extrait avec succès: {len(json_text)} caractères")
|
||||
|
||||
+ # Vérifier si le JSON a la structure attendue
|
||||
+ if not isinstance(echanges_json, dict) or "chronologie_echanges" not in echanges_json:
|
||||
+ # Tenter de corriger la structure si possible
|
||||
+ if len(echanges_json) > 0 and isinstance(list(echanges_json.values())[0], list):
|
||||
+ # Prendre la première liste comme chronologie
|
||||
+ key = list(echanges_json.keys())[0]
|
||||
+ echanges_json = {"chronologie_echanges": echanges_json[key]}
|
||||
+ logger.info(f"Structure JSON corrigée en utilisant la clé: {key}")
|
||||
+ else:
|
||||
+ logger.warning("Structure JSON incorrecte et non réparable")
|
||||
+ return texte_rapport, None, None
|
||||
+
|
||||
# Convertir en tableau Markdown
|
||||
echanges_markdown = "| Date | Émetteur | Type | Contenu | Statut |\n"
|
||||
|
||||
# Remplacer le JSON dans le texte par le tableau Markdown
|
||||
- rapport_traite = texte_rapport.replace(json_match.group(0), echanges_markdown)
|
||||
+ # Si le JSON était entouré de backticks, remplacer tout le bloc
|
||||
+ if json_match:
|
||||
+ rapport_traite = texte_rapport.replace(json_match.group(0), echanges_markdown)
|
||||
+ else:
|
||||
+ # Sinon, remplacer juste le texte JSON
|
||||
+ rapport_traite = texte_rapport.replace(json_text, echanges_markdown)
|
||||
|
||||
return rapport_traite, echanges_json, echanges_markdown
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Erreur lors du décodage JSON: {e}")
|
||||
+ logger.debug(f"Contenu JSON problématique: {json_text[:100]}...")
|
||||
return texte_rapport, None, None
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```diff
|
||||
]
|
||||
}
|
||||
+ - La structure doit être EXACTEMENT comme indiquée, avec le nom de clé "chronologie_echanges" obligatoirement
|
||||
- Chaque message du ticket doit apparaître comme un objet dans la liste
|
||||
- Indique clairement qui est CLIENT et qui est SUPPORT dans le champ "emetteur"
|
||||
- Si une question n'a pas de réponse, assure-toi de le noter clairement
|
||||
- Toute mention de "CBAD" doit être remplacée par "CBAO" qui est le nom correct de la société
|
||||
- Tu dois synthétiser au mieux les échanges (le plus court et clair possible)
|
||||
+
|
||||
+ IMPORTANT POUR LE FORMAT:
|
||||
+ - Le JSON doit être valide et parsable
|
||||
+ - Utilise ```json et ``` pour délimiter le bloc JSON
|
||||
+ - Ne modifie pas la structure des clés ("chronologie_echanges", "date", "emetteur", "type", "contenu")
|
||||
+ - Assure-toi que les accolades et crochets sont correctement équilibrés
|
||||
|
||||
Structure ton rapport:
|
||||
1. Résumé exécutif: Synthèse du problème initial (nom de la demande + description)
|
||||
- 2. Chronologie des échanges: Objet JSON avec la structure imposée ci-dessus
|
||||
+ 2. Chronologie des échanges: Objet JSON avec la structure imposée ci-dessus (partie CRUCIALE)
|
||||
3. Analyse des images: Ce que montrent les captures d'écran et leur pertinence
|
||||
4. Diagnostic technique: Interprétation des informations techniques pertinentes
|
||||
|
||||
Reste factuel et précis dans ton analyse.
|
||||
Les données d'échanges client/support sont l'élément le plus important du rapport.
|
||||
- Tu DOIS inclure le JSON des échanges dans ta réponse au format:
|
||||
+ Tu DOIS inclure le JSON des échanges dans ta réponse exactement au format:
|
||||
```json
|
||||
{
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```diff
|
||||
params["temperature"] -= 0.05
|
||||
elif "ollama" in self.llm.__class__.__name__.lower():
|
||||
- params["temperature"] += 0.1
|
||||
+ # Ajustements spécifiques pour Ollama pour forcer le respect du format
|
||||
+ params["temperature"] = 0.1 # Température plus basse pour plus de précision
|
||||
params.update({
|
||||
- "num_ctx": 2048,
|
||||
- "repeat_penalty": 1.1,
|
||||
- })
|
||||
+ "num_ctx": 4096, # Contexte plus large
|
||||
+ "repeat_penalty": 1.3, # Pénalité plus forte pour éviter la répétition
|
||||
+ "top_k": 30, # Limiter les choix pour plus de cohérence
|
||||
+ "num_predict": 2048, # Nombre de tokens à prédire
|
||||
+ "stop": ["```", "</json>"] # Arrêter à ces marqueurs pour mieux délimiter le JSON
|
||||
+ })
|
||||
+
|
||||
+ # Ajouter des exemples dans le système prompt pour Ollama si ce n'est pas déjà fait
|
||||
+ if not "EXEMPLES D'ÉCHANGES" in self.llm.prompt_system:
|
||||
+ exemple_json = """
|
||||
+ EXEMPLES D'ÉCHANGES POUR RÉFÉRENCE:
|
||||
+
|
||||
+ Exemple 1:
|
||||
+ ```json
|
||||
+ {
|
||||
+ "chronologie_echanges": [
|
||||
+ {"date": "2023-01-15", "emetteur": "CLIENT", "type": "Question", "contenu": "Je n'arrive pas à me connecter à l'application"},
|
||||
+ {"date": "2023-01-16", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Avez-vous essayé de réinitialiser votre mot de passe?"}
|
||||
+ ]
|
||||
+ }
|
||||
+ ```
|
||||
+
|
||||
+ Exemple 2:
|
||||
+ ```json
|
||||
+ {
|
||||
+ "chronologie_echanges": [
|
||||
+ {"date": "2023-02-10", "emetteur": "CLIENT", "type": "Information technique", "contenu": "Version de l'application: 2.3.1"},
|
||||
+ {"date": "2023-02-11", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Cette version contient un bug connu, veuillez mettre à jour"}
|
||||
+ ]
|
||||
+ }
|
||||
+ ```
|
||||
+
|
||||
+ N'oublie pas que le format EXACT est important. Utilise TOUJOURS la clé "chronologie_echanges" comme clé principale.
|
||||
+ """
|
||||
+ self.llm.prompt_system += exemple_json
|
||||
+ logger.info("Exemples JSON ajoutés au prompt système pour Ollama")
|
||||
|
||||
self.llm.configurer(**params)
|
||||
+ logger.info(f"Configuration appliquée au modèle: {str(params)}")
|
||||
+ else:
|
||||
+ logger.warning("Le modèle LLM ne supporte pas la méthode configurer()")
|
||||
|
||||
def executer(self, rapport_data: Dict, rapport_dir: str) -> Tuple[Optional[str], Optional[str]]:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```diff
|
||||
logger.info(f"Ajout de l'analyse de l'image {image_name} au prompt ({len(str(analyse))} caractères)")
|
||||
|
||||
- # Ne pas répéter les instructions déjà présentes dans le system_prompt
|
||||
+ # Instructions claires pour Ollama et autres modèles
|
||||
prompt += f"""
|
||||
- N'oublie pas d'inclure un objet JSON structuré des échanges client/support selon le format demandé.
|
||||
- Assure-toi que le JSON est valide et clairement balisé avec ```json et ``` dans ta réponse.
|
||||
+ ## INSTRUCTIONS POUR LA GÉNÉRATION DU RAPPORT
|
||||
+
|
||||
+ 1. Résume d'abord le problème principal du ticket en quelques phrases.
|
||||
+
|
||||
+ 2. GÉNÉRER OBLIGATOIREMENT LE JSON DES ÉCHANGES CLIENT/SUPPORT:
|
||||
+ - Les données d'échanges sont l'élément le plus important du rapport
|
||||
+ - Utilise EXACTEMENT la structure suivante, sans la modifier:
|
||||
+ ```json
|
||||
+ {{
|
||||
+ "chronologie_echanges": [
|
||||
+ {{"date": "date1", "emetteur": "CLIENT", "type": "Question", "contenu": "contenu de la question"}},
|
||||
+ {{"date": "date2", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "contenu de la réponse"}}
|
||||
+ ]
|
||||
+ }}
|
||||
+ ```
|
||||
+ - La clé principale DOIT être "chronologie_echanges"
|
||||
+ - N'ajoute pas de commentaires ou de texte dans le JSON
|
||||
+ - Assure-toi que le JSON est valide et correspond EXACTEMENT au format demandé
|
||||
+ - Entoure le JSON avec ```json et ``` pour faciliter l'extraction
|
||||
+
|
||||
+ 3. Après le JSON, analyse les images pertinentes et leur contribution à la compréhension du problème.
|
||||
+
|
||||
+ 4. Termine par une analyse technique des causes probables du problème.
|
||||
+
|
||||
+ IMPORTANT: Le JSON des échanges client/support est OBLIGATOIRE et doit être parfaitement formaté.
|
||||
+ """
|
||||
+
|
||||
+ # Vérifier si nous utilisons Ollama et ajouter un exemple supplémentaire si nécessaire
|
||||
+ if hasattr(self.llm, "__class__") and "ollama" in self.llm.__class__.__name__.lower():
|
||||
+ prompt += """
|
||||
+ EXEMPLE EXACT DU FORMAT JSON ATTENDU:
|
||||
+ ```json
|
||||
+ {
|
||||
+ "chronologie_echanges": [
|
||||
+ {"date": "2023-05-10", "emetteur": "CLIENT", "type": "Question", "contenu": "L'application affiche une erreur lors de la connexion"},
|
||||
+ {"date": "2023-05-11", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Pouvez-vous préciser le message d'erreur?"},
|
||||
+ {"date": "2023-05-12", "emetteur": "CLIENT", "type": "Information technique", "contenu": "Message: Erreur de connexion au serveur"}
|
||||
+ ]
|
||||
+ }
|
||||
+ ```
|
||||
"""
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
J'ai apporté plusieurs modifications importantes pour résoudre le problème de disparité entre Mistral et Ollama concernant la génération du tableau question/réponse:
|
||||
|
||||
1. **Extraction JSON améliorée**:
|
||||
- Ajout de multiples patterns de recherche pour s'adapter aux différentes façons dont les modèles formatent le JSON
|
||||
- Méthode alternative d'extraction basée sur l'analyse des accolades quand les expressions régulières échouent
|
||||
- Nettoyage et correction du JSON extrait pour augmenter les chances de réussite du parsing
|
||||
|
||||
2. **Prompt système renforcé**:
|
||||
- Instructions plus précises sur la structure JSON attendue
|
||||
- Mise en évidence de l'importance de la clé "chronologie_echanges"
|
||||
- Ajout d'une section dédiée au format pour renforcer les contraintes
|
||||
|
||||
3. **Configuration spécifique pour Ollama**:
|
||||
- Température réduite à 0.1 pour favoriser une génération plus déterministe
|
||||
- Paramètres spécifiques ajustés: repeat_penalty, top_k, num_ctx, num_predict
|
||||
- Ajout de tokens d'arrêt pour mieux délimiter le JSON
|
||||
- Exemples concrets intégrés au prompt système
|
||||
|
||||
4. **Instructions dans le prompt de génération**:
|
||||
- Format JSON explicitement montré avec la structure attendue
|
||||
- Instructions étape par étape plus claires
|
||||
- Exemple supplémentaire pour Ollama
|
||||
|
||||
Ces changements devraient harmoniser le comportement entre Mistral et Ollama en:
|
||||
- Renforçant les contraintes de format pour Ollama
|
||||
- Améliorant la robustesse de l'extraction JSON quand le format n'est pas parfaitement respecté
|
||||
- Fournissant plus d'exemples concrets pour guider Ollama
|
||||
|
||||
Les modifications permettront d'obtenir des tableaux de questions/réponses cohérents, quel que soit le modèle utilisé.
|
||||
|
||||
---
|
||||
|
||||
_**User**_
|
||||
|
||||
pourquoi j'ai cette partie de code a quoi sert elle?
|
||||
|
||||
---
|
||||
|
||||
_**Assistant**_
|
||||
|
||||
<details>
|
||||
<summary>Searched codebase "pourquoi j'ai cette partie de code a quoi sert elle?" • **25** results</summary>
|
||||
|
||||
| File | Lines |
|
||||
|------|-------|
|
||||
| `.specstory/history/2025-04-01_13-55-amélioration-de-l'extraction-des-pièces-jointes.md` | L13646-13684 |
|
||||
| `.specstory/history/2025-04-01_13-55-problème-d'exécution-du-programme.md` | L1957-1991 |
|
||||
| `.specstory/history/2025-04-01_13-55-problème-d'exécution-du-programme.md` | L11216-11254 |
|
||||
| `llm_classes/base_llm.py` | L60-93 |
|
||||
| `.specstory/history/2025-04-01_13-55-problème-d'exécution-du-programme.md` | L5124-5204 |
|
||||
| `.specstory/history/2025-04-01_13-55-amélioration-de-l'extraction-des-pièces-jointes.md` | L14674-14707 |
|
||||
| `.specstory/history/2025-04-01_13-55-amélioration-de-l'extraction-des-pièces-jointes.md` | L11758-11799 |
|
||||
| `.specstory/history/2025-04-01_13-55-amélioration-de-l'extraction-des-pièces-jointes.md` | L3244-3296 |
|
||||
| `.specstory/history/2025-04-01_13-55-amélioration-de-l'extraction-des-pièces-jointes.md` | L6527-6630 |
|
||||
| `.specstory/history/2025-04-01_13-55-amélioration-de-l'extraction-des-pièces-jointes.md` | L14403-14468 |
|
||||
| `.specstory/history/2025-04-07_07-17-analyse-de-code-et-ajout-de-logs.md` | L4351-4407 |
|
||||
| `.specstory/history/2025-04-01_13-55-amélioration-de-l'extraction-des-pièces-jointes.md` | L11799-11843 |
|
||||
| `.specstory/history/2025-04-07_07-12-analyse-de-code-et-ajout-de-logs.md` | L205-239 |
|
||||
| `orchestrator.py` | L145-162 |
|
||||
| `.specstory/history/2025-04-01_13-55-problème-d'exécution-du-programme.md` | L11295-11491 |
|
||||
| `.specstory/history/2025-04-01_13-55-amélioration-de-l'extraction-des-pièces-jointes.md` | L14468-14508 |
|
||||
| `.specstory/history/2025-04-01_13-55-amélioration-de-l'extraction-des-pièces-jointes.md` | L11364-11399 |
|
||||
| `.specstory/history/2025-04-07_07-17-analyse-de-code-et-ajout-de-logs.md` | L2536-2724 |
|
||||
| `.specstory/history/2025-04-01_13-55-amélioration-de-l'extraction-des-pièces-jointes.md` | L3014-3052 |
|
||||
| `.specstory/history/2025-04-07_07-17-analyse-de-code-et-ajout-de-logs.md` | L4312-4351 |
|
||||
| `.specstory/history/2025-04-07_07-17-analyse-de-code-et-ajout-de-logs.md` | L859-886 |
|
||||
| `.specstory/history/2025-04-07_07-17-analyse-de-code-et-ajout-de-logs.md` | L1210-1239 |
|
||||
| `.specstory/history/2025-04-01_13-55-amélioration-de-l'extraction-des-pièces-jointes.md` | L11320-11364 |
|
||||
| `.specstory/history/2025-04-01_13-55-problème-d'exécution-du-programme.md` | L11019-11061 |
|
||||
| `.specstory/history/2025-04-07_07-17-analyse-de-code-et-ajout-de-logs.md` | L248-276 |
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
Read file: agents/agent_report_generator.py
|
||||
|
||||
---
|
||||
|
||||
Les modifications que vous avez récemment acceptées concernent trois aspects principaux pour résoudre les problèmes de compatibilité entre Mistral et Ollama lors de la génération du tableau question/réponse dans le rapport :
|
||||
|
||||
1. **L'extraction robuste du JSON** (méthode `_extraire_et_traiter_json`)
|
||||
- Cette partie de code a été modifiée pour rechercher le JSON dans différents formats possibles
|
||||
- Elle utilise plusieurs expressions régulières et une méthode basée sur le comptage des accolades
|
||||
- Elle nettoie et corrige automatiquement le JSON mal formaté
|
||||
- Elle transforme ensuite ce JSON en tableau Markdown structuré
|
||||
|
||||
2. **Le prompt spécifique pour Ollama** (méthode `_formater_prompt_pour_rapport`)
|
||||
- Des instructions plus précises ont été ajoutées pour guider spécifiquement Ollama
|
||||
- Un exemple concret du format JSON attendu a été ajouté pour Ollama
|
||||
- Le code vérifie si on utilise Ollama et adapte le prompt en conséquence
|
||||
|
||||
3. **La configuration spécifique pour Ollama** (méthode `_appliquer_config_locale`)
|
||||
- Paramètres ajustés pour Ollama (température, repeat_penalty, etc.)
|
||||
- Ajout d'exemples dans le prompt système quand Ollama est détecté
|
||||
- Configuration de tokens d'arrêt spécifiques pour Ollama
|
||||
|
||||
Ces modifications étaient nécessaires car Ollama (en tant que modèle open-source local) a tendance à être moins strict dans le respect des formats spécifiés comparé à Mistral (modèle API). Les changements garantissent que les deux modèles produisent des résultats compatibles pour le tableau question/réponse, soit en guidant Ollama pour produire un format JSON correct, soit en améliorant la robustesse de l'extraction JSON quand le format n'est pas parfait.
|
||||
|
||||
---
|
||||
|
||||
_**User**_
|
||||
|
||||
je veux parler de la partie ajustements selon le type de modèle qu'elle impact a t elle quels sont les paramètres pris en compte ceux du début du fichier ou ceux-ci car si c'est le cas mon protocole de test est alors erroné
|
||||
|
||||
---
|
||||
|
||||
_**Assistant**_
|
||||
|
||||
Read file: agents/agent_report_generator.py
|
||||
|
||||
---
|
||||
|
||||
Read file: agents/agent_report_generator.py
|
||||
|
||||
---
|
||||
|
||||
La partie "Ajustements selon le type de modèle" dans la méthode `_appliquer_config_locale()` a un impact crucial sur votre protocole de test, car elle modifie significativement les paramètres initiaux définis au début de la classe.
|
||||
|
||||
Voici ce qui se passe exactement:
|
||||
|
||||
1. **Définition initiale des paramètres** - Au début de la classe, vous définissez:
|
||||
```python
|
||||
self.temperature = 0.4
|
||||
self.top_p = 0.9
|
||||
self.max_tokens = 2500
|
||||
```
|
||||
|
||||
2. **Ajustements spécifiques par modèle** - Dans la méthode `_appliquer_config_locale()`, ces valeurs sont modifiées:
|
||||
- Pour Mistral Medium:
|
||||
- temperature = 0.45 (0.4 + 0.05)
|
||||
- max_tokens = 1000 (remplacé complètement)
|
||||
- Pour Pixtral:
|
||||
- temperature = 0.35 (0.4 - 0.05)
|
||||
- Pour Ollama:
|
||||
- temperature = 0.1 (remplacé complètement)
|
||||
- Paramètres supplémentaires: num_ctx, repeat_penalty, top_k, num_predict, stop
|
||||
|
||||
3. **Application effective des paramètres** - Ces paramètres modifiés sont ceux qui sont réellement appliqués au modèle via `self.llm.configurer(**params)`, et non les valeurs initiales.
|
||||
|
||||
**Impact sur votre protocole de test:**
|
||||
|
||||
Si votre protocole de test considère que les paramètres utilisés sont ceux définis au début de la classe (0.4, 0.9, 2500), alors vos résultats seront trompeurs car ce ne sont pas les valeurs effectivement utilisées. Pour Ollama en particulier, la différence est importante:
|
||||
- Température 0.4 → 0.1 (beaucoup plus déterministe)
|
||||
- Ajout de paramètres spécifiques à Ollama qui changent complètement son comportement
|
||||
|
||||
Pour corriger votre protocole de test:
|
||||
1. Vous devez considérer les paramètres effectifs après les ajustements spécifiques par modèle
|
||||
2. Si vous souhaitez tester avec des paramètres identiques, vous devriez désactiver ces ajustements spécifiques
|
||||
3. Les logs contiennent les paramètres appliqués grâce à la ligne: `logger.info(f"Configuration appliquée au modèle: {str(params)}")`
|
||||
|
||||
Cette différence de paramétrage explique potentiellement pourquoi vous obteniez des résultats différents entre Mistral et Ollama dans votre situation initiale - ils n'utilisaient pas du tout les mêmes configurations.
|
||||
|
||||
---
|
||||
|
||||
_**User**_
|
||||
|
||||
peux tu supprimer ce paramètrage spécifique de mon agent pour avoir les meme paramètres tout le temps
|
||||
|
||||
---
|
||||
|
||||
_**Assistant**_
|
||||
|
||||
```diff
|
||||
self.llm.prompt_system = self.system_prompt
|
||||
|
||||
- # Appliquer les paramètres
|
||||
+ # Appliquer les paramètres - mêmes paramètres pour tous les modèles
|
||||
if hasattr(self.llm, "configurer"):
|
||||
params = {
|
||||
}
|
||||
|
||||
- # 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():
|
||||
- # Ajustements spécifiques pour Ollama pour forcer le respect du format
|
||||
- params["temperature"] = 0.1 # Température plus basse pour plus de précision
|
||||
- params.update({
|
||||
- "num_ctx": 4096, # Contexte plus large
|
||||
- "repeat_penalty": 1.3, # Pénalité plus forte pour éviter la répétition
|
||||
- "top_k": 30, # Limiter les choix pour plus de cohérence
|
||||
- "num_predict": 2048, # Nombre de tokens à prédire
|
||||
- "stop": ["```", "</json>"] # Arrêter à ces marqueurs pour mieux délimiter le JSON
|
||||
- })
|
||||
-
|
||||
- # Ajouter des exemples dans le système prompt pour Ollama si ce n'est pas déjà fait
|
||||
+ # Ajout des exemples dans le prompt système pour tous les modèles
|
||||
if not "EXEMPLES D'ÉCHANGES" in self.llm.prompt_system:
|
||||
exemple_json = """
|
||||
"""
|
||||
self.llm.prompt_system += exemple_json
|
||||
- logger.info("Exemples JSON ajoutés au prompt système pour Ollama")
|
||||
+ logger.info("Exemples JSON ajoutés au prompt système")
|
||||
|
||||
self.llm.configurer(**params)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```diff
|
||||
logger.info(f"Ajout de l'analyse de l'image {image_name} au prompt ({len(str(analyse))} caractères)")
|
||||
|
||||
- # Instructions claires pour Ollama et autres modèles
|
||||
+ # Instructions claires pour tous les modèles
|
||||
prompt += f"""
|
||||
## INSTRUCTIONS POUR LA GÉNÉRATION DU RAPPORT
|
||||
|
||||
IMPORTANT: Le JSON des échanges client/support est OBLIGATOIRE et doit être parfaitement formaté.
|
||||
- """
|
||||
|
||||
- # Vérifier si nous utilisons Ollama et ajouter un exemple supplémentaire si nécessaire
|
||||
- if hasattr(self.llm, "__class__") and "ollama" in self.llm.__class__.__name__.lower():
|
||||
- prompt += """
|
||||
EXEMPLE EXACT DU FORMAT JSON ATTENDU:
|
||||
```json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
J'ai modifié le code pour uniformiser les paramètres utilisés par tous les modèles LLM :
|
||||
|
||||
1. Dans la méthode `_appliquer_config_locale()` :
|
||||
- Suppression des ajustements spécifiques pour Mistral, Pixtral et Ollama
|
||||
- Conservation des paramètres initiaux (temperature=0.4, top_p=0.9, max_tokens=2500) pour tous les modèles
|
||||
- Ajout des exemples JSON au prompt système pour tous les modèles, pas seulement pour Ollama
|
||||
|
||||
2. Dans la méthode `_formater_prompt_pour_rapport()` :
|
||||
- Suppression de la condition spécifique à Ollama
|
||||
- Ajout de l'exemple JSON pour tous les modèles
|
||||
|
||||
Avec ces modifications, vos tests seront plus cohérents car tous les modèles utiliseront exactement les mêmes paramètres et recevront les mêmes instructions, ce qui devrait faciliter la comparaison des résultats.
|
||||
|
||||
Ces changements vous permettront d'avoir un protocole de test plus rigoureux en éliminant les variables qui pourraient influencer les résultats différemment selon le modèle utilisé.
|
||||
|
||||
---
|
||||
|
||||
_**User**_
|
||||
|
||||
roblème de génération de rapport
|
||||
|
||||
---
|
||||
|
||||
_**Assistant**_
|
||||
|
||||
Read file: agents/agent_report_generator.py
|
||||
|
||||
---
|
||||
|
||||
```diff
|
||||
|
||||
IMPORTANT: Le JSON des échanges client/support est OBLIGATOIRE et doit être parfaitement formaté.
|
||||
+ """
|
||||
|
||||
+ # Ajouter l'exemple non formaté (sans f-string) pour éviter les erreurs de formatage
|
||||
+ prompt += """
|
||||
EXEMPLE EXACT DU FORMAT JSON ATTENDU:
|
||||
```json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```bash
|
||||
cd ~/llm-ticket3 && cat agents/agent_report_generator.py | grep -A 3 -B 3 "Invalid format"
|
||||
```
|
||||
|
||||
```
|
||||
< cat agents/agent_report_generator.py | grep -A 3 -B 3 "Invalid format"
|
||||
fgras-ca@PC-DEV:~/llm-ticket3$
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```bash
|
||||
cd ~/llm-ticket3 && python traitement.py T0101
|
||||
```
|
||||
|
||||
```
|
||||
fgras-ca@PC-DEV:~/llm-ticket3$ cd ~/llm-ticket3 && python traitement.py T0101
|
||||
python: can't open file '/home/fgras-ca/llm-ticket3/traitement.py': [Errno 2] No such file or directory
|
||||
fgras-ca@PC-DEV:~/llm-ticket3$
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
@ -57,21 +57,28 @@ EXIGENCE ABSOLUE - GÉNÉRATION DE DONNÉES EN FORMAT JSON:
|
||||
... autres échanges ...
|
||||
]
|
||||
}
|
||||
- La structure doit être EXACTEMENT comme indiquée, avec le nom de clé "chronologie_echanges" obligatoirement
|
||||
- Chaque message du ticket doit apparaître comme un objet dans la liste
|
||||
- Indique clairement qui est CLIENT et qui est SUPPORT dans le champ "emetteur"
|
||||
- Si une question n'a pas de réponse, assure-toi de le noter clairement
|
||||
- Toute mention de "CBAD" doit être remplacée par "CBAO" qui est le nom correct de la société
|
||||
- Tu dois synthétiser au mieux les échanges (le plus court et clair possible)
|
||||
|
||||
IMPORTANT POUR LE FORMAT:
|
||||
- Le JSON doit être valide et parsable
|
||||
- Utilise ```json et ``` pour délimiter le bloc JSON
|
||||
- Ne modifie pas la structure des clés ("chronologie_echanges", "date", "emetteur", "type", "contenu")
|
||||
- Assure-toi que les accolades et crochets sont correctement équilibrés
|
||||
|
||||
Structure ton rapport:
|
||||
1. Résumé exécutif: Synthèse du problème initial (nom de la demande + description)
|
||||
2. Chronologie des échanges: Objet JSON avec la structure imposée ci-dessus
|
||||
2. Chronologie des échanges: Objet JSON avec la structure imposée ci-dessus (partie CRUCIALE)
|
||||
3. Analyse des images: Ce que montrent les captures d'écran et leur pertinence
|
||||
4. Diagnostic technique: Interprétation des informations techniques pertinentes
|
||||
|
||||
Reste factuel et précis dans ton analyse.
|
||||
Les données d'échanges client/support sont l'élément le plus important du rapport.
|
||||
Tu DOIS inclure le JSON des échanges dans ta réponse au format:
|
||||
Tu DOIS inclure le JSON des échanges dans ta réponse exactement au format:
|
||||
```json
|
||||
{
|
||||
"chronologie_echanges": [
|
||||
@ -94,7 +101,7 @@ Tu DOIS inclure le JSON des échanges dans ta réponse au format:
|
||||
if hasattr(self.llm, "prompt_system"):
|
||||
self.llm.prompt_system = self.system_prompt
|
||||
|
||||
# Appliquer les paramètres
|
||||
# Appliquer les paramètres - mêmes paramètres pour tous les modèles
|
||||
if hasattr(self.llm, "configurer"):
|
||||
params = {
|
||||
"temperature": self.temperature,
|
||||
@ -102,21 +109,41 @@ Tu DOIS inclure le JSON des échanges dans ta réponse au format:
|
||||
"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,
|
||||
})
|
||||
# Ajout des exemples dans le prompt système pour tous les modèles
|
||||
if not "EXEMPLES D'ÉCHANGES" in self.llm.prompt_system:
|
||||
exemple_json = """
|
||||
EXEMPLES D'ÉCHANGES POUR RÉFÉRENCE:
|
||||
|
||||
Exemple 1:
|
||||
```json
|
||||
{
|
||||
"chronologie_echanges": [
|
||||
{"date": "2023-01-15", "emetteur": "CLIENT", "type": "Question", "contenu": "Je n'arrive pas à me connecter à l'application"},
|
||||
{"date": "2023-01-16", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Avez-vous essayé de réinitialiser votre mot de passe?"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Exemple 2:
|
||||
```json
|
||||
{
|
||||
"chronologie_echanges": [
|
||||
{"date": "2023-02-10", "emetteur": "CLIENT", "type": "Information technique", "contenu": "Version de l'application: 2.3.1"},
|
||||
{"date": "2023-02-11", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Cette version contient un bug connu, veuillez mettre à jour"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
N'oublie pas que le format EXACT est important. Utilise TOUJOURS la clé "chronologie_echanges" comme clé principale.
|
||||
"""
|
||||
self.llm.prompt_system += exemple_json
|
||||
logger.info("Exemples JSON ajoutés au prompt système")
|
||||
|
||||
self.llm.configurer(**params)
|
||||
|
||||
logger.info(f"Configuration appliquée au modèle: {str(params)}")
|
||||
else:
|
||||
logger.warning("Le modèle LLM ne supporte pas la méthode configurer()")
|
||||
|
||||
def executer(self, rapport_data: Dict, rapport_dir: str) -> Tuple[Optional[str], Optional[str]]:
|
||||
"""
|
||||
Génère un rapport à partir des analyses effectuées
|
||||
@ -632,10 +659,47 @@ Je vais d'abord vérifier que j'ai bien reçu les données d'analyses:
|
||||
prompt += f"\n### IMAGE {i}: {image_name}\n{analyse}\n"
|
||||
logger.info(f"Ajout de l'analyse de l'image {image_name} au prompt ({len(str(analyse))} caractères)")
|
||||
|
||||
# Ne pas répéter les instructions déjà présentes dans le system_prompt
|
||||
# Instructions claires pour tous les modèles
|
||||
prompt += f"""
|
||||
N'oublie pas d'inclure un objet JSON structuré des échanges client/support selon le format demandé.
|
||||
Assure-toi que le JSON est valide et clairement balisé avec ```json et ``` dans ta réponse.
|
||||
## INSTRUCTIONS POUR LA GÉNÉRATION DU RAPPORT
|
||||
|
||||
1. Résume d'abord le problème principal du ticket en quelques phrases.
|
||||
|
||||
2. GÉNÉRER OBLIGATOIREMENT LE JSON DES ÉCHANGES CLIENT/SUPPORT:
|
||||
- Les données d'échanges sont l'élément le plus important du rapport
|
||||
- Utilise EXACTEMENT la structure suivante, sans la modifier:
|
||||
```json
|
||||
{{
|
||||
"chronologie_echanges": [
|
||||
{{"date": "date1", "emetteur": "CLIENT", "type": "Question", "contenu": "contenu de la question"}},
|
||||
{{"date": "date2", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "contenu de la réponse"}}
|
||||
]
|
||||
}}
|
||||
```
|
||||
- La clé principale DOIT être "chronologie_echanges"
|
||||
- N'ajoute pas de commentaires ou de texte dans le JSON
|
||||
- Assure-toi que le JSON est valide et correspond EXACTEMENT au format demandé
|
||||
- Entoure le JSON avec ```json et ``` pour faciliter l'extraction
|
||||
|
||||
3. Après le JSON, analyse les images pertinentes et leur contribution à la compréhension du problème.
|
||||
|
||||
4. Termine par une analyse technique des causes probables du problème.
|
||||
|
||||
IMPORTANT: Le JSON des échanges client/support est OBLIGATOIRE et doit être parfaitement formaté.
|
||||
"""
|
||||
|
||||
# Ajouter l'exemple non formaté (sans f-string) pour éviter les erreurs de formatage
|
||||
prompt += """
|
||||
EXEMPLE EXACT DU FORMAT JSON ATTENDU:
|
||||
```json
|
||||
{
|
||||
"chronologie_echanges": [
|
||||
{"date": "2023-05-10", "emetteur": "CLIENT", "type": "Question", "contenu": "L'application affiche une erreur lors de la connexion"},
|
||||
{"date": "2023-05-11", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "Pouvez-vous préciser le message d'erreur?"},
|
||||
{"date": "2023-05-12", "emetteur": "CLIENT", "type": "Information technique", "contenu": "Message: Erreur de connexion au serveur"}
|
||||
]
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
logger.info(f"Prompt formaté: {len(prompt)} caractères au total")
|
||||
@ -654,19 +718,90 @@ Assure-toi que le JSON est valide et clairement balisé avec ```json et ``` dans
|
||||
# Remplacer CBAD par CBAO dans tout le rapport
|
||||
texte_rapport = texte_rapport.replace("CBAD", "CBAO")
|
||||
|
||||
# Rechercher un objet JSON dans le texte
|
||||
json_match = re.search(r'```json\s*({.*?})\s*```', texte_rapport, re.DOTALL)
|
||||
# Patterns de recherche plus variés pour s'adapter aux différents modèles
|
||||
patterns = [
|
||||
r'```json\s*({.*?})\s*```', # Pattern standard avec backticks triples
|
||||
r'```\s*({.*?"chronologie_echanges".*?})\s*```', # Pattern sans spécifier json mais avec le contenu attendu
|
||||
r'{[\s\n]*"chronologie_echanges"[\s\n]*:[\s\n]*\[.*?\][\s\n]*}', # Pattern sans backticks
|
||||
r'<json>(.*?)</json>' # Pattern alternatif avec balises xml
|
||||
]
|
||||
|
||||
if not json_match:
|
||||
# Essayer chaque pattern
|
||||
json_text = None
|
||||
for pattern in patterns:
|
||||
json_match = re.search(pattern, texte_rapport, re.DOTALL)
|
||||
if json_match:
|
||||
json_text = json_match.group(1).strip()
|
||||
logger.info(f"JSON trouvé avec le pattern: {pattern[:20]}...")
|
||||
break
|
||||
|
||||
# Si aucun pattern n'a fonctionné, tenter une approche plus agressive pour extraire le JSON
|
||||
if not json_text:
|
||||
# Chercher des indices de début de JSON dans le texte
|
||||
potential_starts = [
|
||||
texte_rapport.find('{"chronologie_echanges"'),
|
||||
texte_rapport.find('{\n "chronologie_echanges"'),
|
||||
texte_rapport.find('{ "chronologie_echanges"')
|
||||
]
|
||||
|
||||
# Filtrer les indices valides (non -1)
|
||||
valid_starts = [idx for idx in potential_starts if idx != -1]
|
||||
|
||||
if valid_starts:
|
||||
# Prendre l'indice le plus petit (premier dans le texte)
|
||||
start_idx = min(valid_starts)
|
||||
# Chercher la fin du JSON (accolade fermante suivie d'une nouvelle ligne ou de la fin du texte)
|
||||
json_extract = texte_rapport[start_idx:]
|
||||
# Compter les accolades pour trouver la fermeture du JSON
|
||||
open_braces = 0
|
||||
close_idx = -1
|
||||
|
||||
for i, char in enumerate(json_extract):
|
||||
if char == '{':
|
||||
open_braces += 1
|
||||
elif char == '}':
|
||||
open_braces -= 1
|
||||
if open_braces == 0:
|
||||
close_idx = i
|
||||
break
|
||||
|
||||
if close_idx != -1:
|
||||
json_text = json_extract[:close_idx + 1]
|
||||
logger.info(f"JSON extrait par analyse d'accolades: {len(json_text)} caractères")
|
||||
|
||||
if not json_text:
|
||||
logger.warning("Aucun JSON trouvé dans le rapport")
|
||||
return texte_rapport, None, None
|
||||
|
||||
# Extraire le JSON et le parser
|
||||
json_text = json_match.group(1)
|
||||
# Nettoyage supplémentaire du JSON
|
||||
# Enlever caractères non imprimables ou indésirables qui pourraient être ajoutés par certains modèles
|
||||
json_text = re.sub(r'[\x00-\x1F\x7F]', '', json_text)
|
||||
|
||||
try:
|
||||
# Vérifier que le texte commence par { et se termine par }
|
||||
if not (json_text.startswith('{') and json_text.endswith('}')):
|
||||
logger.warning(f"Format JSON incorrect, tentative de correction. Texte: {json_text[:50]}...")
|
||||
# Chercher les délimiteurs du JSON
|
||||
start = json_text.find('{')
|
||||
end = json_text.rfind('}')
|
||||
if start != -1 and end != -1 and start < end:
|
||||
json_text = json_text[start:end+1]
|
||||
|
||||
echanges_json = json.loads(json_text)
|
||||
logger.info(f"JSON extrait avec succès: {len(json_text)} caractères")
|
||||
|
||||
# Vérifier si le JSON a la structure attendue
|
||||
if not isinstance(echanges_json, dict) or "chronologie_echanges" not in echanges_json:
|
||||
# Tenter de corriger la structure si possible
|
||||
if len(echanges_json) > 0 and isinstance(list(echanges_json.values())[0], list):
|
||||
# Prendre la première liste comme chronologie
|
||||
key = list(echanges_json.keys())[0]
|
||||
echanges_json = {"chronologie_echanges": echanges_json[key]}
|
||||
logger.info(f"Structure JSON corrigée en utilisant la clé: {key}")
|
||||
else:
|
||||
logger.warning("Structure JSON incorrecte et non réparable")
|
||||
return texte_rapport, None, None
|
||||
|
||||
# Convertir en tableau Markdown
|
||||
echanges_markdown = "| Date | Émetteur | Type | Contenu | Statut |\n"
|
||||
echanges_markdown += "|------|---------|------|---------|--------|\n"
|
||||
@ -704,12 +839,18 @@ Assure-toi que le JSON est valide et clairement balisé avec ```json et ``` dans
|
||||
echanges_markdown += "\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n\n"
|
||||
|
||||
# Remplacer le JSON dans le texte par le tableau Markdown
|
||||
rapport_traite = texte_rapport.replace(json_match.group(0), echanges_markdown)
|
||||
# Si le JSON était entouré de backticks, remplacer tout le bloc
|
||||
if json_match:
|
||||
rapport_traite = texte_rapport.replace(json_match.group(0), echanges_markdown)
|
||||
else:
|
||||
# Sinon, remplacer juste le texte JSON
|
||||
rapport_traite = texte_rapport.replace(json_text, echanges_markdown)
|
||||
|
||||
return rapport_traite, echanges_json, echanges_markdown
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Erreur lors du décodage JSON: {e}")
|
||||
logger.debug(f"Contenu JSON problématique: {json_text[:100]}...")
|
||||
return texte_rapport, None, None
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
|
||||
717
agents/agent_report_generator.py.bak
Normal file
717
agents/agent_report_generator.py.bak
Normal file
@ -0,0 +1,717 @@
|
||||
import json
|
||||
import os
|
||||
from .base_agent import BaseAgent
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Tuple, Optional
|
||||
import logging
|
||||
import traceback
|
||||
import re
|
||||
|
||||
logger = logging.getLogger("AgentReportGenerator")
|
||||
|
||||
class AgentReportGenerator(BaseAgent):
|
||||
"""
|
||||
Agent pour générer un rapport complet à partir des analyses de ticket et d'images.
|
||||
|
||||
Cet agent prend en entrée :
|
||||
- L'analyse du ticket
|
||||
- Les analyses des images pertinentes
|
||||
- Les métadonnées associées
|
||||
|
||||
Format de données attendu:
|
||||
- JSON est le format principal de données en entrée et en sortie
|
||||
- Le rapport Markdown est généré à partir du JSON uniquement pour la présentation
|
||||
|
||||
Structure des données d'analyse d'images:
|
||||
- Deux structures possibles sont supportées:
|
||||
1. Liste d'objets: rapport_data["images_analyses"] = [{image_name, analyse}, ...]
|
||||
2. Dictionnaire: rapport_data["analyse_images"] = {chemin_image: {sorting: {...}, analysis: {...}}, ...}
|
||||
|
||||
Flux de traitement:
|
||||
1. Préparation des données d'entrée
|
||||
2. Génération du rapport avec le LLM
|
||||
3. Sauvegarde au format JSON (format principal)
|
||||
4. Conversion et sauvegarde au format Markdown (pour présentation)
|
||||
"""
|
||||
def __init__(self, llm):
|
||||
super().__init__("AgentReportGenerator", llm)
|
||||
|
||||
# Configuration locale de l'agent (remplace AgentConfig)
|
||||
self.temperature = 0.4 # Génération de rapport factuelle mais bien structurée
|
||||
self.top_p = 0.9
|
||||
self.max_tokens = 2500
|
||||
self.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG-Lab pour la société CBAO.
|
||||
Ta mission est de synthétiser les analyses (ticket et images) en un rapport structuré et exploitable.
|
||||
|
||||
EXIGENCE ABSOLUE - GÉNÉRATION DE DONNÉES EN FORMAT JSON:
|
||||
- Tu DOIS IMPÉRATIVEMENT inclure dans ta réponse un objet JSON structuré pour les échanges client/support
|
||||
- Le format de chaque échange dans le JSON DOIT être:
|
||||
{
|
||||
"chronologie_echanges": [
|
||||
{
|
||||
"date": "date de l'échange",
|
||||
"emetteur": "CLIENT ou SUPPORT",
|
||||
"type": "Question ou Réponse ou Information technique",
|
||||
"contenu": "contenu synthétisé de l'échange"
|
||||
},
|
||||
... autres échanges ...
|
||||
]
|
||||
}
|
||||
- Chaque message du ticket doit apparaître comme un objet dans la liste
|
||||
- Indique clairement qui est CLIENT et qui est SUPPORT dans le champ "emetteur"
|
||||
- Si une question n'a pas de réponse, assure-toi de le noter clairement
|
||||
- Toute mention de "CBAD" doit être remplacée par "CBAO" qui est le nom correct de la société
|
||||
- Tu dois synthétiser au mieux les échanges (le plus court et clair possible)
|
||||
|
||||
Structure ton rapport:
|
||||
1. Résumé exécutif: Synthèse du problème initial (nom de la demande + description)
|
||||
2. Chronologie des échanges: Objet JSON avec la structure imposée ci-dessus
|
||||
3. Analyse des images: Ce que montrent les captures d'écran et leur pertinence
|
||||
4. Diagnostic technique: Interprétation des informations techniques pertinentes
|
||||
|
||||
Reste factuel et précis dans ton analyse.
|
||||
Les données d'échanges client/support sont l'élément le plus important du rapport.
|
||||
Tu DOIS inclure le JSON des échanges dans ta réponse au format:
|
||||
```json
|
||||
{
|
||||
"chronologie_echanges": [
|
||||
{"date": "...", "emetteur": "CLIENT", "type": "Question", "contenu": "..."},
|
||||
{"date": "...", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "..."}
|
||||
]
|
||||
}
|
||||
```"""
|
||||
|
||||
# Appliquer la configuration au LLM
|
||||
self._appliquer_config_locale()
|
||||
|
||||
logger.info("AgentReportGenerator initialisé")
|
||||
|
||||
def _appliquer_config_locale(self) -> None:
|
||||
"""
|
||||
Applique la configuration locale au modèle LLM.
|
||||
"""
|
||||
# Appliquer le prompt système
|
||||
if hasattr(self.llm, "prompt_system"):
|
||||
self.llm.prompt_system = self.system_prompt
|
||||
|
||||
# Appliquer les paramètres
|
||||
if hasattr(self.llm, "configurer"):
|
||||
params = {
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
|
||||
# Ajustements selon le type de modèle
|
||||
if "mistral_medium" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] += 0.05
|
||||
params["max_tokens"] = 1000
|
||||
elif "pixtral" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] -= 0.05
|
||||
elif "ollama" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] += 0.1
|
||||
params.update({
|
||||
"num_ctx": 2048,
|
||||
"repeat_penalty": 1.1,
|
||||
})
|
||||
|
||||
self.llm.configurer(**params)
|
||||
|
||||
def executer(self, rapport_data: Dict, rapport_dir: str) -> Tuple[Optional[str], Optional[str]]:
|
||||
"""
|
||||
Génère un rapport à partir des analyses effectuées
|
||||
|
||||
Args:
|
||||
rapport_data: Dictionnaire contenant toutes les données analysées
|
||||
Doit contenir au moins une des clés:
|
||||
- "ticket_analyse" ou "analyse_json": Analyse du ticket
|
||||
- "analyse_images": Analyses des images (facultatif)
|
||||
rapport_dir: Répertoire où sauvegarder le rapport
|
||||
|
||||
Returns:
|
||||
Tuple (chemin vers le rapport JSON, chemin vers le rapport Markdown)
|
||||
"""
|
||||
# Récupérer l'ID du ticket depuis les données
|
||||
ticket_id = rapport_data.get("ticket_id", "")
|
||||
if not ticket_id and "ticket_data" in rapport_data and isinstance(rapport_data["ticket_data"], dict):
|
||||
ticket_id = rapport_data["ticket_data"].get("code", "")
|
||||
|
||||
if not ticket_id:
|
||||
ticket_id = os.path.basename(os.path.dirname(rapport_dir))
|
||||
if not ticket_id.startswith("T"):
|
||||
# Dernier recours, utiliser le dernier segment du chemin
|
||||
ticket_id = os.path.basename(rapport_dir)
|
||||
|
||||
logger.info(f"Génération du rapport pour le ticket: {ticket_id}")
|
||||
print(f"AgentReportGenerator: Génération du rapport pour {ticket_id}")
|
||||
|
||||
# Validation des données d'entrée
|
||||
logger.info("Vérification de la complétude des données d'entrée:")
|
||||
if "ticket_data" in rapport_data:
|
||||
logger.info(f" - Données de ticket présentes: {len(str(rapport_data['ticket_data']))} caractères")
|
||||
else:
|
||||
logger.warning(" - Données de ticket manquantes")
|
||||
|
||||
# Vérification des analyses
|
||||
ticket_analyse_exists = False
|
||||
if "ticket_analyse" in rapport_data and rapport_data["ticket_analyse"]:
|
||||
ticket_analyse_exists = True
|
||||
logger.info(f" - Analyse du ticket présente: {len(rapport_data['ticket_analyse'])} caractères")
|
||||
elif "analyse_json" in rapport_data and rapport_data["analyse_json"]:
|
||||
ticket_analyse_exists = True
|
||||
logger.info(f" - Analyse JSON présente: {len(rapport_data['analyse_json'])} caractères")
|
||||
else:
|
||||
logger.warning(" - Analyse du ticket manquante")
|
||||
|
||||
# Vérification des analyses d'images
|
||||
if "analyse_images" in rapport_data and rapport_data["analyse_images"]:
|
||||
n_images = len(rapport_data["analyse_images"])
|
||||
n_relevant = sum(1 for _, data in rapport_data["analyse_images"].items()
|
||||
if "sorting" in data and isinstance(data["sorting"], dict) and data["sorting"].get("is_relevant", False))
|
||||
n_analyzed = sum(1 for _, data in rapport_data["analyse_images"].items()
|
||||
if "analysis" in data and data["analysis"])
|
||||
|
||||
logger.info(f" - Analyses d'images présentes: {n_images} images, {n_relevant} pertinentes, {n_analyzed} analysées")
|
||||
else:
|
||||
logger.warning(" - Analyses d'images manquantes")
|
||||
|
||||
# S'assurer que le répertoire existe
|
||||
if not os.path.exists(rapport_dir):
|
||||
os.makedirs(rapport_dir)
|
||||
logger.info(f"Répertoire de rapport créé: {rapport_dir}")
|
||||
|
||||
try:
|
||||
# Préparer les données formatées pour l'analyse
|
||||
ticket_analyse = None
|
||||
|
||||
# Vérifier que l'analyse du ticket est disponible sous l'une des clés possibles
|
||||
if "ticket_analyse" in rapport_data and rapport_data["ticket_analyse"]:
|
||||
ticket_analyse = rapport_data["ticket_analyse"]
|
||||
logger.info("Utilisation de ticket_analyse")
|
||||
elif "analyse_json" in rapport_data and rapport_data["analyse_json"]:
|
||||
ticket_analyse = rapport_data["analyse_json"]
|
||||
logger.info("Utilisation de analyse_json en fallback")
|
||||
else:
|
||||
# Créer une analyse par défaut si aucune n'est disponible
|
||||
logger.warning("Aucune analyse de ticket disponible, création d'un message par défaut")
|
||||
ticket_data = rapport_data.get("ticket_data", {})
|
||||
ticket_name = ticket_data.get("name", "Sans titre")
|
||||
ticket_desc = ticket_data.get("description", "Pas de description disponible")
|
||||
ticket_analyse = f"Analyse par défaut du ticket:\nNom: {ticket_name}\nDescription: {ticket_desc}\n(Aucune analyse détaillée n'a été fournie par l'agent d'analyse de ticket)"
|
||||
|
||||
# Préparer les données d'analyse d'images
|
||||
images_analyses = []
|
||||
analyse_images_data = rapport_data.get("analyse_images", {})
|
||||
|
||||
# Statistiques pour les métadonnées
|
||||
total_images = len(analyse_images_data) if analyse_images_data else 0
|
||||
images_pertinentes = 0
|
||||
|
||||
# Collecter des informations sur les agents et LLM utilisés
|
||||
agents_info = self._collecter_info_agents(rapport_data)
|
||||
|
||||
# Transformer les analyses d'images en liste structurée pour le prompt
|
||||
for image_path, analyse_data in analyse_images_data.items():
|
||||
image_name = os.path.basename(image_path)
|
||||
|
||||
# Vérifier si l'image est pertinente
|
||||
is_relevant = False
|
||||
if "sorting" in analyse_data and isinstance(analyse_data["sorting"], dict):
|
||||
is_relevant = analyse_data["sorting"].get("is_relevant", False)
|
||||
if is_relevant:
|
||||
images_pertinentes += 1
|
||||
|
||||
# Récupérer l'analyse détaillée si elle existe et que l'image est pertinente
|
||||
analyse_detail = None
|
||||
if is_relevant:
|
||||
if "analysis" in analyse_data and analyse_data["analysis"]:
|
||||
# Vérifier différentes structures possibles de l'analyse
|
||||
if isinstance(analyse_data["analysis"], dict):
|
||||
if "analyse" in analyse_data["analysis"]:
|
||||
analyse_detail = analyse_data["analysis"]["analyse"]
|
||||
elif "error" in analyse_data["analysis"] and not analyse_data["analysis"].get("error", True):
|
||||
# Si pas d'erreur et que l'analyse est directement dans le dictionnaire
|
||||
analyse_detail = str(analyse_data["analysis"])
|
||||
else:
|
||||
# Essayer de récupérer directement le contenu du dictionnaire
|
||||
analyse_detail = json.dumps(analyse_data["analysis"], ensure_ascii=False, indent=2)
|
||||
elif isinstance(analyse_data["analysis"], str):
|
||||
# Si l'analyse est directement une chaîne
|
||||
analyse_detail = analyse_data["analysis"]
|
||||
|
||||
# Si l'analyse n'a pas été trouvée mais que l'image est pertinente
|
||||
if not analyse_detail:
|
||||
analyse_detail = f"Image marquée comme pertinente. Raison: {analyse_data['sorting'].get('reason', 'Non spécifiée')}"
|
||||
|
||||
# Analyse détaillée
|
||||
if analyse_detail:
|
||||
images_analyses.append({
|
||||
"image_name": image_name,
|
||||
"analyse": analyse_detail
|
||||
})
|
||||
logger.info(f"Analyse de l'image {image_name} ajoutée au rapport (longueur: {len(str(analyse_detail))} caractères)")
|
||||
else:
|
||||
logger.warning(f"Analyse non trouvée pour l'image pertinente {image_name}")
|
||||
else:
|
||||
logger.info(f"Image {image_name} ignorée car non pertinente")
|
||||
|
||||
# Créer le chemin du fichier de rapport JSON (sortie principale)
|
||||
json_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.json")
|
||||
md_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.md")
|
||||
|
||||
# Formater les données pour le LLM
|
||||
prompt = self._formater_prompt_pour_rapport(ticket_analyse, images_analyses, ticket_id)
|
||||
|
||||
# Générer le rapport avec le LLM
|
||||
logger.info("Génération du rapport avec le LLM")
|
||||
print(f" Génération du rapport avec le LLM...")
|
||||
|
||||
# Interroger le LLM
|
||||
rapport_genere = self.llm.interroger(prompt)
|
||||
logger.info(f"Rapport généré: {len(rapport_genere)} caractères")
|
||||
print(f" Rapport généré: {len(rapport_genere)} caractères")
|
||||
|
||||
# Traiter le JSON pour extraire la chronologie des échanges et le convertir en tableau Markdown
|
||||
rapport_traite, echanges_json, echanges_markdown = self._extraire_et_traiter_json(rapport_genere)
|
||||
if echanges_json and echanges_markdown:
|
||||
logger.info(f"Échanges JSON convertis en tableau Markdown: {len(str(echanges_markdown))} caractères")
|
||||
print(f" Échanges client/support convertis du JSON vers le format Markdown")
|
||||
# Utiliser le rapport traité avec le tableau Markdown à la place du JSON
|
||||
rapport_genere = rapport_traite
|
||||
else:
|
||||
logger.warning("Aucun JSON d'échanges trouvé dans le rapport, conservation du format original")
|
||||
|
||||
# Tracer l'historique avec le prompt pour la transparence
|
||||
self.ajouter_historique("generation_rapport",
|
||||
{
|
||||
"ticket_id": ticket_id,
|
||||
"prompt_taille": len(prompt),
|
||||
"timestamp": self._get_timestamp()
|
||||
},
|
||||
rapport_genere)
|
||||
|
||||
# Préparer les métadonnées complètes pour le rapport
|
||||
timestamp = self._get_timestamp()
|
||||
|
||||
# Préparer le JSON complet du rapport (format principal)
|
||||
rapport_data_complet = {
|
||||
"ticket_id": ticket_id,
|
||||
"timestamp": timestamp,
|
||||
"rapport_genere": rapport_genere,
|
||||
"ticket_analyse": ticket_analyse,
|
||||
"images_analyses": images_analyses,
|
||||
"statistiques": {
|
||||
"total_images": total_images,
|
||||
"images_pertinentes": images_pertinentes,
|
||||
"analyses_generees": len(images_analyses)
|
||||
}
|
||||
}
|
||||
|
||||
# Ajouter les métadonnées pour la traçabilité
|
||||
metadata = {
|
||||
"timestamp": timestamp,
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens,
|
||||
"agents": agents_info
|
||||
}
|
||||
|
||||
rapport_data_complet["metadata"] = metadata
|
||||
|
||||
# S'assurer que les clés nécessaires pour le markdown sont présentes
|
||||
if "ticket_analyse" not in rapport_data_complet:
|
||||
rapport_data_complet["ticket_analyse"] = ticket_analyse
|
||||
|
||||
# ÉTAPE 1: Sauvegarder le rapport au format JSON (FORMAT PRINCIPAL)
|
||||
with open(json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(rapport_data_complet, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"Rapport JSON (format principal) sauvegardé: {json_path}")
|
||||
print(f" Rapport JSON sauvegardé: {json_path}")
|
||||
|
||||
# ÉTAPE 2: Générer et sauvegarder le rapport au format Markdown (pour présentation)
|
||||
markdown_content = self._generer_markdown_depuis_json(rapport_data_complet)
|
||||
|
||||
with open(md_path, "w", encoding="utf-8") as f:
|
||||
f.write(markdown_content)
|
||||
|
||||
logger.info(f"Rapport Markdown (pour présentation) sauvegardé: {md_path}")
|
||||
print(f" Rapport Markdown sauvegardé: {md_path}")
|
||||
|
||||
logger.info(f"Taille du rapport Markdown: {len(markdown_content)} caractères")
|
||||
|
||||
# Retourner les chemins des deux fichiers (JSON en premier, Markdown en second)
|
||||
return json_path, md_path
|
||||
|
||||
except Exception as e:
|
||||
error_message = f"Erreur lors de la génération du rapport: {str(e)}"
|
||||
logger.error(error_message)
|
||||
print(f" ERREUR: {error_message}")
|
||||
return None, None
|
||||
|
||||
def _collecter_info_agents(self, rapport_data: Dict) -> Dict:
|
||||
"""
|
||||
Collecte des informations sur les agents utilisés dans l'analyse
|
||||
|
||||
Args:
|
||||
rapport_data: Données du rapport
|
||||
|
||||
Returns:
|
||||
Dictionnaire contenant les informations sur les agents
|
||||
"""
|
||||
agents_info = {}
|
||||
|
||||
# Informations sur l'agent JSON Analyser
|
||||
if "analyse_json" in rapport_data:
|
||||
json_analysis = rapport_data["analyse_json"]
|
||||
# Vérifier si l'analyse JSON contient des métadonnées
|
||||
if isinstance(json_analysis, dict) and "metadata" in json_analysis:
|
||||
agents_info["json_analyser"] = json_analysis["metadata"]
|
||||
|
||||
# Informations sur les agents d'image
|
||||
if "analyse_images" in rapport_data and rapport_data["analyse_images"]:
|
||||
# Image Sorter
|
||||
sorter_info = {}
|
||||
analyser_info = {}
|
||||
|
||||
for img_path, img_data in rapport_data["analyse_images"].items():
|
||||
# Collecter info du sorter
|
||||
if "sorting" in img_data and isinstance(img_data["sorting"], dict) and "metadata" in img_data["sorting"]:
|
||||
if "model_info" in img_data["sorting"]["metadata"]:
|
||||
sorter_info = img_data["sorting"]["metadata"]["model_info"]
|
||||
|
||||
# Collecter info de l'analyser
|
||||
if "analysis" in img_data and img_data["analysis"] and isinstance(img_data["analysis"], dict) and "metadata" in img_data["analysis"]:
|
||||
if "model_info" in img_data["analysis"]["metadata"]:
|
||||
analyser_info = img_data["analysis"]["metadata"]["model_info"]
|
||||
|
||||
# Une fois qu'on a trouvé les deux, on peut sortir
|
||||
if sorter_info and analyser_info:
|
||||
break
|
||||
|
||||
if sorter_info:
|
||||
agents_info["image_sorter"] = sorter_info
|
||||
if analyser_info:
|
||||
agents_info["image_analyser"] = analyser_info
|
||||
|
||||
# Ajouter les informations de l'agent report generator
|
||||
agents_info["report_generator"] = {
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
|
||||
return agents_info
|
||||
|
||||
def _generer_markdown_depuis_json(self, rapport_data: Dict) -> str:
|
||||
"""
|
||||
Génère un rapport Markdown directement à partir des données JSON
|
||||
|
||||
Args:
|
||||
rapport_data: Données JSON complètes du rapport
|
||||
Format attendu:
|
||||
- ticket_id: ID du ticket
|
||||
- metadata: Métadonnées (timestamp, modèle, etc.)
|
||||
- rapport_genere: Texte du rapport généré par le LLM
|
||||
- ticket_analyse: Analyse du ticket
|
||||
- images_analyses: Liste des analyses d'images (format privilégié)
|
||||
OU
|
||||
- analyse_images: Dictionnaire des analyses d'images (format alternatif)
|
||||
|
||||
Returns:
|
||||
Contenu Markdown du rapport
|
||||
"""
|
||||
ticket_id = rapport_data.get("ticket_id", "")
|
||||
timestamp = rapport_data.get("metadata", {}).get("timestamp", self._get_timestamp())
|
||||
|
||||
logger.info(f"Génération du rapport Markdown pour le ticket {ticket_id}")
|
||||
|
||||
# Contenu de base du rapport (partie générée par le LLM)
|
||||
rapport_contenu = rapport_data.get("rapport_genere", "")
|
||||
|
||||
# Entête du document
|
||||
markdown = f"# Rapport d'analyse du ticket #{ticket_id}\n\n"
|
||||
markdown += f"*Généré le: {timestamp}*\n\n"
|
||||
|
||||
# Ajouter le rapport principal généré par le LLM
|
||||
markdown += rapport_contenu + "\n\n"
|
||||
|
||||
# Section séparatrice pour les détails d'analyse
|
||||
markdown += "---\n\n"
|
||||
markdown += "# Détails des analyses effectuées\n\n"
|
||||
|
||||
# Ajouter un résumé du processus d'analyse complet
|
||||
markdown += "## Processus d'analyse\n\n"
|
||||
|
||||
# 1. Analyse de ticket
|
||||
ticket_analyse = rapport_data.get("ticket_analyse", "")
|
||||
if not ticket_analyse and "analyse_json" in rapport_data:
|
||||
ticket_analyse = rapport_data.get("analyse_json", "")
|
||||
|
||||
if ticket_analyse:
|
||||
markdown += "### Étape 1: Analyse du ticket\n\n"
|
||||
markdown += "L'agent d'analyse de ticket a extrait les informations suivantes du ticket d'origine:\n\n"
|
||||
markdown += "<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"
|
||||
logger.info(f"Analyse du ticket ajoutée au rapport Markdown ({len(str(ticket_analyse))} caractères)")
|
||||
else:
|
||||
logger.warning("Aucune analyse de ticket disponible pour le rapport Markdown")
|
||||
|
||||
# 2. Tri des images
|
||||
markdown += "### Étape 2: Tri des images\n\n"
|
||||
markdown += "L'agent de tri d'images a évalué chaque image pour déterminer sa pertinence par rapport au problème client:\n\n"
|
||||
|
||||
# Vérifier quelle structure de données est disponible pour les images
|
||||
has_analyse_images = "analyse_images" in rapport_data and rapport_data["analyse_images"]
|
||||
has_images_analyses = "images_analyses" in rapport_data and isinstance(rapport_data["images_analyses"], list) and rapport_data["images_analyses"]
|
||||
|
||||
logger.info(f"Structure des données d'images: analyse_images={has_analyse_images}, images_analyses={has_images_analyses}")
|
||||
|
||||
analyse_images_data = {}
|
||||
if has_analyse_images:
|
||||
analyse_images_data = rapport_data["analyse_images"]
|
||||
|
||||
if analyse_images_data:
|
||||
# Créer un tableau pour le tri des images
|
||||
markdown += "| Image | Pertinence | Raison |\n"
|
||||
markdown += "|-------|------------|--------|\n"
|
||||
|
||||
for image_path, analyse_data in analyse_images_data.items():
|
||||
image_name = os.path.basename(image_path)
|
||||
|
||||
# Information de tri
|
||||
is_relevant = "Non"
|
||||
reason = "Non spécifiée"
|
||||
|
||||
if "sorting" in analyse_data:
|
||||
sorting_data = analyse_data["sorting"]
|
||||
if isinstance(sorting_data, dict):
|
||||
is_relevant = "Oui" if sorting_data.get("is_relevant", False) else "Non"
|
||||
reason = sorting_data.get("reason", "Non spécifiée")
|
||||
|
||||
markdown += f"| {image_name} | {is_relevant} | {reason} |\n"
|
||||
|
||||
markdown += "\n"
|
||||
logger.info(f"Tableau de tri des images ajouté au rapport Markdown ({len(analyse_images_data)} images)")
|
||||
elif has_images_analyses and rapport_data["images_analyses"]:
|
||||
# Si nous avons les analyses d'images mais pas les données de tri, créer un tableau simplifié
|
||||
markdown += "| Image | Pertinence |\n"
|
||||
markdown += "|-------|------------|\n"
|
||||
|
||||
for img_data in rapport_data["images_analyses"]:
|
||||
image_name = img_data.get("image_name", "Image inconnue")
|
||||
markdown += f"| {image_name} | Oui |\n"
|
||||
|
||||
markdown += "\n"
|
||||
logger.info(f"Tableau de tri simplifié ajouté au rapport Markdown ({len(rapport_data['images_analyses'])} images)")
|
||||
else:
|
||||
markdown += "*Aucune image n'a été trouvée ou analysée.*\n\n"
|
||||
logger.warning("Aucune analyse d'images disponible pour le tableau de tri")
|
||||
|
||||
# 3. Analyse des images pertinentes
|
||||
markdown += "### Étape 3: Analyse détaillée des images pertinentes\n\n"
|
||||
|
||||
# Traiter directement les images_analyses du rapport_data si disponible
|
||||
images_pertinentes = 0
|
||||
|
||||
# D'abord essayer d'utiliser la liste images_analyses qui est déjà traitée
|
||||
if has_images_analyses:
|
||||
images_list = rapport_data["images_analyses"]
|
||||
for i, img_data in enumerate(images_list, 1):
|
||||
images_pertinentes += 1
|
||||
image_name = img_data.get("image_name", f"Image {i}")
|
||||
analyse_detail = img_data.get("analyse", "Analyse non disponible")
|
||||
|
||||
markdown += f"#### Image pertinente {images_pertinentes}: {image_name}\n\n"
|
||||
markdown += "<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"
|
||||
|
||||
logger.info(f"Analyse de l'image {image_name} ajoutée au rapport Markdown (from images_analyses)")
|
||||
# Sinon, traiter les données brutes d'analyse_images
|
||||
elif has_analyse_images:
|
||||
analyse_images_data = rapport_data["analyse_images"]
|
||||
for image_path, analyse_data in analyse_images_data.items():
|
||||
# Vérifier si l'image est pertinente
|
||||
is_relevant = False
|
||||
if "sorting" in analyse_data and isinstance(analyse_data["sorting"], dict):
|
||||
is_relevant = analyse_data["sorting"].get("is_relevant", False)
|
||||
|
||||
if is_relevant:
|
||||
images_pertinentes += 1
|
||||
image_name = os.path.basename(image_path)
|
||||
|
||||
# Récupérer l'analyse détaillée avec gestion des différents formats possibles
|
||||
analyse_detail = "Analyse non disponible"
|
||||
if "analysis" in analyse_data and analyse_data["analysis"]:
|
||||
if isinstance(analyse_data["analysis"], dict):
|
||||
if "analyse" in analyse_data["analysis"]:
|
||||
analyse_detail = analyse_data["analysis"]["analyse"]
|
||||
logger.info(f"Analyse de l'image {image_name} récupérée via analyse_data['analysis']['analyse']")
|
||||
elif "error" in analyse_data["analysis"] and not analyse_data["analysis"].get("error", True):
|
||||
analyse_detail = str(analyse_data["analysis"])
|
||||
logger.info(f"Analyse de l'image {image_name} récupérée via str(analyse_data['analysis'])")
|
||||
else:
|
||||
analyse_detail = json.dumps(analyse_data["analysis"], ensure_ascii=False, indent=2)
|
||||
logger.info(f"Analyse de l'image {image_name} récupérée via json.dumps")
|
||||
elif isinstance(analyse_data["analysis"], str):
|
||||
analyse_detail = analyse_data["analysis"]
|
||||
logger.info(f"Analyse de l'image {image_name} récupérée directement (string)")
|
||||
else:
|
||||
logger.warning(f"Aucune analyse disponible pour l'image pertinente {image_name}")
|
||||
|
||||
markdown += f"#### Image pertinente {images_pertinentes}: {image_name}\n\n"
|
||||
markdown += "<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 pour ce ticket.*\n\n"
|
||||
logger.warning("Aucune image pertinente identifiée pour l'analyse détaillée")
|
||||
else:
|
||||
logger.info(f"{images_pertinentes} images pertinentes ajoutées au rapport Markdown")
|
||||
|
||||
# 4. Synthèse (rapport final)
|
||||
markdown += "### Étape 4: Génération du rapport de synthèse\n\n"
|
||||
markdown += "L'agent de génération de rapport a synthétisé toutes les analyses précédentes pour produire le rapport ci-dessus.\n\n"
|
||||
|
||||
# Informations techniques
|
||||
markdown += "## Informations techniques\n\n"
|
||||
|
||||
# Statistiques d'analyse
|
||||
markdown += "### Statistiques\n\n"
|
||||
|
||||
total_images = 0
|
||||
if has_analyse_images:
|
||||
total_images = len(analyse_images_data)
|
||||
elif "statistiques" in rapport_data and "total_images" in rapport_data["statistiques"]:
|
||||
total_images = rapport_data["statistiques"]["total_images"]
|
||||
|
||||
markdown += f"- **Images analysées**: {total_images}\n"
|
||||
markdown += f"- **Images pertinentes**: {images_pertinentes}\n\n"
|
||||
|
||||
logger.info(f"Rapport Markdown généré ({len(markdown)} caractères)")
|
||||
return markdown
|
||||
|
||||
def _formater_prompt_pour_rapport(self, ticket_analyse, images_analyses, ticket_id):
|
||||
"""
|
||||
Formate le prompt pour la génération du rapport
|
||||
|
||||
Args:
|
||||
ticket_analyse: Analyse du ticket
|
||||
images_analyses: Liste des analyses d'images, format [{image_name, analyse}, ...]
|
||||
ticket_id: ID du ticket
|
||||
|
||||
Returns:
|
||||
Prompt formaté pour le LLM
|
||||
"""
|
||||
num_images = len(images_analyses)
|
||||
logger.info(f"Formatage du prompt avec {num_images} analyses d'images")
|
||||
|
||||
# Inclure une vérification des données reçues
|
||||
prompt = f"""Génère un rapport technique complet pour le ticket #{ticket_id}, en te basant sur les analyses suivantes.
|
||||
|
||||
## VÉRIFICATION DES DONNÉES REÇUES
|
||||
Je vais d'abord vérifier que j'ai bien reçu les données d'analyses:
|
||||
- Analyse du ticket : {"PRÉSENTE" if ticket_analyse else "MANQUANTE"}
|
||||
- Analyses d'images : {"PRÉSENTES (" + str(num_images) + " images)" if num_images > 0 else "MANQUANTES"}
|
||||
|
||||
## ANALYSE DU TICKET
|
||||
{ticket_analyse}
|
||||
|
||||
## ANALYSES DES IMAGES ({num_images} images analysées)
|
||||
"""
|
||||
|
||||
# Ajouter l'analyse de chaque image
|
||||
for i, img_analyse in enumerate(images_analyses, 1):
|
||||
image_name = img_analyse.get("image_name", f"Image {i}")
|
||||
analyse = img_analyse.get("analyse", "Analyse non disponible")
|
||||
prompt += f"\n### IMAGE {i}: {image_name}\n{analyse}\n"
|
||||
logger.info(f"Ajout de l'analyse de l'image {image_name} au prompt ({len(str(analyse))} caractères)")
|
||||
|
||||
# Ne pas répéter les instructions déjà présentes dans le system_prompt
|
||||
prompt += f"""
|
||||
N'oublie pas d'inclure un objet JSON structuré des échanges client/support selon le format demandé.
|
||||
Assure-toi que le JSON est valide et clairement balisé avec ```json et ``` dans ta réponse.
|
||||
"""
|
||||
|
||||
logger.info(f"Prompt formaté: {len(prompt)} caractères au total")
|
||||
return prompt
|
||||
|
||||
def _extraire_et_traiter_json(self, texte_rapport):
|
||||
"""
|
||||
Extrait l'objet JSON des échanges du texte du rapport et le convertit en Markdown
|
||||
|
||||
Args:
|
||||
texte_rapport: Texte complet du rapport généré par le LLM
|
||||
|
||||
Returns:
|
||||
Tuple (rapport_traité, echanges_json, echanges_markdown)
|
||||
"""
|
||||
# Remplacer CBAD par CBAO dans tout le rapport
|
||||
texte_rapport = texte_rapport.replace("CBAD", "CBAO")
|
||||
|
||||
# Rechercher un objet JSON dans le texte
|
||||
json_match = re.search(r'```json\s*({.*?})\s*```', texte_rapport, re.DOTALL)
|
||||
|
||||
if not json_match:
|
||||
logger.warning("Aucun JSON trouvé dans le rapport")
|
||||
return texte_rapport, None, None
|
||||
|
||||
# Extraire le JSON et le parser
|
||||
json_text = json_match.group(1)
|
||||
try:
|
||||
echanges_json = json.loads(json_text)
|
||||
logger.info(f"JSON extrait avec succès: {len(json_text)} caractères")
|
||||
|
||||
# Convertir en tableau Markdown
|
||||
echanges_markdown = "| Date | Émetteur | Type | Contenu | Statut |\n"
|
||||
echanges_markdown += "|------|---------|------|---------|--------|\n"
|
||||
|
||||
if "chronologie_echanges" in echanges_json and isinstance(echanges_json["chronologie_echanges"], list):
|
||||
# Pré-traitement pour vérifier les questions sans réponse
|
||||
questions_sans_reponse = {}
|
||||
for i, echange in enumerate(echanges_json["chronologie_echanges"]):
|
||||
if echange.get("type", "").lower() == "question" and echange.get("emetteur", "").lower() == "client":
|
||||
has_response = False
|
||||
# Vérifier si la question a une réponse
|
||||
for j in range(i+1, len(echanges_json["chronologie_echanges"])):
|
||||
next_echange = echanges_json["chronologie_echanges"][j]
|
||||
if next_echange.get("type", "").lower() == "réponse" and next_echange.get("emetteur", "").lower() == "support":
|
||||
has_response = True
|
||||
break
|
||||
questions_sans_reponse[i] = not has_response
|
||||
|
||||
# Générer le tableau
|
||||
for i, echange in enumerate(echanges_json["chronologie_echanges"]):
|
||||
date = echange.get("date", "-")
|
||||
emetteur = echange.get("emetteur", "-")
|
||||
type_msg = echange.get("type", "-")
|
||||
contenu = echange.get("contenu", "-")
|
||||
|
||||
# Ajouter un statut pour les questions sans réponse
|
||||
statut = ""
|
||||
if emetteur.lower() == "client" and type_msg.lower() == "question" and questions_sans_reponse.get(i, False):
|
||||
statut = "**Sans réponse**"
|
||||
|
||||
echanges_markdown += f"| {date} | {emetteur} | {type_msg} | {contenu} | {statut} |\n"
|
||||
|
||||
# Ajouter une note si aucune réponse du support n'a été trouvée
|
||||
if not any(echange.get("emetteur", "").lower() == "support" for echange in echanges_json["chronologie_echanges"]):
|
||||
echanges_markdown += "\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n\n"
|
||||
|
||||
# Remplacer le JSON dans le texte par le tableau Markdown
|
||||
rapport_traite = texte_rapport.replace(json_match.group(0), echanges_markdown)
|
||||
|
||||
return rapport_traite, echanges_json, echanges_markdown
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Erreur lors du décodage JSON: {e}")
|
||||
return texte_rapport, None, None
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""Retourne un timestamp au format YYYYMMDD_HHMMSS"""
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
717
agents/agent_report_generator.py.bak2
Normal file
717
agents/agent_report_generator.py.bak2
Normal file
@ -0,0 +1,717 @@
|
||||
import json
|
||||
import os
|
||||
from .base_agent import BaseAgent
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Tuple, Optional
|
||||
import logging
|
||||
import traceback
|
||||
import re
|
||||
|
||||
logger = logging.getLogger("AgentReportGenerator")
|
||||
|
||||
class AgentReportGenerator(BaseAgent):
|
||||
"""
|
||||
Agent pour générer un rapport complet à partir des analyses de ticket et d'images.
|
||||
|
||||
Cet agent prend en entrée :
|
||||
- L'analyse du ticket
|
||||
- Les analyses des images pertinentes
|
||||
- Les métadonnées associées
|
||||
|
||||
Format de données attendu:
|
||||
- JSON est le format principal de données en entrée et en sortie
|
||||
- Le rapport Markdown est généré à partir du JSON uniquement pour la présentation
|
||||
|
||||
Structure des données d'analyse d'images:
|
||||
- Deux structures possibles sont supportées:
|
||||
1. Liste d'objets: rapport_data["images_analyses"] = [{image_name, analyse}, ...]
|
||||
2. Dictionnaire: rapport_data["analyse_images"] = {chemin_image: {sorting: {...}, analysis: {...}}, ...}
|
||||
|
||||
Flux de traitement:
|
||||
1. Préparation des données d'entrée
|
||||
2. Génération du rapport avec le LLM
|
||||
3. Sauvegarde au format JSON (format principal)
|
||||
4. Conversion et sauvegarde au format Markdown (pour présentation)
|
||||
"""
|
||||
def __init__(self, llm):
|
||||
super().__init__("AgentReportGenerator", llm)
|
||||
|
||||
# Configuration locale de l'agent (remplace AgentConfig)
|
||||
self.temperature = 0.4 # Génération de rapport factuelle mais bien structurée
|
||||
self.top_p = 0.9
|
||||
self.max_tokens = 2500
|
||||
self.system_prompt = """Tu es un expert en génération de rapports techniques pour BRG-Lab pour la société CBAO.
|
||||
Ta mission est de synthétiser les analyses (ticket et images) en un rapport structuré et exploitable.
|
||||
|
||||
EXIGENCE ABSOLUE - GÉNÉRATION DE DONNÉES EN FORMAT JSON:
|
||||
- Tu DOIS IMPÉRATIVEMENT inclure dans ta réponse un objet JSON structuré pour les échanges client/support
|
||||
- Le format de chaque échange dans le JSON DOIT être:
|
||||
{
|
||||
"chronologie_echanges": [
|
||||
{
|
||||
"date": "date de l'échange",
|
||||
"emetteur": "CLIENT ou SUPPORT",
|
||||
"type": "Question ou Réponse ou Information technique",
|
||||
"contenu": "contenu synthétisé de l'échange"
|
||||
},
|
||||
... autres échanges ...
|
||||
]
|
||||
}
|
||||
- Chaque message du ticket doit apparaître comme un objet dans la liste
|
||||
- Indique clairement qui est CLIENT et qui est SUPPORT dans le champ "emetteur"
|
||||
- Si une question n'a pas de réponse, assure-toi de le noter clairement
|
||||
- Toute mention de "CBAD" doit être remplacée par "CBAO" qui est le nom correct de la société
|
||||
- Tu dois synthétiser au mieux les échanges (le plus court et clair possible)
|
||||
|
||||
Structure ton rapport:
|
||||
1. Résumé exécutif: Synthèse du problème initial (nom de la demande + description)
|
||||
2. Chronologie des échanges: Objet JSON avec la structure imposée ci-dessus
|
||||
3. Analyse des images: Ce que montrent les captures d'écran et leur pertinence
|
||||
4. Diagnostic technique: Interprétation des informations techniques pertinentes
|
||||
|
||||
Reste factuel et précis dans ton analyse.
|
||||
Les données d'échanges client/support sont l'élément le plus important du rapport.
|
||||
Tu DOIS inclure le JSON des échanges dans ta réponse au format:
|
||||
```json
|
||||
{
|
||||
"chronologie_echanges": [
|
||||
{"date": "...", "emetteur": "CLIENT", "type": "Question", "contenu": "..."},
|
||||
{"date": "...", "emetteur": "SUPPORT", "type": "Réponse", "contenu": "..."}
|
||||
]
|
||||
}
|
||||
```"""
|
||||
|
||||
# Appliquer la configuration au LLM
|
||||
self._appliquer_config_locale()
|
||||
|
||||
logger.info("AgentReportGenerator initialisé")
|
||||
|
||||
def _appliquer_config_locale(self) -> None:
|
||||
"""
|
||||
Applique la configuration locale au modèle LLM.
|
||||
"""
|
||||
# Appliquer le prompt système
|
||||
if hasattr(self.llm, "prompt_system"):
|
||||
self.llm.prompt_system = self.system_prompt
|
||||
|
||||
# Appliquer les paramètres
|
||||
if hasattr(self.llm, "configurer"):
|
||||
params = {
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
|
||||
# Ajustements selon le type de modèle
|
||||
if "mistral_medium" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] += 0.05
|
||||
params["max_tokens"] = 1000
|
||||
elif "pixtral" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] -= 0.05
|
||||
elif "ollama" in self.llm.__class__.__name__.lower():
|
||||
params["temperature"] += 0.1
|
||||
params.update({
|
||||
"num_ctx": 2048,
|
||||
"repeat_penalty": 1.1,
|
||||
})
|
||||
|
||||
self.llm.configurer(**params)
|
||||
|
||||
def executer(self, rapport_data: Dict, rapport_dir: str) -> Tuple[Optional[str], Optional[str]]:
|
||||
"""
|
||||
Génère un rapport à partir des analyses effectuées
|
||||
|
||||
Args:
|
||||
rapport_data: Dictionnaire contenant toutes les données analysées
|
||||
Doit contenir au moins une des clés:
|
||||
- "ticket_analyse" ou "analyse_json": Analyse du ticket
|
||||
- "analyse_images": Analyses des images (facultatif)
|
||||
rapport_dir: Répertoire où sauvegarder le rapport
|
||||
|
||||
Returns:
|
||||
Tuple (chemin vers le rapport JSON, chemin vers le rapport Markdown)
|
||||
"""
|
||||
# Récupérer l'ID du ticket depuis les données
|
||||
ticket_id = rapport_data.get("ticket_id", "")
|
||||
if not ticket_id and "ticket_data" in rapport_data and isinstance(rapport_data["ticket_data"], dict):
|
||||
ticket_id = rapport_data["ticket_data"].get("code", "")
|
||||
|
||||
if not ticket_id:
|
||||
ticket_id = os.path.basename(os.path.dirname(rapport_dir))
|
||||
if not ticket_id.startswith("T"):
|
||||
# Dernier recours, utiliser le dernier segment du chemin
|
||||
ticket_id = os.path.basename(rapport_dir)
|
||||
|
||||
logger.info(f"Génération du rapport pour le ticket: {ticket_id}")
|
||||
print(f"AgentReportGenerator: Génération du rapport pour {ticket_id}")
|
||||
|
||||
# Validation des données d'entrée
|
||||
logger.info("Vérification de la complétude des données d'entrée:")
|
||||
if "ticket_data" in rapport_data:
|
||||
logger.info(f" - Données de ticket présentes: {len(str(rapport_data['ticket_data']))} caractères")
|
||||
else:
|
||||
logger.warning(" - Données de ticket manquantes")
|
||||
|
||||
# Vérification des analyses
|
||||
ticket_analyse_exists = False
|
||||
if "ticket_analyse" in rapport_data and rapport_data["ticket_analyse"]:
|
||||
ticket_analyse_exists = True
|
||||
logger.info(f" - Analyse du ticket présente: {len(rapport_data['ticket_analyse'])} caractères")
|
||||
elif "analyse_json" in rapport_data and rapport_data["analyse_json"]:
|
||||
ticket_analyse_exists = True
|
||||
logger.info(f" - Analyse JSON présente: {len(rapport_data['analyse_json'])} caractères")
|
||||
else:
|
||||
logger.warning(" - Analyse du ticket manquante")
|
||||
|
||||
# Vérification des analyses d'images
|
||||
if "analyse_images" in rapport_data and rapport_data["analyse_images"]:
|
||||
n_images = len(rapport_data["analyse_images"])
|
||||
n_relevant = sum(1 for _, data in rapport_data["analyse_images"].items()
|
||||
if "sorting" in data and isinstance(data["sorting"], dict) and data["sorting"].get("is_relevant", False))
|
||||
n_analyzed = sum(1 for _, data in rapport_data["analyse_images"].items()
|
||||
if "analysis" in data and data["analysis"])
|
||||
|
||||
logger.info(f" - Analyses d'images présentes: {n_images} images, {n_relevant} pertinentes, {n_analyzed} analysées")
|
||||
else:
|
||||
logger.warning(" - Analyses d'images manquantes")
|
||||
|
||||
# S'assurer que le répertoire existe
|
||||
if not os.path.exists(rapport_dir):
|
||||
os.makedirs(rapport_dir)
|
||||
logger.info(f"Répertoire de rapport créé: {rapport_dir}")
|
||||
|
||||
try:
|
||||
# Préparer les données formatées pour l'analyse
|
||||
ticket_analyse = None
|
||||
|
||||
# Vérifier que l'analyse du ticket est disponible sous l'une des clés possibles
|
||||
if "ticket_analyse" in rapport_data and rapport_data["ticket_analyse"]:
|
||||
ticket_analyse = rapport_data["ticket_analyse"]
|
||||
logger.info("Utilisation de ticket_analyse")
|
||||
elif "analyse_json" in rapport_data and rapport_data["analyse_json"]:
|
||||
ticket_analyse = rapport_data["analyse_json"]
|
||||
logger.info("Utilisation de analyse_json en fallback")
|
||||
else:
|
||||
# Créer une analyse par défaut si aucune n'est disponible
|
||||
logger.warning("Aucune analyse de ticket disponible, création d'un message par défaut")
|
||||
ticket_data = rapport_data.get("ticket_data", {})
|
||||
ticket_name = ticket_data.get("name", "Sans titre")
|
||||
ticket_desc = ticket_data.get("description", "Pas de description disponible")
|
||||
ticket_analyse = f"Analyse par défaut du ticket:\nNom: {ticket_name}\nDescription: {ticket_desc}\n(Aucune analyse détaillée n'a été fournie par l'agent d'analyse de ticket)"
|
||||
|
||||
# Préparer les données d'analyse d'images
|
||||
images_analyses = []
|
||||
analyse_images_data = rapport_data.get("analyse_images", {})
|
||||
|
||||
# Statistiques pour les métadonnées
|
||||
total_images = len(analyse_images_data) if analyse_images_data else 0
|
||||
images_pertinentes = 0
|
||||
|
||||
# Collecter des informations sur les agents et LLM utilisés
|
||||
agents_info = self._collecter_info_agents(rapport_data)
|
||||
|
||||
# Transformer les analyses d'images en liste structurée pour le prompt
|
||||
for image_path, analyse_data in analyse_images_data.items():
|
||||
image_name = os.path.basename(image_path)
|
||||
|
||||
# Vérifier si l'image est pertinente
|
||||
is_relevant = False
|
||||
if "sorting" in analyse_data and isinstance(analyse_data["sorting"], dict):
|
||||
is_relevant = analyse_data["sorting"].get("is_relevant", False)
|
||||
if is_relevant:
|
||||
images_pertinentes += 1
|
||||
|
||||
# Récupérer l'analyse détaillée si elle existe et que l'image est pertinente
|
||||
analyse_detail = None
|
||||
if is_relevant:
|
||||
if "analysis" in analyse_data and analyse_data["analysis"]:
|
||||
# Vérifier différentes structures possibles de l'analyse
|
||||
if isinstance(analyse_data["analysis"], dict):
|
||||
if "analyse" in analyse_data["analysis"]:
|
||||
analyse_detail = analyse_data["analysis"]["analyse"]
|
||||
elif "error" in analyse_data["analysis"] and not analyse_data["analysis"].get("error", True):
|
||||
# Si pas d'erreur et que l'analyse est directement dans le dictionnaire
|
||||
analyse_detail = str(analyse_data["analysis"])
|
||||
else:
|
||||
# Essayer de récupérer directement le contenu du dictionnaire
|
||||
analyse_detail = json.dumps(analyse_data["analysis"], ensure_ascii=False, indent=2)
|
||||
elif isinstance(analyse_data["analysis"], str):
|
||||
# Si l'analyse est directement une chaîne
|
||||
analyse_detail = analyse_data["analysis"]
|
||||
|
||||
# Si l'analyse n'a pas été trouvée mais que l'image est pertinente
|
||||
if not analyse_detail:
|
||||
analyse_detail = f"Image marquée comme pertinente. Raison: {analyse_data['sorting'].get('reason', 'Non spécifiée')}"
|
||||
|
||||
# Analyse détaillée
|
||||
if analyse_detail:
|
||||
images_analyses.append({
|
||||
"image_name": image_name,
|
||||
"analyse": analyse_detail
|
||||
})
|
||||
logger.info(f"Analyse de l'image {image_name} ajoutée au rapport (longueur: {len(str(analyse_detail))} caractères)")
|
||||
else:
|
||||
logger.warning(f"Analyse non trouvée pour l'image pertinente {image_name}")
|
||||
else:
|
||||
logger.info(f"Image {image_name} ignorée car non pertinente")
|
||||
|
||||
# Créer le chemin du fichier de rapport JSON (sortie principale)
|
||||
json_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.json")
|
||||
md_path = os.path.join(rapport_dir, f"{ticket_id}_rapport_final.md")
|
||||
|
||||
# Formater les données pour le LLM
|
||||
prompt = self._formater_prompt_pour_rapport(ticket_analyse, images_analyses, ticket_id)
|
||||
|
||||
# Générer le rapport avec le LLM
|
||||
logger.info("Génération du rapport avec le LLM")
|
||||
print(f" Génération du rapport avec le LLM...")
|
||||
|
||||
# Interroger le LLM
|
||||
rapport_genere = self.llm.interroger(prompt)
|
||||
logger.info(f"Rapport généré: {len(rapport_genere)} caractères")
|
||||
print(f" Rapport généré: {len(rapport_genere)} caractères")
|
||||
|
||||
# Traiter le JSON pour extraire la chronologie des échanges et le convertir en tableau Markdown
|
||||
rapport_traite, echanges_json, echanges_markdown = self._extraire_et_traiter_json(rapport_genere)
|
||||
if echanges_json and echanges_markdown:
|
||||
logger.info(f"Échanges JSON convertis en tableau Markdown: {len(str(echanges_markdown))} caractères")
|
||||
print(f" Échanges client/support convertis du JSON vers le format Markdown")
|
||||
# Utiliser le rapport traité avec le tableau Markdown à la place du JSON
|
||||
rapport_genere = rapport_traite
|
||||
else:
|
||||
logger.warning("Aucun JSON d'échanges trouvé dans le rapport, conservation du format original")
|
||||
|
||||
# Tracer l'historique avec le prompt pour la transparence
|
||||
self.ajouter_historique("generation_rapport",
|
||||
{
|
||||
"ticket_id": ticket_id,
|
||||
"prompt_taille": len(prompt),
|
||||
"timestamp": self._get_timestamp()
|
||||
},
|
||||
rapport_genere)
|
||||
|
||||
# Préparer les métadonnées complètes pour le rapport
|
||||
timestamp = self._get_timestamp()
|
||||
|
||||
# Préparer le JSON complet du rapport (format principal)
|
||||
rapport_data_complet = {
|
||||
"ticket_id": ticket_id,
|
||||
"timestamp": timestamp,
|
||||
"rapport_genere": rapport_genere,
|
||||
"ticket_analyse": ticket_analyse,
|
||||
"images_analyses": images_analyses,
|
||||
"statistiques": {
|
||||
"total_images": total_images,
|
||||
"images_pertinentes": images_pertinentes,
|
||||
"analyses_generees": len(images_analyses)
|
||||
}
|
||||
}
|
||||
|
||||
# Ajouter les métadonnées pour la traçabilité
|
||||
metadata = {
|
||||
"timestamp": timestamp,
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens,
|
||||
"agents": agents_info
|
||||
}
|
||||
|
||||
rapport_data_complet["metadata"] = metadata
|
||||
|
||||
# S'assurer que les clés nécessaires pour le markdown sont présentes
|
||||
if "ticket_analyse" not in rapport_data_complet:
|
||||
rapport_data_complet["ticket_analyse"] = ticket_analyse
|
||||
|
||||
# ÉTAPE 1: Sauvegarder le rapport au format JSON (FORMAT PRINCIPAL)
|
||||
with open(json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(rapport_data_complet, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"Rapport JSON (format principal) sauvegardé: {json_path}")
|
||||
print(f" Rapport JSON sauvegardé: {json_path}")
|
||||
|
||||
# ÉTAPE 2: Générer et sauvegarder le rapport au format Markdown (pour présentation)
|
||||
markdown_content = self._generer_markdown_depuis_json(rapport_data_complet)
|
||||
|
||||
with open(md_path, "w", encoding="utf-8") as f:
|
||||
f.write(markdown_content)
|
||||
|
||||
logger.info(f"Rapport Markdown (pour présentation) sauvegardé: {md_path}")
|
||||
print(f" Rapport Markdown sauvegardé: {md_path}")
|
||||
|
||||
logger.info(f"Taille du rapport Markdown: {len(markdown_content)} caractères")
|
||||
|
||||
# Retourner les chemins des deux fichiers (JSON en premier, Markdown en second)
|
||||
return json_path, md_path
|
||||
|
||||
except Exception as e:
|
||||
error_message = f"Erreur lors de la génération du rapport: {str(e)}"
|
||||
logger.error(error_message)
|
||||
print(f" ERREUR: {error_message}")
|
||||
return None, None
|
||||
|
||||
def _collecter_info_agents(self, rapport_data: Dict) -> Dict:
|
||||
"""
|
||||
Collecte des informations sur les agents utilisés dans l'analyse
|
||||
|
||||
Args:
|
||||
rapport_data: Données du rapport
|
||||
|
||||
Returns:
|
||||
Dictionnaire contenant les informations sur les agents
|
||||
"""
|
||||
agents_info = {}
|
||||
|
||||
# Informations sur l'agent JSON Analyser
|
||||
if "analyse_json" in rapport_data:
|
||||
json_analysis = rapport_data["analyse_json"]
|
||||
# Vérifier si l'analyse JSON contient des métadonnées
|
||||
if isinstance(json_analysis, dict) and "metadata" in json_analysis:
|
||||
agents_info["json_analyser"] = json_analysis["metadata"]
|
||||
|
||||
# Informations sur les agents d'image
|
||||
if "analyse_images" in rapport_data and rapport_data["analyse_images"]:
|
||||
# Image Sorter
|
||||
sorter_info = {}
|
||||
analyser_info = {}
|
||||
|
||||
for img_path, img_data in rapport_data["analyse_images"].items():
|
||||
# Collecter info du sorter
|
||||
if "sorting" in img_data and isinstance(img_data["sorting"], dict) and "metadata" in img_data["sorting"]:
|
||||
if "model_info" in img_data["sorting"]["metadata"]:
|
||||
sorter_info = img_data["sorting"]["metadata"]["model_info"]
|
||||
|
||||
# Collecter info de l'analyser
|
||||
if "analysis" in img_data and img_data["analysis"] and isinstance(img_data["analysis"], dict) and "metadata" in img_data["analysis"]:
|
||||
if "model_info" in img_data["analysis"]["metadata"]:
|
||||
analyser_info = img_data["analysis"]["metadata"]["model_info"]
|
||||
|
||||
# Une fois qu'on a trouvé les deux, on peut sortir
|
||||
if sorter_info and analyser_info:
|
||||
break
|
||||
|
||||
if sorter_info:
|
||||
agents_info["image_sorter"] = sorter_info
|
||||
if analyser_info:
|
||||
agents_info["image_analyser"] = analyser_info
|
||||
|
||||
# Ajouter les informations de l'agent report generator
|
||||
agents_info["report_generator"] = {
|
||||
"model": getattr(self.llm, "modele", str(type(self.llm))),
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
|
||||
return agents_info
|
||||
|
||||
def _generer_markdown_depuis_json(self, rapport_data: Dict) -> str:
|
||||
"""
|
||||
Génère un rapport Markdown directement à partir des données JSON
|
||||
|
||||
Args:
|
||||
rapport_data: Données JSON complètes du rapport
|
||||
Format attendu:
|
||||
- ticket_id: ID du ticket
|
||||
- metadata: Métadonnées (timestamp, modèle, etc.)
|
||||
- rapport_genere: Texte du rapport généré par le LLM
|
||||
- ticket_analyse: Analyse du ticket
|
||||
- images_analyses: Liste des analyses d'images (format privilégié)
|
||||
OU
|
||||
- analyse_images: Dictionnaire des analyses d'images (format alternatif)
|
||||
|
||||
Returns:
|
||||
Contenu Markdown du rapport
|
||||
"""
|
||||
ticket_id = rapport_data.get("ticket_id", "")
|
||||
timestamp = rapport_data.get("metadata", {}).get("timestamp", self._get_timestamp())
|
||||
|
||||
logger.info(f"Génération du rapport Markdown pour le ticket {ticket_id}")
|
||||
|
||||
# Contenu de base du rapport (partie générée par le LLM)
|
||||
rapport_contenu = rapport_data.get("rapport_genere", "")
|
||||
|
||||
# Entête du document
|
||||
markdown = f"# Rapport d'analyse du ticket #{ticket_id}\n\n"
|
||||
markdown += f"*Généré le: {timestamp}*\n\n"
|
||||
|
||||
# Ajouter le rapport principal généré par le LLM
|
||||
markdown += rapport_contenu + "\n\n"
|
||||
|
||||
# Section séparatrice pour les détails d'analyse
|
||||
markdown += "---\n\n"
|
||||
markdown += "# Détails des analyses effectuées\n\n"
|
||||
|
||||
# Ajouter un résumé du processus d'analyse complet
|
||||
markdown += "## Processus d'analyse\n\n"
|
||||
|
||||
# 1. Analyse de ticket
|
||||
ticket_analyse = rapport_data.get("ticket_analyse", "")
|
||||
if not ticket_analyse and "analyse_json" in rapport_data:
|
||||
ticket_analyse = rapport_data.get("analyse_json", "")
|
||||
|
||||
if ticket_analyse:
|
||||
markdown += "### Étape 1: Analyse du ticket\n\n"
|
||||
markdown += "L'agent d'analyse de ticket a extrait les informations suivantes du ticket d'origine:\n\n"
|
||||
markdown += "<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"
|
||||
logger.info(f"Analyse du ticket ajoutée au rapport Markdown ({len(str(ticket_analyse))} caractères)")
|
||||
else:
|
||||
logger.warning("Aucune analyse de ticket disponible pour le rapport Markdown")
|
||||
|
||||
# 2. Tri des images
|
||||
markdown += "### Étape 2: Tri des images\n\n"
|
||||
markdown += "L'agent de tri d'images a évalué chaque image pour déterminer sa pertinence par rapport au problème client:\n\n"
|
||||
|
||||
# Vérifier quelle structure de données est disponible pour les images
|
||||
has_analyse_images = "analyse_images" in rapport_data and rapport_data["analyse_images"]
|
||||
has_images_analyses = "images_analyses" in rapport_data and isinstance(rapport_data["images_analyses"], list) and rapport_data["images_analyses"]
|
||||
|
||||
logger.info(f"Structure des données d'images: analyse_images={has_analyse_images}, images_analyses={has_images_analyses}")
|
||||
|
||||
analyse_images_data = {}
|
||||
if has_analyse_images:
|
||||
analyse_images_data = rapport_data["analyse_images"]
|
||||
|
||||
if analyse_images_data:
|
||||
# Créer un tableau pour le tri des images
|
||||
markdown += "| Image | Pertinence | Raison |\n"
|
||||
markdown += "|-------|------------|--------|\n"
|
||||
|
||||
for image_path, analyse_data in analyse_images_data.items():
|
||||
image_name = os.path.basename(image_path)
|
||||
|
||||
# Information de tri
|
||||
is_relevant = "Non"
|
||||
reason = "Non spécifiée"
|
||||
|
||||
if "sorting" in analyse_data:
|
||||
sorting_data = analyse_data["sorting"]
|
||||
if isinstance(sorting_data, dict):
|
||||
is_relevant = "Oui" if sorting_data.get("is_relevant", False) else "Non"
|
||||
reason = sorting_data.get("reason", "Non spécifiée")
|
||||
|
||||
markdown += f"| {image_name} | {is_relevant} | {reason} |\n"
|
||||
|
||||
markdown += "\n"
|
||||
logger.info(f"Tableau de tri des images ajouté au rapport Markdown ({len(analyse_images_data)} images)")
|
||||
elif has_images_analyses and rapport_data["images_analyses"]:
|
||||
# Si nous avons les analyses d'images mais pas les données de tri, créer un tableau simplifié
|
||||
markdown += "| Image | Pertinence |\n"
|
||||
markdown += "|-------|------------|\n"
|
||||
|
||||
for img_data in rapport_data["images_analyses"]:
|
||||
image_name = img_data.get("image_name", "Image inconnue")
|
||||
markdown += f"| {image_name} | Oui |\n"
|
||||
|
||||
markdown += "\n"
|
||||
logger.info(f"Tableau de tri simplifié ajouté au rapport Markdown ({len(rapport_data['images_analyses'])} images)")
|
||||
else:
|
||||
markdown += "*Aucune image n'a été trouvée ou analysée.*\n\n"
|
||||
logger.warning("Aucune analyse d'images disponible pour le tableau de tri")
|
||||
|
||||
# 3. Analyse des images pertinentes
|
||||
markdown += "### Étape 3: Analyse détaillée des images pertinentes\n\n"
|
||||
|
||||
# Traiter directement les images_analyses du rapport_data si disponible
|
||||
images_pertinentes = 0
|
||||
|
||||
# D'abord essayer d'utiliser la liste images_analyses qui est déjà traitée
|
||||
if has_images_analyses:
|
||||
images_list = rapport_data["images_analyses"]
|
||||
for i, img_data in enumerate(images_list, 1):
|
||||
images_pertinentes += 1
|
||||
image_name = img_data.get("image_name", f"Image {i}")
|
||||
analyse_detail = img_data.get("analyse", "Analyse non disponible")
|
||||
|
||||
markdown += f"#### Image pertinente {images_pertinentes}: {image_name}\n\n"
|
||||
markdown += "<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"
|
||||
|
||||
logger.info(f"Analyse de l'image {image_name} ajoutée au rapport Markdown (from images_analyses)")
|
||||
# Sinon, traiter les données brutes d'analyse_images
|
||||
elif has_analyse_images:
|
||||
analyse_images_data = rapport_data["analyse_images"]
|
||||
for image_path, analyse_data in analyse_images_data.items():
|
||||
# Vérifier si l'image est pertinente
|
||||
is_relevant = False
|
||||
if "sorting" in analyse_data and isinstance(analyse_data["sorting"], dict):
|
||||
is_relevant = analyse_data["sorting"].get("is_relevant", False)
|
||||
|
||||
if is_relevant:
|
||||
images_pertinentes += 1
|
||||
image_name = os.path.basename(image_path)
|
||||
|
||||
# Récupérer l'analyse détaillée avec gestion des différents formats possibles
|
||||
analyse_detail = "Analyse non disponible"
|
||||
if "analysis" in analyse_data and analyse_data["analysis"]:
|
||||
if isinstance(analyse_data["analysis"], dict):
|
||||
if "analyse" in analyse_data["analysis"]:
|
||||
analyse_detail = analyse_data["analysis"]["analyse"]
|
||||
logger.info(f"Analyse de l'image {image_name} récupérée via analyse_data['analysis']['analyse']")
|
||||
elif "error" in analyse_data["analysis"] and not analyse_data["analysis"].get("error", True):
|
||||
analyse_detail = str(analyse_data["analysis"])
|
||||
logger.info(f"Analyse de l'image {image_name} récupérée via str(analyse_data['analysis'])")
|
||||
else:
|
||||
analyse_detail = json.dumps(analyse_data["analysis"], ensure_ascii=False, indent=2)
|
||||
logger.info(f"Analyse de l'image {image_name} récupérée via json.dumps")
|
||||
elif isinstance(analyse_data["analysis"], str):
|
||||
analyse_detail = analyse_data["analysis"]
|
||||
logger.info(f"Analyse de l'image {image_name} récupérée directement (string)")
|
||||
else:
|
||||
logger.warning(f"Aucune analyse disponible pour l'image pertinente {image_name}")
|
||||
|
||||
markdown += f"#### Image pertinente {images_pertinentes}: {image_name}\n\n"
|
||||
markdown += "<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 pour ce ticket.*\n\n"
|
||||
logger.warning("Aucune image pertinente identifiée pour l'analyse détaillée")
|
||||
else:
|
||||
logger.info(f"{images_pertinentes} images pertinentes ajoutées au rapport Markdown")
|
||||
|
||||
# 4. Synthèse (rapport final)
|
||||
markdown += "### Étape 4: Génération du rapport de synthèse\n\n"
|
||||
markdown += "L'agent de génération de rapport a synthétisé toutes les analyses précédentes pour produire le rapport ci-dessus.\n\n"
|
||||
|
||||
# Informations techniques
|
||||
markdown += "## Informations techniques\n\n"
|
||||
|
||||
# Statistiques d'analyse
|
||||
markdown += "### Statistiques\n\n"
|
||||
|
||||
total_images = 0
|
||||
if has_analyse_images:
|
||||
total_images = len(analyse_images_data)
|
||||
elif "statistiques" in rapport_data and "total_images" in rapport_data["statistiques"]:
|
||||
total_images = rapport_data["statistiques"]["total_images"]
|
||||
|
||||
markdown += f"- **Images analysées**: {total_images}\n"
|
||||
markdown += f"- **Images pertinentes**: {images_pertinentes}\n\n"
|
||||
|
||||
logger.info(f"Rapport Markdown généré ({len(markdown)} caractères)")
|
||||
return markdown
|
||||
|
||||
def _formater_prompt_pour_rapport(self, ticket_analyse, images_analyses, ticket_id):
|
||||
"""
|
||||
Formate le prompt pour la génération du rapport
|
||||
|
||||
Args:
|
||||
ticket_analyse: Analyse du ticket
|
||||
images_analyses: Liste des analyses d'images, format [{image_name, analyse}, ...]
|
||||
ticket_id: ID du ticket
|
||||
|
||||
Returns:
|
||||
Prompt formaté pour le LLM
|
||||
"""
|
||||
num_images = len(images_analyses)
|
||||
logger.info(f"Formatage du prompt avec {num_images} analyses d'images")
|
||||
|
||||
# Inclure une vérification des données reçues
|
||||
prompt = f"""Génère un rapport technique complet pour le ticket #{ticket_id}, en te basant sur les analyses suivantes.
|
||||
|
||||
## VÉRIFICATION DES DONNÉES REÇUES
|
||||
Je vais d'abord vérifier que j'ai bien reçu les données d'analyses:
|
||||
- Analyse du ticket : {"PRÉSENTE" if ticket_analyse else "MANQUANTE"}
|
||||
- Analyses d'images : {"PRÉSENTES (" + str(num_images) + " images)" if num_images > 0 else "MANQUANTES"}
|
||||
|
||||
## ANALYSE DU TICKET
|
||||
{ticket_analyse}
|
||||
|
||||
## ANALYSES DES IMAGES ({num_images} images analysées)
|
||||
"""
|
||||
|
||||
# Ajouter l'analyse de chaque image
|
||||
for i, img_analyse in enumerate(images_analyses, 1):
|
||||
image_name = img_analyse.get("image_name", f"Image {i}")
|
||||
analyse = img_analyse.get("analyse", "Analyse non disponible")
|
||||
prompt += f"\n### IMAGE {i}: {image_name}\n{analyse}\n"
|
||||
logger.info(f"Ajout de l'analyse de l'image {image_name} au prompt ({len(str(analyse))} caractères)")
|
||||
|
||||
# Ne pas répéter les instructions déjà présentes dans le system_prompt
|
||||
prompt += f"""
|
||||
N'oublie pas d'inclure un objet JSON structuré des échanges client/support selon le format demandé.
|
||||
Assure-toi que le JSON est valide et clairement balisé avec ```json et ``` dans ta réponse.
|
||||
"""
|
||||
|
||||
logger.info(f"Prompt formaté: {len(prompt)} caractères au total")
|
||||
return prompt
|
||||
|
||||
def _extraire_et_traiter_json(self, texte_rapport):
|
||||
"""
|
||||
Extrait l'objet JSON des échanges du texte du rapport et le convertit en Markdown
|
||||
|
||||
Args:
|
||||
texte_rapport: Texte complet du rapport généré par le LLM
|
||||
|
||||
Returns:
|
||||
Tuple (rapport_traité, echanges_json, echanges_markdown)
|
||||
"""
|
||||
# Remplacer CBAD par CBAO dans tout le rapport
|
||||
texte_rapport = texte_rapport.replace("CBAD", "CBAO")
|
||||
|
||||
# Rechercher un objet JSON dans le texte
|
||||
json_match = re.search(r'```json\s*({.*?})\s*```', texte_rapport, re.DOTALL)
|
||||
|
||||
if not json_match:
|
||||
logger.warning("Aucun JSON trouvé dans le rapport")
|
||||
return texte_rapport, None, None
|
||||
|
||||
# Extraire le JSON et le parser
|
||||
json_text = json_match.group(1)
|
||||
try:
|
||||
echanges_json = json.loads(json_text)
|
||||
logger.info(f"JSON extrait avec succès: {len(json_text)} caractères")
|
||||
|
||||
# Convertir en tableau Markdown
|
||||
echanges_markdown = "| Date | Émetteur | Type | Contenu | Statut |\n"
|
||||
echanges_markdown += "|------|---------|------|---------|--------|\n"
|
||||
|
||||
if "chronologie_echanges" in echanges_json and isinstance(echanges_json["chronologie_echanges"], list):
|
||||
# Pré-traitement pour vérifier les questions sans réponse
|
||||
questions_sans_reponse = {}
|
||||
for i, echange in enumerate(echanges_json["chronologie_echanges"]):
|
||||
if echange.get("type", "").lower() == "question" and echange.get("emetteur", "").lower() == "client":
|
||||
has_response = False
|
||||
# Vérifier si la question a une réponse
|
||||
for j in range(i+1, len(echanges_json["chronologie_echanges"])):
|
||||
next_echange = echanges_json["chronologie_echanges"][j]
|
||||
if next_echange.get("type", "").lower() == "réponse" and next_echange.get("emetteur", "").lower() == "support":
|
||||
has_response = True
|
||||
break
|
||||
questions_sans_reponse[i] = not has_response
|
||||
|
||||
# Générer le tableau
|
||||
for i, echange in enumerate(echanges_json["chronologie_echanges"]):
|
||||
date = echange.get("date", "-")
|
||||
emetteur = echange.get("emetteur", "-")
|
||||
type_msg = echange.get("type", "-")
|
||||
contenu = echange.get("contenu", "-")
|
||||
|
||||
# Ajouter un statut pour les questions sans réponse
|
||||
statut = ""
|
||||
if emetteur.lower() == "client" and type_msg.lower() == "question" and questions_sans_reponse.get(i, False):
|
||||
statut = "**Sans réponse**"
|
||||
|
||||
echanges_markdown += f"| {date} | {emetteur} | {type_msg} | {contenu} | {statut} |\n"
|
||||
|
||||
# Ajouter une note si aucune réponse du support n'a été trouvée
|
||||
if not any(echange.get("emetteur", "").lower() == "support" for echange in echanges_json["chronologie_echanges"]):
|
||||
echanges_markdown += "\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n\n"
|
||||
|
||||
# Remplacer le JSON dans le texte par le tableau Markdown
|
||||
rapport_traite = texte_rapport.replace(json_match.group(0), echanges_markdown)
|
||||
|
||||
return rapport_traite, echanges_json, echanges_markdown
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Erreur lors du décodage JSON: {e}")
|
||||
return texte_rapport, None, None
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""Retourne un timestamp au format YYYYMMDD_HHMMSS"""
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
@ -0,0 +1,44 @@
|
||||
{
|
||||
"ticket_id": "T0101",
|
||||
"timestamp": "20250408_110045",
|
||||
"rapport_genere": "### Rapport Technique pour le Ticket #T0101\n\n#### 1. Résumé exécutif\n**Nom de la demande:** ACTIVATION LOGICIEL\n\n**Description du problème:** Le client rencontre un problème de licence lors de la réinstallation du logiciel ESQ sur un nouveau serveur pour permettre le télétravail. Le client se demande si le numéro de licence a été modifié suite à un achat de version réseau en 2019 et demande de l'aide pour résoudre ce problème d'activation.\n\n#### 2. Chronologie des échanges\n| Date | Émetteur | Type | Contenu | Statut |\n|------|---------|------|---------|--------|\n| 26/03/2020 14:43:45 | CLIENT | Question | Le logiciel demande une activation après réinstallation sur un nouveau serveur. Le numéro de licence a-t-il été modifié suite à un achat de version réseau en 2019 ? Aide pour résoudre le problème d'activation. | **Sans réponse** |\n| 26/03/2020 14:43:45 | CLIENT | Information technique | Réinstallation du logiciel ESQ sur un nouveau serveur pour le télétravail. Achat d'une version réseau en 2019 par JB Lafitte. Captures d'écran de la fenêtre d'activation du logiciel (image006.jpg, image005.jpg). | |\n\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n\n\n\n#### 3. Analyse des images\n**IMAGE 1: image005.jpg**\n\n**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 boutons pour différentes méthodes d'activation.\n\n**Éléments techniques clés:**\n- **Titre de la fenêtre:** \"Activation du logiciel\"\n- **Champ d'ID du logiciel:** \"ID du logiciel\" avec un champ de texte vide\n- **Message d'instructions:** \"Afin d'activer votre logiciel, veuillez saisir l'ID du logiciel fourni par CBAO. Si vous ne disposez pas de votre ID de logiciel, veuillez contacter CBAO par mail à support@cbao.com ou par téléphone au 01 60 15 31 00 en cliquant sur le bouton téléphone ci-dessous.\"\n- **Boutons d'activation:**\n - \"Activer le logiciel (par internet)\"\n - \"Activer plus tard (4 jours restants)\"\n - \"Activation par téléphone\"\n\n**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 après une réinstallation sur un nouveau serveur pour le télétravail. 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é par le client. Le message d'instructions indique que le client doit contacter CBAO pour obtenir l'ID de licence, ce qui pourrait être une étape nécessaire pour résoudre le problème d'activation.\n\n#### 4. Diagnostic technique\nLe problème d'activation du logiciel ESQ semble être lié à l'absence de l'ID de licence nécessaire pour compléter le processus d'activation. Le client a réinstallé le logiciel sur un nouveau serveur pour le télétravail et mentionne un achat de version réseau en 2019. Les captures d'écran montrent que le logiciel demande un ID de licence pour être activé, et le message d'instructions suggère de contacter CBAO pour obtenir cet ID.\n\n**Recommandations:**\n1. Le client doit vérifier s'il dispose de l'ID de licence fourni par CBAO.\n2. Si l'ID de licence n'est pas disponible, le client doit contacter CBAO par mail à support@cbao.com ou par téléphone au 01 60 15 31 00 pour obtenir l'ID de licence nécessaire.\n3. Une fois l'ID de licence obtenu, le client doit entrer cet ID dans le champ prévu à cet effet et suivre les instructions pour activer le logiciel.",
|
||||
"ticket_analyse": "### 1. Analyse du problème initial\n\n**Nom de la demande:** ACTIVATION LOGICIEL\n\n**Description du problème:** Problème de licence.\n\nLe problème initial concerne l'activation d'un logiciel suite à une réinstallation sur un nouveau serveur pour permettre le télétravail. Le client mentionne un problème de licence et se demande si le numéro de licence a été modifié suite à un achat de version réseau en 2019.\n\n### 2. Informations techniques essentielles\n\n- **Logiciel concerné:** ESQ\n- **Contexte:** Réinstallation sur un nouveau serveur pour le télétravail\n- **Version:** Version réseau achetée en 2019\n- **Problème technique:** Demande d'activation du logiciel, problème de licence\n- **Images fournies:** image006.jpg, image005.jpg (probablement des captures d'écran de la fenêtre d'activation du logiciel)\n\n### 3. Chronologie des échanges client/support\n\n**Message 1 - [AUTRE] De: Inconnu - Date: 26/03/2020 14:43:45**\n\n**Questions posées par le client:**\n1. Le logiciel demande une activation après réinstallation sur un nouveau serveur.\n2. Le client se demande si le numéro de licence a été modifié suite à un achat de version réseau en 2019.\n3. Le client demande de l'aide pour résoudre ce problème d'activation.\n\n**Informations techniques fournies par le client:**\n- Réinstallation du logiciel ESQ sur un nouveau serveur pour le télétravail.\n- Achat d'une version réseau en 2019 par JB Lafitte.\n- Captures d'écran de la fenêtre d'activation du logiciel (image006.jpg, image005.jpg).\n\n**Réponses fournies par le support:**\n- Aucune réponse du support n'est mentionnée dans les échanges fournis.\n\n**Conclusion:**\nLe ticket concerne un problème d'activation du logiciel ESQ après sa réinstallation sur un nouveau serveur pour permettre le télétravail. Le client fournit des captures d'écran de la fenêtre d'activation et mentionne un achat de version réseau en 2019. Aucune réponse du support n'est disponible dans les échanges fournis.",
|
||||
"images_analyses": [
|
||||
{
|
||||
"image_name": "image005.jpg",
|
||||
"analyse": "### Analyse de l'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 plusieurs éléments, notamment un champ pour entrer l'ID du logiciel, un message d'instructions, et trois boutons pour différentes méthodes d'activation.\n\n#### 2. Éléments techniques clés\n- **Titre de la fenêtre:** \"Activation du logiciel\"\n- **Champ d'ID du logiciel:** \"ID du logiciel\" avec un champ de texte vide\n- **Message d'instructions:** \"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 01 60 15 31 00 en cliquant sur le bouton téléphone ci-dessous.\"\n- **Boutons 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 après une réinstallation sur un nouveau serveur pour le télétravail. 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é par le client. Le message d'instructions indique que le client doit contacter CBAD pour obtenir l'ID de licence, ce qui pourrait être une étape nécessaire pour résoudre le problème d'activation."
|
||||
}
|
||||
],
|
||||
"statistiques": {
|
||||
"total_images": 2,
|
||||
"images_pertinentes": 1,
|
||||
"analyses_generees": 1
|
||||
},
|
||||
"metadata": {
|
||||
"timestamp": "20250408_110045",
|
||||
"model": "mistral-large-latest",
|
||||
"temperature": 0.4,
|
||||
"top_p": 0.9,
|
||||
"max_tokens": 2500,
|
||||
"agents": {
|
||||
"image_sorter": {
|
||||
"model": "pixtral-12b-latest",
|
||||
"temperature": 0.2,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 300
|
||||
},
|
||||
"image_analyser": {
|
||||
"model": "pixtral-12b-latest",
|
||||
"temperature": 0.3,
|
||||
"top_p": 0.9,
|
||||
"max_tokens": 1200
|
||||
},
|
||||
"report_generator": {
|
||||
"model": "mistral-large-latest",
|
||||
"temperature": 0.4,
|
||||
"top_p": 0.9,
|
||||
"max_tokens": 2500
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,147 @@
|
||||
# Rapport d'analyse du ticket #T0101
|
||||
|
||||
*Généré le: 20250408_110045*
|
||||
|
||||
### Rapport Technique pour le Ticket #T0101
|
||||
|
||||
#### 1. Résumé exécutif
|
||||
**Nom de la demande:** ACTIVATION LOGICIEL
|
||||
|
||||
**Description du problème:** Le client rencontre un problème de licence lors de la réinstallation du logiciel ESQ sur un nouveau serveur pour permettre le télétravail. Le client se demande si le numéro de licence a été modifié suite à un achat de version réseau en 2019 et demande de l'aide pour résoudre ce problème d'activation.
|
||||
|
||||
#### 2. Chronologie des échanges
|
||||
| Date | Émetteur | Type | Contenu | Statut |
|
||||
|------|---------|------|---------|--------|
|
||||
| 26/03/2020 14:43:45 | CLIENT | Question | Le logiciel demande une activation après réinstallation sur un nouveau serveur. Le numéro de licence a-t-il été modifié suite à un achat de version réseau en 2019 ? Aide pour résoudre le problème d'activation. | **Sans réponse** |
|
||||
| 26/03/2020 14:43:45 | CLIENT | Information technique | Réinstallation du logiciel ESQ sur un nouveau serveur pour le télétravail. Achat d'une version réseau en 2019 par JB Lafitte. Captures d'écran de la fenêtre d'activation du logiciel (image006.jpg, image005.jpg). | |
|
||||
|
||||
**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**
|
||||
|
||||
|
||||
|
||||
#### 3. Analyse des images
|
||||
**IMAGE 1: image005.jpg**
|
||||
|
||||
**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 boutons pour différentes méthodes d'activation.
|
||||
|
||||
**Éléments techniques clés:**
|
||||
- **Titre de la fenêtre:** "Activation du logiciel"
|
||||
- **Champ d'ID du logiciel:** "ID du logiciel" avec un champ de texte vide
|
||||
- **Message d'instructions:** "Afin d'activer votre logiciel, veuillez saisir l'ID du logiciel fourni par CBAO. Si vous ne disposez pas de votre ID de logiciel, veuillez contacter CBAO par mail à support@cbao.com ou par téléphone au 01 60 15 31 00 en cliquant sur le bouton téléphone ci-dessous."
|
||||
- **Boutons d'activation:**
|
||||
- "Activer le logiciel (par internet)"
|
||||
- "Activer plus tard (4 jours restants)"
|
||||
- "Activation par téléphone"
|
||||
|
||||
**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 après une réinstallation sur un nouveau serveur pour le télétravail. 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é par le client. Le message d'instructions indique que le client doit contacter CBAO pour obtenir l'ID de licence, ce qui pourrait être une étape nécessaire pour résoudre le problème d'activation.
|
||||
|
||||
#### 4. Diagnostic technique
|
||||
Le problème d'activation du logiciel ESQ semble être lié à l'absence de l'ID de licence nécessaire pour compléter le processus d'activation. Le client a réinstallé le logiciel sur un nouveau serveur pour le télétravail et mentionne un achat de version réseau en 2019. Les captures d'écran montrent que le logiciel demande un ID de licence pour être activé, et le message d'instructions suggère de contacter CBAO pour obtenir cet ID.
|
||||
|
||||
**Recommandations:**
|
||||
1. Le client doit vérifier s'il dispose de l'ID de licence fourni par CBAO.
|
||||
2. Si l'ID de licence n'est pas disponible, le client doit contacter CBAO par mail à support@cbao.com ou par téléphone au 01 60 15 31 00 pour obtenir l'ID de licence nécessaire.
|
||||
3. Une fois l'ID de licence obtenu, le client doit entrer cet ID dans le champ prévu à cet effet et suivre les instructions pour activer le logiciel.
|
||||
|
||||
---
|
||||
|
||||
# Détails des analyses effectuées
|
||||
|
||||
## Processus d'analyse
|
||||
|
||||
### Étape 1: Analyse du ticket
|
||||
|
||||
L'agent d'analyse de ticket a extrait les informations suivantes du ticket d'origine:
|
||||
|
||||
<details>
|
||||
<summary>Cliquez pour voir l'analyse complète du ticket</summary>
|
||||
|
||||
```
|
||||
### 1. Analyse du problème initial
|
||||
|
||||
**Nom de la demande:** ACTIVATION LOGICIEL
|
||||
|
||||
**Description du problème:** Problème de licence.
|
||||
|
||||
Le problème initial concerne l'activation d'un logiciel suite à une réinstallation sur un nouveau serveur pour permettre le télétravail. Le client mentionne un problème de licence et se demande si le numéro de licence a été modifié suite à un achat de version réseau en 2019.
|
||||
|
||||
### 2. Informations techniques essentielles
|
||||
|
||||
- **Logiciel concerné:** ESQ
|
||||
- **Contexte:** Réinstallation sur un nouveau serveur pour le télétravail
|
||||
- **Version:** Version réseau achetée en 2019
|
||||
- **Problème technique:** Demande d'activation du logiciel, problème de licence
|
||||
- **Images fournies:** image006.jpg, image005.jpg (probablement des captures d'écran de la fenêtre d'activation du logiciel)
|
||||
|
||||
### 3. Chronologie des échanges client/support
|
||||
|
||||
**Message 1 - [AUTRE] De: Inconnu - Date: 26/03/2020 14:43:45**
|
||||
|
||||
**Questions posées par le client:**
|
||||
1. Le logiciel demande une activation après réinstallation sur un nouveau serveur.
|
||||
2. Le client se demande si le numéro de licence a été modifié suite à un achat de version réseau en 2019.
|
||||
3. Le client demande de l'aide pour résoudre ce problème d'activation.
|
||||
|
||||
**Informations techniques fournies par le client:**
|
||||
- Réinstallation du logiciel ESQ sur un nouveau serveur pour le télétravail.
|
||||
- Achat d'une version réseau en 2019 par JB Lafitte.
|
||||
- Captures d'écran de la fenêtre d'activation du logiciel (image006.jpg, image005.jpg).
|
||||
|
||||
**Réponses fournies par le support:**
|
||||
- Aucune réponse du support n'est mentionnée dans les échanges fournis.
|
||||
|
||||
**Conclusion:**
|
||||
Le ticket concerne un problème d'activation du logiciel ESQ après sa réinstallation sur un nouveau serveur pour permettre le télétravail. Le client fournit des captures d'écran de la fenêtre d'activation et mentionne un achat de version réseau en 2019. Aucune réponse du support n'est disponible dans les échanges fournis.
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Étape 2: Tri des images
|
||||
|
||||
L'agent de tri d'images a évalué chaque image pour déterminer sa pertinence par rapport au problème client:
|
||||
|
||||
| Image | Pertinence |
|
||||
|-------|------------|
|
||||
| image005.jpg | Oui |
|
||||
|
||||
### Étape 3: Analyse détaillée des images pertinentes
|
||||
|
||||
#### Image pertinente 1: image005.jpg
|
||||
|
||||
<details>
|
||||
<summary>Cliquez pour voir l'analyse complète de l'image</summary>
|
||||
|
||||
```
|
||||
### Analyse de l'image
|
||||
|
||||
#### 1. Description objective
|
||||
L'image montre une fenêtre d'activation de logiciel intitulée "Activation du logiciel". La fenêtre contient plusieurs éléments, notamment un champ pour entrer l'ID du logiciel, un message d'instructions, et trois boutons pour différentes méthodes d'activation.
|
||||
|
||||
#### 2. Éléments techniques clés
|
||||
- **Titre de la fenêtre:** "Activation du logiciel"
|
||||
- **Champ d'ID du logiciel:** "ID du logiciel" avec un champ de texte vide
|
||||
- **Message d'instructions:** "Afin d'activer votre logiciel, veuillez saisir l'ID du logiciel fourni par CBAD. Si vous ne disposez pas de votre ID de logiciel, veuillez contacter CBAD par mail à support@cbad.com ou par téléphone au 01 60 15 31 00 en cliquant sur le bouton téléphone ci-dessous."
|
||||
- **Boutons 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 après une réinstallation sur un nouveau serveur pour le télétravail. 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é par le client. Le message d'instructions indique que le client doit contacter CBAD pour obtenir l'ID de licence, ce qui pourrait être une étape nécessaire pour résoudre le problème d'activation.
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Étape 4: Génération du rapport de synthèse
|
||||
|
||||
L'agent de génération de rapport a synthétisé toutes les analyses précédentes pour produire le rapport ci-dessus.
|
||||
|
||||
## Informations techniques
|
||||
|
||||
### Statistiques
|
||||
|
||||
- **Images analysées**: 2
|
||||
- **Images pertinentes**: 1
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
{
|
||||
"ticket_id": "T0150",
|
||||
"timestamp": "20250408_113128",
|
||||
"rapport_genere": "## Rapport Technique pour le Ticket #T0150\n\n### Résumé du Problème Principal\nLe Conseil Départemental 66 a rencontré un problème avec la version web de BRG-LAB, où les PV ou rapports d'essais n'affichent plus que les résistances brutes sans la correction de 1 MPa. La norme exige l'affichage de la force maximale à rupture non corrigée (en N) et de la résistance non corrigée (en MPa). L'affichage de la résistance corrigée est possible mais doit être clairement défini dans le contrat client pour éviter des problèmes d'accréditation COFRAC.\n\n### Chronologie des Échanges\n| Date | Émetteur | Type | Contenu | Statut |\n|------|---------|------|---------|--------|\n| 2020-04-22 | CLIENT | Question | Les PV ou rapports d'essais n'affichent plus que les résistances brutes sans la correction de 1 MPa. Contact avec CBAO pour ajouter une option permettant d'afficher la résistance corrigée. Pierre Paya avait indiqué de n'afficher que la résistance brute, mais il semble que l'usage soit de prendre en compte la résistance corrigée. | |\n| 2020-04-23 | SUPPORT | Réponse | Confirmation des exigences de la norme : Affichage impératif de la force maximale à rupture non corrigée (en N) et de la résistance non corrigée (en MPa). Possibilité d'afficher la résistance corrigée, mais cela peut être considéré comme une interprétation du résultat par le COFRAC. Pratique CEREMA : Affichage des résistances brutes et corrigées sur leurs PV (sous accréditation COFRAC). Pierre PAYA reste disponible pour toute précision. | |\n\n\n### Analyse des Images\n- **Nombre d'images analysées :** 0\n- **Contribution à la compréhension du problème :** Aucune image n'a été fournie, donc aucune contribution visuelle à l'analyse du problème.\n\n### Analyse Technique des Causes Probables du Problème\n1. **Configuration Logicielle :**\n - Il est possible que la configuration actuelle de BRG-LAB ne prenne pas en compte la correction de 1 MPa pour les résistances. Cette configuration pourrait avoir été modifiée récemment, ou elle n'aurait jamais été correctement mise en place.\n\n2. **Mise à Jour du Logiciel :**\n - Une mise à jour récente de BRG-LAB pourrait avoir introduit un bug qui empêche l'affichage des résistances corrigées. Il serait utile de vérifier les notes de version pour identifier si une telle modification a été apportée.\n\n3. **Paramètres Utilisateur :**\n - Les paramètres d'affichage ou de configuration spécifiques à l'utilisateur du Conseil Départemental 66 pourraient ne pas être correctement configurés pour afficher les résistances corrigées. Il serait nécessaire de vérifier ces paramètres et de les ajuster si nécessaire.\n\n4. **Base de Données :**\n - Les données stockées dans la base de données de BRG-LAB pourraient ne pas inclure les valeurs corrigées des résistances. Une inspection de la structure de la base de données et des requêtes effectuées pour générer les rapports pourrait aider à identifier ce problème.\n\n5. **Compliance avec la Norme :**\n - La norme exigeant l'affichage de la force maximale à rupture non corrigée (en N) et de la résistance non corrigée (en MPa) doit être respectée. Si le logiciel ne permet pas cette configuration, une mise à jour ou un ajustement du code pourrait être nécessaire pour se conformer à la norme.\n\n### Conclusion\nPour résoudre ce problème, il est recommandé de :\n1. Vérifier et ajuster la configuration actuelle de BRG-LAB pour inclure la correction de 1 MPa.\n2. Examiner les notes de version récentes pour identifier d'éventuels bugs liés à l'affichage des résistances corrigées.\n3. Réviser les paramètres utilisateur spécifiques au Conseil Départemental 66.\n4. Inspecter la base de données et les requêtes pour s'assurer que les valeurs corrigées sont bien stockées et récupérées.\n5. S'assurer que le logiciel est conforme à la norme en matière d'affichage des résultats.\n\nEn suivant ces étapes, il devrait être possible de corriger l'affichage des résistances corrigées dans BRG-LAB.",
|
||||
"ticket_analyse": "### Analyse du Problème Initial\n\n**Problématique :**\n- Le Conseil Départemental 66 a rencontré un problème avec la version web de BRG-LAB concernant l'affichage des résultats d'essais des écrasements des éprouvettes 11x22.\n- Les PV ou rapports d'essais n'affichent plus que les résistances brutes sans la correction de 1 MPa.\n\n### Informations Techniques Importantes\n\n**Informations Clés :**\n- **Résultats Affichés Actuellement :** Seules les résistances brutes sont affichées.\n- **Résultats Souhaités :** Affichage des résistances corrigées (avec correction de 1 MPa).\n- **Norme :** La norme exige l'affichage de la résistance non corrigée (en MPa) et de la force maximale à rupture non corrigée (en N).\n- **COFRAC :** L'affichage de la résistance corrigée est considéré comme une interprétation du résultat et peut nécessiter une définition claire dans le contrat client.\n\n### Chronologie des Échanges\n\n1. **22/04/2020 - CAZARD Yoan (Conseil Départemental 66) :**\n - **Problème Signalé :** Les PV ou rapports d'essais n'affichent plus que les résistances brutes sans la correction de 1 MPa.\n - **Action Prise :** Contact avec CBAO pour ajouter une option permettant d'afficher la résistance corrigée.\n - **Contexte :** Pierre Paya avait indiqué de n'afficher que la résistance brute, mais il semble que l'usage soit de prendre en compte la résistance corrigée.\n\n2. **23/04/2020 - Pierre PAYA (CEREMA) :**\n - **Réponse :** Confirmation des exigences de la norme :\n - Affichage impératif de la force maximale à rupture non corrigée (en N) et de la résistance non corrigée (en MPa).\n - Possibilité d'afficher la résistance corrigée, mais cela peut être considéré comme une interprétation du résultat par le COFRAC.\n - **Pratique CEREMA :** Affichage des résistances brutes et corrigées sur leurs PV (sous accréditation COFRAC).\n - **Disponibilité :** Pierre PAYA reste disponible pour toute précision.\n\n### Synthèse\n\n- **Problème Initial :** Les rapports d'essais n'affichent plus la résistance corrigée.\n- **Solution Proposée :** Ajout d'une option dans BRG-LAB pour afficher la résistance corrigée.\n- **Conformité Normative :** Affichage obligatoire de la force maximale à rupture non corrigée et de la résistance non corrigée. L'affichage de la résistance corrigée est possible mais doit être clairement défini dans le contrat client pour éviter des problèmes d'accréditation COFRAC.\n- **État Actuel :** En attente d'une solution technique de CBAO.",
|
||||
"images_analyses": [],
|
||||
"statistiques": {
|
||||
"total_images": 0,
|
||||
"images_pertinentes": 0,
|
||||
"analyses_generees": 0
|
||||
},
|
||||
"metadata": {
|
||||
"timestamp": "20250408_113128",
|
||||
"model": "qwen2.5:72b-instruct-q8_0",
|
||||
"temperature": 0.4,
|
||||
"top_p": 0.9,
|
||||
"max_tokens": 2500,
|
||||
"agents": {
|
||||
"report_generator": {
|
||||
"model": "qwen2.5:72b-instruct-q8_0",
|
||||
"temperature": 0.4,
|
||||
"top_p": 0.9,
|
||||
"max_tokens": 2500
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
# Rapport d'analyse du ticket #T0150
|
||||
|
||||
*Généré le: 20250408_113128*
|
||||
|
||||
## Rapport Technique pour le Ticket #T0150
|
||||
|
||||
### Résumé du Problème Principal
|
||||
Le Conseil Départemental 66 a rencontré un problème avec la version web de BRG-LAB, où les PV ou rapports d'essais n'affichent plus que les résistances brutes sans la correction de 1 MPa. La norme exige l'affichage de la force maximale à rupture non corrigée (en N) et de la résistance non corrigée (en MPa). L'affichage de la résistance corrigée est possible mais doit être clairement défini dans le contrat client pour éviter des problèmes d'accréditation COFRAC.
|
||||
|
||||
### Chronologie des Échanges
|
||||
| Date | Émetteur | Type | Contenu | Statut |
|
||||
|------|---------|------|---------|--------|
|
||||
| 2020-04-22 | CLIENT | Question | Les PV ou rapports d'essais n'affichent plus que les résistances brutes sans la correction de 1 MPa. Contact avec CBAO pour ajouter une option permettant d'afficher la résistance corrigée. Pierre Paya avait indiqué de n'afficher que la résistance brute, mais il semble que l'usage soit de prendre en compte la résistance corrigée. | |
|
||||
| 2020-04-23 | SUPPORT | Réponse | Confirmation des exigences de la norme : Affichage impératif de la force maximale à rupture non corrigée (en N) et de la résistance non corrigée (en MPa). Possibilité d'afficher la résistance corrigée, mais cela peut être considéré comme une interprétation du résultat par le COFRAC. Pratique CEREMA : Affichage des résistances brutes et corrigées sur leurs PV (sous accréditation COFRAC). Pierre PAYA reste disponible pour toute précision. | |
|
||||
|
||||
|
||||
### Analyse des Images
|
||||
- **Nombre d'images analysées :** 0
|
||||
- **Contribution à la compréhension du problème :** Aucune image n'a été fournie, donc aucune contribution visuelle à l'analyse du problème.
|
||||
|
||||
### Analyse Technique des Causes Probables du Problème
|
||||
1. **Configuration Logicielle :**
|
||||
- Il est possible que la configuration actuelle de BRG-LAB ne prenne pas en compte la correction de 1 MPa pour les résistances. Cette configuration pourrait avoir été modifiée récemment, ou elle n'aurait jamais été correctement mise en place.
|
||||
|
||||
2. **Mise à Jour du Logiciel :**
|
||||
- Une mise à jour récente de BRG-LAB pourrait avoir introduit un bug qui empêche l'affichage des résistances corrigées. Il serait utile de vérifier les notes de version pour identifier si une telle modification a été apportée.
|
||||
|
||||
3. **Paramètres Utilisateur :**
|
||||
- Les paramètres d'affichage ou de configuration spécifiques à l'utilisateur du Conseil Départemental 66 pourraient ne pas être correctement configurés pour afficher les résistances corrigées. Il serait nécessaire de vérifier ces paramètres et de les ajuster si nécessaire.
|
||||
|
||||
4. **Base de Données :**
|
||||
- Les données stockées dans la base de données de BRG-LAB pourraient ne pas inclure les valeurs corrigées des résistances. Une inspection de la structure de la base de données et des requêtes effectuées pour générer les rapports pourrait aider à identifier ce problème.
|
||||
|
||||
5. **Compliance avec la Norme :**
|
||||
- La norme exigeant l'affichage de la force maximale à rupture non corrigée (en N) et de la résistance non corrigée (en MPa) doit être respectée. Si le logiciel ne permet pas cette configuration, une mise à jour ou un ajustement du code pourrait être nécessaire pour se conformer à la norme.
|
||||
|
||||
### Conclusion
|
||||
Pour résoudre ce problème, il est recommandé de :
|
||||
1. Vérifier et ajuster la configuration actuelle de BRG-LAB pour inclure la correction de 1 MPa.
|
||||
2. Examiner les notes de version récentes pour identifier d'éventuels bugs liés à l'affichage des résistances corrigées.
|
||||
3. Réviser les paramètres utilisateur spécifiques au Conseil Départemental 66.
|
||||
4. Inspecter la base de données et les requêtes pour s'assurer que les valeurs corrigées sont bien stockées et récupérées.
|
||||
5. S'assurer que le logiciel est conforme à la norme en matière d'affichage des résultats.
|
||||
|
||||
En suivant ces étapes, il devrait être possible de corriger l'affichage des résistances corrigées dans BRG-LAB.
|
||||
|
||||
---
|
||||
|
||||
# Détails des analyses effectuées
|
||||
|
||||
## Processus d'analyse
|
||||
|
||||
### Étape 1: Analyse du ticket
|
||||
|
||||
L'agent d'analyse de ticket a extrait les informations suivantes du ticket d'origine:
|
||||
|
||||
<details>
|
||||
<summary>Cliquez pour voir l'analyse complète du ticket</summary>
|
||||
|
||||
```
|
||||
### Analyse du Problème Initial
|
||||
|
||||
**Problématique :**
|
||||
- Le Conseil Départemental 66 a rencontré un problème avec la version web de BRG-LAB concernant l'affichage des résultats d'essais des écrasements des éprouvettes 11x22.
|
||||
- Les PV ou rapports d'essais n'affichent plus que les résistances brutes sans la correction de 1 MPa.
|
||||
|
||||
### Informations Techniques Importantes
|
||||
|
||||
**Informations Clés :**
|
||||
- **Résultats Affichés Actuellement :** Seules les résistances brutes sont affichées.
|
||||
- **Résultats Souhaités :** Affichage des résistances corrigées (avec correction de 1 MPa).
|
||||
- **Norme :** La norme exige l'affichage de la résistance non corrigée (en MPa) et de la force maximale à rupture non corrigée (en N).
|
||||
- **COFRAC :** L'affichage de la résistance corrigée est considéré comme une interprétation du résultat et peut nécessiter une définition claire dans le contrat client.
|
||||
|
||||
### Chronologie des Échanges
|
||||
|
||||
1. **22/04/2020 - CAZARD Yoan (Conseil Départemental 66) :**
|
||||
- **Problème Signalé :** Les PV ou rapports d'essais n'affichent plus que les résistances brutes sans la correction de 1 MPa.
|
||||
- **Action Prise :** Contact avec CBAO pour ajouter une option permettant d'afficher la résistance corrigée.
|
||||
- **Contexte :** Pierre Paya avait indiqué de n'afficher que la résistance brute, mais il semble que l'usage soit de prendre en compte la résistance corrigée.
|
||||
|
||||
2. **23/04/2020 - Pierre PAYA (CEREMA) :**
|
||||
- **Réponse :** Confirmation des exigences de la norme :
|
||||
- Affichage impératif de la force maximale à rupture non corrigée (en N) et de la résistance non corrigée (en MPa).
|
||||
- Possibilité d'afficher la résistance corrigée, mais cela peut être considéré comme une interprétation du résultat par le COFRAC.
|
||||
- **Pratique CEREMA :** Affichage des résistances brutes et corrigées sur leurs PV (sous accréditation COFRAC).
|
||||
- **Disponibilité :** Pierre PAYA reste disponible pour toute précision.
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Problème Initial :** Les rapports d'essais n'affichent plus la résistance corrigée.
|
||||
- **Solution Proposée :** Ajout d'une option dans BRG-LAB pour afficher la résistance corrigée.
|
||||
- **Conformité Normative :** Affichage obligatoire de la force maximale à rupture non corrigée et de la résistance non corrigée. L'affichage de la résistance corrigée est possible mais doit être clairement défini dans le contrat client pour éviter des problèmes d'accréditation COFRAC.
|
||||
- **État Actuel :** En attente d'une solution technique de CBAO.
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Étape 2: Tri des images
|
||||
|
||||
L'agent de tri d'images a évalué chaque image pour déterminer sa pertinence par rapport au problème client:
|
||||
|
||||
*Aucune image n'a été trouvée ou analysée.*
|
||||
|
||||
### Étape 3: Analyse détaillée des images pertinentes
|
||||
|
||||
*Aucune image pertinente n'a été identifiée pour ce ticket.*
|
||||
|
||||
### Étape 4: Génération du rapport de synthèse
|
||||
|
||||
L'agent de génération de rapport a synthétisé toutes les analyses précédentes pour produire le rapport ci-dessus.
|
||||
|
||||
## Informations techniques
|
||||
|
||||
### Statistiques
|
||||
|
||||
- **Images analysées**: 0
|
||||
- **Images pertinentes**: 0
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user