LostPikachu commited on
Commit
dfe94ac
·
verified ·
1 Parent(s): ef69074

Upload 5 files

Browse files

RAG Mistral AI avec citation des Chunks de référence par document ayant impactés la réponse

Files changed (5) hide show
  1. RAG_Mistral.py +62 -61
  2. chunked_docs.pkl +2 -2
  3. embeddings.npy +3 -0
  4. faiss_index.bin +2 -2
  5. 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) # Charger l'index FAISS
49
  with open(chunks_path, "rb") as f:
50
- chunked_docs = pickle.load(f) # Charger les chunks de texte
51
- print(" Index et chunks chargés avec succès !")
 
 
 
52
  else:
53
  print("⚡ Création et stockage d'un nouvel index FAISS...")
54
 
55
- # 📌 Extraction et segmentation des PDF
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  pdf_folder = 'C:/Users/MIPO10053340/OneDrive - Groupe Avril/Bureau/Salon_Agriculture_2024/Micka_API_Call/Docs_pdf/'
57
- chunked_docs = SimpleDirectoryReader(pdf_folder).load_data()
58
- chunked_docs = [doc.text for doc in chunked_docs]
59
 
60
- # 📌 Génération des embeddings
61
- embeddings = []
62
- batch_size = 5
63
- for i in range(0, len(chunked_docs), batch_size):
64
- batch = chunked_docs[i:i + batch_size]
65
- embeddings_batch_response = client.embeddings.create(
66
- model=model_embedding,
67
- inputs=batch,
68
- )
69
- batch_embeddings = [data.embedding for data in embeddings_batch_response.data]
70
- embeddings.extend(batch_embeddings)
71
- embeddings = np.array(embeddings).astype('float32')
 
 
 
 
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) # Sauvegarde de l'index
82
 
83
- # 📌 Sauvegarde des chunks de texte
84
  with open(chunks_path, "wb") as f:
85
  pickle.dump(chunked_docs, f)
86
- print(" Index et chunks sauvegardés !")
 
 
 
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
- """Génère une réponse basée sur le contexte extrait du corpus avec une basse température et un contrôle de probabilité."""
 
113
  messages = [
114
- {"role": "system", "content": f"Voici des informations contextuelles à utiliser avec priorité : {context}"},
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 = "Bonjour le Chat, je suis éléveur de poulets depuis plus de 20 ans et j'ai un doctorat de nutrition animale.Qu’est-ce qu’une protéine idéale en poule pondeuse ? Peux-tu suggérer une protéine idéale en pondeuse ? Merci d'être exhaustif et d'approfondir tes réponses et de ne pas survoler le sujet"
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:002933b799b3b8d0824a2144045cd30f9a0bc3adcbbec594f3b3b0fab6f9b384
3
- size 1940652
 
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:2e097afeea3f73b331e09a0a81f4a09d920478ec100bb52c83ba550a6bf29600
3
- size 2293805
 
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