import gradio as gr import pandas as pd import numpy as np import matplotlib.pyplot as plt import os import json import re from io import BytesIO import base64 from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer from sklearn.metrics.pairwise import cosine_similarity, euclidean_distances from sklearn.decomposition import TruncatedSVD class SemanticSearch: def __init__(self): # Palabras de parada (stopwords) en español extendidas self.spanish_stopwords = [ 'a', 'al', 'ante', 'bajo', 'cabe', 'con', 'contra', 'de', 'del', 'desde', 'durante', 'en', 'entre', 'hacia', 'hasta', 'mediante', 'para', 'por', 'según', 'sin', 'so', 'sobre', 'tras', 'el', 'la', 'lo', 'los', 'las', 'un', 'una', 'unos', 'unas', 'y', 'e', 'ni', 'o', 'u', 'pero', 'mas', 'sino', 'que', 'aunque', 'si', 'como', 'cuando', 'donde', 'quien', 'cuyo', 'cuya', 'cuyos', 'cuyas', 'este', 'esta', 'esto', 'estos', 'estas', 'ese', 'esa', 'eso', 'esos', 'esas', 'aquel', 'aquella', 'aquello', 'aquellos', 'aquellas', 'mi', 'mis', 'tu', 'tus', 'su', 'sus', 'nuestro', 'nuestra', 'nuestros', 'nuestras', 'vuestro', 'vuestra', 'vuestros', 'vuestras', 'le', 'les', 'me', 'nos', 'te', 'os', 'lo', 'los', 'la', 'las', 'se', 'muy', 'mucho', 'mucha', 'muchos', 'muchas', 'poco', 'poca', 'pocos', 'pocas', 'bastante', 'bastantes', 'demasiado', 'demasiada', 'demasiados', 'demasiadas', 'más', 'menos', 'tan', 'tanto', 'tanta', 'tantos', 'tantas', 'alguno', 'alguna', 'algunos', 'algunas', 'ninguno', 'ninguna', 'ningunos', 'ningunas', 'otro', 'otra', 'otros', 'otras', 'mismo', 'misma', 'mismos', 'mismas', 'tal', 'tales', 'así', 'también', 'tampoco', 'siempre', 'nunca', 'jamás', 'ahora', 'después', 'antes', 'luego', 'despacio', 'pronto', 'cerca', 'lejos', 'aquí', 'allí', 'hoy', 'ayer', 'mañana', 'sí', 'no', 'quizá', 'quizás', 'acaso', 'bien', 'mal', 'mejor', 'peor', 'estoy', 'estás', 'está', 'estamos', 'estáis', 'están', 'esté', 'estés', 'estemos', 'estéis', 'estén', 'estaré', 'estarás', 'estará', 'estaremos', 'estaréis', 'estarán', 'estaría', 'estarías', 'estaríamos', 'estaríais', 'estarían', 'estaba', 'estabas', 'estábamos', 'estabais', 'estaban', 'estuve', 'estuviste', 'estuvo', 'estuvimos', 'estuvisteis', 'estuvieron', 'estuviera', 'estuvieras', 'estuviéramos', 'estuvierais', 'estuvieran', 'estuviese', 'estuvieses', 'estuviésemos', 'estuvieseis', 'estuviesen', 'soy', 'eres', 'es', 'somos', 'sois', 'son', 'sea', 'seas', 'seamos', 'seáis', 'sean', 'seré', 'serás', 'será', 'seremos', 'seréis', 'serán', 'sería', 'serías', 'seríamos', 'seríais', 'serían', 'era', 'eras', 'éramos', 'erais', 'eran', 'fui', 'fuiste', 'fue', 'fuimos', 'fuisteis', 'fueron', 'fuera', 'fueras', 'fuéramos', 'fuerais', 'fueran', 'fuese', 'fueses', 'fuésemos', 'fueseis', 'fuesen', 'tengo', 'tienes', 'tiene', 'tenemos', 'tenéis', 'tienen', 'tenga', 'tengas', 'tengamos', 'tengáis', 'tengan', 'tendré', 'tendrás', 'tendrá', 'tendremos', 'tendréis', 'tendrán', 'tendría', 'tendrías', 'tendríamos', 'tendríais', 'tendrían', 'tenía', 'tenías', 'teníamos', 'teníais', 'tenían', 'tuve', 'tuviste', 'tuvo', 'tuvimos', 'tuvisteis', 'tuvieron', 'tuviera', 'tuvieras', 'tuviéramos', 'tuvierais', 'tuvieran', 'tuviese', 'tuvieses', 'tuviésemos', 'tuvieseis', 'tuviesen', 'he', 'has', 'ha', 'hemos', 'habéis', 'han', 'haya', 'hayas', 'hayamos', 'hayáis', 'hayan', 'habré', 'habrás', 'habrá', 'habremos', 'habréis', 'habrán', 'habría', 'habrías', 'habríamos', 'habríais', 'habrían', 'había', 'habías', 'habíamos', 'habíais', 'habían', 'hube', 'hubiste', 'hubo', 'hubimos', 'hubisteis', 'hubieron', 'hubiera', 'hubieras', 'hubiéramos', 'hubierais', 'hubieran', 'hubiese', 'hubieses', 'hubiésemos', 'hubieseis', 'hubiesen', 'voy', 'vas', 'va', 'vamos', 'vais', 'van', 'vaya', 'vayas', 'vayamos', 'vayáis', 'vayan', 'iré', 'irás', 'irá', 'iremos', 'iréis', 'irán', 'iría', 'irías', 'iríamos', 'iríais', 'irían', 'iba', 'ibas', 'íbamos', 'ibais', 'iban', 'fui', 'fuiste', 'fue', 'fuimos', 'fuisteis', 'fueron', 'fuera', 'fueras', 'fuéramos', 'fuerais', 'fueran', 'fuese', 'fueses', 'fuésemos', 'fueseis', 'fuesen', ] # Lista de términos emocionales y psicológicos relevantes (para dar mayor peso) self.emotional_terms = [ 'tristeza', 'felicidad', 'ansiedad', 'depresión', 'miedo', 'ira', 'rabia', 'alegría', 'angustia', 'pánico', 'obsesión', 'compulsión', 'trauma', 'estrés', 'duelo', 'pérdida', 'amor', 'odio', 'soledad', 'culpa', 'vergüenza', 'emoción', 'sentimiento', 'pensamiento', 'conducta', 'comportamiento', 'personalidad', 'trastorno', 'síndrome', 'crisis', 'episodio', 'recaída', 'recuperación', 'terapia', 'tratamiento', 'diagnóstico', 'psicología', 'psiquiatría', 'mental', 'cognitivo', 'afectivo', 'conductual', 'emocional', 'borderline', 'narcisista', 'psicótico', 'neurótico', 'delirante', 'fobia', 'manía', 'bipolar', 'esquizofrenia', 'esquizoide', 'paranoia', 'paranoide' ] self.embeddings = {} self.datasets = {} self.synonyms = self._load_synonyms() def _load_synonyms(self): """Carga diccionario de sinónimos para términos psicológicos comunes""" # Diccionario simplificado de sinónimos synonyms = { 'triste': ['tristeza', 'melancolía', 'depresión', 'abatimiento', 'pena'], 'deprimido': ['depresión', 'tristeza', 'melancolía', 'abatimiento'], 'nervioso': ['ansiedad', 'nerviosismo', 'inquietud', 'agitación'], 'ansioso': ['ansiedad', 'angustia', 'nerviosismo', 'inquietud'], 'angustiado': ['angustia', 'ansiedad', 'aflicción', 'desasosiego'], 'miedoso': ['miedo', 'temor', 'pavor', 'fobia', 'pánico'], 'enojado': ['ira', 'enfado', 'rabia', 'furia', 'cólera', 'enojo'], 'feliz': ['felicidad', 'alegría', 'dicha', 'júbilo', 'gozo'], 'solo': ['soledad', 'aislamiento', 'abandono'], 'culpable': ['culpa', 'remordimiento', 'arrepentimiento'], 'avergonzado': ['vergüenza', 'bochorno', 'pudor'], 'obsesivo': ['obsesión', 'compulsión', 'rumiación', 'idea fija'], 'traumado': ['trauma', 'traumático', 'traumatizado'], 'estresado': ['estrés', 'tensión', 'presión', 'agobio'], 'bipolar': ['bipolaridad', 'maníaco-depresivo', 'ciclotimia'], 'esquizofrénico': ['esquizofrenia', 'psicosis'], 'paranoico': ['paranoia', 'persecutorio', 'suspicacia'], 'fóbico': ['fobia', 'miedo irracional', 'aversión'], 'compulsivo': ['compulsión', 'obsesivo-compulsivo', 'ritual'], 'maníaco': ['manía', 'euforia', 'hiperactividad'], 'psicótico': ['psicosis', 'delirio', 'alucinación', 'desconexión'] } return synonyms def load_csv_data(self, file_path): """Carga datos de archivos CSV.""" try: if os.path.exists(file_path): data = pd.read_csv(file_path) # Limpiar y estandarizar columnas if 'Description' in data.columns: # Eliminar indicadores de truncamiento data['Description'] = data['Description'].str.replace('...[Truncated]', '', regex=False) return data else: print(f"Archivo no encontrado: {file_path}") return None except Exception as e: print(f"Error cargando {file_path}: {str(e)}") return None def preprocess_text(self, text): """ Preprocesa el texto para mejorar la calidad de la búsqueda - Elimina caracteres no alfanuméricos - Convierte a minúsculas - Elimina stopwords - Expande sinónimos de términos psicológicos """ if not text: return "" # Convertir a minúsculas text = text.lower() # Eliminar caracteres especiales text = re.sub(r'[^\w\s]', ' ', text) # Dividir en palabras words = text.split() # Eliminar stopwords filtered_words = [word for word in words if word not in self.spanish_stopwords] # Expandir texto con sinónimos para términos clave expanded_words = filtered_words.copy() for word in filtered_words: # Buscar sinónimos if word in self.synonyms: # Añadir sinónimos al texto expanded_words.extend(self.synonyms[word]) # Aumentar peso de términos emocionales repitiendo palabras clave weighted_words = expanded_words.copy() for word in expanded_words: if word in self.emotional_terms: # Repetir 2 veces los términos emocionales para aumentar su peso weighted_words.append(word) weighted_words.append(word) # Unir palabras return ' '.join(weighted_words) def create_embeddings(self, dataframe, method='tfidf', text_column='Description', min_df=2, max_df=0.95, ngram_range=(1, 2), lsa_components=None): """ Crea embeddings para datos de texto usando diferentes métodos. Args: dataframe: DataFrame con los textos method: Método de vectorización ('tfidf', 'count', 'binary') text_column: Columna con el texto min_df: Frecuencia mínima de documento max_df: Frecuencia máxima de documento ngram_range: Rango de n-gramas lsa_components: Número de componentes para LSA (opcional) Returns: dict: Diccionario con vectorizador, embeddings y dataframe """ if dataframe is None or len(dataframe) == 0 or text_column not in dataframe.columns: return None # Preprocesar textos texts = dataframe[text_column].fillna('').astype(str) cleaned_texts = [] for text in texts: cleaned_text = self.preprocess_text(text) cleaned_texts.append(cleaned_text) # Crear vectorizador según el método if method == 'tfidf': vectorizer = TfidfVectorizer( min_df=min_df, max_df=max_df, ngram_range=ngram_range, stop_words='english' # Ya eliminamos stopwords en el preprocesamiento ) elif method == 'count': vectorizer = CountVectorizer( min_df=min_df, max_df=max_df, ngram_range=ngram_range, stop_words='english' ) elif method == 'binary': vectorizer = CountVectorizer( min_df=min_df, max_df=max_df, ngram_range=ngram_range, stop_words='english', binary=True ) else: # Por defecto usar TF-IDF vectorizer = TfidfVectorizer( min_df=min_df, max_df=max_df, ngram_range=ngram_range, stop_words='english' ) # Crear vectores embeddings = vectorizer.fit_transform(cleaned_texts) # Aplicar LSA/LSI si se especifica svd = None if lsa_components is not None and lsa_components > 0: svd = TruncatedSVD(n_components=min(lsa_components, embeddings.shape[1]-1)) embeddings_lsa = svd.fit_transform(embeddings) embeddings = embeddings_lsa return { 'vectorizer': vectorizer, 'embeddings': embeddings, 'dataframe': dataframe, 'svd': svd } def search_embeddings(self, query, embedding_data, n_results=5, similarity_metric='cosine', threshold=0.1, highlight_terms=True): """ Busca resultados similares utilizando embeddings. Args: query: Consulta de búsqueda embedding_data: Datos de embeddings n_results: Número de resultados a retornar similarity_metric: Métrica de similitud ('cosine', 'euclidean') threshold: Umbral de relevancia highlight_terms: Si se deben resaltar términos en resultados Returns: list: Lista de resultados """ if embedding_data is None: return [] vectorizer = embedding_data['vectorizer'] embeddings = embedding_data['embeddings'] dataframe = embedding_data['dataframe'] svd = embedding_data.get('svd') # Preprocesar consulta (expandir con sinónimos y dar peso a términos emocionales) processed_query = self.preprocess_text(query) # Vectorizar consulta query_vector = vectorizer.transform([processed_query]) # Aplicar LSA/LSI si se usó en el entrenamiento if svd is not None: query_vector = svd.transform(query_vector) # Calcular similitud según la métrica elegida if similarity_metric == 'cosine': similarities = cosine_similarity(query_vector, embeddings)[0] elif similarity_metric == 'euclidean': # Convertir distancia euclidiana a similitud (menor distancia = mayor similitud) distances = euclidean_distances(query_vector, embeddings)[0] max_dist = np.max(distances) similarities = 1 - (distances / max_dist) # Normalizar a [0,1] else: # Por defecto usar similitud del coseno similarities = cosine_similarity(query_vector, embeddings)[0] # Obtener los N mejores resultados top_indices = np.argsort(similarities)[-n_results:][::-1] # Extraer términos relevantes para resaltado original_query_terms = query.lower().split() results = [] for idx in top_indices: if similarities[idx] > threshold: # Aplicar umbral de relevancia result = { 'score': float(similarities[idx]), 'id': dataframe.iloc[idx]['ID'] if 'ID' in dataframe.columns else None, 'description': dataframe.iloc[idx]['Description'] if 'Description' in dataframe.columns else None } # Añadir metadatos adicionales si están disponibles for col in dataframe.columns: if col not in ['ID', 'Description']: result[col.lower()] = dataframe.iloc[idx][col] # Resaltar términos de búsqueda si está habilitado if highlight_terms and 'description' in result and result['description']: result['highlighted_description'] = self.highlight_search_terms( result['description'], original_query_terms ) results.append(result) return results def highlight_search_terms(self, text, search_terms): """ Resalta términos de búsqueda en un texto. Args: text: Texto donde buscar search_terms: Lista de términos a resaltar Returns: str: Texto con términos resaltados en markdown """ if not text or not search_terms: return text # Obtener todas las palabras del texto highlighted = text # Expandir términos de búsqueda con sinónimos para mejorar el resaltado expanded_terms = set(search_terms) for term in search_terms: if term in self.synonyms: expanded_terms.update(self.synonyms[term]) # Resaltar cada término de búsqueda for term in expanded_terms: if len(term) > 2: # Solo términos significativos # Crear patrón para búsqueda insensible a mayúsculas/minúsculas pattern = re.compile(r'\b' + re.escape(term) + r'\b', re.IGNORECASE) # Reemplazar con versión resaltada highlighted = pattern.sub(r'**\g<0>**', highlighted) return highlighted def initialize_datasets(self): """Inicializa los conjuntos de datos y crea embeddings.""" # Cargar datos CSV self.datasets = { 'mecanismos_defensa': self.load_csv_data('attached_assets/mecanismos_defensa.csv'), 'dsmv': self.load_csv_data('attached_assets/dsmv.csv'), 'psicoterapias': self.load_csv_data('attached_assets/psicoterapias.csv'), 'semiologia': self.load_csv_data('attached_assets/semiologia.csv'), 'emociones': self.load_csv_data('attached_assets/emociones_fixed.csv') } # Crear embeddings para cada conjunto de datos con configuración por defecto for dataset_name, dataset in self.datasets.items(): if dataset is not None and len(dataset) > 0: self.embeddings[dataset_name] = self.create_embeddings(dataset, text_column='Description') return self.embeddings def create_all_embeddings(self, method='tfidf', min_df=2, max_df=0.95, ngram_min=1, ngram_max=2, lsa_components=None): """ Crea embeddings para todos los conjuntos de datos con parámetros personalizados. Args: method: Método de vectorización min_df: Frecuencia mínima de documento max_df: Frecuencia máxima de documento ngram_min: N-grama mínimo ngram_max: N-grama máximo lsa_components: Componentes LSA Returns: dict: Embeddings generados """ ngram_range = (ngram_min, ngram_max) # Crear embeddings para cada conjunto de datos con parámetros personalizados self.embeddings = {} for dataset_name, dataset in self.datasets.items(): if dataset is not None and len(dataset) > 0: self.embeddings[dataset_name] = self.create_embeddings( dataset, method=method, text_column='Description', min_df=min_df, max_df=max_df, ngram_range=ngram_range, lsa_components=lsa_components ) return self.embeddings def get_dataset_stats(self): """ Obtiene estadísticas sobre los conjuntos de datos. Returns: str: Texto con estadísticas """ stats = "### Estadísticas de bases de conocimiento\n\n" if not self.datasets: return stats + "No se han cargado bases de conocimiento." stats += "| Base de conocimiento | Registros | Campos |\n" stats += "|---------------------|-----------|--------|\n" for name, df in self.datasets.items(): if df is not None: stats += f"| {name} | {len(df)} | {', '.join(df.columns)} |\n" else: stats += f"| {name} | No disponible | - |\n" return stats def multi_dataset_search(self, query, datasets, n_results=3, similarity_metric='cosine', threshold=0.1, highlight_terms=True): """ Realiza búsqueda en múltiples conjuntos de datos. Args: query: Consulta de búsqueda datasets: Lista de conjuntos de datos donde buscar n_results: Número de resultados por dataset similarity_metric: Métrica de similitud threshold: Umbral de relevancia highlight_terms: Si se deben resaltar términos Returns: str: Resultados de búsqueda formateados """ if not query or len(query) < 3: return "Por favor ingrese al menos 3 caracteres para buscar." results_md = "" # Analizar la consulta para determinar términos más importantes important_terms = self._extract_psychological_terms(query) if important_terms: results_md += f"### Términos psicológicos detectados:\n" results_md += ", ".join(important_terms) + "\n\n" # Buscar en cada conjunto de datos seleccionado for dataset_name in datasets: if dataset_name not in self.embeddings: continue embedding_data = self.embeddings[dataset_name] if not embedding_data: continue results = self.search_embeddings( query, embedding_data, n_results=n_results, similarity_metric=similarity_metric, threshold=threshold, highlight_terms=highlight_terms ) if not results: continue # Añadir encabezado para este conjunto de datos results_md += f"## Resultados en {dataset_name.title()}\n\n" # Formatear resultados for result in results: results_md += f"### {result.get('id', 'Resultado')} (Relevancia: {result.get('score', 0):.2f})\n\n" # Mostrar categoría si está disponible if 'categoría' in result: results_md += f"**Categoría:** {result.get('categoría')}\n\n" elif 'category' in result: results_md += f"**Categoría:** {result.get('category')}\n\n" # Mostrar descripción (con términos resaltados si corresponde) if 'highlighted_description' in result: results_md += f"{result.get('highlighted_description', 'No hay descripción disponible.')}\n\n" else: results_md += f"{result.get('description', 'No hay descripción disponible.')}\n\n" # Mostrar metadatos adicionales específicos de cada base if dataset_name == 'mecanismos_defensa': if 'mecanismo de defensa' in result: results_md += f"**Mecanismo:** {result.get('mecanismo de defensa')}\n" if 'puntaje' in result: results_md += f"**Puntaje:** {result.get('puntaje')}\n" if 'justificación' in result: results_md += f"**Justificación:** {result.get('justificación')}\n" if 'impacto en relaciones' in result: results_md += f"**Impacto en relaciones:** {result.get('impacto en relaciones')}\n" elif dataset_name == 'emociones': if 'emoción' in result: results_md += f"**Emoción:** {result.get('emoción')}\n" if 'intensidad' in result: results_md += f"**Intensidad:** {result.get('intensidad')}\n" if 'valencia' in result: results_md += f"**Valencia:** {result.get('valencia')}\n" if 'manifestación física' in result: results_md += f"**Manifestación física:** {result.get('manifestación física')}\n" results_md += "---\n\n" if not results_md: return "No se encontraron resultados para esta búsqueda." return results_md def _extract_psychological_terms(self, text): """ Extrae términos psicológicos y emocionales relevantes del texto. Args: text: Texto a analizar Returns: list: Lista de términos psicológicos encontrados """ words = text.lower().split() # Buscar términos emocionales y sus sinónimos found_terms = [] # Buscar directamente términos emocionales for word in words: if word in self.emotional_terms: found_terms.append(word) # Buscar términos a través de sinónimos for word in words: for key, synonyms in self.synonyms.items(): if word == key or word in synonyms: if key not in found_terms: found_terms.append(key) return found_terms def visualize_embedding(self, dataset): """ Visualiza el espacio de embeddings para un conjunto de datos. Args: dataset: Nombre del conjunto de datos Returns: str: Imagen en base64 del gráfico """ if dataset not in self.embeddings: return None embedding_data = self.embeddings[dataset] if not embedding_data: return None embeddings = embedding_data['embeddings'] dataframe = embedding_data['dataframe'] # Usar SVD para reducir a 2 dimensiones para visualización svd = TruncatedSVD(n_components=2) embeddings_2d = svd.fit_transform(embeddings) # Crear figura fig, ax = plt.subplots(figsize=(10, 8)) # Dibujar puntos scatter = ax.scatter( embeddings_2d[:, 0], embeddings_2d[:, 1], alpha=0.6 ) # Etiquetas para algunos puntos (no todos para evitar saturación) n_labels = min(10, len(dataframe)) indices = np.random.choice(len(dataframe), n_labels, replace=False) for idx in indices: ax.annotate( dataframe.iloc[idx]['ID'] if 'ID' in dataframe.columns else f"Doc {idx}", (embeddings_2d[idx, 0], embeddings_2d[idx, 1]), fontsize=8 ) # Título y etiquetas ax.set_title(f'Visualización de embeddings: {dataset}') ax.set_xlabel('Dimensión 1') ax.set_ylabel('Dimensión 2') # Guardar como imagen base64 buf = BytesIO() fig.savefig(buf, format='png', bbox_inches='tight') buf.seek(0) img_str = base64.b64encode(buf.read()).decode('utf-8') plt.close(fig) return f"data:image/png;base64,{img_str}" # Inicializar búsqueda semántica semantic_search = SemanticSearch() semantic_search.initialize_datasets() # Función para búsqueda semántica avanzada def search_semantic_advanced(query, selected_datasets, n_results, similarity_metric, threshold, vectorization_method, highlight_terms): # Verificar si se seleccionaron datasets if not selected_datasets: return "Por favor, seleccione al menos una base de conocimiento." # Verificar la consulta if not query or len(query) < 3: return "Por favor ingrese al menos 3 caracteres para buscar." # Realizar búsqueda return semantic_search.multi_dataset_search( query=query, datasets=selected_datasets, n_results=n_results, similarity_metric=similarity_metric, threshold=threshold, highlight_terms=highlight_terms ) # Función para actualizar los embeddings def update_embeddings(vectorization_method, min_df, max_df, ngram_min, ngram_max, use_lsa, lsa_components): # Validar parámetros min_df = max(1, min_df) max_df = min(1.0, max_df) ngram_min = max(1, ngram_min) ngram_max = max(ngram_min, ngram_max) # Crear embeddings con parámetros personalizados lsa_comp = int(lsa_components) if use_lsa else None semantic_search.create_all_embeddings( method=vectorization_method, min_df=min_df, max_df=max_df, ngram_min=ngram_min, ngram_max=ngram_max, lsa_components=lsa_comp ) # Obtener estadísticas stats = semantic_search.get_dataset_stats() return stats + "\n\nEmbeddings actualizados correctamente con la configuración especificada." # Función para visualizar embeddings def visualize_embeddings(dataset): img = semantic_search.visualize_embedding(dataset) if img: return img else: return None # Definición de la interfaz def build_interface(): with gr.Blocks(title="Búsqueda Semántica Avanzada") as demo: gr.Markdown("# Búsqueda Semántica Avanzada") gr.Markdown(""" Esta herramienta permite explorar bases de conocimiento psicológico mediante búsqueda semántica avanzada. - Busque en múltiples bases de conocimiento simultáneamente - Ajuste parámetros de búsqueda y vectorización - Visualice el espacio de embeddings - Procesamiento avanzado de consultas con expansión de sinónimos """) with gr.Tabs(): # Pestaña de búsqueda with gr.Tab("Búsqueda"): with gr.Row(): with gr.Column(scale=2): query_input = gr.Textbox( label="Consulta", placeholder="Ingrese términos o conceptos para buscar...", lines=2 ) with gr.Row(): datasets_input = gr.CheckboxGroup( choices=["dsmv", "mecanismos_defensa", "psicoterapias", "semiologia", "emociones"], value=["dsmv", "mecanismos_defensa", "psicoterapias", "semiologia", "emociones"], label="Bases de conocimiento" ) with gr.Column(scale=1): with gr.Accordion("Opciones de búsqueda", open=True): results_slider = gr.Slider( minimum=1, maximum=10, value=3, step=1, label="Número de resultados por base" ) similarity_input = gr.Radio( choices=["cosine", "euclidean"], value="cosine", label="Métrica de similitud" ) threshold_slider = gr.Slider( minimum=0.0, maximum=0.5, value=0.1, step=0.01, label="Umbral de relevancia" ) vectorization_input = gr.Radio( choices=["tfidf", "count", "binary"], value="tfidf", label="Método de vectorización" ) highlight_checkbox = gr.Checkbox( value=True, label="Resaltar términos de búsqueda" ) search_button = gr.Button("Buscar") results_output = gr.Markdown(label="Resultados de búsqueda") # Ejemplos de búsqueda gr.Examples( examples=[ ["Me siento triste y con ganas de llorar todo el tiempo"], ["Mi hijo tiene problemas para concentrarse en la escuela"], ["Tengo miedo de salir de casa y estar en lugares con mucha gente"], ["No puedo dejar de pensar en que algo malo va a pasar"], ["A veces escucho voces que me dicen cosas negativas"] ], inputs=query_input, outputs=results_output, fn=lambda q: search_semantic_advanced( q, ["dsmv", "mecanismos_defensa", "psicoterapias", "semiologia", "emociones"], 3, "cosine", 0.1, "tfidf", True ), label="Ejemplos de consultas" ) search_button.click( fn=search_semantic_advanced, inputs=[ query_input, datasets_input, results_slider, similarity_input, threshold_slider, vectorization_input, highlight_checkbox ], outputs=results_output ) # Pestaña de configuración avanzada with gr.Tab("Configuración"): gr.Markdown("### Configuración avanzada de embeddings") gr.Markdown(""" Esta sección permite personalizar los parámetros de vectorización para los embeddings. Los cambios se aplicarán a todas las bases de conocimiento. """) with gr.Row(): with gr.Column(): adv_vectorization = gr.Radio( choices=["tfidf", "count", "binary"], value="tfidf", label="Método de vectorización" ) with gr.Row(): min_df_input = gr.Number(value=2, label="Frecuencia mínima de documento") max_df_input = gr.Number(value=0.95, label="Frecuencia máxima de documento") with gr.Row(): ngram_min_input = gr.Number(value=1, label="N-grama mínimo") ngram_max_input = gr.Number(value=2, label="N-grama máximo") with gr.Column(): use_lsa_checkbox = gr.Checkbox(value=False, label="Aplicar LSA/LSI (Reducción de dimensionalidad)") lsa_components_slider = gr.Slider( minimum=10, maximum=300, value=100, step=10, label="Componentes LSA", visible=False ) # Hacer visible el slider de componentes LSA solo si se activa LSA def update_lsa_visibility(use_lsa): return gr.update(visible=use_lsa) use_lsa_checkbox.change( fn=update_lsa_visibility, inputs=use_lsa_checkbox, outputs=lsa_components_slider ) update_button = gr.Button("Actualizar embeddings") config_status = gr.Markdown() update_button.click( fn=update_embeddings, inputs=[ adv_vectorization, min_df_input, max_df_input, ngram_min_input, ngram_max_input, use_lsa_checkbox, lsa_components_slider ], outputs=config_status ) # Pestaña de visualización with gr.Tab("Visualización"): gr.Markdown("### Visualización de espacios de embeddings") gr.Markdown(""" Esta sección permite visualizar el espacio de embeddings de las diferentes bases de conocimiento. Los puntos representan documentos en un espacio bidimensional (reducido mediante SVD). """) dataset_vis = gr.Radio( choices=["dsmv", "mecanismos_defensa", "psicoterapias", "semiologia", "emociones"], value="dsmv", label="Base de conocimiento a visualizar" ) vis_button = gr.Button("Generar visualización") embedding_vis = gr.Image(label="Visualización de embeddings") vis_button.click( fn=visualize_embeddings, inputs=dataset_vis, outputs=embedding_vis ) # Pestaña de información with gr.Tab("Información"): gr.Markdown("### Información sobre el procesamiento de consultas") gr.Markdown(""" #### Mejoras en el procesamiento de texto: 1. **Expansión de sinónimos**: - Las consultas se expanden automáticamente con sinónimos de términos psicológicos - Por ejemplo: "triste" → "tristeza", "melancolía", "depresión" 2. **Ponderación de términos emocionales**: - Términos emocionales y psicológicos reciben mayor peso en la búsqueda - Esto mejora la relevancia de los resultados relacionados con conceptos clínicos 3. **Resaltado mejorado**: - Los términos de búsqueda y sus sinónimos se resaltan en los resultados - Permite identificar mejor por qué un documento fue seleccionado 4. **Extracción de términos clave**: - La consulta se analiza para identificar términos psicológicos relevantes - Estos términos se muestran al inicio de los resultados 5. **Eliminación de palabras irrelevantes**: - Lista extendida de stopwords en español - Mejora la precisión al enfocarse en términos significativos """) # Estadísticas iniciales gr.Markdown("### Información de las bases de conocimiento") stats_output = gr.Markdown(value=semantic_search.get_dataset_stats()) return demo # Iniciar la aplicación if __name__ == "__main__": demo = build_interface() demo.launch(server_name="0.0.0.0", server_port=5000)