hf-chat-tester: Una interfaz ChatGPT para experimentar con modelos de HuggingFace y LM Studio localmente
Probar modelos de lenguaje de última generación suele requerir integrar APIs complejas, manejar autenticaciones y enfrentarse a la falta de interfaces amigables para la experimentación. Como ingeniero de IA, sentía esta necesidad especialmente cuando quería comparar rápidamente modelos locales y en la nube, analizar sus respuestas en tiempo real y compartir resultados con mi equipo. Así nació hf-chat-tester: una app que replica la experiencia ChatGPT, pero te permite poner a prueba cualquier modelo compatible con HuggingFace o LM Studio desde tu propio entorno, con visualización interactiva y streaming en tiempo real.
¿Por qué necesitábamos hf-chat-tester?
El auge de los LLMs ha traído una avalancha de modelos nuevos, tanto open-source (como los de HuggingFace) como soluciones privadas que puedes correr localmente (por ejemplo, LM Studio). Sin embargo, faltaba una herramienta sencilla que:
- Permita alternar entre modelos locales y cloud de forma fácil.
- Ofrezca respuestas rápidas y streaming token a token, para experimentar con prompts largos o múltiples turnos.
- Sea fácil de extender, hackear y compartir en entornos colaborativos (notebooks, laboratorios de ML, etc).
La mayoría de soluciones existentes estaban acopladas a un stack concreto, requerían mucho setup, o no soportaban ambos mundos (local/cloud). Por eso, decidí apostar por una arquitectura flexible y un stack que conocía bien: Python, TypeScript y Jupyter Notebook.
Arquitectura: Hexagonal para máxima flexibilidad
Un requisito clave era aislar la lógica de negocio de los detalles de integración con cada proveedor de modelos. Así, podríamos añadir nuevos backends o interfaces sin reescribir todo el core. La Hexagonal Architecture (o Ports & Adapters) encajaba perfectamente.
Estructura de carpetas
1backend/src/2├── domain/ # Lógica central (chat, formato de prompts, etc)3├── ports/ # Interfaces abstractas (por ejemplo, ModelClient)4├── adapters/5│ ├── primary/ # Handlers HTTP y WebSocket6│ └── secondary/ # Integraciones con LM Studio y HuggingFace7└── shared/ # Utilidades y excepciones
Diagrama conceptual
1[ UI React ] <---> [ FastAPI + WebSocket ] <--(Port)--> [ Core de negocio ]2 ^3 (Adapters) |4 [ LM Studio ] [ HuggingFace API ]
- Adapters primarios: exponen la lógica por HTTP y WebSocket.
- Adapters secundarios: abstraen cada proveedor de modelos.
- Dominio: gestiona las conversaciones, turnos, formato, etc.
Esta separación nos permitió, por ejemplo, simular la API de OpenAI o añadir nuevos backends (como Ollama) sin tocar el frontend ni el core.
Componentes principales
1. Backend (Python + FastAPI)
- Endpoints REST y WebSocket: para chat en tiempo real (streaming) y modo batch.
- Gestión dinámica de modelos: detecta modelos disponibles y permite seleccionarlos desde el frontend.
- Adaptadores para LM Studio y HuggingFace: cada uno implementa un
ModelClientque traduce las peticiones al formato esperado por cada API. - Streaming eficiente: aprovechando la capacidad de FastAPI para enviar tokens conforme se reciben, logrando una experiencia similar a ChatGPT.
Snippet: Adaptador LM Studio
1class LMStudioClient(ModelClient):2 def stream_chat(self, prompt):3 url = f"{self.base_url}/v1/completions"4 response = requests.post(url, json={"prompt": prompt, ...}, stream=True)5 for line in response.iter_lines():6 token = decode_token(line)7 yield token
2. Frontend (React + TypeScript + Vite)
- Interfaz tipo ChatGPT con soporte para temas (oscuro/claro).
- Selector de modelos: dropdown para alternar entre modelos disponibles.
- Streaming de respuestas: recibe tokens por WebSocket y los muestra “en vivo”.
- UX ágil: input multilinea, scroll automático, historial de conversación.
Snippet: Consumiendo el WebSocket
1const ws = new WebSocket(`/api/chat/ws?model=${selectedModel}`);2ws.onmessage = (event) => {3 setResponse(prev => prev + event.data);4};
3. Integración con Jupyter Notebook
Aunque el backend es autónomo, se pensó desde el principio para ser lanzado y gestionado desde notebooks, facilitando la vida a quienes usan entornos colaborativos, experimentan con datos, o documentan el proceso paso a paso.
Decisiones técnicas clave
1. Streaming vs batch
La experiencia ChatGPT es mucho más agradable cuando ves los tokens aparecer en tiempo real. Para lograrlo:
- Usé WebSockets en lugar de solo HTTP.
- Implementé generadores en Python que procesan e inyectan los tokens conforme llegan de la API.
- En el frontend, manejo el buffer de tokens para minimizar parpadeos y asegurar scroll suave.
2. Soporte multi-backend
No quería un “switch” gigante en el código. Por eso, todo el manejo de modelos se hace vía interfaces (ModelClient). Añadir un nuevo proveedor es tan simple como implementar ese contrato.
3. Entorno cross-platform
Quería que corriera igual en Windows, Mac o Linux, y que se pudiera lanzar desde scripts o desde Jupyter. Por eso, los scripts de arranque (start-app.bat), el uso de Makefiles y la configuración vía .env son sencillos y claros.
4. UI minimalista y funcional
Evité dependencias pesadas en el frontend. Todo es React + CSS simple, y el diseño está pensado para ser extendido (por ejemplo, añadir soporte para archivos, imágenes, etc).
Challenges técnicos y cómo los resolví
1. Streaming robusto con diferentes APIs
Cada backend (LM Studio, HuggingFace) maneja el streaming de forma distinta. Por ejemplo, LM Studio puede enviar datos en fragmentos o “eventos”, mientras que HuggingFace puede no soportar streaming en todos sus modelos.
- Solución: Implementé un parser de eventos robusto, con timeouts y reconexión automática. Cuando el backend no soporta streaming, el sistema cae automáticamente a modo batch.
2. Sincronización de estado en el frontend
El WebSocket puede enviar tokens fuera de orden o de forma fragmentada. Tuve que gestionar buffers y asegurar que el estado de la UI refleje exactamente el estado del backend, especialmente cuando el usuario cambia de modelo o reinicia la conversación.
- Solución: Usé un sistema de IDs de sesión y limpieza de buffers cada vez que el usuario cambia de modelo, asegurando que no se mezclen respuestas.
3. Manejo de errores y timeouts
Las APIs de modelos pueden fallar por múltiples motivos: modelos descargando, falta de memoria, límites de rate, etc. Era importante mostrar mensajes claros y no dejar la UI “colgada”.
- Solución: Añadí capturas de excepciones detalladas en backend y frontend, y un sistema de notificaciones para informar al usuario del tipo de error (model not available, timeout, etc).
Métricas y resultados
- Tiempo de respuesta: Para modelos locales en LM Studio, el streaming comienza en 300-700ms tras enviar el prompt.
- Facilidad de integración: Añadir soporte para un nuevo backend (por ejemplo, Ollama) tomó menos de 1.5h gracias a la arquitectura basada en puertos.
- Ahorro de tiempo en experimentación: Usuarios del laboratorio reportaron que podían comparar 3-4 modelos en menos de 10 minutos, cuando antes les llevaba horas de scripts y setups manuales.
- Adopción: El repositorio fue forked y adaptado por otros equipos para soportar modelos privados y flujos customizados.
Aprendizajes y mejoras futuras
Lo que funcionó bien
- Apostar por Hexagonal Architecture fue clave para la extensibilidad.
- El streaming real da una experiencia “wow” y mejora mucho la iteración.
- El soporte para Jupyter Notebook facilitó la vida a quienes experimentan y documentan.
Lo que podría mejorar
- Soporte para más tipos de modelos: Integrar Ollama, DeepSpeed o servidores custom.
- Historial persistente: Actualmente, el historial de chat se pierde al recargar.
- Soporte multimodal: Permitir prompts con imágenes o archivos sería el siguiente paso lógico.
- Despliegue más sencillo: Un Docker Compose facilitaría aún más el setup para usuarios no técnicos.
Conclusión
Desarrollar hf-chat-tester fue un reto técnico y de producto que me permitió aplicar conceptos de arquitectura limpia y construir una herramienta realmente útil para la comunidad de IA. Creo firmemente que la experimentación rápida y visual es clave para avanzar en el desarrollo de modelos de lenguaje, y espero que este proyecto ayude a otros investigadores y developers a iterar más rápido y aprender más.
¿Te interesa contribuir o probarlo? Visita el repo y déjame tu feedback. ¡Seguimos iterando! 🚀