Spaces:
Sleeping
Sleeping
Upload 5 files
Browse filesRAG Mistral AI avec citation des Chunks de référence par document ayant impactés la réponse
- RAG_Mistral.py +62 -61
- chunked_docs.pkl +2 -2
- embeddings.npy +3 -0
- faiss_index.bin +2 -2
- metadata.pkl +3 -0
RAG_Mistral.py
CHANGED
@@ -1,23 +1,10 @@
|
|
1 |
-
# -*- coding: utf-8 -*-
|
2 |
-
"""
|
3 |
-
Created on Mon Feb 24 15:51:34 2025
|
4 |
-
|
5 |
-
@author: MIPO10053340
|
6 |
-
|
7 |
-
C:/Users/MIPO10053340/OneDrive - Groupe Avril/Bureau/Salon_Agriculture_2024/Micka_API_Call/Docs_pdf/Docs_pdf/
|
8 |
-
|
9 |
-
"""
|
10 |
-
|
11 |
-
# -*- coding: utf-8 -*-
|
12 |
-
"""
|
13 |
-
Optimisation du RAG avec MistralAI - Embeddings en batch
|
14 |
-
"""
|
15 |
import os
|
16 |
import numpy as np
|
17 |
import fitz # PyMuPDF pour extraction PDF
|
18 |
import faiss
|
19 |
import pickle
|
20 |
import matplotlib.pyplot as plt
|
|
|
21 |
from mistralai import Mistral
|
22 |
from sklearn.manifold import TSNE
|
23 |
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
|
@@ -32,7 +19,6 @@ client = Mistral(api_key=MISTRAL_API_KEY)
|
|
32 |
model_embedding = "mistral-embed"
|
33 |
model_chat = "ministral-8b-latest"
|
34 |
temperature = 0.1 # Réduction de la température pour privilégier la RAG
|
35 |
-
probability = 0.9 # Ajustement de la probabilité pour plus de contrôle
|
36 |
|
37 |
# 📌 Paramètres de segmentation
|
38 |
chunk_size = 256 # Réduction du chunk size pour un meilleur contrôle du contexte
|
@@ -41,96 +27,111 @@ chunk_overlap = 15
|
|
41 |
# 📌 Définition des chemins de stockage
|
42 |
index_path = "faiss_index.bin"
|
43 |
chunks_path = "chunked_docs.pkl"
|
|
|
|
|
44 |
|
45 |
# 📌 Vérification et chargement des données
|
46 |
-
if os.path.exists(index_path) and os.path.exists(chunks_path):
|
47 |
print("🔄 Chargement des données existantes...")
|
48 |
-
index = faiss.read_index(index_path)
|
49 |
with open(chunks_path, "rb") as f:
|
50 |
-
chunked_docs = pickle.load(f)
|
51 |
-
|
|
|
|
|
|
|
52 |
else:
|
53 |
print("⚡ Création et stockage d'un nouvel index FAISS...")
|
54 |
|
55 |
-
# 📌 Extraction
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
pdf_folder = 'C:/Users/MIPO10053340/OneDrive - Groupe Avril/Bureau/Salon_Agriculture_2024/Micka_API_Call/Docs_pdf/'
|
57 |
-
chunked_docs =
|
58 |
-
chunked_docs = [doc.text for doc in chunked_docs]
|
59 |
|
60 |
-
# 📌 Génération des embeddings
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
batch
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
)
|
69 |
-
|
70 |
-
|
71 |
-
|
|
|
|
|
|
|
|
|
72 |
|
73 |
-
# 📌 Vérification avant d’indexer dans FAISS
|
74 |
-
if embeddings is None or len(embeddings) == 0:
|
75 |
-
raise ValueError("⚠️ ERREUR : Aucun embedding généré ! Vérifie l'étape de génération des embeddings.")
|
76 |
-
|
77 |
# 📌 Création et stockage de l'index FAISS
|
78 |
dimension = embeddings.shape[1]
|
79 |
index = faiss.IndexFlatL2(dimension)
|
80 |
index.add(embeddings)
|
81 |
-
faiss.write_index(index, index_path)
|
82 |
|
83 |
-
# 📌 Sauvegarde des
|
84 |
with open(chunks_path, "wb") as f:
|
85 |
pickle.dump(chunked_docs, f)
|
86 |
-
|
|
|
|
|
|
|
87 |
|
88 |
# 📌 Récupération des chunks les plus pertinents
|
89 |
def retrieve_relevant_chunks(question, k=5):
|
90 |
-
"""Recherche les chunks les plus pertinents en fonction de la similarité des embeddings."""
|
91 |
question_embedding_response = client.embeddings.create(
|
92 |
model=model_embedding,
|
93 |
inputs=[question],
|
94 |
)
|
95 |
question_embedding = np.array(question_embedding_response.data[0].embedding).astype('float32').reshape(1, -1)
|
96 |
-
|
97 |
-
# Vérification de la compatibilité des dimensions
|
98 |
-
dimension = index.d
|
99 |
-
if question_embedding.shape[1] != dimension:
|
100 |
-
raise ValueError(f"⚠️ ERREUR : La dimension de l'embedding de la question ({question_embedding.shape[1]}) ne correspond pas aux embeddings indexés ({dimension}).")
|
101 |
-
|
102 |
distances, indices = index.search(question_embedding, k)
|
103 |
-
|
104 |
if len(indices[0]) == 0:
|
105 |
print("⚠️ Avertissement : Aucun chunk pertinent trouvé, réponse possible moins précise.")
|
106 |
-
return []
|
107 |
-
|
108 |
return [chunked_docs[i] for i in indices[0]]
|
109 |
|
110 |
# 📌 Génération de réponse avec MistralAI
|
111 |
-
def generate_response(context, question):
|
112 |
-
""
|
|
|
113 |
messages = [
|
114 |
-
{"role": "system", "content": f"Voici
|
115 |
{"role": "user", "content": question}
|
116 |
]
|
117 |
-
|
118 |
response = client.chat.complete(model=model_chat, messages=messages, temperature=temperature)
|
119 |
-
return response.choices[0].message.content
|
120 |
|
121 |
# 📌 Exécuter une requête utilisateur
|
122 |
-
user_question = "
|
123 |
relevant_chunks = retrieve_relevant_chunks(user_question)
|
124 |
-
context = "\n".join(relevant_chunks)
|
125 |
-
answer = generate_response(context, user_question)
|
126 |
|
127 |
-
# 📊 Affichage de la réponse
|
128 |
print("\n🔹 Réponse Mistral :")
|
129 |
print(answer)
|
|
|
|
|
130 |
|
131 |
# 💾 Sauvegarde des résultats
|
132 |
with open("mistral_response_types.txt", "w", encoding="utf-8") as f:
|
133 |
f.write(f"Question : {user_question}\n")
|
134 |
f.write(f"Réponse :\n{answer}\n")
|
|
|
135 |
|
136 |
-
print("\n✅ Réponse enregistrée dans 'mistral_response_types.txt'")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import os
|
2 |
import numpy as np
|
3 |
import fitz # PyMuPDF pour extraction PDF
|
4 |
import faiss
|
5 |
import pickle
|
6 |
import matplotlib.pyplot as plt
|
7 |
+
from concurrent.futures import ThreadPoolExecutor
|
8 |
from mistralai import Mistral
|
9 |
from sklearn.manifold import TSNE
|
10 |
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
|
|
|
19 |
model_embedding = "mistral-embed"
|
20 |
model_chat = "ministral-8b-latest"
|
21 |
temperature = 0.1 # Réduction de la température pour privilégier la RAG
|
|
|
22 |
|
23 |
# 📌 Paramètres de segmentation
|
24 |
chunk_size = 256 # Réduction du chunk size pour un meilleur contrôle du contexte
|
|
|
27 |
# 📌 Définition des chemins de stockage
|
28 |
index_path = "faiss_index.bin"
|
29 |
chunks_path = "chunked_docs.pkl"
|
30 |
+
metadata_path = "metadata.pkl"
|
31 |
+
embeddings_path = "embeddings.npy"
|
32 |
|
33 |
# 📌 Vérification et chargement des données
|
34 |
+
if os.path.exists(index_path) and os.path.exists(chunks_path) and os.path.exists(metadata_path) and os.path.exists(embeddings_path):
|
35 |
print("🔄 Chargement des données existantes...")
|
36 |
+
index = faiss.read_index(index_path)
|
37 |
with open(chunks_path, "rb") as f:
|
38 |
+
chunked_docs = pickle.load(f)
|
39 |
+
with open(metadata_path, "rb") as f:
|
40 |
+
metadata_list = pickle.load(f)
|
41 |
+
embeddings = np.load(embeddings_path)
|
42 |
+
print("✅ Index, chunks, embeddings et métadonnées chargés avec succès !")
|
43 |
else:
|
44 |
print("⚡ Création et stockage d'un nouvel index FAISS...")
|
45 |
|
46 |
+
# 📌 Extraction des documents et métadonnées
|
47 |
+
def extract_and_chunk_pdfs(pdf_folder):
|
48 |
+
documents = SimpleDirectoryReader(pdf_folder, recursive=True).load_data()
|
49 |
+
chunked_docs, metadata_list = [], []
|
50 |
+
for doc in documents:
|
51 |
+
doc_text = doc.text
|
52 |
+
file_name = doc.metadata.get("file_name", "Inconnu")
|
53 |
+
title = doc.metadata.get("title") or os.path.splitext(file_name)[0] # Utilisation du nom de fichier comme fallback
|
54 |
+
doc_metadata = {"source": file_name, "title": title}
|
55 |
+
for i in range(0, len(doc_text), chunk_size):
|
56 |
+
chunk = doc_text[i:i + chunk_size]
|
57 |
+
chunked_docs.append({"text": chunk, "metadata": doc_metadata})
|
58 |
+
metadata_list.append(doc_metadata)
|
59 |
+
return chunked_docs, metadata_list
|
60 |
+
|
61 |
pdf_folder = 'C:/Users/MIPO10053340/OneDrive - Groupe Avril/Bureau/Salon_Agriculture_2024/Micka_API_Call/Docs_pdf/'
|
62 |
+
chunked_docs, metadata_list = extract_and_chunk_pdfs(pdf_folder)
|
|
|
63 |
|
64 |
+
# 📌 Génération des embeddings en parallèle
|
65 |
+
def get_embeddings_in_batches(text_chunks, batch_size=5):
|
66 |
+
embeddings = []
|
67 |
+
|
68 |
+
def process_batch(batch):
|
69 |
+
response = client.embeddings.create(model=model_embedding, inputs=[chunk["text"] for chunk in batch])
|
70 |
+
return [data.embedding for data in response.data]
|
71 |
+
|
72 |
+
with ThreadPoolExecutor(max_workers=5) as executor:
|
73 |
+
future_batches = [executor.submit(process_batch, text_chunks[i:i+batch_size]) for i in range(0, len(text_chunks), batch_size)]
|
74 |
+
for future in future_batches:
|
75 |
+
embeddings.extend(future.result())
|
76 |
+
|
77 |
+
return np.array(embeddings).astype('float32')
|
78 |
+
|
79 |
+
embeddings = get_embeddings_in_batches(chunked_docs)
|
80 |
|
|
|
|
|
|
|
|
|
81 |
# 📌 Création et stockage de l'index FAISS
|
82 |
dimension = embeddings.shape[1]
|
83 |
index = faiss.IndexFlatL2(dimension)
|
84 |
index.add(embeddings)
|
85 |
+
faiss.write_index(index, index_path)
|
86 |
|
87 |
+
# 📌 Sauvegarde des données
|
88 |
with open(chunks_path, "wb") as f:
|
89 |
pickle.dump(chunked_docs, f)
|
90 |
+
with open(metadata_path, "wb") as f:
|
91 |
+
pickle.dump(metadata_list, f)
|
92 |
+
np.save(embeddings_path, embeddings) # Sauvegarde des embeddings
|
93 |
+
print("✅ Index, chunks, embeddings et métadonnées sauvegardés !")
|
94 |
|
95 |
# 📌 Récupération des chunks les plus pertinents
|
96 |
def retrieve_relevant_chunks(question, k=5):
|
|
|
97 |
question_embedding_response = client.embeddings.create(
|
98 |
model=model_embedding,
|
99 |
inputs=[question],
|
100 |
)
|
101 |
question_embedding = np.array(question_embedding_response.data[0].embedding).astype('float32').reshape(1, -1)
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
distances, indices = index.search(question_embedding, k)
|
|
|
103 |
if len(indices[0]) == 0:
|
104 |
print("⚠️ Avertissement : Aucun chunk pertinent trouvé, réponse possible moins précise.")
|
105 |
+
return [], []
|
|
|
106 |
return [chunked_docs[i] for i in indices[0]]
|
107 |
|
108 |
# 📌 Génération de réponse avec MistralAI
|
109 |
+
def generate_response(context, question, sources):
|
110 |
+
chunk_references = [f"[{i+1}]" for i in range(len(sources))]
|
111 |
+
chunk_texts = "\n\n".join([f"{chunk_references[i]} (Source: {src['metadata']['source']}) :\n{src['text']}" for i, src in enumerate(sources)])
|
112 |
messages = [
|
113 |
+
{"role": "system", "content": f"Voici les informations extraites des documents :\n{chunk_texts}\n\nUtilise ces informations pour répondre."},
|
114 |
{"role": "user", "content": question}
|
115 |
]
|
|
|
116 |
response = client.chat.complete(model=model_chat, messages=messages, temperature=temperature)
|
117 |
+
return response.choices[0].message.content + " " + "".join(chunk_references), chunk_texts
|
118 |
|
119 |
# 📌 Exécuter une requête utilisateur
|
120 |
+
user_question = "Quels sont les besoins en protéines des poulets de chair en phase de croissance ?"
|
121 |
relevant_chunks = retrieve_relevant_chunks(user_question)
|
122 |
+
context = "\n".join([chunk["text"] for chunk in relevant_chunks])
|
123 |
+
answer, citations = generate_response(context, user_question, relevant_chunks)
|
124 |
|
125 |
+
# 📊 Affichage de la réponse avec sources
|
126 |
print("\n🔹 Réponse Mistral :")
|
127 |
print(answer)
|
128 |
+
print("\n📌 **Chunks utilisés :**")
|
129 |
+
print(citations)
|
130 |
|
131 |
# 💾 Sauvegarde des résultats
|
132 |
with open("mistral_response_types.txt", "w", encoding="utf-8") as f:
|
133 |
f.write(f"Question : {user_question}\n")
|
134 |
f.write(f"Réponse :\n{answer}\n")
|
135 |
+
f.write(f"{citations}\n")
|
136 |
|
137 |
+
print("\n✅ Réponse enregistrée avec les chunks exacts et références dans 'mistral_response_types.txt'")
|
chunked_docs.pkl
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
-
size
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:2ddc15183eeb50de92b6dc9c84a4193e5072c4361d33ffefc71ffe0791dcfcac
|
3 |
+
size 12882142
|
embeddings.npy
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a7a02b23e8cbad7b58f6e5d5686b9d23359c1b4ed64241c04cf31ba976aa83eb
|
3 |
+
size 193323136
|
faiss_index.bin
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
-
size
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:d1efc2f126ee5ddb4e851bcf0d56c7ea22ad109b88fbe3ce4c095737649e753d
|
3 |
+
size 193323053
|
metadata.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:467ef98889c4aea1c16e8e967e49b5a6ca13f8f37db2628fc3d0a2e9bd6e656a
|
3 |
+
size 463274
|