Aidahaouas commited on
Commit
bc17620
·
verified ·
1 Parent(s): ffe2991

Update pinecone_utilsB.py

Browse files
Files changed (1) hide show
  1. pinecone_utilsB.py +105 -67
pinecone_utilsB.py CHANGED
@@ -1,117 +1,153 @@
1
  from sentence_transformers import SentenceTransformer
2
  from pinecone_text.sparse import BM25Encoder
 
 
 
 
3
  import pinecone
4
  import streamlit as st
5
- from config import sparse_index as indexB, llm
6
  import nltk
7
- from nltk.corpus import stopwords
8
  import zlib
9
  import base64
10
- from rank_bm25 import BM25Okapi
11
- import numpy as np
12
- nltk.download("stopwords")
13
-
14
-
15
- nltk.download("punkt_tab")
16
 
17
  class HybridSearchEngine:
18
  def __init__(self):
 
19
  self.model = SentenceTransformer("intfloat/multilingual-e5-large")
20
-
21
- # Initialisation des variables Streamlit
22
- if "bm25_index" not in st.session_state:
23
- st.session_state.bm25_index = None
 
 
24
  if "bm25_corpus" not in st.session_state:
25
  st.session_state.bm25_corpus = []
26
  if "indexing_done" not in st.session_state:
27
- st.session_state.indexing_done = False
 
 
 
 
 
 
 
 
 
 
28
 
29
  def is_initialized(self):
30
- return st.session_state.bm25_index is not None and bool(st.session_state.bm25_corpus)
 
31
 
32
  def tokenize(self, text):
33
- stop_words = set(stopwords.words("french"))
34
- tokens = nltk.word_tokenize(text.lower())
35
- return [t for t in tokens if t.isalnum() and t not in stop_words]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  def index_pdf_B(self, texts):
 
38
  if not texts:
39
  st.error("La liste des textes ne peut pas être vide.")
40
  return
41
 
42
- st.session_state.indexing_done = False
43
  st.write("Indexation en cours, veuillez patienter...")
44
 
45
- dense_vectors = self.model.encode([t[:512] for t in texts])
 
46
 
47
- st.session_state.bm25_corpus = texts
48
- tokenized_corpus = [self.tokenize(text) for text in texts]
49
- if not any(tokenized_corpus):
50
- st.error("Le corpus tokenisé est vide. Vérifiez vos textes.")
51
- return
52
-
53
- st.session_state.bm25_index = BM25Okapi(tokenized_corpus)
54
- st.session_state.indexing_done = True
55
- st.success("Indexation BM25 terminée avec succès.")
56
 
57
- # Indexer les vecteurs dans Pinecone
58
- for i, (dense_vector, text) in enumerate(zip(dense_vectors, texts)):
59
  chunks = self.split_text_into_chunks(text, max_chunk_size=1024)
60
  for j, chunk in enumerate(chunks):
61
  compressed_chunk = self.compress_text(chunk)
 
 
 
 
 
 
 
 
62
  metadata = {"compressed_text": compressed_chunk}
 
63
 
64
- if self.get_metadata_size(metadata) > 40960:
65
- chunk = chunk[:512]
66
- compressed_chunk = self.compress_text(chunk)
67
- metadata = {"compressed_text": compressed_chunk}
68
- if self.get_metadata_size(metadata) > 40960:
69
- continue
70
-
71
- indexB.upsert([
72
- {
73
- "id": f"vec_{i}_{j}",
74
- "values": dense_vector.tolist(),
75
- "metadata": metadata
76
- }
77
- ])
78
 
79
- def hybrid_search(self, query):
80
- query_dense_vector = self.model.encode([query[:512]]).tolist()[0]
 
 
 
 
81
 
 
 
82
  if not self.is_initialized():
83
- st.error("BM25 n'est pas encore indexé. Exécutez l'indexation d'abord.")
84
  return []
85
 
86
- tokenized_query = self.tokenize(query)
87
- sparse_scores = st.session_state.bm25_index.get_scores(tokenized_query)
 
88
 
89
- if np.max(sparse_scores) > 0:
90
- sparse_scores = (sparse_scores - np.min(sparse_scores)) / (np.max(sparse_scores) - np.min(sparse_scores) + 1e-9)
 
 
 
 
 
 
91
 
