Spaces:
Sleeping
Sleeping
Commit
·
516b795
1
Parent(s):
8758044
fixing issues-2
Browse files- .gitignore +27 -2
- app.py +124 -104
- prepare_embedding.py +16 -0
.gitignore
CHANGED
|
@@ -1,2 +1,27 @@
|
|
| 1 |
-
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Fichiers lourds
|
| 2 |
+
RAG_IPCC/
|
| 3 |
+
chroma_db/
|
| 4 |
+
*.pdf
|
| 5 |
+
|
| 6 |
+
# Python
|
| 7 |
+
__pycache__/
|
| 8 |
+
*.pyc
|
| 9 |
+
*.pyo
|
| 10 |
+
*.pyd
|
| 11 |
+
.Python
|
| 12 |
+
env/
|
| 13 |
+
venv/
|
| 14 |
+
*.egg-info/
|
| 15 |
+
|
| 16 |
+
# NLTK data
|
| 17 |
+
nltk_data/
|
| 18 |
+
|
| 19 |
+
# IDE
|
| 20 |
+
.vscode/
|
| 21 |
+
.idea/
|
| 22 |
+
*.swp
|
| 23 |
+
*.swo
|
| 24 |
+
|
| 25 |
+
# OS
|
| 26 |
+
.DS_Store
|
| 27 |
+
Thumbs.db
|
app.py
CHANGED
|
@@ -26,65 +26,11 @@ from langchain_community.embeddings.sentence_transformer import SentenceTransfor
|
|
| 26 |
from huggingface_hub import InferenceClient
|
| 27 |
import gradio as gr
|
| 28 |
|
| 29 |
-
"""# GESTION DE LA BASE DE DONNÉES
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
# Chemin du dossier où l'on souhaite télécharger les fichiers
|
| 35 |
-
chemin_dossier = "./RAG_IPCC"
|
| 36 |
-
if not os.path.exists(chemin_dossier):
|
| 37 |
-
os.makedirs(chemin_dossier)
|
| 38 |
-
|
| 39 |
-
# URLs des fichiers à télécharger
|
| 40 |
-
urls = { "6th_report": "https://www.ipcc.ch/report/ar6/syr/downloads/report/IPCC_AR6_SYR_FullVolume.pdf" }
|
| 41 |
-
|
| 42 |
-
# Télécharger les fichiers dans le dossier (seulement s'ils n'existent pas déjà)
|
| 43 |
-
for name, url in urls.items():
|
| 44 |
-
file_path = os.path.join(chemin_dossier, f"{name}.pdf")
|
| 45 |
-
if not os.path.exists(file_path):
|
| 46 |
-
print(f"Téléchargement de {name}...")
|
| 47 |
-
response = requests.get(url)
|
| 48 |
-
with open(file_path, 'wb') as file:
|
| 49 |
-
file.write(response.content)
|
| 50 |
-
print(f"{name} a été téléchargé.")
|
| 51 |
-
else:
|
| 52 |
-
print(f"{name} existe déjà, téléchargement ignoré.")
|
| 53 |
-
|
| 54 |
-
"""## Etape 2 : Extraction du texte des fichiers PDF"""
|
| 55 |
-
|
| 56 |
-
# Chemin du dossier contenant les fichiers PDF
|
| 57 |
-
chemin_dossier = "./RAG_IPCC"
|
| 58 |
-
|
| 59 |
-
# Liste des fichiers PDF dans le dossier
|
| 60 |
-
fichiers_pdf = [f for f in os.listdir(chemin_dossier) if f.endswith('.pdf')]
|
| 61 |
-
|
| 62 |
-
# Liste pour stocker le texte extrait de chaque PDF
|
| 63 |
-
extracted_text = []
|
| 64 |
-
|
| 65 |
-
# Boucle à travers chaque fichier PDF
|
| 66 |
-
for pdf in fichiers_pdf:
|
| 67 |
-
print(f"*** PROCESSING FILE : {pdf} ***")
|
| 68 |
-
|
| 69 |
-
# Chemin complet du fichier PDF
|
| 70 |
-
chemin_pdf = os.path.join(chemin_dossier, pdf)
|
| 71 |
-
|
| 72 |
-
# Ouverture du fichier PDF en mode lecture binaire
|
| 73 |
-
with open(chemin_pdf, 'rb') as file:
|
| 74 |
-
# Création d'un objet de lecteur PDF
|
| 75 |
-
pdf_reader = PyPDF2.PdfReader(file)
|
| 76 |
-
|
| 77 |
-
# Boucle à travers chaque page du PDF
|
| 78 |
-
for page_num in range(len(pdf_reader.pages)):
|
| 79 |
-
# Extraction du texte de la page actuelle
|
| 80 |
-
page = pdf_reader.pages[page_num]
|
| 81 |
-
text = page.extract_text()
|
| 82 |
-
|
| 83 |
-
# Ajout du texte extrait à la liste
|
| 84 |
-
extracted_text.append({"document": pdf, "page": page_num, "content": text})
|
| 85 |
-
|
| 86 |
-
# Affichage du texte extrait
|
| 87 |
-
print(f"Extracted {len(extracted_text)} pages from PDFs")
|
| 88 |
|
| 89 |
"""## Etape 3 : Traitement du texte en chunks propres"""
|
| 90 |
|
|
@@ -186,29 +132,6 @@ def contains_mainly_digits(text, threshold=0.5):
|
|
| 186 |
def remove_mostly_digits_chunks(chunks, threshold=0.5):
|
| 187 |
return [chunk for chunk in chunks if not contains_mainly_digits(chunk['content'])]
|
| 188 |
|
| 189 |
-
#### EXECUTION ####
|
| 190 |
-
|
| 191 |
-
# Split intelligent avec différents paramètres
|
| 192 |
-
text_splitter = RecursiveCharacterTextSplitter(
|
| 193 |
-
chunk_size=500,
|
| 194 |
-
chunk_overlap=20,
|
| 195 |
-
length_function=len,
|
| 196 |
-
is_separator_regex=False,
|
| 197 |
-
)
|
| 198 |
-
|
| 199 |
-
# Split pertinent qui garde la structure du document
|
| 200 |
-
chunks = []
|
| 201 |
-
for page_content in extracted_text:
|
| 202 |
-
chunks_list = text_splitter.split_text(page_content['content'])
|
| 203 |
-
|
| 204 |
-
for chunk in chunks_list:
|
| 205 |
-
text=clean_text(chunk)
|
| 206 |
-
chunks.append({"document": page_content['document'],
|
| 207 |
-
"page": page_content['page'],
|
| 208 |
-
"content": text})
|
| 209 |
-
chunks=remove_mostly_digits_chunks(chunks)
|
| 210 |
-
print(f"Created {len(chunks)} chunks after processing")
|
| 211 |
-
|
| 212 |
"""# IMPLEMENTATION DU MODELE DE RECHERCHE RETENU"""
|
| 213 |
|
| 214 |
class TextRetriever:
|
|
@@ -296,21 +219,102 @@ class TextRetriever:
|
|
| 296 |
best_chunks = self.get_best_chunks(query, top_k=1)
|
| 297 |
return best_chunks[0].page_content
|
| 298 |
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
|
|
|
|
|
|
| 305 |
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
print("Loading existing embeddings database...")
|
| 310 |
-
retriever.load_embeddings(db_path)
|
| 311 |
-
else:
|
| 312 |
-
print("Creating new embeddings database...")
|
| 313 |
-
retriever.store_embeddings(all_chunks, db_path)
|
| 314 |
|
| 315 |
"""# MODELE LLM
|
| 316 |
|
|
@@ -318,8 +322,6 @@ else:
|
|
| 318 |
"""
|
| 319 |
|
| 320 |
# Initialiser le client d'inférence HuggingFace (modèle gratuit et léger)
|
| 321 |
-
# Utilisation de Mistral-7B-Instruct via l'API gratuite au lieu de le télécharger
|
| 322 |
-
print("Initializing HuggingFace Inference Client...")
|
| 323 |
llm_client = InferenceClient(model="mistralai/Mistral-7B-Instruct-v0.2")
|
| 324 |
|
| 325 |
## FONCTIONS
|
|
@@ -406,30 +408,48 @@ ch = ConversationHistoryLoader(k=3)
|
|
| 406 |
|
| 407 |
# Fonction principale pour répondre aux questions
|
| 408 |
def get_response(query):
|
|
|
|
|
|
|
| 409 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
# Obtenir le contexte pertinent
|
| 411 |
context = get_context_from_query(query)
|
| 412 |
-
|
| 413 |
# Générer la réponse avec contexte et historique
|
| 414 |
chat_history = ch.create_conversation_history_prompt()
|
| 415 |
response = generate_response_with_context(query, context, chat_history)
|
| 416 |
-
|
| 417 |
# Mettre à jour l historique
|
| 418 |
ch.update_conversation_history(query, response)
|
| 419 |
-
|
| 420 |
return response
|
| 421 |
-
|
| 422 |
except Exception as e:
|
|
|
|
|
|
|
|
|
|
| 423 |
return f"Erreur: {str(e)}"
|
| 424 |
|
| 425 |
# Interface Gradio
|
| 426 |
print("Creating Gradio interface...")
|
| 427 |
iface = gr.Interface(
|
| 428 |
-
fn=get_response,
|
| 429 |
-
inputs=gr.Textbox(lines=2, placeholder="Posez votre question sur le climat..."),
|
| 430 |
outputs=gr.Textbox(lines=5, label="Réponse"),
|
| 431 |
-
title="🌍 RAG Chatbot - Questions Climatiques",
|
| 432 |
-
description="Posez vos questions sur le changement climatique basées sur les rapports IPCC.
|
|
|
|
|
|
|
|
|
|
| 433 |
examples=[
|
| 434 |
"Quels sont les principaux impacts du réchauffement climatique ?",
|
| 435 |
"Comment les océans sont-ils affectés par le changement climatique ?",
|
|
|
|
| 26 |
from huggingface_hub import InferenceClient
|
| 27 |
import gradio as gr
|
| 28 |
|
| 29 |
+
"""# GESTION DE LA BASE DE DONNÉES - VARIABLES GLOBALES"""
|
| 30 |
|
| 31 |
+
# Variables globales pour lazy loading
|
| 32 |
+
retriever = None
|
| 33 |
+
is_initialized = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
"""## Etape 3 : Traitement du texte en chunks propres"""
|
| 36 |
|
|
|
|
| 132 |
def remove_mostly_digits_chunks(chunks, threshold=0.5):
|
| 133 |
return [chunk for chunk in chunks if not contains_mainly_digits(chunk['content'])]
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
"""# IMPLEMENTATION DU MODELE DE RECHERCHE RETENU"""
|
| 136 |
|
| 137 |
class TextRetriever:
|
|
|
|
| 219 |
best_chunks = self.get_best_chunks(query, top_k=1)
|
| 220 |
return best_chunks[0].page_content
|
| 221 |
|
| 222 |
+
"""# FONCTION D'INITIALISATION LAZY"""
|
| 223 |
+
|
| 224 |
+
def initialize_system():
|
| 225 |
+
"""
|
| 226 |
+
Initialise le système RAG de manière lazy (seulement au premier appel).
|
| 227 |
+
Télécharge les PDFs, extrait le texte, crée les chunks et les embeddings.
|
| 228 |
+
"""
|
| 229 |
+
global retriever, is_initialized
|
| 230 |
+
|
| 231 |
+
if is_initialized:
|
| 232 |
+
return "Système déjà initialisé"
|
| 233 |
+
|
| 234 |
+
try:
|
| 235 |
+
print("=" * 50)
|
| 236 |
+
print("INITIALISATION DU SYSTÈME RAG")
|
| 237 |
+
print("=" * 50)
|
| 238 |
+
|
| 239 |
+
# Etape 1: Téléchargement des PDFs
|
| 240 |
+
chemin_dossier = "./RAG_IPCC"
|
| 241 |
+
if not os.path.exists(chemin_dossier):
|
| 242 |
+
os.makedirs(chemin_dossier)
|
| 243 |
+
|
| 244 |
+
urls = { "6th_report": "https://www.ipcc.ch/report/ar6/syr/downloads/report/IPCC_AR6_SYR_FullVolume.pdf" }
|
| 245 |
+
|
| 246 |
+
for name, url in urls.items():
|
| 247 |
+
file_path = os.path.join(chemin_dossier, f"{name}.pdf")
|
| 248 |
+
if not os.path.exists(file_path):
|
| 249 |
+
print(f"📥 Téléchargement de {name}...")
|
| 250 |
+
response = requests.get(url)
|
| 251 |
+
with open(file_path, 'wb') as file:
|
| 252 |
+
file.write(response.content)
|
| 253 |
+
print(f"✅ {name} téléchargé")
|
| 254 |
+
else:
|
| 255 |
+
print(f"✅ {name} existe déjà")
|
| 256 |
+
|
| 257 |
+
# Etape 2: Extraction du texte
|
| 258 |
+
print("\n📄 Extraction du texte des PDFs...")
|
| 259 |
+
fichiers_pdf = [f for f in os.listdir(chemin_dossier) if f.endswith('.pdf')]
|
| 260 |
+
extracted_text = []
|
| 261 |
+
|
| 262 |
+
for pdf in fichiers_pdf:
|
| 263 |
+
chemin_pdf = os.path.join(chemin_dossier, pdf)
|
| 264 |
+
with open(chemin_pdf, 'rb') as file:
|
| 265 |
+
pdf_reader = PyPDF2.PdfReader(file)
|
| 266 |
+
for page_num in range(len(pdf_reader.pages)):
|
| 267 |
+
page = pdf_reader.pages[page_num]
|
| 268 |
+
text = page.extract_text()
|
| 269 |
+
extracted_text.append({"document": pdf, "page": page_num, "content": text})
|
| 270 |
+
|
| 271 |
+
print(f"✅ {len(extracted_text)} pages extraites")
|
| 272 |
+
|
| 273 |
+
# Etape 3: Création des chunks
|
| 274 |
+
print("\n✂️ Création des chunks de texte...")
|
| 275 |
+
text_splitter = RecursiveCharacterTextSplitter(
|
| 276 |
+
chunk_size=500,
|
| 277 |
+
chunk_overlap=20,
|
| 278 |
+
length_function=len,
|
| 279 |
+
is_separator_regex=False,
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
chunks = []
|
| 283 |
+
for page_content in extracted_text:
|
| 284 |
+
chunks_list = text_splitter.split_text(page_content['content'])
|
| 285 |
+
for chunk in chunks_list:
|
| 286 |
+
text = clean_text(chunk)
|
| 287 |
+
chunks.append({"document": page_content['document'],
|
| 288 |
+
"page": page_content['page'],
|
| 289 |
+
"content": text})
|
| 290 |
+
|
| 291 |
+
chunks = remove_mostly_digits_chunks(chunks)
|
| 292 |
+
print(f"✅ {len(chunks)} chunks créés")
|
| 293 |
+
|
| 294 |
+
# Etape 4: Initialisation du retriever et des embeddings
|
| 295 |
+
print("\n🤖 Initialisation du TextRetriever...")
|
| 296 |
+
retriever = TextRetriever()
|
| 297 |
+
|
| 298 |
+
all_chunks = [chunk['content'] for chunk in chunks]
|
| 299 |
+
|
| 300 |
+
# Vérifier si la base de données existe déjà
|
| 301 |
+
db_path = "./chroma_db"
|
| 302 |
+
if os.path.exists(db_path):
|
| 303 |
+
print("📂 Chargement de la base de données existante...")
|
| 304 |
+
retriever.load_embeddings(db_path)
|
| 305 |
+
else:
|
| 306 |
+
print("🔨 Création de la base de données d'embeddings...")
|
| 307 |
+
retriever.store_embeddings(all_chunks, db_path)
|
| 308 |
|
| 309 |
+
is_initialized = True
|
| 310 |
+
print("\n" + "=" * 50)
|
| 311 |
+
print("✅ SYSTÈME INITIALISÉ AVEC SUCCÈS")
|
| 312 |
+
print("=" * 50)
|
| 313 |
+
return "✅ Système initialisé avec succès !"
|
| 314 |
|
| 315 |
+
except Exception as e:
|
| 316 |
+
print(f"❌ Erreur lors de l'initialisation: {str(e)}")
|
| 317 |
+
return f"❌ Erreur: {str(e)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
|
| 319 |
"""# MODELE LLM
|
| 320 |
|
|
|
|
| 322 |
"""
|
| 323 |
|
| 324 |
# Initialiser le client d'inférence HuggingFace (modèle gratuit et léger)
|
|
|
|
|
|
|
| 325 |
llm_client = InferenceClient(model="mistralai/Mistral-7B-Instruct-v0.2")
|
| 326 |
|
| 327 |
## FONCTIONS
|
|
|
|
| 408 |
|
| 409 |
# Fonction principale pour répondre aux questions
|
| 410 |
def get_response(query):
|
| 411 |
+
global retriever, is_initialized
|
| 412 |
+
|
| 413 |
try:
|
| 414 |
+
# Initialiser le système au premier appel
|
| 415 |
+
if not is_initialized:
|
| 416 |
+
init_message = initialize_system()
|
| 417 |
+
if "❌" in init_message:
|
| 418 |
+
return init_message
|
| 419 |
+
|
| 420 |
+
# Vérifier que le retriever est bien initialisé
|
| 421 |
+
if retriever is None:
|
| 422 |
+
return "❌ Le système n'est pas correctement initialisé. Veuillez réessayer."
|
| 423 |
+
|
| 424 |
# Obtenir le contexte pertinent
|
| 425 |
context = get_context_from_query(query)
|
| 426 |
+
|
| 427 |
# Générer la réponse avec contexte et historique
|
| 428 |
chat_history = ch.create_conversation_history_prompt()
|
| 429 |
response = generate_response_with_context(query, context, chat_history)
|
| 430 |
+
|
| 431 |
# Mettre à jour l historique
|
| 432 |
ch.update_conversation_history(query, response)
|
| 433 |
+
|
| 434 |
return response
|
| 435 |
+
|
| 436 |
except Exception as e:
|
| 437 |
+
import traceback
|
| 438 |
+
error_details = traceback.format_exc()
|
| 439 |
+
print(f"Erreur détaillée: {error_details}")
|
| 440 |
return f"Erreur: {str(e)}"
|
| 441 |
|
| 442 |
# Interface Gradio
|
| 443 |
print("Creating Gradio interface...")
|
| 444 |
iface = gr.Interface(
|
| 445 |
+
fn=get_response,
|
| 446 |
+
inputs=gr.Textbox(lines=2, placeholder="Posez votre question sur le climat..."),
|
| 447 |
outputs=gr.Textbox(lines=5, label="Réponse"),
|
| 448 |
+
title="🌍 RAG Chatbot - Questions Climatiques",
|
| 449 |
+
description="""Posez vos questions sur le changement climatique basées sur les rapports IPCC.
|
| 450 |
+
|
| 451 |
+
⚠️ **Note**: Le système s'initialise automatiquement au premier appel (téléchargement du PDF + création des embeddings).
|
| 452 |
+
La première requête peut prendre 2-3 minutes. Les requêtes suivantes seront rapides !""",
|
| 453 |
examples=[
|
| 454 |
"Quels sont les principaux impacts du réchauffement climatique ?",
|
| 455 |
"Comment les océans sont-ils affectés par le changement climatique ?",
|
prepare_embedding.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# prepare_embeddings.py
|
| 2 |
+
from sentence_transformers import SentenceTransformer
|
| 3 |
+
from langchain_chroma import Chroma
|
| 4 |
+
from langchain_community.embeddings import SentenceTransformerEmbeddings
|
| 5 |
+
import pickle
|
| 6 |
+
|
| 7 |
+
# Charger tous les chunks (copier le code de traitement des PDFs)
|
| 8 |
+
# ... [ton code de traitement des PDFs] ...
|
| 9 |
+
|
| 10 |
+
# Créer les embeddings
|
| 11 |
+
embedding_model = SentenceTransformerEmbeddings(model_name="mixedbread-ai/mxbai-embed-large-v1")
|
| 12 |
+
all_chunks = [chunk['content'] for chunk in chunks]
|
| 13 |
+
|
| 14 |
+
# Sauvegarder
|
| 15 |
+
db = Chroma.from_texts(all_chunks, embedding=embedding_model, persist_directory="./chroma_db")
|
| 16 |
+
print("✅ Embeddings saved to ./chroma_db")
|