DEV Community

Jesus Oviedo Riquelme
Jesus Oviedo Riquelme

Posted on

LLMZ25-2 Review : Construyendo Interfaces LLM con Streamlit

Construir interfaces de usuario para aplicaciones de Modelos de Lenguaje Grande (LLM) puede ser desafiante, especialmente cuando quieres enfocarte en la lógica de IA en lugar del desarrollo frontend. Streamlit resuelve este problema proporcionando un framework simple basado en Python que transforma scripts de datos en aplicaciones web interactivas en minutos.

Esta guía te llevará a través del proceso de crear interfaces LLM con Streamlit, comparándolo con alternativas y demostrando patrones de implementación práctica usando ejemplos del mundo real.

Guía Paso a Paso para Integración con Streamlit

Paso 1: Instalación y Configuración

# Instalar Streamlit
pip install streamlit

# Crear tu primera app
streamlit hello
Enter fullscreen mode Exit fullscreen mode

Paso 2: Estructura Básica de Interfaz LLM

import streamlit as st
import openai
from datetime import datetime

# Configuración de página
st.set_page_config(
    page_title="LLM Assistant",
    page_icon="🤖",
    layout="wide"
)

# Inicializar estado de sesión
if "conversation" not in st.session_state:
    st.session_state.conversation = []

# Barra lateral para configuración
with st.sidebar:
    st.header("Configuration")
    api_key = st.text_input("OpenAI API Key", type="password")
    model = st.selectbox("Model", ["gpt-4o-mini", "gpt-4o", "gpt-3.5-turbo"])
    temperature = st.slider("Temperature", 0.0, 2.0, 0.7)
Enter fullscreen mode Exit fullscreen mode

Paso 3: Implementación de Interfaz de Chat

# Interfaz principal de chat
st.title("🤖 AI Legal Assistant")

# Mostrar historial de conversación
for message in st.session_state.conversation:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# Entrada de chat
if prompt := st.chat_input("Ask a legal question..."):
    # Agregar mensaje de usuario
    st.session_state.conversation.append({
        "role": "user", 
        "content": prompt,
        "timestamp": datetime.now()
    })

    # Mostrar mensaje de usuario
    with st.chat_message("user"):
        st.markdown(prompt)

    # Generar respuesta de IA
    with st.chat_message("assistant"):
        with st.spinner("Analyzing your question..."):
            response = generate_legal_response(prompt, api_key, model, temperature)
            st.markdown(response)

    # Agregar respuesta de IA a la conversación
    st.session_state.conversation.append({
        "role": "assistant", 
        "content": response,
        "timestamp": datetime.now()
    })
Enter fullscreen mode Exit fullscreen mode

Comparación con Alternativas

Streamlit vs Gradio

Característica Streamlit Gradio
Curva de Aprendizaje Suave, enfocado en Python Empinada, requiere conocimiento ML
Personalización Alta flexibilidad Limitada a componentes predefinidos
Despliegue Múltiples opciones Principalmente Hugging Face
Caso de Uso Apps web generales Demos y prototipos ML
Complejidad de Código Python simple Basado en componentes

Streamlit vs Flask/FastAPI

Característica Streamlit Flask/FastAPI
Velocidad de Desarrollo Muy rápida Moderada a lenta
Conocimiento Frontend No requerido HTML/CSS/JS necesario
Desarrollo de API Limitado Excelente
Escalabilidad Buena para prototipos Excelente para producción
Curva de Aprendizaje Mínima Empinada

Caso de Estudio: Proyecto lus-laboris-py

El proyecto lus-laboris-py es un excelente ejemplo de cómo Streamlit puede complementar una aplicación existente. Actualmente, este proyecto cuenta con una API REST robusta implementada con FastAPI, pero le falta una interfaz de usuario accesible. Aquí es donde Streamlit puede agregar un valor significativo.

Estado Actual del Proyecto

lus-laboris-py actualmente tiene:

  • API REST completa con FastAPI
  • Sistema RAG funcional para investigación legal
  • Base de datos con documentos legales
  • Endpoints bien definidos para consultas
  • Interfaz de usuario (ausente)

Recomendación: Agregar Streamlit como Frontend

¿Por qué Streamlit es la solución perfecta para lus-laboris-py?

  1. Complementa FastAPI: Streamlit puede consumir la API existente sin modificar el backend
  2. Desarrollo rápido: Se puede crear una UI completa en horas, no días
  3. Sin conocimientos frontend: El equipo puede enfocarse en la lógica legal, no en HTML/CSS
  4. Integración perfecta: Python nativo se integra naturalmente con FastAPI