92
- alpha = 0.7
93
- hybrid_scores = alpha * np.array(query_dense_vector) + (1 - alpha) * np.pad(sparse_scores, (0, max(0, len(query_dense_vector) - len(sparse_scores))), 'constant')
 
94
 
95
- results = indexB.query(
96
- vector=hybrid_scores.tolist(),
97
- top_k=30,
98
- include_metadata=True,
99
- )
100
 
101
- relevant_docs = []
102
- for match in results.get("matches", []):
103
- metadata = match.get("metadata", {})
104
- compressed_text = metadata.get("compressed_text")
105
- if compressed_text:
106
- relevant_docs.append(self.decompress_text(compressed_text))
107
 
108
- return relevant_docs
109
 
110
  def compress_text(self, text):
 
111
  compressed = zlib.compress(text.encode("utf-8"))
112
  return base64.b64encode(compressed).decode("utf-8")
113
 
114
  def decompress_text(self, compressed_text):
 
115
  try:
116
  compressed_data = base64.b64decode(compressed_text.encode("utf-8"))
117
  return zlib.decompress(compressed_data).decode("utf-8")
@@ -119,8 +155,10 @@ class HybridSearchEngine:
119
  st.error(f"Erreur de décompression : {e}")
120
  return ""
121
 
122
- def split_text_into_chunks(self, text, max_chunk_size=512):
 
123
  return [text[i:i+max_chunk_size] for i in range(0, len(text), max_chunk_size)]
124
 
125
  def get_metadata_size(self, metadata):
126
- return len(str(metadata).encode("utf-8"))
 
 
1
  from sentence_transformers import SentenceTransformer
2
  from pinecone_text.sparse import BM25Encoder
3
+ from langchain.retrievers import PineconeHybridSearchRetriever
4
+ from langchain.embeddings import HuggingFaceEmbeddings
5
+ from langchain_pinecone import PineconeVectorStore
6
+ from langchain.schema import Document # Import the Document class
7
  import pinecone
8
  import streamlit as st
9
+ from config import sparse_index as indexB
10
  import nltk
 
11
  import zlib
12
  import base64
 
 
 
 
 
 
13
 
14
  class HybridSearchEngine:
15
  def __init__(self):
16
+ # Initialisation des modèles et encodeurs
17
  self.model = SentenceTransformer("intfloat/multilingual-e5-large")
18
+ self.sparse_encoder = BM25Encoder().default() # Initialisation de BM25Encoder avec des valeurs par défaut
19
+
20
+ # Créer une instance de HuggingFaceEmbeddings
21
+ self.embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-large")
22
+
23
+ # Utiliser st.session_state pour stocker l'état de l'indexation
24
  if "bm25_corpus" not in st.session_state:
25
  st.session_state.bm25_corpus = []
26
  if "indexing_done" not in st.session_state:
27
+ st.session_state.indexing_done = False # Ajout d'un indicateur d'indexation
28
+
29
+ # Initialisation du PineconeVectorStore
30
+ self.vectorstore = PineconeVectorStore(index=indexB, embedding=self.embeddings)
31
+
32
+ # Initialisation du retriever hybride
33
+ self.retriever = PineconeHybridSearchRetriever(
34
+ embeddings=self.embeddings,
35
+ sparse_encoder=self.sparse_encoder,
36
+ index=indexB
37
+ )
38
 
39
  def is_initialized(self):
40
+ """Vérifie si l'index BM25 est initialisé."""
41
+ return bool(st.session_state.bm25_corpus)
42
 
43
  def tokenize(self, text):
44
+ """Tokenise un texte avec NLTK."""
45
+ return nltk.word_tokenize(text.lower())
46
+ def get_existing_vectors(self):
47
+ """Récupère les textes compressés déjà indexés dans Pinecone."""
48
+ existing_texts = set()
49
+
50
+ try:
51
+ # Effectuer une recherche avec un mot-clé fictif pour récupérer des documents
52
+ results = self.vectorstore.similarity_search("random_query", k=10000) # Ajuster k selon l'index
53
+
54
+ for doc in results:
55
+ if "compressed_text" in doc.metadata:
56
+ existing_texts.add(doc.metadata["compressed_text"]) # Stocker les textes existants
57
+
58
+ except Exception as e:
59
+ st.error(f"Erreur lors de la récupération des vecteurs existants : {e}")
60
+
61
+ return existing_texts
62
+
63
 
64
  def index_pdf_B(self, texts):
