287 lines
14 KiB
Python
287 lines
14 KiB
Python
import re
|
||
from pathlib import Path
|
||
import requests
|
||
import json
|
||
import time
|
||
import zipfile
|
||
import streamlit as st
|
||
|
||
from nettoyer_pgpt import (
|
||
delete_documents_by_criteria
|
||
)
|
||
|
||
from utils.config import (
|
||
TEMP_SECTIONS,
|
||
session_uuid,
|
||
API_URL, PROMPT_METHODOLOGIE
|
||
)
|
||
|
||
def ingest_document(file_path: Path) -> bool:
|
||
"""Ingère un document dans PrivateGPT"""
|
||
try:
|
||
with open(file_path, "rb") as f:
|
||
file_name = file_path.name
|
||
|
||
files = {"file": (file_name, f, "text/markdown")}
|
||
# Ajouter des métadonnées pour identifier facilement ce fichier d'entrée
|
||
metadata = {
|
||
"type": "input_file",
|
||
"session_id": session_uuid,
|
||
"document_type": "rapport_analyse_input"
|
||
}
|
||
response = requests.post(
|
||
f"{API_URL}/ingest/file",
|
||
files=files,
|
||
data={"metadata": json.dumps(metadata)} if "metadata" in requests.get(f"{API_URL}/ingest/file").text else None
|
||
)
|
||
response.raise_for_status()
|
||
print(f"✅ Document '{file_path}' ingéré avec succès sous le nom '{file_name}'")
|
||
return True
|
||
except FileNotFoundError:
|
||
print(f"❌ Fichier '{file_path}' introuvable")
|
||
return False
|
||
except requests.RequestException as e:
|
||
print(f"❌ Erreur lors de l'ingestion du document: {e}")
|
||
return False
|
||
|
||
def generate_text(input_file, full_prompt, system_message, temperature = "0.3", use_context = True):
|
||
"""Génère du texte avec l'API PrivateGPT"""
|
||
try:
|
||
|
||
# Définir les paramètres de la requête
|
||
payload = {
|
||
"messages": [
|
||
{"role": "system", "content": system_message},
|
||
{"role": "user", "content": full_prompt}
|
||
],
|
||
"use_context": use_context, # Active la recherche RAG dans les documents ingérés
|
||
"temperature": temperature, # Température réduite pour plus de cohérence
|
||
"stream": False
|
||
}
|
||
|
||
# Tenter d'ajouter un filtre de contexte (fonctionnalité expérimentale qui peut ne pas être supportée)
|
||
if input_file:
|
||
try:
|
||
# Vérifier si le filtre de contexte est supporté sans faire de requête supplémentaire
|
||
liste_des_fichiers = list(TEMP_SECTIONS.glob(f"*{session_uuid}*.md"))
|
||
filter_metadata = {
|
||
"document_name": [input_file.name] + [f.name for f in liste_des_fichiers]
|
||
}
|
||
payload["filter_metadata"] = filter_metadata
|
||
except Exception as e:
|
||
print(f"ℹ️ Remarque: Impossible d'appliquer le filtre de contexte: {e}")
|
||
|
||
# Envoyer la requête
|
||
response = requests.post(
|
||
f"{API_URL}/chat/completions",
|
||
json=payload,
|
||
headers={"accept": "application/json"}
|
||
)
|
||
response.raise_for_status()
|
||
|
||
# Extraire la réponse générée
|
||
result = response.json()
|
||
if "choices" in result and len(result["choices"]) > 0:
|
||
return result["choices"][0]["message"]["content"]
|
||
else:
|
||
print("⚠️ Format de réponse inattendu:", json.dumps(result, indent=2))
|
||
return None
|
||
|
||
except requests.RequestException as e:
|
||
print(f"❌ Erreur lors de la génération de texte: {e}")
|
||
if hasattr(e, 'response') and e.response is not None:
|
||
print(f"Détails: {e.response.text}")
|
||
return None
|
||
|
||
def ia_analyse(file_names):
|
||
for file in file_names:
|
||
ingest_document(file)
|
||
time.sleep(5)
|
||
|
||
reponse = {}
|
||
for file in file_names:
|
||
produit_final = re.search(r"chemins critiques (.+)\.md$", file.name).group(1)
|
||
|
||
# Préparer le prompt avec le contexte précédent si disponible et demandé
|
||
full_prompt = f"""
|
||
Rédigez une synthèse du fichier {file.name} dédiée au produit final '{produit_final}'.
|
||
Cette synthèse, destinée spécifiquement au Directeur des Risques, membre du COMEX d'une grande entreprise utilisant ce produit, doit être claire et concise (environ 10 lignes).
|
||
|
||
En utilisant impérativement la méthodologie fournie, expliquez en termes simples mais précis, pourquoi et comment les vulnérabilités identifiées constituent un risque concret pour l'entreprise. Mentionnez clairement :
|
||
|
||
- Les composants spécifiques du produit '{produit_final}' concernés par ces vulnérabilités.
|
||
- Les minerais précis responsables de ces vulnérabilités et leur rôle dans l’impact sur les composants.
|
||
- Les points critiques exacts identifiés dans la chaîne d'approvisionnement (par exemple : faible substituabilité, forte concentration géographique, instabilité géopolitique, concurrence élevée entre secteurs industriels).
|
||
- Identifier autant que faire se peut, les pays générant la forte concentration géographiques ou qui sont sujet à instabilité géopolitique, les secteurs en concurrence avec le numérique pour les minerais.
|
||
|
||
Respectez strictement les consignes suivantes :
|
||
|
||
- N'utilisez aucun acronyme ni valeur numérique ; uniquement leur équivalent textuel (ex : criticité de substituabilité, vulnérabilité élevée ou critique, etc.).
|
||
- N'incluez à ce stade aucune préconisation ni recommandation.
|
||
|
||
Votre texte doit être parfaitement adapté à une compréhension rapide par des dirigeants d’entreprise.
|
||
"""
|
||
|
||
|
||
# Définir les paramètres de la requête
|
||
system_message = f"""
|
||
Vous êtes un assistant stratégique expert chargé de rédiger des synthèses destinées à des décideurs de très haut niveau (Directeurs des Risques, membres du COMEX, stratèges industriels). Vous analysez exclusivement les vulnérabilités systémiques affectant les produits numériques, à partir des données précises fournies dans le fichier {file.name}.
|
||
|
||
Votre analyse doit être rigoureuse, accessible, pertinente pour la prise de décision stratégique, et conforme à la méthodologie définie ci-dessous :
|
||
|
||
{PROMPT_METHODOLOGIE}
|
||
|
||
/no_think
|
||
"""
|
||
|
||
reponse[produit_final] = f"\n**{produit_final}**\n\n" + generate_text(file, full_prompt, system_message).split("</think>")[-1].strip()
|
||
# print(reponse[produit_final])
|
||
|
||
corps = "\n\n".join(reponse.values())
|
||
print("Corps")
|
||
|
||
st.session_state["step"] = 2
|
||
|
||
full_prompt = corps + "\n\n" + PROMPT_METHODOLOGIE
|
||
|
||
system_message = """
|
||
Vous êtes un expert en rédaction de rapports stratégiques destinés à un COMEX ou une Direction des Risques.
|
||
|
||
Votre mission est d'écrire une introduction professionnelle, claire et synthétique (maximum 7 lignes) à partir des éléments suivants :
|
||
1. Un corps d’analyse décrivant les vulnérabilités identifiées pour un produit numérique.
|
||
2. La méthodologie détaillée utilisée pour cette analyse (fourni en deuxième partie).
|
||
|
||
Votre introduction doit :
|
||
- Présenter brièvement le sujet traité (vulnérabilités du produit final), quels sont les produits finaux et les minerais concernés.
|
||
- Annoncer clairement le contenu et l'objectif de l'analyse présentée dans le corps.
|
||
- Résumer succinctement les axes méthodologiques principaux (concentration géographique ou industrielle, stabilité géopolitique, criticité de substituabilité, concurrence intersectorielle des minerais).
|
||
- Être facilement compréhensible par des décideurs de haut niveau (pas d'acronymes, ni chiffres ; uniquement des formulations textuelles).
|
||
- Être fluide, agréable à lire, avec un ton sobre et professionnel.
|
||
|
||
Répondez uniquement avec l'introduction rédigée. Ne fournissez aucune autre explication complémentaire.
|
||
|
||
/no_think
|
||
"""
|
||
|
||
|
||
introduction = generate_text("", full_prompt, system_message).split("</think>")[-1].strip()
|
||
print("Introduction")
|
||
|
||
st.session_state["step"] = 3
|
||
|
||
full_prompt = corps + "\n\n" + PROMPT_METHODOLOGIE
|
||
|
||
system_message = """
|
||
Vous êtes un expert stratégique en gestion des risques liés à la chaîne de valeur numérique. Vous conseillez directement le COMEX et la Direction des Risques de grandes entreprises utilisatrices de produits numériques. Ces entreprises n'ont pour levier d’action que le choix de leurs fournisseurs ou l'allongement de la durée de vie de leur matériel.
|
||
|
||
À partir des vulnérabilités identifiées dans la première partie du prompt (corps d'analyse) et en tenant compte du contexte et de la méthodologie décrite en deuxième partie, rédigez un texte clair, structuré en deux parties distinctes :
|
||
|
||
1. **Préconisations stratégiques :**
|
||
Proposez clairement des axes concrets pour limiter les risques identifiés dans l’analyse. Ces préconisations doivent impérativement être réalistes et directement actionnables par les dirigeants compte tenu de leurs leviers limités.
|
||
|
||
2. **Indicateurs de suivi :**
|
||
Identifiez précisément les indicateurs pertinents à suivre pour évaluer régulièrement l’évolution de ces risques. Ces indicateurs doivent être inspirés directement des axes méthodologiques fournis (concentration géographique, stabilité géopolitique, substituabilité, concurrence intersectorielle) ou s’appuyer sur des bonnes pratiques reconnues.
|
||
|
||
Votre rédaction doit être fluide, concise, très professionnelle, et directement accessible à un COMEX. Évitez strictement toute explication complémentaire ou ajout superflu. Ne proposez que le texte demandé.
|
||
"""
|
||
|
||
preconisations = generate_text("", full_prompt, system_message, "0.5").split("</think>")[-1].strip()
|
||
print("Préconisations")
|
||
|
||
st.session_state["step"] = 4
|
||
|
||
full_prompt = corps + "\n\n" + preconisations
|
||
system_message = """
|
||
Vous êtes un expert stratégique spécialisé dans les risques liés à la chaîne de valeur du numérique. Vous conseillez directement le COMEX et la Direction des Risques de grandes entreprises dépendantes du numérique, dont les leviers d’action se limitent au choix des fournisseurs et à l’allongement de la durée d’utilisation du matériel.
|
||
|
||
À partir du résultat de l'analyse des vulnérabilités présenté en première partie du prompt (corps) et des préconisations stratégiques formulées en deuxième partie, rédigez une conclusion synthétique et percutante (environ 6 à 8 lignes maximum) afin de :
|
||
|
||
- Résumer clairement les principaux risques identifiés.
|
||
- Souligner brièvement les axes prioritaires proposés pour agir concrètement.
|
||
- Inviter de manière dynamique le COMEX à passer immédiatement à l'action.
|
||
|
||
Votre rédaction doit être fluide, professionnelle, claire et immédiatement exploitable par des dirigeants. Ne fournissez aucune explication supplémentaire. Ne répondez que par la conclusion demandée.
|
||
|
||
/no_think
|
||
"""
|
||
|
||
conclusion = generate_text("", full_prompt, system_message, "0.7").split("</think>")[-1].strip()
|
||
print("Conclusion")
|
||
|
||
st.session_state["step"] = 5
|
||
|
||
analyse = "# Rapport d'analyse\n\n" + \
|
||
"\n\n## Introduction\n\n" + \
|
||
introduction + \
|
||
"\n\n## Analyse des produits finaux\n\n" + \
|
||
corps + \
|
||
"\n\n## Préconisations\n\n" + \
|
||
preconisations + \
|
||
"\n\n## Conclusion\n\n" + \
|
||
conclusion + \
|
||
"\n\n## Méthodologie\n\n" + \
|
||
PROMPT_METHODOLOGIE
|
||
|
||
# fichier_a_reviser = Path(TEMPLATE_PATH.name.replace(".md", " - analyse à relire.md"))
|
||
# write_report(analyse, TEMP_SECTIONS / fichier_a_reviser)
|
||
# ingest_document(TEMP_SECTIONS / fichier_a_reviser)
|
||
|
||
full_prompt = """
|
||
Suivre scrupuleusement les consignes.
|
||
"""
|
||
|
||
system_message = f"""
|
||
Vous êtes un réviseur professionnel expert en écriture stratégique, maîtrisant parfaitement la langue française et habitué à réviser des textes destinés à des dirigeants de haut niveau (COMEX).
|
||
|
||
Votre tâche unique est d'améliorer strictement la qualité rédactionnelle du texte suivant, sans modifier en aucune manière :
|
||
- la structure existante (sections, titres, sous-titres),
|
||
- l'ordre des paragraphes et des idées,
|
||
- le sens précis du contenu original,
|
||
- sans ajouter aucune information nouvelle.
|
||
|
||
Votre révision doit impérativement respecter les points suivants :
|
||
- Éliminer toutes répétitions ou redondances et varier systématiquement les tournures entre les paragraphes.
|
||
- Rendre chaque phrase claire, directe et concise. Si une phrase est trop longue, scindez-la clairement en plusieurs phrases courtes.
|
||
- Structurer chaque paragraphe en 2 à 3 parties cohérentes, reliées entre elles par des termes logiques (coordination, implication, opposition, etc.) et séparées par des retours à la ligne.
|
||
- Remplacer systématiquement les acronymes par ces expressions précises :
|
||
- ICS → « capacité à substituer un minerai »
|
||
- IHH → « concentration géographique ou industrielle »
|
||
- ISG → « stabilité géopolitique »
|
||
- IVC → « concurrence intersectorielle pour les minerais »
|
||
|
||
Votre texte final doit être parfaitement fluide, agréable à lire, adapté à un COMEX, avec un ton professionnel et sobre.
|
||
|
||
**Important : Ne répondez strictement que par le texte révisé ci-dessous, sans aucun commentaire ou explication supplémentaire.**
|
||
|
||
Voici le texte à réviser précisément :
|
||
|
||
{analyse}
|
||
|
||
/no_think
|
||
"""
|
||
revision = generate_text("", full_prompt, system_message, "0.1", False).split("</think>")[-1].strip()
|
||
print("Relecture")
|
||
|
||
return revision
|
||
|
||
def supprimer_fichiers(session_uuid):
|
||
try:
|
||
delete_documents_by_criteria(session_uuid)
|
||
for temp_file in TEMP_SECTIONS.glob(f"*{session_uuid}*.md"):
|
||
temp_file.unlink()
|
||
return True
|
||
except:
|
||
return False
|
||
|
||
def generer_rapport_final(rapport, analyse, resultat):
|
||
try:
|
||
rapport = Path(rapport)
|
||
analyse = Path(analyse)
|
||
with zipfile.ZipFile(resultat, "w") as zipf:
|
||
zipf.write(rapport, arcname=rapport.name)
|
||
zipf.write(analyse, arcname=analyse.name)
|
||
return True
|
||
except Exception as e:
|
||
print(f"Erreur lors du zip : {e}")
|
||
return False
|