pmelnechuk commited on
Commit
bf7afac
·
verified ·
1 Parent(s): 09d087b
src/__pycache__/prueba.cpython-312.pyc ADDED
Binary file (275 Bytes). View file
 
src/app.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Este script realiza las siguientes tareas:
3
+ 1. Carga archivos desde un directorio especificado utilizando la clase `Loader` del módulo `preprocess`.
4
+ 2. Procesa los archivos mediante limpieza de texto y división en fragmentos.
5
+ 3. Genera representaciones vectoriales de los textos utilizando `sentence-transformers`.
6
+ 4. Almacena los vectores en una base de datos Chroma para su posterior recuperación.
7
+ 5. Inicializa un modelo y ejecuta una interfaz para interactuar con los datos procesados.
8
+
9
+ Módulos utilizados:
10
+ - `preprocess`: Contiene la clase `Loader` para la carga y preprocesamiento de documentos.
11
+ - `vdb`: Se asume que gestiona la base de datos vectorial.
12
+ - `model_load`: Módulo para cargar el modelo de machine learning.
13
+ - `st`: Se asume que proporciona la interfaz de usuario.
14
+
15
+ Estructura del código:
16
+ 1. Define el directorio de los archivos a procesar.
17
+ 2. Carga los archivos y los procesa si el número de archivos es menor a 2.
18
+ 3. Si hay múltiples archivos, los procesa en un bucle y concatena los fragmentos.
19
+ 4. Genera embeddings utilizando `sentence-transformers/all-MiniLM-L12-v2`.
20
+ 5. Almacena los embeddings en ChromaDB y configura un recuperador basado en similitud.
21
+ 6. Carga el modelo de machine learning.
22
+ 7. Inicia la interfaz de usuario.
23
+
24
+ """
25
+
26
+ from preprocess import Loader
27
+ import vdb
28
+ import model_load
29
+ import st
30
+
31
+ if __name__=="__main__":
32
+ # Definición de directorio
33
+ archivo = r"directorio"
34
+
35
+ # Carga de archivos y procesamiento de texto
36
+ if len(archivo) < 2:
37
+ Load = Loader(archivo)
38
+ documentos = Load.load_docs()
39
+ textos_limpios = [Load.limpiar_texto(doc) for doc in documentos.page_content]
40
+ textos = Load.splitter(texto=textos_limpios, chunk_size=500, chunk_overlap=50)
41
+ else:
42
+ textos = []
43
+ for i in range(len(archivo)):
44
+ Load = Loader(archivo)
45
+ documentos = Load.load_docs()
46
+ textos_limpios = [Load.limpiar_texto(doc) for doc in documentos.page_content]
47
+ chunks = Load.splitter(texto=textos_limpios, chunk_size=500, chunk_overlap=50)
48
+ textos.extend(chunks)
49
+
50
+ # Generación de embeddings y almacenamiento en base de datos ChromaDB
51
+ embeddings = EmbeddingGen("sentence-transformers/all-MiniLM-L12-v2")
52
+ db = Chroma("QAMath", embedding_function=embeddings)
53
+ vectorstore = db.from_documents(chunks, embeddings)
54
+ retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 3})
55
+
56
+ # Carga del modelo y ejecución de la interfaz
57
+ load_model()
58
+ interfaz()
59
+
src/model_load.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.chains import RetrievalQAWithSourcesChain
2
+ from langchain.llms import HuggingFacePipeline
3
+ from transformers import AutoTokenizer, pipeline, AutoModelForCausalLM, BitsAndBytesConfig
4
+ import torch
5
+ from langchain.prompts import PromptTemplate
6
+ from langchain.llms import HuggingFaceHub
7
+ from langchain.chains import LLMChain
8
+
9
+ def load_model():
10
+ model_name="tiiuae/Falcon3-10B-Instruct"
11
+
12
+ # Cargar tokenizer y modelo de Hugging Face
13
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
14
+ model = AutoModelForCausalLM.from_pretrained(model_name,
15
+ torch_dtype=torch.float16,
16
+ device_map="auto",
17
+ quantization_config=BitsAndBytesConfig(load_in_8bit=True))
18
+
19
+ # Crear pipeline de generación de texto
20
+ text_generation_pipeline = pipeline(
21
+ "text-generation",
22
+ model=model,
23
+ tokenizer=tokenizer,
24
+ max_new_tokens=128,
25
+ repetition_penalty=1.2,
26
+ device_map="auto"
27
+ )
28
+ # Crear el LLM compatible con LangChain
29
+ llm = HuggingFacePipeline(pipeline=text_generation_pipeline)
30
+
31
+ # Crear la plantilla de prompt que tomará el texto y la pregunta
32
+ prompt_template = """
33
+ Dado el siguiente texto extraído de varios documentos y una pregunta, crea una respuesta utilizando la información proporcionada. Si la pregunta sale por fuera de la información proporcionada responde con "No tengo información al respecto" y corta la respuesta.
34
+
35
+ **Documentos relevantes:**
36
+ {documento}
37
+
38
+ **Pregunta:**
39
+ {pregunta}
40
+
41
+ **Respuesta:**
42
+ """
43
+
44
+ # Crear el prompt con las variables necesarias
45
+ prompt = PromptTemplate(input_variables=["documento", "pregunta"], template=prompt_template)
46
+
47
+ # Crear una cadena de LLMChain que combine el retriever y el prompt
48
+ qa_chain = LLMChain(llm=llm, prompt=prompt)
49
+
50
+ def ask(pregunta: str,retriever):
51
+
52
+ #Busqueda de documentos mediante el retriever
53
+ documentos=retriever.get_relevant_documents(pregunta)
54
+
55
+ #Generacion de la respuesta
56
+ respuesta = qa_chain.invoke({
57
+ "documentos": "\n".join([doc.page_content for doc in docs_relevantes]),
58
+ "pregunta": pregunta
59
+ })
60
+
61
+ return respuesta["text"].split("**Respuesta:**\n")[1]
src/preprocess.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_community.document_loaders import PyMuPDFLoader
2
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
3
+ import re
4
+
5
+ class Loader:
6
+ """Clase encargada de la carga desde PDFs,
7
+ admite PDFs con texto seleccionable unicamente. Realiza
8
+ carga y devuelve lista de chunks de texto.
9
+ """
10
+ def __init__(self, path: str):
11
+ self.path = path
12
+
13
+ def load_docs(self,pag:slice=None):
14
+ """Carga el PDF y devuelve lista de chunks de texto.
15
+ """
16
+ loader=PyMuPDFLoader(self.path)
17
+ docs=loader.load()
18
+ if pag:
19
+ docs=docs[pag]
20
+ return [doc.page_content for doc in docs]
21
+ @staticmethod
22
+
23
+ def limpiar_texto(self,texto: str) -> str:
24
+ """
25
+ Limpia el texto eliminando caracteres basura y normalizando espacios y saltos de línea.
26
+ Esta función está diseñada para preprocesar libros u otros documentos largos,
27
+ facilitando su uso en aplicaciones de Retrieval Augmented Generation (RAG).
28
+
29
+ Args:
30
+ texto (str): El texto original a limpiar.
31
+
32
+ Returns:
33
+ str: El texto limpio.
34
+ """
35
+ # 1. Eliminar saltos de línea, tabulaciones y otros caracteres de control
36
+ texto = re.sub(r'[\r\n\t]+', ' ', texto)
37
+
38
+ # 2. Eliminar caracteres no imprimibles (códigos de control)
39
+ texto = re.sub(r'[\x00-\x1F\x7F]', '', texto)
40
+
41
+ # 3. Sustituir múltiples espacios por uno solo
42
+ texto = re.sub(r'\s+', ' ', texto)
43
+
44
+ # 4. Eliminar caracteres que no sean letras, dígitos o signos de puntuación comunes
45
+ # Se conservan letras con acentos y caracteres propios del español.
46
+ texto = re.sub(r'[^\w\s.,;:¡!¿?\-áéíóúÁÉÍÓÚñÑ]', '', texto)
47
+
48
+ # 5. Eliminar espacios al inicio y al final
49
+ texto = texto.strip()
50
+
51
+ return texto
52
+
53
+ @staticmethod
54
+
55
+ def splitter(self,texto,chunk_size,chunk_overlap):
56
+ """
57
+ Divide el texto en chunks
58
+
59
+ Args:
60
+ chunk_size (int): Largo del chunk.
61
+ chunk_overlap (int): Sobreposicion de chunks
62
+ texto (list): lista de textos a procesar
63
+
64
+ Returns:
65
+ list: Los textos limpios.
66
+ """
67
+ splitter=RecursiveCharacterTextSplitter(
68
+ chunk_size=chunk_size,
69
+ chunk_overlap=chunk_overlap,
70
+ length_function=len,
71
+ separators=["\n\n","\n"," ",""]
72
+ )
73
+ chunks=splitter.create_documents(texto)
74
+
75
+ return chunks
76
+
src/st.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ def interfaz():
4
+
5
+ # Configuración de la página
6
+ st.set_page_config(
7
+ page_title="MathQA - Asistente de Matemáticas",
8
+ page_icon="🧮",
9
+ layout="centered",
10
+ initial_sidebar_state="expanded"
11
+ )
12
+
13
+ # Paleta de colores neutra
14
+ primary_color = "#010001"
15
+ secondary_color = "#E7E6E7"
16
+ background_color = "#FBFBFA"
17
+
18
+ # Estilos CSS
19
+ st.markdown(
20
+ f"""
21
+ <style>
22
+ .stApp {{ background-color: {background_color}; }}
23
+ .stTextInput>div>div>input {{
24
+ color: {primary_color};
25
+ background-color: {secondary_color};
26
+ border-radius: 8px;
27
+ }}
28
+ .stButton>button {{
29
+ color: {primary_color};
30
+ background-color: {secondary_color};
31
+ border-radius: 8px;
32
+ transition: all 0.3s;
33
+ }}
34
+ .history-box {{
35
+ border-left: 4px solid {secondary_color};
36
+ padding: 1rem;
37
+ margin: 1rem 0;
38
+ background-color: {secondary_color
39
+ };
40
+ border-radius: 8px;
41
+ }}
42
+ </style>
43
+ """,
44
+ unsafe_allow_html=True
45
+ )
46
+
47
+ # Inicializar historial
48
+ if 'history' not in st.session_state:
49
+ st.session_state.history = []
50
+
51
+ # Variable auxiliar para gestionar el input
52
+ if 'temp_input' not in st.session_state:
53
+ st.session_state.temp_input = ""
54
+
55
+ # Título de la aplicación
56
+ st.title("🧮 MathQA - Asistente de Matemáticas")
57
+ st.markdown("")
58
+
59
+ # Widget de entrada con variable auxiliar
60
+ user_input = st.text_input(
61
+ "Escribe tu pregunta matemática aquí:",
62
+ value=st.session_state.temp_input,
63
+ key="user_input",
64
+ placeholder="Ej: ¿Que es una integral?"
65
+ )
66
+
67
+ # Botón de acción
68
+ col1, col2, col3 = st.columns([5, 4, 4]) # Columnas vacías a los lados para centrar
69
+ with col2:
70
+ if st.button("Resolver pregunta"):
71
+ if user_input: # Accedemos al valor ingresado
72
+ # Simular respuesta
73
+ mock_answer = ask(user_input,retriever)
74
+
75
+ # Agregar al historial
76
+ st.session_state.history.insert(0, (user_input, mock_answer))
77
+
78
+ # Limpiar la variable auxiliar
79
+ st.session_state.temp_input = ""
80
+
81
+ # Forzar actualización
82
+ st.rerun()
83
+
84
+ # Mostrar historial
85
+ if st.session_state.history:
86
+ st.markdown("---")
87
+ st.subheader("Historial de Consultas")
88
+
89
+ for idx, (pregunta, respuesta) in enumerate(st.session_state.history):
90
+ with st.container():
91
+ st.markdown(
92
+ f"""
93
+ <div class="history-box">
94
+ <strong>Pregunta {len(st.session_state.history)-idx}:</strong>
95
+ <p>{pregunta}</p>
96
+ <strong>Respuesta:</strong>
97
+ <p>{respuesta}</p>
98
+ </div>
99
+ """,
100
+ unsafe_allow_html=True
101
+ )
102
+
103
+ # Pie de página
104
+ st.markdown("---")
105
+ st.markdown("🔍 ¿Necesitas ayuda con álgebra, cálculo o geometría? ¡Estoy aquí para ayudarte!")
src/vdb.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sentence_transformers import SentenceTransformer
2
+ from langchain.schema import Document
3
+
4
+
5
+
6
+ class EmbeddingGen:
7
+
8
+ def __init__(self, model_name: str):
9
+ self.model = SentenceTransformer(model_name)
10
+
11
+ def embed_documents(self, chunks):
12
+ return [self.model.encode(chunk) for chunk in chunks]
13
+
14
+ def embed_query(self, text):
15
+ return self.model.encode(text)
16
+