"""
============================================================================
MÓDULO RAG EDUCATIVO
============================================================================
Sistema RAG (Retrieval-Augmented Generation) educativo para aprender sobre
Máquinas de Boltzmann Restringidas usando papers científicos y Groq AI.
Autor: Sistema de Física
Versión: 1.0.0
"""
import streamlit as st
import os
import json
from pathlib import Path
from typing import List, Dict, Optional
import warnings
warnings.filterwarnings('ignore')
# Verificar si ChromaDB está disponible
try:
import chromadb
from chromadb.config import Settings
CHROMADB_AVAILABLE = True
except ImportError:
CHROMADB_AVAILABLE = False
st.warning("⚠️ ChromaDB no está instalado. Instala con: pip install chromadb")
# Verificar si Groq está disponible
try:
from groq import Groq
GROQ_AVAILABLE = True
except ImportError:
GROQ_AVAILABLE = False
st.warning("⚠️ Groq no está instalado. Instala con: pip install groq")
class EducationalRAG:
"""Sistema RAG educativo para aprender sobre RBMs"""
def __init__(self):
"""Inicializa el sistema RAG"""
self.groq_client = None
self.chroma_client = None
self.collection = None
self.papers_dir = Path("articles")
# Inicializar Groq si está disponible
if GROQ_AVAILABLE:
self._init_groq()
# Inicializar ChromaDB si está disponible
if CHROMADB_AVAILABLE:
self._init_chromadb()
def _init_groq(self):
"""Inicializa el cliente de Groq"""
try:
# Intentar obtener la API key de secrets o variables de entorno
api_key = None
# Primero intentar desde Streamlit secrets
if hasattr(st, 'secrets') and 'GROQ_API_KEY' in st.secrets:
api_key = st.secrets['GROQ_API_KEY']
# Luego desde variables de entorno
elif 'GROQ_API_KEY' in os.environ:
api_key = os.environ['GROQ_API_KEY']
if api_key:
self.groq_client = Groq(api_key=api_key)
else:
st.warning("⚠️ No se encontró GROQ_API_KEY. Configúrala en .streamlit/secrets.toml")
except Exception as e:
st.error(f"❌ Error inicializando Groq: {e}")
def _init_chromadb(self):
"""Inicializa ChromaDB"""
try:
# Crear directorio para ChromaDB
chroma_dir = Path("chroma_rbm_db")
chroma_dir.mkdir(exist_ok=True)
# Inicializar cliente
self.chroma_client = chromadb.PersistentClient(
path=str(chroma_dir),
settings=Settings(anonymized_telemetry=False)
)
# Obtener o crear colección
try:
self.collection = self.chroma_client.get_collection("rbm_papers")
except:
self.collection = self.chroma_client.create_collection(
name="rbm_papers",
metadata={"description": "Papers científicos sobre RBMs"}
)
except Exception as e:
st.error(f"❌ Error inicializando ChromaDB: {e}")
def get_available_papers(self) -> List[str]:
"""Obtiene lista de papers disponibles"""
if not self.papers_dir.exists():
return []
return [f.name for f in self.papers_dir.glob("*.pdf")]
def get_paper_references(self) -> Dict:
"""Carga las referencias de los papers desde el archivo JSON"""
references_file = self.papers_dir / "papers_references.json"
if references_file.exists():
try:
with open(references_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
st.warning(f"⚠️ Error cargando referencias: {e}")
return {}
return {}
def load_papers_to_db(self) -> bool:
"""Carga papers a ChromaDB si no están cargados"""
if not self.collection:
return False
try:
# Verificar si ya hay documentos
count = self.collection.count()
if count > 0:
return True
# Si no hay papers PDF, crear documentos de ejemplo
papers = self.get_available_papers()
if not papers:
st.warning("⚠️ No hay papers PDF. Creando base de conocimiento básica...")
self._create_basic_knowledge()
return True
st.info(f"📚 Cargando {len(papers)} papers a la base de datos...")
# Aquí se cargarían los PDFs reales, por ahora usamos conocimiento básico
self._create_basic_knowledge()
return True
except Exception as e:
st.error(f"❌ Error cargando papers: {e}")
return False
def _get_reference_mapping(self) -> Dict:
"""Obtiene mapeo de referencias desde el JSON o crea uno por defecto"""
references_file = self.papers_dir / "papers_references.json"
# Mapeo por defecto basado en los sources usados
default_mapping = {
"Hinton_2010_Practical_Guide": {
"author": "Geoffrey E. Hinton",
"year": 2010,
"citation": "Hinton, G. E. (2010). A Practical Guide to Training Restricted Boltzmann Machines. Technical Report UTML TR 2010-003, University of Toronto."
},
"Hinton_2002_Training": {
"author": "Geoffrey E. Hinton",
"year": 2002,
"citation": "Hinton, G. E. (2002). Training Products of Experts by Minimizing Contrastive Divergence. Neural Computation, 14(8), 1771-1800."
},
"Hinton_2002_CD": {
"author": "Geoffrey E. Hinton",
"year": 2002,
"citation": "Hinton, G. E. (2002). Training Products of Experts by Minimizing Contrastive Divergence. Neural Computation, 14(8), 1771-1800."
},
"Hinton_2010_Guide": {
"author": "Geoffrey E. Hinton",
"year": 2010,
"citation": "Hinton, G. E. (2010). A Practical Guide to Training Restricted Boltzmann Machines. Technical Report UTML TR 2010-003, University of Toronto."
},
"Salakhutdinov_2007": {
"author": "Salakhutdinov et al.",
"year": 2007,
"citation": "Salakhutdinov, R., Mnih, A., & Hinton, G. (2007). Restricted Boltzmann Machines for Collaborative Filtering. Proceedings of the 24th ICML."
},
"Hinton_2006_Science": {
"author": "Hinton & Salakhutdinov",
"year": 2006,
"citation": "Hinton, G. E., & Salakhutdinov, R. R. (2006). Reducing the Dimensionality of Data with Neural Networks. Science, 313(5786), 504-507."
},
"Hinton_2010_Practical": {
"author": "Geoffrey E. Hinton",
"year": 2010,
"citation": "Hinton, G. E. (2010). A Practical Guide to Training Restricted Boltzmann Machines. Technical Report UTML TR 2010-003, University of Toronto."
},
"Bengio_2013": {
"author": "Yoshua Bengio",
"year": 2013,
"citation": "Bengio, Y. (2013). Deep Learning of Representations: Looking Forward. Statistical Language and Speech Processing, LNCS 7978, 1-37."
},
"Hinton_2006_DBN": {
"author": "Hinton et al.",
"year": 2006,
"citation": "Hinton, G. E., Osindero, S., & Teh, Y. W. (2006). A Fast Learning Algorithm for Deep Belief Nets. Neural Computation, 18(7), 1527-1554."
},
"Tieleman_2008_PCD": {
"author": "Tijmen Tieleman",
"year": 2008,
"citation": "Tieleman, T. (2008). Training Restricted Boltzmann Machines using Approximations to the Likelihood Gradient. Proceedings of the 25th ICML."
}
}
return default_mapping
def _create_basic_knowledge(self):
"""Crea una base de conocimiento básica sobre RBMs"""
basic_knowledge = [
{
"id": "rbm_intro_1",
"content": """Una Máquina de Boltzmann Restringida (RBM) es un tipo de red neuronal generativa
estocástica que puede aprender una distribución de probabilidad sobre su conjunto de entradas.
Fue inventada por Geoffrey Hinton y es un componente fundamental en el aprendizaje profundo.
Las RBMs consisten en dos capas: una capa visible (v) que representa los datos observables y
una capa oculta (h) que captura las características latentes.
Referencia: Hinton, G. E. (2010). A Practical Guide to Training Restricted Boltzmann Machines.
Technical Report UTML TR 2010-003, University of Toronto.""",
"metadata": {"source": "Hinton_2010_Practical_Guide", "topic": "introducción",
"author": "Geoffrey E. Hinton", "year": 2010}
},
{
"id": "rbm_structure_1",
"content": """La arquitectura de una RBM es bipartita, lo que significa que no hay conexiones
entre unidades de la misma capa. Solo existen conexiones entre la capa visible y la capa oculta.
Esta restricción hace que el entrenamiento sea más eficiente que en las Máquinas de Boltzmann
completas. Cada conexión tiene un peso asociado (W), y cada unidad tiene un sesgo (bias).
Referencia: Hinton, G. E. (2002). Training Products of Experts by Minimizing Contrastive Divergence.
Neural Computation, 14(8), 1771-1800.""",
"metadata": {"source": "Hinton_2002_Training", "topic": "arquitectura",
"author": "Geoffrey E. Hinton", "year": 2002}
},
{
"id": "rbm_cd_1",
"content": """El algoritmo Contrastive Divergence (CD) es el método principal para entrenar RBMs.
Fue propuesto por Geoffrey Hinton en 2002. CD es una aproximación al gradiente de la log-verosimilitud
que es mucho más eficiente computacionalmente que el método exacto. El algoritmo alterna entre
muestrear la capa oculta dado la visible y viceversa, típicamente usando CD-1 (una sola iteración).
Referencia: Hinton, G. E. (2002). Training Products of Experts by Minimizing Contrastive Divergence.
Neural Computation, 14(8), 1771-1800.""",
"metadata": {"source": "Hinton_2002_CD", "topic": "entrenamiento",
"author": "Geoffrey E. Hinton", "year": 2002}
},
{
"id": "rbm_energy_1",
"content": """Las RBMs definen una función de energía E(v,h) = -a'v - b'h - v'Wh, donde a y b son
los sesgos de las capas visible y oculta respectivamente, y W es la matriz de pesos. La probabilidad
conjunta está dada por P(v,h) = exp(-E(v,h))/Z, donde Z es la función de partición. El objetivo
del entrenamiento es minimizar esta energía para los datos de entrenamiento.
Referencia: Hinton, G. E. (2010). A Practical Guide to Training Restricted Boltzmann Machines.
Technical Report UTML TR 2010-003, University of Toronto.""",
"metadata": {"source": "Hinton_2010_Guide", "topic": "función_energía",
"author": "Geoffrey E. Hinton", "year": 2010}
},
{
"id": "rbm_applications_1",
"content": """Las RBMs tienen múltiples aplicaciones: 1) Reducción de dimensionalidad y extracción
de características, 2) Sistemas de recomendación (filtrado colaborativo), 3) Inicialización de
redes neuronales profundas (Deep Belief Networks), 4) Modelado de tópicos, 5) Procesamiento de
imágenes y visión por computadora. Son especialmente útiles cuando se necesita aprender
representaciones no supervisadas de datos complejos.
Referencia: Salakhutdinov, R., Mnih, A., & Hinton, G. (2007). Restricted Boltzmann Machines
for Collaborative Filtering. Proceedings of the 24th International Conference on Machine Learning.""",
"metadata": {"source": "Salakhutdinov_2007", "topic": "aplicaciones",
"author": "Salakhutdinov et al.", "year": 2007}
},
{
"id": "rbm_vs_nn_1",
"content": """A diferencia de las redes neuronales tradicionales feedforward, las RBMs son modelos
generativos que pueden generar nuevos datos similares a los de entrenamiento. Las redes neuronales
tradicionales son discriminativas y se enfocan en mapear entradas a salidas. Las RBMs aprenden
la distribución de probabilidad de los datos, mientras que las redes feedforward aprenden una
función de mapeo. Además, las RBMs son no supervisadas, mientras que las redes tradicionales
típicamente requieren etiquetas.
Referencia: Hinton, G. E., & Salakhutdinov, R. R. (2006). Reducing the Dimensionality of Data
with Neural Networks. Science, 313(5786), 504-507.""",
"metadata": {"source": "Hinton_2006_Science", "topic": "comparación",
"author": "Hinton & Salakhutdinov", "year": 2006}
},
{
"id": "rbm_training_1",
"content": """El proceso de entrenamiento de una RBM involucra: 1) Inicializar pesos y sesgos
aleatoriamente, 2) Para cada mini-batch: calcular las activaciones de la capa oculta dado los
datos visibles (fase positiva), 3) Reconstruir la capa visible desde la oculta, 4) Recalcular
las activaciones ocultas (fase negativa), 5) Actualizar pesos usando la diferencia entre las
fases positiva y negativa. Este proceso se repite por múltiples épocas hasta convergencia.
Referencia: Hinton, G. E. (2010). A Practical Guide to Training Restricted Boltzmann Machines.
Technical Report UTML TR 2010-003, University of Toronto.""",
"metadata": {"source": "Hinton_2010_Practical", "topic": "proceso_entrenamiento",
"author": "Geoffrey E. Hinton", "year": 2010}
},
{
"id": "rbm_limitations_1",
"content": """Las RBMs tienen varias limitaciones: 1) Dificultad para entrenar en datasets muy
grandes, 2) La función de partición Z es intratable de calcular exactamente, 3) Pueden sufrir
de modos espurios en la distribución aprendida, 4) Requieren ajuste cuidadoso de hiperparámetros,
5) Han sido en gran parte superadas por métodos más modernos como VAEs y GANs para tareas
generativas. Sin embargo, siguen siendo valiosas para entender el aprendizaje profundo.
Referencia: Bengio, Y. (2013). Deep Learning of Representations: Looking Forward.
Statistical Language and Speech Processing, LNCS 7978, 1-37.""",
"metadata": {"source": "Bengio_2013", "topic": "limitaciones",
"author": "Yoshua Bengio", "year": 2013}
},
{
"id": "rbm_dbn_1",
"content": """Las Deep Belief Networks (DBNs) son redes profundas construidas apilando múltiples
RBMs. El entrenamiento se realiza capa por capa de forma greedy: primero se entrena la primera
RBM con los datos originales, luego se usa su capa oculta como entrada para entrenar la segunda
RBM, y así sucesivamente. Este pre-entrenamiento no supervisado fue crucial para el resurgimiento
del aprendizaje profundo en 2006, permitiendo entrenar redes más profundas efectivamente.
Referencia: Hinton, G. E., Osindero, S., & Teh, Y. W. (2006). A Fast Learning Algorithm for
Deep Belief Nets. Neural Computation, 18(7), 1527-1554.""",
"metadata": {"source": "Hinton_2006_DBN", "topic": "deep_belief_networks",
"author": "Hinton et al.", "year": 2006}
},
{
"id": "rbm_pcd_1",
"content": """Persistent Contrastive Divergence (PCD) es una mejora del algoritmo CD propuesta
por Tieleman en 2008. En lugar de reiniciar la cadena de Markov desde los datos en cada
actualización, PCD mantiene una cadena persistente que continúa entre actualizaciones. Esto
permite que la cadena explore mejor el espacio de estados y puede llevar a mejores modelos,
especialmente cuando se usan múltiples pasos de Gibbs sampling (CD-k con k>1).
Referencia: Tieleman, T. (2008). Training Restricted Boltzmann Machines using Approximations
to the Likelihood Gradient. Proceedings of the 25th International Conference on Machine Learning.""",
"metadata": {"source": "Tieleman_2008_PCD", "topic": "pcd",
"author": "Tijmen Tieleman", "year": 2008}
}
]
# Agregar documentos a la colección
for doc in basic_knowledge:
try:
self.collection.add(
documents=[doc["content"]],
metadatas=[doc["metadata"]],
ids=[doc["id"]]
)
except Exception as e:
st.warning(f"⚠️ Error agregando documento {doc['id']}: {e}")
st.success(f"✅ Base de conocimiento creada con {len(basic_knowledge)} documentos")
def query_papers(self, question: str, n_results: int = 3) -> List[Dict]:
"""Busca información relevante en los papers usando similitud coseno"""
if not self.collection:
return []
try:
results = self.collection.query(
query_texts=[question],
n_results=n_results
)
# Formatear resultados
formatted_results = []
if results and results['documents']:
for i, doc in enumerate(results['documents'][0]):
metadata = results['metadatas'][0][i] if results['metadatas'] else {}
distance = results['distances'][0][i] if results['distances'] else 0
# Calcular similitud coseno (1 - distancia L2 normalizada)
similarity = 1 - distance
formatted_results.append({
'content': doc,
'metadata': metadata,
'distance': distance,
'similarity': similarity
})
return formatted_results
except Exception as e:
st.error(f"❌ Error consultando papers: {e}")
return []
def generate_response(self, question: str, context: List[Dict]) -> str:
"""Genera respuesta usando Groq AI"""
if not self.groq_client:
return "⚠️ Groq AI no está configurado. Por favor configura tu API key."
try:
# Construir contexto
context_text = "\n\n".join([
f"Fragmento {i+1}:\n{doc['content']}"
for i, doc in enumerate(context)
])
# Crear prompt
system_prompt = """Eres un asistente educativo experto en Máquinas de Boltzmann Restringidas (RBMs)
y Deep Learning. Tu objetivo es explicar conceptos de manera clara y pedagógica, usando los papers
científicos como referencia. Siempre proporciona explicaciones detalladas pero accesibles."""
user_prompt = f"""Basándote en los siguientes fragmentos de papers científicos, responde la pregunta:
CONTEXTO:
{context_text}
PREGUNTA: {question}
Por favor proporciona una respuesta clara, detallada y educativa. Si es relevante, menciona las fuentes."""
# Llamar a Groq
response = self.groq_client.chat.completions.create(
model="llama-3.3-70b-versatile",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
temperature=0.7,
max_tokens=2000
)
return response.choices[0].message.content
except Exception as e:
return f"❌ Error generando respuesta: {e}"
[documentos]
def render_educational_rag_module():
"""Renderiza la interfaz del módulo RAG educativo"""
st.title("🎓 Aprende sobre RBMs")
st.markdown("### *Sistema RAG educativo con papers científicos*")
# Verificar dependencias
if not CHROMADB_AVAILABLE or not GROQ_AVAILABLE:
st.error("❌ Faltan dependencias necesarias")
st.markdown("""
**Instala las dependencias:**
```bash
pip install chromadb groq
```
""")
return
# Inicializar RAG
if 'rag_system' not in st.session_state:
with st.spinner("🔧 Inicializando sistema RAG..."):
st.session_state.rag_system = EducationalRAG()
rag = st.session_state.rag_system
# Verificar configuración
if not rag.groq_client:
st.warning("⚠️ Configura tu API key de Groq en `.streamlit/secrets.toml`:")
st.code("""
GROQ_API_KEY = "tu_api_key_aqui"
""")
st.markdown("[Obtén tu API key gratis en Groq](https://console.groq.com)")
return
# Información de papers disponibles
papers = rag.get_available_papers()
col1, col2 = st.columns([2, 1])
with col1:
st.subheader("📚 Base de Conocimiento")
if papers:
st.success(f"✅ {len(papers)} papers científicos disponibles")
# Cargar referencias
references = rag.get_paper_references()
with st.expander("Ver lista de papers con referencias completas"):
for paper in sorted(papers):
if paper in references:
ref = references[paper]
st.markdown(f"""
**📄 {ref['title']}**
*{ref['authors']}* ({ref['year']})
{ref['publication']}
`{paper}`
""")
st.divider()
else:
st.markdown(f"- 📄 {paper}")
else:
st.warning("⚠️ No hay papers cargados")
st.markdown("""
**Para cargar papers:**
1. Ejecuta: `python src/libros.py`
2. O coloca PDFs manualmente en la carpeta `articles/`
""")
with col2:
st.subheader("🤖 Modelo")
st.info("**Llama 3.3 70B**\nvia Groq AI")
st.divider()
# Sección de gestión de papers
with st.expander("📤 Gestionar Papers Personalizados"):
st.markdown("### Agregar Nuevos Papers")
tab1, tab2 = st.tabs(["➕ Agregar URL", "📝 Editar Referencias"])
with tab1:
st.markdown("**Descargar paper desde URL:**")
col1, col2 = st.columns([3, 1])
with col1:
paper_url = st.text_input(
"URL del paper (PDF):",
placeholder="https://ejemplo.com/paper.pdf",
key="paper_url_input"
)
with col2:
st.markdown("<br>", unsafe_allow_html=True)
download_btn = st.button("⬇️ Descargar", use_container_width=True)
if download_btn and paper_url:
with st.spinner("📥 Descargando paper..."):
try:
import requests
response = requests.get(paper_url, timeout=30, headers={'User-Agent': 'Mozilla/5.0'})
response.raise_for_status()
# Generar nombre de archivo
filename = paper_url.split('/')[-1]
if not filename.endswith('.pdf'):
filename = f"paper_{len(papers)+1}.pdf"
filepath = rag.papers_dir / filename
with open(filepath, 'wb') as f:
f.write(response.content)
st.success(f"✅ Paper descargado: {filename}")
st.info("💡 Agrega la referencia completa en la pestaña 'Editar Referencias'")
st.rerun()
except Exception as e:
st.error(f"❌ Error descargando: {e}")
st.markdown("---")
st.markdown("**Papers actuales:**")
if papers:
for paper in sorted(papers):
col1, col2 = st.columns([4, 1])
with col1:
st.text(f"📄 {paper}")
with col2:
if st.button("🗑️", key=f"delete_{paper}"):
try:
(rag.papers_dir / paper).unlink()
st.success(f"✅ Eliminado: {paper}")
st.rerun()
except Exception as e:
st.error(f"❌ Error: {e}")
else:
st.info("No hay papers cargados")
with tab2:
st.markdown("**Editar referencias bibliográficas:**")
# Cargar referencias actuales
references = rag.get_paper_references()
# Selector de paper
if papers:
selected_paper = st.selectbox(
"Selecciona un paper:",
options=["-- Nuevo --"] + sorted(papers),
key="paper_selector"
)
# Si es nuevo o existente
if selected_paper != "-- Nuevo --":
current_ref = references.get(selected_paper, {})
else:
current_ref = {}
selected_paper = st.text_input("Nombre del archivo PDF:", placeholder="ejemplo.pdf", key="new_paper_name")
# Formulario de referencia
if selected_paper and selected_paper != "-- Nuevo --":
st.markdown(f"**Editando:** `{selected_paper}`")
title = st.text_input(
"Título del paper:",
value=current_ref.get('title', ''),
placeholder="Training Restricted Boltzmann Machines",
key="ref_title"
)
authors = st.text_input(
"Autores:",
value=current_ref.get('authors', ''),
placeholder="Geoffrey E. Hinton",
key="ref_authors"
)
year = st.number_input(
"Año:",
min_value=1980,
max_value=2025,
value=current_ref.get('year', 2010),
key="ref_year"
)
publication = st.text_input(
"Publicación:",
value=current_ref.get('publication', ''),
placeholder="Neural Computation, 14(8), 1771-1800",
key="ref_publication"
)
# Generar citación automática
if title and authors and year:
citation = f"{authors} ({year}). {title}. {publication}"
st.text_area("Citación generada:", value=citation, height=100, disabled=True, key="ref_citation_preview")
# Botón guardar
if st.button("💾 Guardar Referencia", type="primary", key="save_ref_btn"):
try:
# Actualizar referencias
references[selected_paper] = {
"title": title,
"authors": authors,
"year": year,
"publication": publication,
"citation": citation if title and authors and year else ""
}
# Guardar en JSON
ref_file = rag.papers_dir / "papers_references.json"
with open(ref_file, 'w', encoding='utf-8') as f:
json.dump(references, f, indent=2, ensure_ascii=False)
st.success("✅ Referencia guardada exitosamente")
st.rerun()
except Exception as e:
st.error(f"❌ Error guardando: {e}")
else:
st.info("No hay papers disponibles. Descarga algunos primero.")
st.divider()
# Cargar papers si no están cargados
if rag.collection and rag.collection.count() == 0:
with st.spinner("📚 Inicializando base de conocimiento..."):
rag.load_papers_to_db()
# Preguntas sugeridas
st.subheader("💡 Preguntas Sugeridas")
suggested_questions = {
"🔰 Básico": [
"¿Qué es una Máquina de Boltzmann Restringida?",
"¿Cuál es la diferencia entre RBM y una red neuronal tradicional?",
"¿Para qué se usan las RBMs?",
],
"🎯 Intermedio": [
"¿Cómo funciona el algoritmo Contrastive Divergence?",
"¿Qué son las unidades visibles y ocultas en una RBM?",
"¿Cómo se entrenan las RBMs?",
],
"🚀 Avanzado": [
"¿Cuáles son las limitaciones de las RBMs?",
"¿Cómo se apilan RBMs para crear Deep Belief Networks?",
"¿Qué es Persistent Contrastive Divergence?",
]
}
# Inicializar question en session_state si no existe
if 'current_question' not in st.session_state:
st.session_state.current_question = ""
# Inicializar trigger para búsqueda automática
if 'auto_search' not in st.session_state:
st.session_state.auto_search = False
cols = st.columns(3)
for i, (level, questions) in enumerate(suggested_questions.items()):
with cols[i]:
st.markdown(f"**{level}**")
for q in questions:
if st.button(q, key=f"suggest_{q}", use_container_width=True):
st.session_state.current_question = q
st.session_state.auto_search = True
st.rerun()
st.divider()
# Chat interface
st.subheader("💬 Haz tu Pregunta")
question = st.text_area(
"Escribe tu pregunta sobre RBMs:",
value=st.session_state.current_question,
height=100,
placeholder="Ejemplo: ¿Cómo funciona el algoritmo Contrastive Divergence en las RBMs?",
key="question_input"
)
col1, col2, col3 = st.columns([2, 1, 1])
with col1:
ask_button = st.button("🔍 Buscar Respuesta", type="primary", use_container_width=True)
with col2:
n_results = st.number_input("Fragmentos", min_value=1, max_value=5, value=3)
with col3:
st.caption("**Métrica:**")
st.caption("Similitud Coseno")
# Ejecutar búsqueda si se presionó el botón O si hay auto_search activado
should_search = (ask_button and question) or (st.session_state.auto_search and question)
if should_search:
# Resetear auto_search
st.session_state.auto_search = False
with st.spinner("🔍 Buscando en papers científicos..."):
# Buscar contexto relevante
context = rag.query_papers(question, n_results=n_results)
if not context:
st.warning("⚠️ No se encontró información relevante. Asegúrate de tener papers cargados.")
return
# Mostrar contexto encontrado
st.info("🔍 **Métrica de búsqueda:** Similitud Coseno (Cosine Similarity)")
with st.expander("📚 Fragmentos Relevantes Encontrados"):
# Obtener mapeo de referencias
ref_mapping = rag._get_reference_mapping()
for i, doc in enumerate(context):
metadata = doc.get('metadata', {})
similarity = doc.get('similarity', 0)
# Obtener información de la fuente
source = metadata.get('source', 'Desconocido')
# Buscar referencia completa en el mapeo
if source in ref_mapping:
ref_info = ref_mapping[source]
author = ref_info['author']
year = ref_info['year']
citation = ref_info['citation']
else:
author = 'Autor desconocido'
year = 'Año desconocido'
citation = source
st.markdown(f"**Fragmento {i+1}** (Similitud Coseno: {similarity:.2%})")
st.caption(f"📄 **Referencia:** {citation}")
# Mostrar el triple de longitud (1500 caracteres en lugar de 500)
content_preview = doc['content'][:1500]
if len(doc['content']) > 1500:
content_preview += "..."
st.markdown(content_preview)
st.divider()
# Generar respuesta
with st.spinner("🤖 Generando respuesta con Groq AI..."):
response = rag.generate_response(question, context)
# Mostrar respuesta
st.subheader("💡 Respuesta")
st.markdown(response)
# Guardar en historial
if 'chat_history' not in st.session_state:
st.session_state.chat_history = []
st.session_state.chat_history.append({
'question': question,
'response': response,
'context_count': len(context)
})
# Mostrar historial
if 'chat_history' in st.session_state and st.session_state.chat_history:
st.divider()
st.subheader("📜 Historial de Conversación")
for i, item in enumerate(reversed(st.session_state.chat_history[-5:])):
with st.expander(f"❓ {item['question'][:100]}..."):
st.markdown(f"**Pregunta:** {item['question']}")
st.markdown(f"**Respuesta:** {item['response']}")
st.caption(f"Basado en {item['context_count']} fragmentos de papers")
if __name__ == "__main__":
print("Módulo RAG educativo cargado correctamente")