File size: 7,415 Bytes
f02db94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import os
import numpy as np
import fitz  # PyMuPDF pour extraction PDF
import faiss
import pickle
import matplotlib.pyplot as plt
from concurrent.futures import ThreadPoolExecutor
from openai import OpenAI
from sklearn.manifold import TSNE
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from dotenv import load_dotenv
import seaborn as sns

# Charger les variables d'environnement
load_dotenv()
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY_static')

# 📌 Initialisation du client OpenAI
client = OpenAI(api_key=OPENAI_API_KEY)
model_embedding = "text-embedding-ada-002"
model_chat = "gpt-4-turbo"
temperature = 0.1  # Réduction de la température pour privilégier la RAG

# 📌 Paramètres de segmentation
chunk_size = 256  # Réduction du chunk size pour un meilleur contrôle du contexte
chunk_overlap = 15

# 📌 Définition des chemins de stockage
index_path = "faiss_index_openai.bin"
chunks_path = "chunked_docs_openai.pkl"
metadata_path = "metadata_openai.pkl"
embeddings_path = "embeddings_openai.npy"

# 📌 Vérification et chargement des données
if os.path.exists(index_path) and os.path.exists(chunks_path) and os.path.exists(metadata_path) and os.path.exists(embeddings_path):
    print("🔄 Chargement des données existantes...")
    index = faiss.read_index(index_path)
    with open(chunks_path, "rb") as f:
        chunked_docs = pickle.load(f)
    with open(metadata_path, "rb") as f:
        metadata_list = pickle.load(f)
    embeddings = np.load(embeddings_path)
    print("✅ Index, chunks, embeddings et métadonnées chargés avec succès !")
else:
    print("⚡ Création et stockage d'un nouvel index FAISS...")
    
    # 📌 Extraction des documents et métadonnées
    def extract_and_chunk_pdfs(pdf_folder):
        documents = SimpleDirectoryReader(pdf_folder, recursive=True).load_data()
        chunked_docs, metadata_list = [], []
        for doc in documents:
            doc_text = doc.text
            file_name = doc.metadata.get("file_name", "Inconnu")
            title = doc.metadata.get("title") or os.path.splitext(file_name)[0]
            doc_metadata = {"source": file_name, "title": title}
            for i in range(0, len(doc_text), chunk_size):
                chunk = doc_text[i:i + chunk_size]
                chunked_docs.append({"text": chunk, "metadata": doc_metadata})
                metadata_list.append(doc_metadata)
        return chunked_docs, metadata_list
    
    pdf_folder = 'C:/Users/MIPO10053340/OneDrive - Groupe Avril/Bureau/Salon_Agriculture_2024/Micka_API_Call/Docs_pdf/'
    chunked_docs, metadata_list = extract_and_chunk_pdfs(pdf_folder)
    
    # 📌 Génération des embeddings en parallèle
    def get_embeddings_in_batches(text_chunks, batch_size=5):
        embeddings = []
        
        def process_batch(batch):
            response = client.embeddings.create(input=[chunk["text"] for chunk in batch], model=model_embedding)
            return [data.embedding for data in response.data]
        
        with ThreadPoolExecutor(max_workers=5) as executor:
            future_batches = [executor.submit(process_batch, text_chunks[i:i+batch_size]) for i in range(0, len(text_chunks), batch_size)]
            for future in future_batches:
                embeddings.extend(future.result())
        
        return np.array(embeddings).astype('float32')
    
    embeddings = get_embeddings_in_batches(chunked_docs)
    
    # 📌 Création et stockage de l'index FAISS
    dimension = embeddings.shape[1]
    index = faiss.IndexFlatL2(dimension)
    index.add(embeddings)
    faiss.write_index(index, index_path)
    
    # 📌 Sauvegarde des données
    with open(chunks_path, "wb") as f:
        pickle.dump(chunked_docs, f)
    with open(metadata_path, "wb") as f:
        pickle.dump(metadata_list, f)
    np.save(embeddings_path, embeddings)  
    print("✅ Index, chunks, embeddings et métadonnées sauvegardés !")

# 📌 Récupération des chunks les plus pertinents
def retrieve_relevant_chunks(question, k=5):
    question_embedding_response = client.embeddings.create(
        input=[question],
        model=model_embedding
    )
    question_embedding = np.array(question_embedding_response.data[0].embedding).astype('float32').reshape(1, -1)
    distances, indices = index.search(question_embedding, k)
    if len(indices[0]) == 0:
        print("⚠️ Aucun chunk pertinent trouvé.")
        return []
    return [chunked_docs[i] for i in indices[0]]

# 📌 Génération de réponse avec OpenAI
def generate_response(context, question, sources):
    chunk_references = [f"[{i+1}]" for i in range(len(sources))]
    chunk_texts = "\n\n".join([f"{chunk_references[i]} (Source: {src['metadata']['source']}) :\n{src['text']}" for i, src in enumerate(sources)])
    messages = [
        {"role": "system", "content": f"Voici les informations extraites des documents :\n{chunk_texts}\n\nUtilise ces informations pour répondre."},
        {"role": "user", "content": question}
    ]
    response = client.chat.completions.create(
        model=model_chat,
        messages=messages,
        temperature=temperature
    )
    return response.choices[0].message.content + " " + "".join(chunk_references), chunk_texts

# 📌 Exécuter une requête utilisateur
user_question = "Quels sont les besoins en protéines des poulets de chair en phase de croissance ?"
relevant_chunks = retrieve_relevant_chunks(user_question)
context = "\n".join([chunk["text"] for chunk in relevant_chunks])
answer, citations = generate_response(context, user_question, relevant_chunks)

# 💾 Sauvegarde des résultats
with open("openai_response.txt", "w", encoding="utf-8") as f:
    f.write(f"Question : {user_question}\n")
    f.write(f"Réponse :\n{answer}\n")
    f.write(f"{citations}\n")

print("\n✅ Réponse enregistrée dans 'openai_response.txt'")

# 📊 Visualisation des embeddings avec t-SNE : 
    
# 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.
# 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.
# 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).

# Génération de la réduction de dimension avec t-SNE
tsne = TSNE(n_components=2, perplexity=min(30, max(2, embeddings.shape[0] - 1)), random_state=42)
embeddings_2d = tsne.fit_transform(embeddings)

# Récupération des étiquettes des sources
source_labels = [chunk['metadata']['source'] for chunk in chunked_docs]

# Création du graphique
plt.figure(figsize=(10, 8))
sns.scatterplot(x=embeddings_2d[:, 0], y=embeddings_2d[:, 1], hue=source_labels, palette='tab10', alpha=0.7)
plt.title('Visualisation des embeddings avec t-SNE')
plt.xlabel('Dimension 1')
plt.ylabel('Dimension 2')
# Limiter la légende aux 10 premières sources
handles, labels = plt.gca().get_legend_handles_labels()
plt.legend(handles[:10], labels[:10], title="Fichiers sources (Top 10)", loc="upper right", bbox_to_anchor=(1.2, 1))


# Sauvegarde du graphique en PNG
output_path = "C:/Users/MIPO10053340/OneDrive - Groupe Avril/Bureau/Salon_Agriculture_2024/Micka_API_Call/embeddings_visualization.png"
plt.savefig(output_path, dpi=300, bbox_inches='tight')