File size: 6,133 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
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 mistralai import Mistral
from sklearn.manifold import TSNE
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from dotenv import load_dotenv

# Charger les variables d'environnement
load_dotenv()
MISTRAL_API_KEY = os.getenv('MISTRAL_API_KEY_static')

# 📌 Initialisation du client Mistral
client = Mistral(api_key=MISTRAL_API_KEY)
model_embedding = "mistral-embed"
model_chat = "ministral-8b-latest"
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.bin"
chunks_path = "chunked_docs.pkl"
metadata_path = "metadata.pkl"
embeddings_path = "embeddings.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]  # Utilisation du nom de fichier comme fallback
            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(model=model_embedding, inputs=[chunk["text"] for chunk in batch])
            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)  # Sauvegarde des 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(
        model=model_embedding,
        inputs=[question],
    )
    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("⚠️ Avertissement : Aucun chunk pertinent trouvé, réponse possible moins précise.")
        return [], []
    return [chunked_docs[i] for i in indices[0]]

# 📌 Génération de réponse avec MistralAI
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.complete(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)

# 📊 Affichage de la réponse avec sources
print("\n🔹 Réponse Mistral :")
print(answer)
print("\n📌 **Chunks utilisés :**")
print(citations)

# 💾 Sauvegarde des résultats
with open("mistral_response_types.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 avec les chunks exacts et références dans 'mistral_response_types.txt'")