Streamlit vs HTML+Flask: por qué los labs de IA usan Python puro para la UI
Cuando se enseña a construir chatbots con LangChain, hay una decisión clave: ¿Flask + HTML + JavaScript, o Streamlit? Desde un punto de vista pedagógico, la diferencia importa mucho. Aquí la comparativa y cuándo usar cada uno.
El problema pedagógico
En un lab de construcción de chatbots con LangChain y Python, la decisión sobre qué framework de UI usar no es trivial. Si el objetivo es enseñar a integrar un LLM con memoria, RAG y herramientas — ¿cuánto tiempo tiene sentido dedicar a HTML, CSS, JavaScript y Flask antes de llegar a lo que importa?
Un notebook que usa Flask con HTML embebido puede tardar tres celdas y 120 líneas de código solo para tener una caja de texto y un botón funcionando. El alumno que llega a esa celda sin background de frontend queda bloqueado en sintaxis HTTP antes de ver la parte interesante: cómo el LLM responde, cómo persiste la memoria, cómo se recuperan documentos desde el vector store.
La premisa: cuando enseñas IA/ML aplicada a desarrolladores, la UI es el vehículo, no el destino. Streamlit elimina la fricción de frontend para que el aprendizaje se quede donde tiene que estar: en la lógica de la cadena LLM.
El mismo chatbot: 120 líneas vs 12
Un chatbot básico sin memoria, con Flask, requiere tres bloques de código separados: el HTML+CSS+JS de la interfaz, el servidor Flask con sus rutas y el threading para arrancar dentro de un notebook. Con Streamlit, el equivalente exacto cabe en 12 líneas:
# Celda 1: ~60 líneas de HTML + CSS + jQuery
html_template = """
<html><body>
<div id="chat">...</div>
<script>
function sendMessage() {
fetch('/chat', {...})
.then(r => r.json())
.then(d => appendMessage(d))
}
</script>
</body></html>
"""
# Celda 2: servidor Flask
from flask import Flask, request, jsonify
import threading
app = Flask(__name__)
@app.route('/chat', methods=['POST'])
def chat():
msg = request.json['message']
response = llm.invoke(msg)
return jsonify({'reply': response.content})
t = threading.Thread(target=app.run)
t.start()
# Celda 3: iframe
serve_kernel_port_as_iframe(5000)import streamlit as st
from langchain_openai import ChatOpenAI
st.title("Chatbot v0")
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0.3
)
user_msg = st.chat_input("Escribe...")
if user_msg:
st.chat_message("user").write(user_msg)
response = llm.invoke(user_msg)
st.chat_message("assistant").write(
response.content
)Mismo comportamiento observable. La diferencia es que el alumno que llega al bloque de Streamlit puede entender cada línea sin saber HTML ni HTTP.
Comparativa completa
| Aspecto | HTML + Flask | Streamlit |
|---|---|---|
| Lenguaje | HTML + CSS + JS + Python | Solo Python |
| Servidor web | Flask manual + threading | Incluido en el framework |
| Estado / memoria del chat | Gestión manual (session, variables globales) | st.session_state automático |
| Arrancar la app | threading + Flask.run() en notebook | streamlit run app.py |
| Reactividad UI | jQuery + fetch manual a Flask | Automática al cambiar widgets |
| Curva de aprendizaje | Alta — requiere dominar 3 lenguajes | Muy baja — solo Python |
| Personalización UI | Total — CSS libre, cualquier layout | Limitada — componentes predefinidos |
| Para producción | Sí, con trabajo de DevOps | Sí para datos/IA; no para apps enterprise |
El concepto que más confunde: session_state
Streamlit re-ejecuta el script completo cada vez que el usuario interactúa con un widget. Esto es contraintuitivo para quien viene de Flask o de React, pero es el modelo de programación del framework. session_state es la solución: un diccionario que sobrevive a cada re-ejecución y permite conservar el historial de mensajes entre turnos.
# Cada vez que el usuario escribe, Streamlit re-ejecuta el script # → el historial de mensajes desaparece # → el chat parece "reiniciarse" en cada mensaje
if "messages" not in st.session_state:
st.session_state.messages = []
# Los mensajes sobreviven a cada re-ejecución
st.session_state.messages.append({"role": "user", "content": prompt})Analogía para clase: "Es como si cada vez que presionas Enter, Python cierra y vuelve a abrir el script desde cero. session_state es la caja donde guardas cosas antes de que eso pase — es la única memoria que sobrevive al reinicio."
Cuándo usar cada uno
Usa HTML + Flask cuando…
Usa Streamlit cuando…
¿Quieres aplicar esto en tu equipo?
En Kuuli Technologies llevamos estas metodologías a equipos reales. Escríbenos si quieres una sesión de trabajo o una consultoría sobre agentes IA y SDD.