Nicolas Gutierrez commited on
Commit
6137cb1
·
1 Parent(s): a4378ed

Add main application logic and requirements file for PDF processing and QA model

Browse files
Files changed (2) hide show
  1. app.py +239 -0
  2. requirements.txt +8 -0
app.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import PyPDF2 as pypdf2
2
+ import os
3
+ import re
4
+ from pathlib import Path
5
+ from sentence_transformers import SentenceTransformer
6
+ import requests
7
+ from chromadb import Client
8
+ from transformers import pipeline
9
+ import gradio as gr
10
+ from dotenv import load_dotenv
11
+
12
+
13
+
14
+ # ==================== CONFIGURACIÓN ====================
15
+
16
+ DATA_DIR = "./"
17
+ CHUNK_SIZE = 500
18
+ OVERLAPPING = 100
19
+ N_SEARCH_RESULTS = 5
20
+
21
+ # ==================== FUNCIONES AUXILIARES ====================
22
+
23
+ def extraer_texto_pdf(pdf_path):
24
+ """Extraer texto de un archivo PDF"""
25
+ try:
26
+ with open(pdf_path, "rb") as file:
27
+ reader = pypdf2.PdfReader(file)
28
+ text = ""
29
+ for page in reader.pages:
30
+ text += page.extract_text()
31
+ return text
32
+ except Exception as e:
33
+ print(f"Error extrayendo texto de {pdf_path}: {str(e)}")
34
+ return ""
35
+
36
+ def limpiar_texto(texto):
37
+ """
38
+ Limpia el texto de espacios múltiples, saltos de línea y tabs
39
+ """
40
+ # Remover tabs
41
+ texto = re.sub(r'\t', ' ', texto)
42
+
43
+ # Remover saltos de línea
44
+ texto = re.sub(r'\n+', ' ', texto)
45
+
46
+ # Remover espacios múltiples (3 o más)
47
+ texto = re.sub(r' {3,}', ' ', texto)
48
+
49
+ # Remover espacios dobles
50
+ texto = re.sub(r' +', ' ', texto)
51
+
52
+ # Remover espacios al inicio y final
53
+ texto = texto.strip()
54
+
55
+ # Remover espacios antes de puntos
56
+ texto = re.sub(r' \.', '.', texto)
57
+
58
+ # Remover puntos repetidos
59
+ texto = re.sub(r'\.{2,}', '.', texto)
60
+
61
+ return texto
62
+
63
+ def dividir_texto(text, chunk_size=CHUNK_SIZE, overlapping=OVERLAPPING):
64
+ """
65
+ Dividir texto en fragmentos con superposición
66
+ """
67
+ chunks = []
68
+ start = 0
69
+
70
+ while start < len(text):
71
+ end = start + chunk_size
72
+
73
+ if end < len(text):
74
+ # Buscar el último espacio para no cortar palabras
75
+ last_space = text.rfind(' ', start, end)
76
+ if last_space > start:
77
+ end = last_space
78
+
79
+ chunk = text[start:end].strip()
80
+ if chunk:
81
+ chunks.append(chunk)
82
+
83
+ start = end - overlapping
84
+
85
+ if start >= len(text):
86
+ break
87
+
88
+ return chunks
89
+
90
+ def cargar_y_procesar_pdfs():
91
+ """Cargar, procesar y crear embeddings de los PDFs"""
92
+ print("\n Leyendo los pdfs del reglamento")
93
+
94
+ # Leer PDFs
95
+ print("\nLeyendo archivos PDF...")
96
+ pdf_folder = DATA_DIR
97
+ pdf_files = [f for f in os.listdir(pdf_folder) if f.endswith('.pdf')]
98
+
99
+ if not pdf_files:
100
+ return None, None, None, None
101
+
102
+ pdf_texts = []
103
+ for pdf_file in pdf_files:
104
+ text = extraer_texto_pdf(os.path.join(pdf_folder, pdf_file))
105
+ if text:
106
+ pdf_texts.append(text)
107
+
108
+ # Limpiar textos
109
+ print("Limpiando textos")
110
+ pdf_texts_limpios = [limpiar_texto(text) for text in pdf_texts]
111
+
112
+ # Dividir en chunks
113
+ print("Dividiendo textos en fragmentos")
114
+ chunks = []
115
+ for texto in pdf_texts_limpios:
116
+ chunks.extend(dividir_texto(texto, chunk_size=CHUNK_SIZE, overlapping=OVERLAPPING))
117
+
118
+ print(f"✓ Total de fragmentos generados: {len(chunks)}")
119
+
120
+ # Crear embeddings
121
+ print("Generando embeddings")
122
+ embedding_model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
123
+ embeddings = embedding_model.encode(chunks)
124
+ print(f"✓ Dimensión de embeddings: {embeddings.shape}")
125
+
126
+ # Crear base de datos vectorial
127
+ print("Creando base de datos vectorial")
128
+ client = Client()
129
+ collection = client.create_collection(
130
+ name="documentos_pdf",
131
+ metadata={"hnsw:space": "cosine"}
132
+ )
133
+
134
+ # Agregar fragmentos a la colección
135
+ collection.add(
136
+ embeddings=embeddings,
137
+ metadatas=[{"source": "pdf", "chunk_id": i} for i in range(len(chunks))],
138
+ documents=chunks,
139
+ ids=[f"chunk_{i}" for i in range(len(chunks))]
140
+ )
141
+
142
+ print(f"Base de datos vectorial creada con {len(chunks)} fragmentos\n")
143
+
144
+ return embedding_model, collection, client, chunks
145
+
146
+ # ==================== INICIALIZACIÓN ====================
147
+
148
+ print("Cargando los documentos y los modelos necesarios")
149
+
150
+ embedding_model, collection, client, chunks = cargar_y_procesar_pdfs()
151
+
152
+ # Cargar modelo QA
153
+ print("Cargando modelo de Preguntas y Respuestas")
154
+ qa_pipeline = pipeline(
155
+ 'question-answering',
156
+ model='mrm8488/distill-bert-base-spanish-wwm-cased-finetuned-spa-squad2-es',
157
+ tokenizer=('mrm8488/distill-bert-base-spanish-wwm-cased-finetuned-spa-squad2-es', {"use_fast": False})
158
+ )
159
+ print("Modelo QA cargado\n")
160
+
161
+ # ==================== FUNCIÓN PRINCIPAL ====================
162
+
163
+ def obtener_respuesta(pregunta):
164
+ if not pregunta.strip():
165
+ return "La pregunta no es valida."
166
+
167
+ if embedding_model is None or collection is None:
168
+ return "Los documentos no se cargaron correctamente."
169
+
170
+ try:
171
+ # Generar embedding de la pregunta
172
+ query_embedding = embedding_model.encode(pregunta)
173
+
174
+ # Buscar documentos similares
175
+ res = collection.query(
176
+ query_embeddings=[query_embedding],
177
+ n_results=N_SEARCH_RESULTS
178
+ )
179
+
180
+ # Combinar contexto
181
+ context = " ".join(res['documents'][0])
182
+
183
+ # Generar respuesta
184
+ answer = qa_pipeline(question=pregunta, context=context)['answer']
185
+
186
+ return answer
187
+
188
+ except Exception as e:
189
+ return f"Error procesando la pregunta: {str(e)}"
190
+
191
+ # ==================== INTERFAZ GRADIO ====================
192
+
193
+ with gr.Blocks(title="Asistente de Reglamentos UNAB") as demo:
194
+ gr.Markdown("""
195
+ #Asistente de Reglamentos UNAB
196
+
197
+ Realiza preguntas sobre los reglamentos de la Universidad Autónoma de Bucaramanga
198
+ utilizando búsqueda semántica y procesamiento de lenguaje natural.
199
+ """)
200
+
201
+ with gr.Row():
202
+ with gr.Column():
203
+ question_input = gr.Textbox(
204
+ lines=3,
205
+ placeholder="Ejemplo: ¿Cuál es el proceso de inscripción?",
206
+ label="Tu pregunta"
207
+ )
208
+ submit_button = gr.Button("Buscar respuesta", variant="primary")
209
+
210
+ with gr.Column():
211
+ answer_output = gr.Textbox(
212
+ label="Respuesta",
213
+ lines=5,
214
+ interactive=False
215
+ )
216
+
217
+ submit_button.click(
218
+ fn=obtener_respuesta,
219
+ inputs=question_input,
220
+ outputs=answer_output
221
+ )
222
+
223
+ # Enter key support
224
+ question_input.submit(
225
+ fn=obtener_respuesta,
226
+ inputs=question_input,
227
+ outputs=answer_output
228
+ )
229
+
230
+ gr.Markdown("""
231
+ ### Información
232
+ - Este asistente responde preguntas basadas en documentos de la UNAB
233
+ - Utiliza búsqueda semántica para encontrar fragmentos relevantes
234
+ - Las respuestas se generan automáticamente mediante IA
235
+ - Para mejores resultados, sé específico en tus preguntas
236
+ """)
237
+
238
+ if __name__ == "__main__":
239
+ demo.launch(share=False)
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ transformers>=4.35.0
2
+ torch
3
+ PyPDF2
4
+ sentence-transformers
5
+ requests
6
+ python-dotenv
7
+ chromadb
8
+ gradio