Spaces:
Sleeping
Sleeping
Florian.Moret
commited on
Commit
·
f02db94
1
Parent(s):
0276406
rangement du repo
Browse files- app.py +4 -4
- .env_sample → files/.env_sample +6 -6
- chunked_docs.pkl → files/chunked_docs.pkl +0 -0
- chunked_docs_openai.pkl → files/chunked_docs_openai.pkl +0 -0
- embeddings.npy → files/embeddings.npy +0 -0
- embeddings_openai.npy → files/embeddings_openai.npy +0 -0
- faiss_index.bin → files/faiss_index.bin +0 -0
- faiss_index_openai.bin → files/faiss_index_openai.bin +0 -0
- metadata.pkl → files/metadata.pkl +0 -0
- metadata_openai.pkl → files/metadata_openai.pkl +0 -0
- RAG_Mistral.py → scripts/RAG_Mistral.py +137 -137
- RAG_OpenAI.py → scripts/RAG_OpenAI.py +165 -165
app.py
CHANGED
@@ -23,10 +23,10 @@ model_embedding = "mistral-embed"
|
|
23 |
chunk_size = 256 # Réduction du chunk size pour un meilleur contrôle du contexte
|
24 |
chunk_overlap = 15
|
25 |
# 📌 Définition des chemins de stockage
|
26 |
-
index_path = "faiss_index.bin"
|
27 |
-
chunks_path = "chunked_docs.pkl"
|
28 |
-
metadata_path = "metadata.pkl"
|
29 |
-
embeddings_path = "embeddings.npy"
|
30 |
|
31 |
print("🔄 Chargement des données existantes...")
|
32 |
index = faiss.read_index(index_path)
|
|
|
23 |
chunk_size = 256 # Réduction du chunk size pour un meilleur contrôle du contexte
|
24 |
chunk_overlap = 15
|
25 |
# 📌 Définition des chemins de stockage
|
26 |
+
index_path = "files/faiss_index.bin"
|
27 |
+
chunks_path = "files/chunked_docs.pkl"
|
28 |
+
metadata_path = "files/metadata.pkl"
|
29 |
+
embeddings_path = "files/embeddings.npy"
|
30 |
|
31 |
print("🔄 Chargement des données existantes...")
|
32 |
index = faiss.read_index(index_path)
|
.env_sample → files/.env_sample
RENAMED
@@ -1,7 +1,7 @@
|
|
1 |
-
REQUESTS_CA_BUNDLE=C:\ProgramData\Netskope\STAgent\data\nscacert.pem
|
2 |
-
SSL_CERT_FILE=C:\ProgramData\Netskope\STAgent\data\nscacert.pem
|
3 |
-
|
4 |
-
MISTRAL_API_KEY_static=
|
5 |
-
LLAMA_API_KEY_static=
|
6 |
-
ANTHROPIC_API_KEY_static=
|
7 |
OPENAI_API_KEY_static=
|
|
|
1 |
+
REQUESTS_CA_BUNDLE=C:\ProgramData\Netskope\STAgent\data\nscacert.pem
|
2 |
+
SSL_CERT_FILE=C:\ProgramData\Netskope\STAgent\data\nscacert.pem
|
3 |
+
|
4 |
+
MISTRAL_API_KEY_static=
|
5 |
+
LLAMA_API_KEY_static=
|
6 |
+
ANTHROPIC_API_KEY_static=
|
7 |
OPENAI_API_KEY_static=
|
chunked_docs.pkl → files/chunked_docs.pkl
RENAMED
File without changes
|
chunked_docs_openai.pkl → files/chunked_docs_openai.pkl
RENAMED
File without changes
|
embeddings.npy → files/embeddings.npy
RENAMED
File without changes
|
embeddings_openai.npy → files/embeddings_openai.npy
RENAMED
File without changes
|
faiss_index.bin → files/faiss_index.bin
RENAMED
File without changes
|
faiss_index_openai.bin → files/faiss_index_openai.bin
RENAMED
File without changes
|
metadata.pkl → files/metadata.pkl
RENAMED
File without changes
|
metadata_openai.pkl → files/metadata_openai.pkl
RENAMED
File without changes
|
RAG_Mistral.py → scripts/RAG_Mistral.py
RENAMED
@@ -1,137 +1,137 @@
|
|
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
|
11 |
-
from dotenv import load_dotenv
|
12 |
-
|
13 |
-
# Charger les variables d'environnement
|
14 |
-
load_dotenv()
|
15 |
-
MISTRAL_API_KEY = os.getenv('MISTRAL_API_KEY_static')
|
16 |
-
|
17 |
-
# 📌 Initialisation du client Mistral
|
18 |
-
client = Mistral(api_key=MISTRAL_API_KEY)
|
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
|
25 |
-
chunk_overlap = 15
|
26 |
-
|
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'")
|
|
|
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
|
11 |
+
from dotenv import load_dotenv
|
12 |
+
|
13 |
+
# Charger les variables d'environnement
|
14 |
+
load_dotenv()
|
15 |
+
MISTRAL_API_KEY = os.getenv('MISTRAL_API_KEY_static')
|
16 |
+
|
17 |
+
# 📌 Initialisation du client Mistral
|
18 |
+
client = Mistral(api_key=MISTRAL_API_KEY)
|
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
|
25 |
+
chunk_overlap = 15
|
26 |
+
|
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'")
|
RAG_OpenAI.py → scripts/RAG_OpenAI.py
RENAMED
@@ -1,165 +1,165 @@
|
|
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 openai import OpenAI
|
9 |
-
from sklearn.manifold import TSNE
|
10 |
-
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
|
11 |
-
from dotenv import load_dotenv
|
12 |
-
import seaborn as sns
|
13 |
-
|
14 |
-
# Charger les variables d'environnement
|
15 |
-
load_dotenv()
|
16 |
-
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY_static')
|
17 |
-
|
18 |
-
# 📌 Initialisation du client OpenAI
|
19 |
-
client = OpenAI(api_key=OPENAI_API_KEY)
|
20 |
-
model_embedding = "text-embedding-ada-002"
|
21 |
-
model_chat = "gpt-4-turbo"
|
22 |
-
temperature = 0.1 # Réduction de la température pour privilégier la RAG
|
23 |
-
|
24 |
-
# 📌 Paramètres de segmentation
|
25 |
-
chunk_size = 256 # Réduction du chunk size pour un meilleur contrôle du contexte
|
26 |
-
chunk_overlap = 15
|
27 |
-
|
28 |
-
# 📌 Définition des chemins de stockage
|
29 |
-
index_path = "faiss_index_openai.bin"
|
30 |
-
chunks_path = "chunked_docs_openai.pkl"
|
31 |
-
metadata_path = "metadata_openai.pkl"
|
32 |
-
embeddings_path = "embeddings_openai.npy"
|
33 |
-
|
34 |
-
# 📌 Vérification et chargement des données
|
35 |
-
if os.path.exists(index_path) and os.path.exists(chunks_path) and os.path.exists(metadata_path) and os.path.exists(embeddings_path):
|
36 |
-
print("🔄 Chargement des données existantes...")
|
37 |
-
index = faiss.read_index(index_path)
|
38 |
-
with open(chunks_path, "rb") as f:
|
39 |
-
chunked_docs = pickle.load(f)
|
40 |
-
with open(metadata_path, "rb") as f:
|
41 |
-
metadata_list = pickle.load(f)
|
42 |
-
embeddings = np.load(embeddings_path)
|
43 |
-
print("✅ Index, chunks, embeddings et métadonnées chargés avec succès !")
|
44 |
-
else:
|
45 |
-
print("⚡ Création et stockage d'un nouvel index FAISS...")
|
46 |
-
|
47 |
-
# 📌 Extraction des documents et métadonnées
|
48 |
-
def extract_and_chunk_pdfs(pdf_folder):
|
49 |
-
documents = SimpleDirectoryReader(pdf_folder, recursive=True).load_data()
|
50 |
-
chunked_docs, metadata_list = [], []
|
51 |
-
for doc in documents:
|
52 |
-
doc_text = doc.text
|
53 |
-
file_name = doc.metadata.get("file_name", "Inconnu")
|
54 |
-
title = doc.metadata.get("title") or os.path.splitext(file_name)[0]
|
55 |
-
doc_metadata = {"source": file_name, "title": title}
|
56 |
-
for i in range(0, len(doc_text), chunk_size):
|
57 |
-
chunk = doc_text[i:i + chunk_size]
|
58 |
-
chunked_docs.append({"text": chunk, "metadata": doc_metadata})
|
59 |
-
metadata_list.append(doc_metadata)
|
60 |
-
return chunked_docs, metadata_list
|
61 |
-
|
62 |
-
pdf_folder = 'C:/Users/MIPO10053340/OneDrive - Groupe Avril/Bureau/Salon_Agriculture_2024/Micka_API_Call/Docs_pdf/'
|
63 |
-
chunked_docs, metadata_list = extract_and_chunk_pdfs(pdf_folder)
|
64 |
-
|
65 |
-
# 📌 Génération des embeddings en parallèle
|
66 |
-
def get_embeddings_in_batches(text_chunks, batch_size=5):
|
67 |
-
embeddings = []
|
68 |
-
|
69 |
-
def process_batch(batch):
|
70 |
-
response = client.embeddings.create(input=[chunk["text"] for chunk in batch], model=model_embedding)
|
71 |
-
return [data.embedding for data in response.data]
|
72 |
-
|
73 |
-
with ThreadPoolExecutor(max_workers=5) as executor:
|
74 |
-
future_batches = [executor.submit(process_batch, text_chunks[i:i+batch_size]) for i in range(0, len(text_chunks), batch_size)]
|
75 |
-
for future in future_batches:
|
76 |
-
embeddings.extend(future.result())
|
77 |
-
|
78 |
-
return np.array(embeddings).astype('float32')
|
79 |
-
|
80 |
-
embeddings = get_embeddings_in_batches(chunked_docs)
|
81 |
-
|
82 |
-
# 📌 Création et stockage de l'index FAISS
|
83 |
-
dimension = embeddings.shape[1]
|
84 |
-
index = faiss.IndexFlatL2(dimension)
|
85 |
-
index.add(embeddings)
|
86 |
-
faiss.write_index(index, index_path)
|
87 |
-
|
88 |
-
# 📌 Sauvegarde des données
|
89 |
-
with open(chunks_path, "wb") as f:
|
90 |
-
pickle.dump(chunked_docs, f)
|
91 |
-
with open(metadata_path, "wb") as f:
|
92 |
-
pickle.dump(metadata_list, f)
|
93 |
-
np.save(embeddings_path, embeddings)
|
94 |
-
print("✅ Index, chunks, embeddings et métadonnées sauvegardés !")
|
95 |
-
|
96 |
-
# 📌 Récupération des chunks les plus pertinents
|
97 |
-
def retrieve_relevant_chunks(question, k=5):
|
98 |
-
question_embedding_response = client.embeddings.create(
|
99 |
-
input=[question],
|
100 |
-
model=model_embedding
|
101 |
-
)
|
102 |
-
question_embedding = np.array(question_embedding_response.data[0].embedding).astype('float32').reshape(1, -1)
|
103 |
-
distances, indices = index.search(question_embedding, k)
|
104 |
-
if len(indices[0]) == 0:
|
105 |
-
print("⚠️ Aucun chunk pertinent trouvé.")
|
106 |
-
return []
|
107 |
-
return [chunked_docs[i] for i in indices[0]]
|
108 |
-
|
109 |
-
# 📌 Génération de réponse avec OpenAI
|
110 |
-
def generate_response(context, question, sources):
|
111 |
-
chunk_references = [f"[{i+1}]" for i in range(len(sources))]
|
112 |
-
chunk_texts = "\n\n".join([f"{chunk_references[i]} (Source: {src['metadata']['source']}) :\n{src['text']}" for i, src in enumerate(sources)])
|
113 |
-
messages = [
|
114 |
-
{"role": "system", "content": f"Voici les informations extraites des documents :\n{chunk_texts}\n\nUtilise ces informations pour répondre."},
|
115 |
-
{"role": "user", "content": question}
|
116 |
-
]
|
117 |
-
response = client.chat.completions.create(
|
118 |
-
model=model_chat,
|
119 |
-
messages=messages,
|
120 |
-
temperature=temperature
|
121 |
-
)
|
122 |
-
return response.choices[0].message.content + " " + "".join(chunk_references), chunk_texts
|
123 |
-
|
124 |
-
# 📌 Exécuter une requête utilisateur
|
125 |
-
user_question = "Quels sont les besoins en protéines des poulets de chair en phase de croissance ?"
|
126 |
-
relevant_chunks = retrieve_relevant_chunks(user_question)
|
127 |
-
context = "\n".join([chunk["text"] for chunk in relevant_chunks])
|
128 |
-
answer, citations = generate_response(context, user_question, relevant_chunks)
|
129 |
-
|
130 |
-
# 💾 Sauvegarde des résultats
|
131 |
-
with open("openai_response.txt", "w", encoding="utf-8") as f:
|
132 |
-
f.write(f"Question : {user_question}\n")
|
133 |
-
f.write(f"Réponse :\n{answer}\n")
|
134 |
-
f.write(f"{citations}\n")
|
135 |
-
|
136 |
-
print("\n✅ Réponse enregistrée dans 'openai_response.txt'")
|
137 |
-
|
138 |
-
# 📊 Visualisation des embeddings avec t-SNE :
|
139 |
-
|
140 |
-
# Nos documents ont un vocabulaire et un contenu très similaires, donc les embeddings générés par Mistral sont proches les uns des autres.
|
141 |
-
# t-SNE réduit la dimensionnalité, mais si les embeddings de base sont très proches, la distinction entre eux est moins visible en 2D.
|
142 |
-
# Un clustering plus clair apparaîtrait si nos documents couvraient des thématiques très variées (ex: alimentation, maladies, croissance de différents animaux).
|
143 |
-
|
144 |
-
# Génération de la réduction de dimension avec t-SNE
|
145 |
-
tsne = TSNE(n_components=2, perplexity=min(30, max(2, embeddings.shape[0] - 1)), random_state=42)
|
146 |
-
embeddings_2d = tsne.fit_transform(embeddings)
|
147 |
-
|
148 |
-
# Récupération des étiquettes des sources
|
149 |
-
source_labels = [chunk['metadata']['source'] for chunk in chunked_docs]
|
150 |
-
|
151 |
-
# Création du graphique
|
152 |
-
plt.figure(figsize=(10, 8))
|
153 |
-
sns.scatterplot(x=embeddings_2d[:, 0], y=embeddings_2d[:, 1], hue=source_labels, palette='tab10', alpha=0.7)
|
154 |
-
plt.title('Visualisation des embeddings avec t-SNE')
|
155 |
-
plt.xlabel('Dimension 1')
|
156 |
-
plt.ylabel('Dimension 2')
|
157 |
-
# Limiter la légende aux 10 premières sources
|
158 |
-
handles, labels = plt.gca().get_legend_handles_labels()
|
159 |
-
plt.legend(handles[:10], labels[:10], title="Fichiers sources (Top 10)", loc="upper right", bbox_to_anchor=(1.2, 1))
|
160 |
-
|
161 |
-
|
162 |
-
# Sauvegarde du graphique en PNG
|
163 |
-
output_path = "C:/Users/MIPO10053340/OneDrive - Groupe Avril/Bureau/Salon_Agriculture_2024/Micka_API_Call/embeddings_visualization.png"
|
164 |
-
plt.savefig(output_path, dpi=300, bbox_inches='tight')
|
165 |
-
|
|
|
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 openai import OpenAI
|
9 |
+
from sklearn.manifold import TSNE
|
10 |
+
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
|
11 |
+
from dotenv import load_dotenv
|
12 |
+
import seaborn as sns
|
13 |
+
|
14 |
+
# Charger les variables d'environnement
|
15 |
+
load_dotenv()
|
16 |
+
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY_static')
|
17 |
+
|
18 |
+
# 📌 Initialisation du client OpenAI
|
19 |
+
client = OpenAI(api_key=OPENAI_API_KEY)
|
20 |
+
model_embedding = "text-embedding-ada-002"
|
21 |
+
model_chat = "gpt-4-turbo"
|
22 |
+
temperature = 0.1 # Réduction de la température pour privilégier la RAG
|
23 |
+
|
24 |
+
# 📌 Paramètres de segmentation
|
25 |
+
chunk_size = 256 # Réduction du chunk size pour un meilleur contrôle du contexte
|
26 |
+
chunk_overlap = 15
|
27 |
+
|
28 |
+
# 📌 Définition des chemins de stockage
|
29 |
+
index_path = "faiss_index_openai.bin"
|
30 |
+
chunks_path = "chunked_docs_openai.pkl"
|
31 |
+
metadata_path = "metadata_openai.pkl"
|
32 |
+
embeddings_path = "embeddings_openai.npy"
|
33 |
+
|
34 |
+
# 📌 Vérification et chargement des données
|
35 |
+
if os.path.exists(index_path) and os.path.exists(chunks_path) and os.path.exists(metadata_path) and os.path.exists(embeddings_path):
|
36 |
+
print("🔄 Chargement des données existantes...")
|
37 |
+
index = faiss.read_index(index_path)
|
38 |
+
with open(chunks_path, "rb") as f:
|
39 |
+
chunked_docs = pickle.load(f)
|
40 |
+
with open(metadata_path, "rb") as f:
|
41 |
+
metadata_list = pickle.load(f)
|
42 |
+
embeddings = np.load(embeddings_path)
|
43 |
+
print("✅ Index, chunks, embeddings et métadonnées chargés avec succès !")
|
44 |
+
else:
|
45 |
+
print("⚡ Création et stockage d'un nouvel index FAISS...")
|
46 |
+
|
47 |
+
# 📌 Extraction des documents et métadonnées
|
48 |
+
def extract_and_chunk_pdfs(pdf_folder):
|
49 |
+
documents = SimpleDirectoryReader(pdf_folder, recursive=True).load_data()
|
50 |
+
chunked_docs, metadata_list = [], []
|
51 |
+
for doc in documents:
|
52 |
+
doc_text = doc.text
|
53 |
+
file_name = doc.metadata.get("file_name", "Inconnu")
|
54 |
+
title = doc.metadata.get("title") or os.path.splitext(file_name)[0]
|
55 |
+
doc_metadata = {"source": file_name, "title": title}
|
56 |
+
for i in range(0, len(doc_text), chunk_size):
|
57 |
+
chunk = doc_text[i:i + chunk_size]
|
58 |
+
chunked_docs.append({"text": chunk, "metadata": doc_metadata})
|
59 |
+
metadata_list.append(doc_metadata)
|
60 |
+
return chunked_docs, metadata_list
|
61 |
+
|
62 |
+
pdf_folder = 'C:/Users/MIPO10053340/OneDrive - Groupe Avril/Bureau/Salon_Agriculture_2024/Micka_API_Call/Docs_pdf/'
|
63 |
+
chunked_docs, metadata_list = extract_and_chunk_pdfs(pdf_folder)
|
64 |
+
|
65 |
+
# 📌 Génération des embeddings en parallèle
|
66 |
+
def get_embeddings_in_batches(text_chunks, batch_size=5):
|
67 |
+
embeddings = []
|
68 |
+
|
69 |
+
def process_batch(batch):
|
70 |
+
response = client.embeddings.create(input=[chunk["text"] for chunk in batch], model=model_embedding)
|
71 |
+
return [data.embedding for data in response.data]
|
72 |
+
|
73 |
+
with ThreadPoolExecutor(max_workers=5) as executor:
|
74 |
+
future_batches = [executor.submit(process_batch, text_chunks[i:i+batch_size]) for i in range(0, len(text_chunks), batch_size)]
|
75 |
+
for future in future_batches:
|
76 |
+
embeddings.extend(future.result())
|
77 |
+
|
78 |
+
return np.array(embeddings).astype('float32')
|
79 |
+
|
80 |
+
embeddings = get_embeddings_in_batches(chunked_docs)
|
81 |
+
|
82 |
+
# 📌 Création et stockage de l'index FAISS
|
83 |
+
dimension = embeddings.shape[1]
|
84 |
+
index = faiss.IndexFlatL2(dimension)
|
85 |
+
index.add(embeddings)
|
86 |
+
faiss.write_index(index, index_path)
|
87 |
+
|
88 |
+
# 📌 Sauvegarde des données
|
89 |
+
with open(chunks_path, "wb") as f:
|
90 |
+
pickle.dump(chunked_docs, f)
|
91 |
+
with open(metadata_path, "wb") as f:
|
92 |
+
pickle.dump(metadata_list, f)
|
93 |
+
np.save(embeddings_path, embeddings)
|
94 |
+
print("✅ Index, chunks, embeddings et métadonnées sauvegardés !")
|
95 |
+
|
96 |
+
# 📌 Récupération des chunks les plus pertinents
|
97 |
+
def retrieve_relevant_chunks(question, k=5):
|
98 |
+
question_embedding_response = client.embeddings.create(
|
99 |
+
input=[question],
|
100 |
+
model=model_embedding
|
101 |
+
)
|
102 |
+
question_embedding = np.array(question_embedding_response.data[0].embedding).astype('float32').reshape(1, -1)
|
103 |
+
distances, indices = index.search(question_embedding, k)
|
104 |
+
if len(indices[0]) == 0:
|
105 |
+
print("⚠️ Aucun chunk pertinent trouvé.")
|
106 |
+
return []
|
107 |
+
return [chunked_docs[i] for i in indices[0]]
|
108 |
+
|
109 |
+
# 📌 Génération de réponse avec OpenAI
|
110 |
+
def generate_response(context, question, sources):
|
111 |
+
chunk_references = [f"[{i+1}]" for i in range(len(sources))]
|
112 |
+
chunk_texts = "\n\n".join([f"{chunk_references[i]} (Source: {src['metadata']['source']}) :\n{src['text']}" for i, src in enumerate(sources)])
|
113 |
+
messages = [
|
114 |
+
{"role": "system", "content": f"Voici les informations extraites des documents :\n{chunk_texts}\n\nUtilise ces informations pour répondre."},
|
115 |
+
{"role": "user", "content": question}
|
116 |
+
]
|
117 |
+
response = client.chat.completions.create(
|
118 |
+
model=model_chat,
|
119 |
+
messages=messages,
|
120 |
+
temperature=temperature
|
121 |
+
)
|
122 |
+
return response.choices[0].message.content + " " + "".join(chunk_references), chunk_texts
|
123 |
+
|
124 |
+
# 📌 Exécuter une requête utilisateur
|
125 |
+
user_question = "Quels sont les besoins en protéines des poulets de chair en phase de croissance ?"
|
126 |
+
relevant_chunks = retrieve_relevant_chunks(user_question)
|
127 |
+
context = "\n".join([chunk["text"] for chunk in relevant_chunks])
|
128 |
+
answer, citations = generate_response(context, user_question, relevant_chunks)
|
129 |
+
|
130 |
+
# 💾 Sauvegarde des résultats
|
131 |
+
with open("openai_response.txt", "w", encoding="utf-8") as f:
|
132 |
+
f.write(f"Question : {user_question}\n")
|
133 |
+
f.write(f"Réponse :\n{answer}\n")
|
134 |
+
f.write(f"{citations}\n")
|
135 |
+
|
136 |
+
print("\n✅ Réponse enregistrée dans 'openai_response.txt'")
|
137 |
+
|
138 |
+
# 📊 Visualisation des embeddings avec t-SNE :
|
139 |
+
|
140 |
+
# Nos documents ont un vocabulaire et un contenu très similaires, donc les embeddings générés par Mistral sont proches les uns des autres.
|
141 |
+
# t-SNE réduit la dimensionnalité, mais si les embeddings de base sont très proches, la distinction entre eux est moins visible en 2D.
|
142 |
+
# Un clustering plus clair apparaîtrait si nos documents couvraient des thématiques très variées (ex: alimentation, maladies, croissance de différents animaux).
|
143 |
+
|
144 |
+
# Génération de la réduction de dimension avec t-SNE
|
145 |
+
tsne = TSNE(n_components=2, perplexity=min(30, max(2, embeddings.shape[0] - 1)), random_state=42)
|
146 |
+
embeddings_2d = tsne.fit_transform(embeddings)
|
147 |
+
|
148 |
+
# Récupération des étiquettes des sources
|
149 |
+
source_labels = [chunk['metadata']['source'] for chunk in chunked_docs]
|
150 |
+
|
151 |
+
# Création du graphique
|
152 |
+
plt.figure(figsize=(10, 8))
|
153 |
+
sns.scatterplot(x=embeddings_2d[:, 0], y=embeddings_2d[:, 1], hue=source_labels, palette='tab10', alpha=0.7)
|
154 |
+
plt.title('Visualisation des embeddings avec t-SNE')
|
155 |
+
plt.xlabel('Dimension 1')
|
156 |
+
plt.ylabel('Dimension 2')
|
157 |
+
# Limiter la légende aux 10 premières sources
|
158 |
+
handles, labels = plt.gca().get_legend_handles_labels()
|
159 |
+
plt.legend(handles[:10], labels[:10], title="Fichiers sources (Top 10)", loc="upper right", bbox_to_anchor=(1.2, 1))
|
160 |
+
|
161 |
+
|
162 |
+
# Sauvegarde du graphique en PNG
|
163 |
+
output_path = "C:/Users/MIPO10053340/OneDrive - Groupe Avril/Bureau/Salon_Agriculture_2024/Micka_API_Call/embeddings_visualization.png"
|
164 |
+
plt.savefig(output_path, dpi=300, bbox_inches='tight')
|
165 |
+
|