"""
Gradio UI for UPB RAG Career Exploration Assistant
Deployed on HuggingFace Spaces
"""
import os
import gradio as gr
from pathlib import Path
import sys
from dotenv import load_dotenv
# Load environment variables from .env file (for local development)
# HuggingFace Spaces will use Secrets instead
load_dotenv()
# Add src to path
sys.path.insert(0, str(Path(__file__).parent / "src"))
from setup_retrieval import setup_retrieval_system
from rag.chain import UPBRAGChain
# Initialize RAG system
print("🚀 Initializing UPB RAG System...")
retriever, vectorstore_manager, chunks = setup_retrieval_system()
rag_chain = UPBRAGChain(retriever, retrieval_method="hybrid")
print("✅ System ready!")
# Custom CSS for better UI
custom_css = """
.header {
text-align: center;
padding: 20px;
background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
color: white;
border-radius: 10px;
margin-bottom: 20px;
}
.header h1 {
margin: 0;
font-size: 2.5em;
font-weight: bold;
}
.header p {
margin: 10px 0 0 0;
font-size: 1.2em;
opacity: 0.9;
}
.disclaimer {
background-color: #fef3c7;
border-left: 4px solid #f59e0b;
padding: 15px;
margin: 15px 0;
border-radius: 5px;
font-size: 0.9em;
}
.footer {
text-align: center;
padding: 20px;
color: #6b7280;
font-size: 0.9em;
}
.chat-message {
padding: 10px;
border-radius: 8px;
margin: 5px 0;
}
.source-box {
background-color: #f3f4f6;
border-left: 3px solid #3b82f6;
padding: 10px;
margin: 10px 0;
border-radius: 5px;
font-size: 0.85em;
}
"""
def format_sources(sources):
"""Format source citations nicely"""
if not sources:
return ""
sources_text = "\n\n---\n\n### 📚 Fuentes consultadas:\n\n"
seen = set()
for source in sources:
source_key = (source.get('category', ''), source.get('source', ''))
if source_key not in seen:
seen.add(source_key)
category = source.get('category', 'general').title()
source_name = source.get('source', 'N/A')
sources_text += f"- **{category}**: `{source_name}`\n"
return sources_text
def chat_with_upb(message, history, include_sources):
"""
Main chat function for Gradio interface
Args:
message: User's message
history: Chat history (list of [user_msg, bot_msg] pairs)
include_sources: Boolean to show source citations
Returns:
Updated chat history
"""
if not message or not message.strip():
return history
try:
# Get response from RAG chain
response = rag_chain.invoke(message, include_sources=include_sources)
# Format answer
answer = response['answer']
# Add sources if requested
if include_sources and response.get('sources'):
answer += format_sources(response['sources'])
# Update history
history.append([message, answer])
return history
except Exception as e:
error_msg = f"⚠️ Lo siento, ocurrió un error: {str(e)}\n\nPor favor, intenta reformular tu pregunta."
history.append([message, error_msg])
return history
def clear_conversation():
"""Clear chat history"""
rag_chain.clear_history()
return []
def get_example_questions():
"""Return example questions for quick start"""
return [
["¿Qué ingenierías ofrece la UPB?"],
["¿Cuáles programas tienen acreditación ABET?"],
["Cuéntame sobre Ingeniería de Sistemas"],
["¿Qué becas están disponibles?"],
["¿Cómo puedo contactar a la UPB?"],
["¿Qué es la Ingeniería en Ciencia de Datos?"],
]
# Create Gradio interface
with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
# Header
gr.HTML("""
""")
# Disclaimer
gr.HTML("""
⚠️ Nota importante: Este asistente proporciona información general sobre los programas de ingeniería de la UPB.
Para información específica sobre matrículas, fechas límite, o detalles particulares del plan de estudios,
por favor contacta directamente con la oficina de admisiones de la UPB.
""")
# Main chat interface
with gr.Row():
with gr.Column(scale=4):
chatbot = gr.Chatbot(
label="💬 Conversación",
height=500,
show_label=True,
avatar_images=(None, "🎓"),
bubble_full_width=False,
)
with gr.Row():
msg = gr.Textbox(
label="Tu pregunta",
placeholder="Escribe tu pregunta aquí... (ejemplo: ¿Qué ingenierías ofrece la UPB?)",
scale=4,
lines=2,
)
submit_btn = gr.Button("Enviar 📤", variant="primary", scale=1)
with gr.Row():
clear_btn = gr.Button("🗑️ Limpiar conversación", variant="secondary")
sources_checkbox = gr.Checkbox(
label="📚 Mostrar fuentes",
value=True,
info="Incluir referencias a documentos fuente"
)
with gr.Column(scale=1):
gr.Markdown("### 💡 Preguntas de ejemplo")
gr.Markdown("Haz clic en cualquier pregunta para probarla:")
example_btns = []
for example in get_example_questions():
btn = gr.Button(example[0], size="sm")
example_btns.append((btn, example[0]))
# Information section
with gr.Accordion("ℹ️ Información del Sistema", open=False):
gr.Markdown(f"""
### Acerca de este asistente
Este asistente utiliza **Retrieval-Augmented Generation (RAG)** para proporcionar información precisa
sobre los programas de ingeniería de la Universidad Pontificia Bolivariana.
**Características:**
- 🤖 **LLM**: Azure GPT-4o-mini (temperatura: 0.0 para máxima precisión)
- 📊 **Base de conocimiento**: {len(chunks)} fragmentos de {len(set(doc.metadata.get('source') for doc in chunks if hasattr(doc, 'metadata')))} documentos
- 🔍 **Método de búsqueda**: Híbrido (BM25 + Vector con RRF)
- 📚 **Programas incluidos**: 12 ingenierías
- ✅ **Información de acreditaciones**: ABET, Alta Calidad
**Temas cubiertos:**
- Información general sobre la UPB
- 12 programas de ingeniería (descripción, plan de estudios, perfil profesional)
- Procesos de inscripción y admisión
- Becas y financiación
- Información de contacto
**Limitaciones:**
- La información está basada en documentos curados manualmente
- Para detalles específicos de fechas, costos exactos, o cambios recientes, consulta directamente con la UPB
- El sistema puede no tener información sobre cursos muy específicos del plan de estudios
**Consejos de uso:**
- Haz preguntas específicas y claras
- Puedes hacer preguntas de seguimiento (el sistema mantiene el contexto)
- Usa el botón "Limpiar conversación" para empezar un tema nuevo
""")
# Statistics
with gr.Accordion("📊 Estadísticas del Sistema", open=False):
gr.Markdown(f"""
- **Total de documentos cargados**: {len(set(doc.metadata.get('source') for doc in chunks if hasattr(doc, 'metadata')))}
- **Total de fragmentos procesados**: {len(chunks)}
- **Promedio de caracteres por fragmento**: {sum(len(doc.page_content) for doc in chunks) // len(chunks) if chunks else 0}
- **Categorías disponibles**: Ingenierías, Información general, Becas, Inscripciones, Contacto
- **Modelo de embeddings**: Azure text-embedding-3-small
- **Vector store**: FAISS (CPU)
""")
# Footer
gr.HTML("""
""")
# Event handlers
def submit_message(message, history, sources):
return "", chat_with_upb(message, history, sources)
# Submit button
submit_btn.click(
submit_message,
inputs=[msg, chatbot, sources_checkbox],
outputs=[msg, chatbot],
)
# Enter key
msg.submit(
submit_message,
inputs=[msg, chatbot, sources_checkbox],
outputs=[msg, chatbot],
)
# Clear button
clear_btn.click(
clear_conversation,
outputs=chatbot,
)
# Example buttons
for btn, text in example_btns:
btn.click(
lambda t=text: (t, ""),
outputs=[msg, msg],
).then(
submit_message,
inputs=[msg, chatbot, sources_checkbox],
outputs=[msg, chatbot],
)
# Launch configuration
if __name__ == "__main__":
# For local development
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
show_error=True,
)