Implementación Propuesta

# app_streamlit.py - Frontend para lus-laboris-py
import streamlit as st
import requests
import json

# Configuración
API_BASE_URL = "http://localhost:8000"  # URL de la API FastAPI

st.set_page_config(
    page_title="Lus Laboris - Asistente Legal",
    page_icon="⚖️",
    layout="wide"
)

st.title("⚖️ Lus Laboris - Asistente Legal")

# Sidebar para configuración
with st.sidebar:
    st.header("Configuración")
    api_url = st.text_input("URL de la API", value=API_BASE_URL)

# Interfaz principal de consulta legal
st.subheader("Consulta Legal")

query = st.text_area(
    "Describe tu consulta legal:",
    placeholder="Ejemplo: ¿Cuáles son los requisitos para un contrato de trabajo?",
    height=100
)

if st.button("Consultar", type="primary"):
    if query.strip():
        with st.spinner("Procesando consulta legal..."):
            try:
                # Llamada a la API FastAPI existente
                response = requests.post(
                    f"{api_url}/query",
                    json={"query": query},
                    headers={"Content-Type": "application/json"}
                )

                if response.status_code == 200:
                    result = response.json()

                    # Mostrar respuesta
                    st.success("Consulta procesada exitosamente")

                    # Respuesta principal
                    st.subheader("Respuesta:")
                    st.markdown(result.get("answer", "No se encontró respuesta"))

                    # Documentos fuente
                    if "sources" in result:
                        st.subheader("Documentos Fuente:")
                        for i, source in enumerate(result["sources"], 1):
                            with st.expander(f"Documento {i}: {source.get('title', 'Sin título')}"):
                                st.write(source.get("content", ""))
                                st.caption(f"Relevancia: {source.get('score', 'N/A')}")

                    # Métricas de rendimiento
                    if "metrics" in result:
                        col1, col2, col3 = st.columns(3)
                        with col1:
                            st.metric("Tiempo de Respuesta", f"{result['metrics'].get('response_time', 0):.2f}s")
                        with col2:
                            st.metric("Documentos Recuperados", result['metrics'].get('documents_found', 0))
                        with col3:
                            st.metric("Confianza", f"{result['metrics'].get('confidence', 0):.2%}")

                else:
                    st.error(f"Error en la API: {response.status_code}")

            except requests.exceptions.RequestException as e:
                st.error(f"Error de conexión: {str(e)}")
    else:
        st.warning("Por favor, ingresa una consulta legal")

# Sección de gestión de documentos
st.subheader("Gestión de Documentos")

tab1, tab2, tab3 = st.tabs(["Subir Documentos", "Buscar Documentos", "Estadísticas"])

with tab1:
    st.write("Cargar nuevos documentos legales al sistema")
    uploaded_file = st.file_uploader(
        "Seleccionar archivo legal",
        type=['pdf', 'docx', 'txt'],
        help="Formatos soportados: PDF, DOCX, TXT"
    )

    if uploaded_file:
        if st.button("Procesar Documento"):
            with st.spinner("Procesando documento..."):
                files = {"file": uploaded_file.getvalue()}
                response = requests.post(f"{api_url}/upload", files=files)

                if response.status_code == 200:
                    st.success("Documento procesado exitosamente")
                else:
                    st.error("Error al procesar el documento")

with tab2:
    st.write("Buscar documentos en la base de datos")
    search_query = st.text_input("Término de búsqueda")

    if st.button("Buscar"):
        if search_query:
            with st.spinner("Buscando..."):
                response = requests.get(f"{api_url}/search", params={"q": search_query})

                if response.status_code == 200:
                    results = response.json()
                    for doc in results.get("documents", []):
                        with st.expander(f"{doc.get('title', 'Sin título')}"):
                            st.write(doc.get("summary", ""))
                            st.caption(f"Fecha: {doc.get('date', 'N/A')}")

with tab3:
    st.write("Estadísticas del sistema")

    # Obtener estadísticas de la API
    try:
        stats_response = requests.get(f"{api_url}/stats")
        if stats_response.status_code == 200:
            stats = stats_response.json()

            col1, col2, col3, col4 = st.columns(4)

            with col1:
                st.metric("Total Documentos", stats.get("total_documents", 0))
            with col2:
                st.metric("Consultas Hoy", stats.get("queries_today", 0))
            with col3:
                st.metric("Tiempo Promedio", f"{stats.get('avg_response_time', 0):.2f}s")
            with col4:
                st.metric("Tasa de Éxito", f"{stats.get('success_rate', 0):.1%}")

            # Gráfico de consultas por día
            if "daily_queries" in stats:
                st.subheader("Consultas por Día")
                st.line_chart(stats["daily_queries"])

    except requests.exceptions.RequestException:
        st.error("No se pudieron cargar las estadísticas")