65
+ """Indexe les textes en évitant les doublons (même contenu)."""
66
  if not texts:
67
  st.error("La liste des textes ne peut pas être vide.")
68
  return
69
 
70
+ st.session_state.indexing_done = False
71
  st.write("Indexation en cours, veuillez patienter...")
72
 
73
+ # Récupérer les textes déjà indexés dans Pinecone
74
+ existing_texts = self.get_existing_vectors()
75
 
76
+ # Initialiser BM25
77
+ st.session_state.bm25_corpus = texts
78
+ self.sparse_encoder.fit(texts)
 
 
 
 
 
 
79
 
80
+ documents = []
81
+ for i, text in enumerate(texts):
82
  chunks = self.split_text_into_chunks(text, max_chunk_size=1024)
83
  for j, chunk in enumerate(chunks):
84
  compressed_chunk = self.compress_text(chunk)
85
+
86
+ # Vérifier si ce texte est déjà dans l'index Pinecone
87
+ if compressed_chunk in existing_texts:
88
+ continue # Ignorer ce document car il est déjà indexé
89
+
90
+ # Générer un ID unique pour ce chunk
91
+ doc_id = f"doc_{zlib.crc32(chunk.encode('utf-8'))}"
92
+
93
  metadata = {"compressed_text": compressed_chunk}
94
+ metadata_size = self.get_metadata_size(metadata)
95
 
96
+ if metadata_size <= 40960: # 40 KB
97
+ document = Document(
98
+ page_content=chunk,
99
+ metadata=metadata
100
+ )
101
+ documents.append((doc_id, document))
 
 
 
 
 
 
 
 
102
 
103
+ # Ajouter uniquement les nouveaux documents
104
+ if documents:
105
+ self.vectorstore.add_documents([doc for _, doc in documents]) # Remplacer upsert() par add_documents()
106
+
107
+ st.session_state.indexing_done = True
108
+ st.success("Indexation terminée sans duplication de contenu.")
109
 
110
+ def hybrid_search(self, query):
111
+ """Récupère les documents pertinents en combinant les résultats de Pinecone et BM25."""
112
  if not self.is_initialized():
113
+ st.warning("L'index BM25 n'est pas encore prêt. Veuillez patienter pendant l'indexation...")
114
  return []
115
 
116
+ try:
117
+ # Recherche hybride avec PineconeHybridSearchRetriever
118
+ results = self.retriever.get_relevant_documents(query)
119
 
120
+ # Récupérer les documents pertinents
121
+ relevant_docs = []
122
+ for result in results:
123
+ # Vérifier si le résultat est un objet Document
124
+ if hasattr(result, "metadata"):
125
+ metadata = result.metadata or {} # Assurez-vous que metadata n'est jamais None
126
+ else:
127
+ metadata = {}
128
 
129
+ # Vérifier si 'context' existe avant d'y accéder
130
+ if "context" in metadata:
131
+ _ = metadata.pop("context", None) # Sécuriser l'accès à 'context'
132
 
133
+ compressed_text = metadata.get("compressed_text")
134
+ if compressed_text:
135
+ relevant_docs.append(self.decompress_text(compressed_text))
 
 
136
 
137
+ return relevant_docs
138
+
139
+ except Exception as e:
140
+ st.error(f"Erreur lors de la recherche hybride : {e}")
141
+ return []
 
142
 
 
143
 
144
  def compress_text(self, text):
145
+ """Compresse un texte en base64."""
146
  compressed = zlib.compress(text.encode("utf-8"))
147
  return base64.b64encode(compressed).decode("utf-8")
148
 
149
  def decompress_text(self, compressed_text):
150
+ """Décompresse un texte compressé en base64."""
151
  try:
152
  compressed_data = base64.b64decode(compressed_text.encode("utf-8"))
153
  return zlib.decompress(compressed_data).decode("utf-8")
 
155
  st.error(f"Erreur de décompression : {e}")
156
  return ""
157
 
158
+ def split_text_into_chunks(self, text, max_chunk_size=1024):
159
+ """Divise un texte en morceaux de taille maximale `max_chunk_size`."""
160
  return [text[i:i+max_chunk_size] for i in range(0, len(text), max_chunk_size)]
161
 
162
  def get_metadata_size(self, metadata):
163
+ """Calcule la taille des métadonnées en octets."""
164
+ return len(str(metadata).encode("utf-8"))