# Footer
st.markdown("---")
st.markdown("**Lus Laboris** - Sistema de Investigación Legal Asistida por IA")
st.caption("Powered by FastAPI + Streamlit + RAG")
Enter fullscreen mode Exit fullscreen mode

Beneficios de esta Implementación

  1. Reutilización del Backend: No se modifica la API FastAPI existente
  2. UI Completa: Interfaz intuitiva para usuarios no técnicos
  3. Desarrollo Rápido: Implementación en horas, no semanas
  4. Mantenimiento Simple: Un solo archivo Python para toda la UI
  5. Escalabilidad: Fácil despliegue con Streamlit Cloud

Arquitectura Propuesta

lus-laboris-py/
├── api/                    # Backend FastAPI existente
│   ├── main.py
│   ├── models/
│   └── routes/
├── frontend/               # Nuevo frontend Streamlit
│   ├── app_streamlit.py   # Aplicación principal
│   ├── components/        # Componentes reutilizables
│   └── utils/             # Utilidades
├── data/                  # Documentos legales
└── requirements.txt       # Dependencias actualizadas
Enter fullscreen mode Exit fullscreen mode

Patrones de Diseño Comunes

Patrón 1: Aplicaciones Multi-Página

# Navegación de páginas
pages = {
    "Chat": chat_page,
    "Documents": documents_page,
    "Analytics": analytics_page,
    "Settings": settings_page
}

selected_page = st.sidebar.selectbox("Navigate", list(pages.keys()))
pages[selected_page]()
Enter fullscreen mode Exit fullscreen mode

Patrón 2: Actualizaciones en Tiempo Real

# Auto-refresh para datos en tiempo real
if st.button("Enable Real-time Updates"):
    placeholder = st.empty()

    while True:
        latest_data = fetch_latest_data()
        placeholder.json(latest_data)
        time.sleep(5)  # Actualizar cada 5 segundos
Enter fullscreen mode Exit fullscreen mode

Patrón 3: Pipeline de Procesamiento de Archivos

# Procesamiento de archivos con progreso
def process_files(uploaded_files):
    progress_bar = st.progress(0)
    status_text = st.empty()

    for i, file in enumerate(uploaded_files):
        status_text.text(f"Processing {file.name}...")
        process_single_file(file)
        progress_bar.progress((i + 1) / len(uploaded_files))

    st.success("All files processed!")
Enter fullscreen mode Exit fullscreen mode

Tips de Despliegue

1. Streamlit Community Cloud

# .streamlit/config.toml
[server]
port = 8501
headless = true

[browser]
gatherUsageStats = false
Enter fullscreen mode Exit fullscreen mode

2. Despliegue con Docker

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .
EXPOSE 8501

CMD ["streamlit", "run", "app_streamlit.py", "--server.port=8501", "--server.address=0.0.0.0"]
Enter fullscreen mode Exit fullscreen mode

3. Configuración de Ambiente

# Variables de ambiente
import os
from dotenv import load_dotenv

load_dotenv()

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")
API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8000")
DEBUG_MODE = os.getenv("DEBUG_MODE", "False").lower() == "true"
Enter fullscreen mode Exit fullscreen mode

Optimización de Rendimiento

1. Caché

@st.cache_data
def load_legal_documents():
    return load_documents_from_database()

@st.cache_resource
def initialize_llm_model():
    return load_llm_model()
Enter fullscreen mode Exit fullscreen mode

2. Operaciones Asíncronas

import asyncio
import aiohttp

async def fetch_legal_data_async(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        return await asyncio.gather(*tasks)
Enter fullscreen mode Exit fullscreen mode

3. Connection Pooling de Base de Datos

from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool

engine = create_engine(
    DATABASE_URL,
    poolclass=QueuePool,
    pool_size=10,
    max_overflow=20
)
Enter fullscreen mode Exit fullscreen mode

Mejores Prácticas

  1. Gestión de Estado de Sesión: Usa st.session_state para mantener datos de usuario
  2. Manejo de Errores: Implementa manejo comprensivo de errores para llamadas API
  3. Estados de Carga: Siempre muestra indicadores de carga para operaciones largas
  4. Diseño Responsivo: Usa columnas y contenedores para mejor layout
  5. Seguridad: Nunca expongas API keys en código del lado del cliente
  6. Testing: Escribe pruebas unitarias para tus funciones de lógica central

Recursos:

Top comments (0)