Chatbot rápido con LangChain

Chatbot rápido con LangChain

La implementación de modelos de lenguaje conversacionales se puede agilizar de manera sencilla gracias a LangChain. Además, la capacidad de unir múltiples funcionalidades hace de este framework el preferido por los desarrolladores y científicos de datos.
Alberto Pérez Galende
Data Scientist & Desarrollador Python
Esta semana os compartimos en un post de Linkedin un framework ampliamente utilizado en python para implementar facil y rapido modelos de lenguaje en diferentes aplicaciones software. Langchain, lanzado en octubre de 2022, permite implementar modelos de lenguaje (LLM) en diferentes tareas como pueden ser Reality Augmented Generation (RAG), agentes, cadenas, etc. De esta manera, con pocas líneas de código, se puede crear una aplicación completa que tenga un chatbot incorporado capaz de consultar archivos personalizados para ofrecer las mejores respuestas posibles.
En este blog vamos a diseñar un pequeño chatbot capaz de acceder a una serie de CVs para poder preguntarle al modelo acerca de los candidatos registrados.
langchain3

ChatBot con LangChain y OpenAI

Requisitos

El Se usara Python en un Jupyter Notebook. Además, para usar el modelo, es necesario tener una cuenta de OpenAI y crear una API KEY para hacer las llamadas a su API. En cuanto a las dependencias a instalar con pip son:

  • Langchain: Librería principal de LangChain con todas las funcionalidades básicas.
  • Langchain-openai: Librería necesaria para hacer las llamadas a la API de OpenAI a través de LangChain
Y ya, no se necesitan más dependencias para replicar este cuaderno.

Inicio del cuaderno: Importaciones

Se comienza importando las clases y funciones necesarias para crear la memoria, la base de datos vectorial, el modelo, etc. Además, cargamos la API_KEY de Openai y el directorio donde vamos a guardar los CVs de la gente que queramos analizar.

import os
from dotenv import load_dotenv
 
from langchain.vectorstores import FAISS
from langchain_openai.chat_models import ChatOpenAI
from langchain_openai.embeddings import OpenAIEmbeddings
from langchain_community.document_loaders import PyPDFDirectoryLoader
from langchain.memory.buffer import ConversationBufferMemory
from langchain.chains.conversational_retrieval.base import ConversationalRetrievalChain
load_dotenv()
# Load openai API-KEY
API_KEY = os.environ[«OPENAI_API_KEY»]
# Load file directory
CV_path_directory = «./resources/»

Recursos para el chatbot

Una vez se han cargado las clases, funciones y variables necesarias es hora de empezar a crear las herramientas a las que el chatbot tendrá acceso a la hora de responder a las preguntas de los usuarios.
La primera parte, y la más importante en mi opinión, es la creación de una base de datos con los archivos deseados. Estos archivos se guardarán en una base de datos vectorial, FAISS, a través de unos embeddings creados gracias a el modelo _text-embedding-ada-002_ de openai.
loader = PyPDFDirectoryLoader(CV_path_directory)
pages = []
for page in loader.lazy_load():
    pages.append(page)
embeddings = OpenAIEmbeddings(api_key=API_KEY)
vectorstore = FAISS.from_documents(documents=pages, embedding=embeddings)
retriever = vectorstore.as_retriever()
Una vez tenemos la base de datos a la que se va a consultar, necesitamos un modelo de lenguaje. Para este blog se va a utilizar _gpt-4o-mini-2024-07-18_, un modelo relativamente pequeño y barato de openai, pero que para estas tareas concisas, que en este caso no va a tener un contexto excesivo, es más que suficiente.
llm = ChatOpenAI(model=«gpt-4o-mini-2024-07-18», api_key=API_KEY, temperature=0.1)
Por último, para transformar un simple modelo en un chatbot necesita memoria. Si no es capaz de recordar los mensajes anteriores, las llamadas serían independientes y cualquier instrucción o corrección se perdería por el camino.
memory = ConversationBufferMemory(memory_key=«chat_history», return_messages=True)
Y ya tenemos todas las herramientas para que este chatbot funcione, siendo capaz de acceder a dichos archivos para encontrar la información más relevante y crear respuestas adecuadas.

Cadena Conversacional

Ahora que ya tenemos todas las herramientas es hora de crear nuestro chatbot. Y aunque parezca una tarea compleja y costosa, gracias a LangChain, se convierte en algo sencillo e inmediato.
retrieval_chain = ConversationalRetrievalChain.from_llm(
    llm=llm,
    retriever=retriever,
    memory=memory,
)
Y listo, ya tenemos nuestro chatbot, con unas pocas líneas de código. Para hacer llamadas basta con usar la función _invoke_ y proporcionará la respuesta más adecuada.

Ejemplos

Los archivos usados han sido dos CVs, uno de Alberto Pérez Galende y otro de Juan Sánchez Blázquez. Al hacerle preguntas acerca de cualquiera de las dos personas el modelo es capaz de acceder al archivo correspondiente y responder con exactitud.
question_1 = «Who is Alberto Perez Galende?»
response = retrieval_chain.invoke({«question»: question_1})
response[«answer»]
«Alberto Perez Galende is a data scientist and Python developer based in Salamanca, Spain. He has a background in mathematics and physics and has experience in applying machine learning algorithms to various types of data. Currently, he works at Axpe Consulting as a data scientist and backend developer, focusing on computer vision processes, data extraction, and web app development using FastAPI and Docker. He holds a B.S. in Mathematics with a minor in Physics from Campbellsville University and a Master’s in Mathematical Research from the Polytechnic University of Valencia. He is also interested in improving his teamwork skills and learning more about supervised machine learning algorithms.»
Veamos ahora que ocurre al referirse a una pregunta o respuesta anterior. Al no proporcionar un nombre, y gracias a la memoria, el modelo deduce que esta pregunta va unida a la misma persona que la anterior.
question_2 = «What degree he studied?»
response_2 = retrieval_chain.invoke({«question»: question_2})
response_2[«answer»]
«He studied a B.S. major in Mathematics and a minor in Physics at Campbellsville University.»
Ahora preguntémosle algo relacionado con otro archivo.
question_3 = «Who is Juan Sanchez?»
response_3 = retrieval_chain.invoke({«question»: question_3})
response_3[«answer»]
«Juan Sánchez is a data scientist based in Salamanca, Spain, with a background in Mathematics. He has experience in data processing and analysis, visualization of results, and development of microservices in Python. His skills include Python, machine learning, deep learning, natural language processing, and working with relational databases. He is currently working as a Python developer at uXcale and has previously held a position as a data engineer at the same company. Juan is fluent in Spanish and English and has a B1 level in German. He is also pursuing a degree in Mathematics from the University of Salamanca.»
question_4 = «What degree he studied?»
response_4 = retrieval_chain.invoke({«question»: question_4})
response_4[«answer»]
«Juan Sánchez studied a degree in Mathematics at the Universidad de Salamanca.»

Conclusiones

Se puede ver que el modelo da respuestas concisas y fiables acerca de los archivos proporcionados, además de recordar los últimos mensajes que se han proporcionado. En este ejemplo se han usado cadenas, RAG, memoria y otras funcionalidades que, sin LangChain habría llevado días y varios archivos de código mucho más extensos. Sin embargo, gracias a este framework, esta implementación ha sido rápida y eficaz. Además de estas herramientas, LangChain proporciona soporte para agentes, cadenas mucho más complejas y otras funcionalidades, las cuales se pueden combinar, complementar y trabajar juntas para conseguir que la inteligencia artificial generativa sea lo más útil posible de la manera más sencilla.

¿Cómo de bien podría funcionar un equipo de agentes en el cual uno extrae información de una persona, otro agente analiza su rendimiento laboral, y un último, con la información de ambos, escribe un reporte acerca de dicho empleado?

LangChain ofrece oportunidades casi infinitas para interactuar con IA generativa y modelos de lenguaje. Y a ti, ¿qué casos de uso se te ocurren? ¿Has probado LangChain antes? ¡Cuéntanos tu experiencia y que nuevos casos de uso se te ocurren para usar este framework!
Read more

REST vs GraphQL: ¿Cuál es la mejor opción para tu API?

REST vs GraphQl: ¿Cuál es la mejor opción para tu API?

Aroa Nieto Rodríguez | Mario Rodrigo Marcos | Julia García Vega
uXcale Backend and APIs Developers

En el desarrollo de software moderno, las APIs son esenciales para la comunicación entre servicios y aplicaciones. Existen diferentes enfoques para construir APIs, siendo REST y GraphQL dos de los más populares. Pero, ¿cuál de ellos deberías elegir para tu proyecto? 

¿Qué es REST?

REST (Representational State Transfer) es un estilo arquitectónico que utiliza los métodos HTTP para interactuar con recursos. Cada recurso se identifica mediante una URL única, y las operaciones se realizan mediante métodos HTTP como GET, POST, PUT, y DELETE. 

Ventajas de REST

  • Simplicidad: Fácil de entender y usar. 
  • Caché: Mejora el rendimiento al almacenar las respuestas. 
  • Estándares: Utiliza protocolos bien establecidos, lo que facilita la integración con otros sistemas.

En este artículo, vamos a explorar las principales diferencias entre REST y GraphQL, qué ventajas y desventajas ofrece cada uno, y cuándo es mejor usar cada uno según tus necesidades. 

Desventajas de REST

  • Sobrecarga de datos: A veces, se devuelve más información de la necesaria. 
  • Múltiples solicitudes: Necesitas hacer varias solicitudes para obtener datos relacionados, lo que puede aumentar la latencia. 

¿Qué es GraphQL?

GraphQL es un lenguaje de consulta para APIs, desarrollado por Facebook, que permite a los clientes solicitar exactamente los datos que necesitan. En lugar de tener múltiples endpoints, GraphQL usa un solo endpoint para todas las consultas, lo que lo hace más flexible y eficiente. 

Ventajas de GraphQL

  • Consulta precisa: Los clientes solo obtienen los datos que realmente necesitan. 
  • Menos solicitudes: Puedes obtener datos relacionados en una sola consulta. 
  • Evolución de la API: Es fácil agregar nuevos campos sin romper la compatibilidad con los clientes. 

Desventajas de GraphQL

  • Complejidad: Más difícil de implementar y entender, especialmente para novatos. 
  • Caché: La caché puede ser más difícil de manejar debido a la naturaleza dinámica de las consultas. 

¿Cuándo usar REST?

REST es ideal cuando: 

  • La simplicidad y la facilidad de uso son prioritarias. 
  • Trabajas con recursos estáticos y bien definidos. 
  • Necesitas caché para mejorar el rendimiento. 
  • La API no tiene interacciones complejas entre recursos. 

Ejemplo de caso de uso de REST - Sistema de gestión de bibliotecas

Imagina una API donde los recursos son libros, autores y usuarios. Cada recurso tiene una URL específica y las operaciones (consultas, actualizaciones, eliminaciones) son simples y directas. 

¿Cuándo usar GraphQL?

GraphQL es perfecto cuando: 

  • Necesitas flexibilidad en las consultas y quieres evitar la sobrecarga de datos. 
  • Tu aplicación tiene múltiples tipos de datos relacionados que deben ser consultados al mismo tiempo. 
  • La API va a evolucionar con frecuencia y se necesitarán cambios. 

Ejemplo de caso de uso GraphQL - Aplicación de redes sociales

Aplicación de redes sociales: Los usuarios pueden tener muchas publicaciones, comentarios y reacciones. GraphQL permite que los clientes pidan solo la información necesaria, como el nombre del usuario y sus últimas publicaciones, en una única consulta. 

Resumen Comparativo

Y entonces... ¿Qué debo de escoger?

Tanto REST como GraphQL tienen sus ventajas dependiendo del tipo de proyecto. Si estás trabajando en una aplicación simple con recursos bien definidos, REST es la opción más fácil y rápida. Sin embargo, si tu aplicación requiere flexibilidad, consultas complejas o una API que evolucione constantemente, GraphQL será más adecuado. 

La clave está en entender las necesidades de tu proyecto y elegir la tecnología que mejor se adapte a ellas.

¡Ahora que conoces las diferencias, es tu turno de decidir! 
Read more

Diseño Centrado en el Usuario​

Diseño Centrado en el Usuario

Una metodología que prioriza las necesidades del usuario en el desarrollo de productos. A través de investigación, conceptualización y pruebas iterativas, se crean soluciones intuitivas y funcionales. Este enfoque mejora la experiencia, fomenta la innovación y aumenta la fidelización.
Francisco Javier Matías Hernández
uXcale Frontend Developer

En un mundo donde las expectativas de los usuarios están en constante evolución, la forma en que diseñamos productos y servicios juega un papel crucial en su éxito. Uno de los enfoques más utilizados para asegurar que los resultados sean satisfactorios es la metodología de Diseño Centrado en el Usuario (DCU). Pero, ¿qué significa realmente diseñar con el usuario en mente y cómo se implementa en el proceso de creación?

¿Qué es el Diseño Centrado en el Usuario?

El Diseño Centrado en el Usuario es una metodología que pone al usuario en el centro del proceso de diseño. En lugar de tomar decisiones basadas únicamente en suposiciones o en los deseos de los desarrolladores, se busca comprender las necesidades, comportamientos, motivaciones y desafíos de las personas que utilizarán el producto o servicio. El objetivo es crear soluciones que sean funcionales, usables y, sobre todo, que ofrezcan una experiencia significativa para el usuario final.

    Diseño centrado en el usuario

    Fases del Diseño Centrado en el Usuario

    El proceso se divide generalmente en varias fases que permiten una investigación profunda y una prueba constante con los usuarios. Aquí te mostramos las principales etapas:

    I. Descubrimiento

    Antes de diseñar, es crucial entender a quién va dirigido el producto. Esto implica realizar entrevistas, encuestas y estudios de mercado para conocer las necesidades, problemas y deseos del usuario. El objetivo es recopilar información cualitativa y cuantitativa que guíe todo el proceso de diseño. 

    A partir de los datos obtenidos, se define claramente el problema que se debe resolver. Esta fase ayuda a identificar las oportunidades de diseño, enfocándose en las áreas donde el usuario tiene dificultades o deseos insatisfechos.

      II. Conceptualización

      En esta etapa, los diseñadores, junto con otros miembros del equipo, generan ideas y posibles soluciones. La creatividad es clave, y aquí se buscan enfoques innovadores para resolver el problema identificado en la fase anterior.

      A medida que las ideas comienzan a tomar forma, se desarrollan diseños de baja fidelidad (sketches, wireframes, maquetas) y flujos de navegación para poner a prueba las soluciones propuestas. Estos prototipos permiten a los diseñadores evaluar rápidamente las ideas antes de comprometer recursos en desarrollos más avanzados.

      III. Prototipado y Pruebas con Usuarios

      Una de las fases más importantes del Diseño Centrado en el Usuario es la prueba. Los prototipos de alta fidelidad son evaluados por los usuarios finales a través de pruebas de usabilidad. Se recopila feedback sobre cómo los usuarios interactúan con el producto, qué dificultades encuentran y qué aspectos les parecen intuitivos o problemáticos.

      Con la información obtenida en las pruebas, el diseño se ajusta y se mejora. El proceso de DCU es iterativo: siempre se vuelve a probar, ajustar y mejorar, lo que garantiza que el producto final sea verdaderamente centrado en el usuario y sus necesidades.

      ¿Por qué es tan importante esta metodología?

      1. Mejora la Experiencia del Usuario
        El principal beneficio de aplicar esta metodología es que garantiza que el producto final sea fácil de usar, intuitivo y accesible por el usuario objetivo. Un diseño que se adapta a las necesidades del usuario crea una experiencia mucho más agradable y eficiente.
      2. Fomenta la Innovación
        Al tener al usuario en el centro del proceso de diseño, los equipos pueden identificar nuevas oportunidades y desarrollar soluciones innovadoras que realmente marquen la diferencia en el mercado.
      3. Reducción de Costos a Largo Plazo
        Aunque el proceso de diseño centrado en el usuario puede implicar una inversión inicial mayor, los costos a largo plazo disminuyen. Los productos bien diseñados tienen menos fallos y requieren menos cambios.
      4. Mayor Satisfacción y Fidelización del Cliente
        Un producto que responde de manera efectiva a las expectativas y necesidades del usuario tiene más probabilidades de generar una relación a largo plazo con el cliente, lo que aumenta la fidelidad y reduce las tasas de abandono.

      Conclusión

      El Diseño Centrado en el Usuario no es solo una metodología, sino una filosofía que pone al usuario en el centro de todos los procesos de diseño. Con este enfoque, no solo se crean productos más efectivos y útiles, sino que también se construyen experiencias memorables que generan lealtad y satisfacción. La clave para tener éxito en el mundo digital actual es comprender que el diseño no es solo estético o funcional, sino profundamente conectado con las emociones, necesidades y expectativas de los usuarios.

      Así que la próxima vez que enfrentes un desafío de diseño, recuerda: la mejor solución no es la que tú crees que es la más innovadora, sino la que resuelve de forma efectiva los problemas de tus usuarios.

      Read more

      Domina el Manejo de Logs en Spring Boot

      ✨️ Domina el Manejo de Logs en Spring Boot

      ¿Alguna vez te has sentido perdido entre errores, advertencias y mensajes crípticos cuando tu aplicación Spring Boot no hace lo que esperas? Si es así, nuestro artículo te encantará
      Aroa Nieto Rodríguez | Mario Rodrigo Marcos | Julia García Vega
      uXcale Backend and APIs Developers

      Si alguna vez te has sentido perdido entre errores, advertencias y mensajes crípticos cuando tu aplicación Spring Boot no hace lo que esperas, tranquilo, nos ha pasado a todos. Aquí es donde los logs se convierten en tu mejor aliado. En este artículo aprenderás a usarlos a tu favor, configurarlos correctamente y sacarles el máximo provecho.

      Codigo-Python

      🌟 ¿Por qué los logs son imprescindibles?

      Imagina que tu aplicación es un auto y los logs son los retrovisores. Te permiten ver si el motor funciona bien, si hay una luz encendida en el tablero o si alguien dejó la puerta abierta. Los logs te dan visibilidad sobre lo que ocurre en el sistema, permitiéndote anticiparte a problemas antes de que sea tarde. Sin ellos, estarías manejando a ciegas.

      Casos en los que un buen log te salva:

      • ¿Por qué mi aplicación se cayó a las 3 AM? → El log tiene la respuesta y te ayuda a diagnosticar la causa.
      • ¿Por qué un usuario no puede acceder al sistema? → Un log te lo dice, mostrando errores de autenticación o permisos.
      • ¿Por qué el rendimiento de la app bajó de repente? → Los logs te ayudan a encontrar el cuello de botella y optimizar el sistema.

      Un buen sistema de logging no solo te ayuda en la resolución de problemas, sino que también facilita la auditoría y la mejora continua del software.

      🛠️ Spring Boot: un gran aliado para manejar logs

      Si trabajas con Spring Boot, estás de suerte. Este framework usa SLF4J para manejar logs y te da flexibilidad para escribirlos en consola, archivos o sistemas externos. Con solo unos ajustes, puedes obtener información clave de tu aplicación y mejorar la observabilidad

      1. Configuración básica: Simplicidad ante todo

      Si solo necesitas lo esencial, puedes configurar los logs en application.properties o application.yml.

      logging.level.root=INFOlogging.level.org.springframework=ERROR

      ¿Qué significa esto?

      • INFO: Verás los mensajes más importantes sin saturarte de detalles.
      • ERROR: Solo se mostrarán errores críticos del framework, ignorando lo irrelevante.

      Es como ajustar el volumen de la radio: solo escuchas lo que realmente importa. Si necesitas más detalle, puedes cambiar el nivel a DEBUG o TRACE.

      2. Usando Logback para un control más preciso

      Spring Boot usa Logback por defecto, lo que te permite definir patrones y niveles de logs con más detalle.

      Si tu aplicación genera demasiados logs, puedes filtrarlos fácilmente:

      <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
          <encoder>
              <pattern>%msg%n</pattern>
          </encoder>
      </appender>

      <root level="ERROR">
          <appender-ref ref="console" />
      </root>

      Con esto, solo los errores se mostrarán en consola, ignorando mensajes de depuración innecesarios. También puedes configurar logs en archivos y definir rotaciones para evitar saturar el disco.

      <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
          <file>logs/application.log</file>
          <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
              <fileNamePattern>logs/application-%d{yyyy-MM-dd}.log</fileNamePattern>
              <maxHistory>30</maxHistory>
          </rollingPolicy>
          <encoder>
              <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
          </encoder>
      </appender>

      3. Logs asíncronos: rendimiento sin interrupciones

      Si necesitas máxima eficiencia, puedes hacer que los logs se manejen de forma asíncrona para no frenar tu aplicación. Esto evita bloqueos innecesarios y mejora el rendimiento.

      <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
          <appender-ref ref="console" />
      </appender>

      Así, los logs se registran sin afectar la velocidad de ejecución de la app. Es especialmente útil en aplicaciones de alto rendimiento o microservicios con alta carga de trabajo.

      4. Monitoreo y gestión en producción

      Ya tienes los logs funcionando de manera eficiente, pero ¿cómo supervisarlos en entornos productivos? Aquí entran en juego herramientas de monitoreo como ELK (Elasticsearch, Logstash y Kibana) o Grafana con Loki.

      Estas herramientas te permiten:

      • Centralizar logs de múltiples servicios y obtener una vista general.
      • Visualizar datos en tiempo real para detectar problemas rápidamente.
      • Generar alertas cuando ocurre un error crítico y actuar a tiempo.
      • Analizar tendencias para mejorar el rendimiento y la experiencia del usuario.

      Un buen monitoreo no solo detecta fallos, sino que también ayuda en la optimización de la infraestructura y los tiempos de respuesta.

      🔍 Conclusión

      Manejar logs en Spring Boot no tiene por qué ser complicado. Desde configuraciones básicas hasta estrategias avanzadas de monitoreo, tienes todas las herramientas para mantener el control de tu aplicación y anticiparte a problemas.

      Un buen sistema de logs no solo te ahorra dolores de cabeza, sino que también mejora la calidad del software, facilita la auditoría y permite responder de forma eficiente a incidentes.

       

      ¡Ahora pon en práctica estos consejos y haz que los logs trabajen para ti! 🚀

      Read more

      La Metodología de diseño C.R.A.P.

      La Metodología de diseño C.R.A.P.

      Descubre cómo la metodología C.R.A.P. puede transformar tu diseño web: mejora la experiencia de usuario y crea interfaces más atractivas y funcionales
      Paula de la Fuente Ayuso
      uXcale Frontend Developer

      En el mundo del diseño web, la experiencia del usuario (UX) y la interfaz de usuario (UI) son clave para crear sitios efectivos y atractivos. Una metodología fundamental que ayuda a lograr estos objetivos es el llamado C.R.A.P., un acrónimo un tanto peculiar que hace referencia a cuatro principios esenciales para crear diseños visuales claros y eficaces.

      Su uso en el diseño de interfaces no solo mejora la estética de un sitio, sino que también facilita la interacción del usuario con el contenido. La correcta implementación de la metodología asegura que el sitio sea accesible, funcional y fácil de navegar, lo que puede traducirse en una mejor experiencia de usuario y, por lo tanto, en un mayor nivel de satisfacción.

      I. C: Contraste (Contrast)

      El contraste es crucial para garantizar que los elementos sean fácilmente legibles y destacables. Asegurarse de que haya un buen contraste entre el texto y el fondo, entre los botones y los elementos circundantes, mejora la accesibilidad y la estética. En el diseño web, esto puede lograrse con el uso adecuado de colores, tipografías y tamaños de fuente:

      • Usar diferentes tamaños y grosores de fuentes para diferenciar secciones.
      • Contrastar el color del texto con el fondo para facilitar la lectura.
      • Asegurarse de que los botones y CTA (Call to Action) sean prominentes.
      Good and Bad Contrast Example

      II. R: Repetición (Repetition)

      La repetición no solo crea armonía, sino que también ayuda a que los usuarios reconozcan patrones y se familiaricen rápidamente con la interfaz. Al repetir ciertos elementos visuales, como los iconos, los colores y las fuentes, se refuerzan las funciones y las secciones dentro del sitio:

      • Repetir los mismos colores para los botones de navegación o acciones.
      • Utilizar un estilo consistente en las tipografías y los elementos gráficos a lo largo de todo el sitio.
      • Repetir los patrones de diseño en las páginas internas para mantener la coherencia visual.

      III. A: Alineación (Alignment)

      Los elementos alineados crean una sensación de organización y equilibrio. La alineación adecuada guía al usuario a través de la página sin distracciones. Un diseño desordenado puede causar confusión, mientras que una alineación precisa mejora la claridad y la organización:

      • Asegurar que los elementos en las páginas (texto, imágenes, botones) estén alineados de forma clara, ya sea a la izquierda, derecha o centrado.
      • Usar una cuadrícula de diseño para organizar los elementos de manera coherente.
      • Evitar el desorden y asegurarse de que cada componente esté alineado con otros para mantener la armonía.
      • Respetar espaciado y márgenes siguiendo las pautas de diseño.

      IV. P: Proximidad (Proximity)

      La proximidad se refiere a la agrupación de elementos relacionados entre sí. Los elementos que están estrechamente relacionados deben colocarse cerca unos de otros, mientras que los elementos que no tienen relación deben separarse. Esto ayuda a los usuarios a interpretar la información de manera más rápida y precisa y reducir la carga cognitiva:

      • Agrupar elementos que realizan una función similar, como campos de formulario o menús de navegación.
      • Separar los botones de acción de las informaciones secundarias para evitar confusión.
      • Utilizar el espacio mejorar la jerarquía visual.

      Y tú, ¿utilizas esta metodología?

      Read more
      Big-data

      PANDAS VS POLARS: SENCILLEZ VS EFICIENCIA

      PANDAS VS POLARS: SENCILLEZ VS EFICIENCIA

      Pandas ha sido la herramienta principal para la transformación y análisis de datos durante años. Sin embargo, en los ultimos años, han aparecido nuevas herramientas que permiten realizar estas mismas tareas igualmente. Una de estas nuevas opciones es Polars.
      Alberto Perez Galende
      Matemático/Científico de datos. uXcale AI Engineer

      Pandas ha sido la herramienta principal para la transformación y análisis de datos durante años, y la preferida por los científicos de datos con diferencia. Es fácil, de usar, con funciones sencillas e intuitivas, y permite a aquellos que se están iniciando en el mundo de los datos aprender las nociones básicas del ETL. Esto ha permitido que los usuarios creen una gran cantidad de tutoriales y documentación, haciendo más accesible a los nuevos usuarios el inicio.

      Sin embargo, en los ultimos años, han aparecido nuevas herramientas que permiten realizar estas mismas tareas igualmente. Una de estas nuevas opciones es Polars, lanzado en junio de 2024. Entre las mejoras que incorpora Polars respecto a Pandas están:

      1. Evaluación diferida
      2. API más clara y funcional
      3. Procesamiento en paralelo
      4. Optimización de la memoria
      En este blog vamos a analizar tiempos de ejecución de estas dos librerías tanto para procesos sencillos como para procesos de tratamiento de datos más complejos. Además se calcularán los tiempos usados en cada tipo de operación y en total, a lo largo de 10 iteraciones. Los procesos a ejecutar son:

      1. Carga de datasets desde .csv
      2. Concatenación de dataframes
      3. Reordenación de entradas
      4. Filtrado de entradas respecto a un valor
      5. Transformación de una columna
      Big-data

      # PRUEBAS

      # PANDAS #

      #### Ordenación

      ```python

          pd_df.sort_values(by="deaths", ascending=False)

      ```

      El tiempo medio de ejecución de esta consulta en un notebook es 1.00104 ms, llegando a un tiempo máximo de 2.00277 ms.

      #### Filtrado

      ```python

          pd_df_filtered = pd_df[(pd_df["deaths"] > 1000) & (pd_df["region"] == "Turkey")]

      ```

      El tiempo medio de ejecución es de 0.29994 ms con un tiempo máximo de 1.00185 ms.

      #### Transformación

      ```python

          pd_df["month"].apply(convert_month)

      ```

      El tiempo medio de ejecución es de 0.20017 ms con un tiempo máximo de 1.00183 ms.

      #### Proceso completo

      ```python

      pd_df_1 = pd.read_csv("data/earthquakes.csv")

          pd_df_2 = pd.read_csv("data/earthquakes_2.csv")

         

          pd_df = pd.concat([pd_df_1, pd_df_2], axis=0, ignore_index=True)

         

          pd_df.sort_values(by="deaths", ascending=False)

          pd_df_filtered = pd_df[(pd_df["deaths"] > 1000) & (pd_df["region"] == "Turkey")]

         

          pd_df_filtered["month"].apply(convert_month)

         

          pd_df_filtered.to_csv("data/earthquakes_combined_pd.csv", index=False)

      ```

      En esta parte se mide el tiempo total de estos procesos hasta llegar a guardar los nuevos datos en un archivo cvs. En total, de media, este proceso usando **Pandas** tardó 9.352 ms.

      # POLARS #

      #### Ordenación

      ```python

          pl_df.sort(by="deaths", descending=True)

      ```

      El tiempo medio de ejecución de este código en un jupyter notebook es de 0.59876 ms con un tiempo máximo de 2.00128 ms.

      #### Filtrado

      ```python

          pl_df_filtered = pl_df.filter((pl.col("deaths") > 1000) & (pl.col("region") == "Turkey"))

      ```

      El tiempo medio de ejecución es de 0.96478 ms con un tiempo máximo de 4.54258 ms.

      #### Transformación

      ```python

          pl_df.with_columns(pl.col("month").replace_strict(month_map))

      ```

      El tiempo medio de ejecución es de 2.20010 ms y el tiempo máximo es de 1.700019 ms.


      #### Proceso completo

      ```python

      pl_df_1 = pl.read_csv("data/earthquakes.csv", null_values="NA")

          pl_df_2 = pl.read_csv("data/earthquakes_2.csv", null_values="NA")

         

          pl_df = pl.concat([pl_df_1, pl_df_2])

         

          pd_df.sort_values(by="deaths", ascending=False)

          pl_df_filtered = pl_df.filter((pl.col("deaths") > 1000) & (pl.col("region") == "Turkey"))

         

          pl_df_filtered.with_columns(pl.col("month").replace_strict(month_map))

         

          pl_df_filtered.write_csv("data/earthquakes_combined_pl.csv")

      ```

      El tiempo medio de ejecución de todo el proceso completo usando **Polars** es de 5.051 ms.

      # CREAMOS UN SET DE PRUEBAS A MAYORES, PARA VERIFICAR RESULTADOS #

      Para finalizar también se ejecutó un proceso más complejo, con operaciones encadenadas y cálculos más costosos con ambas librerías.

      #### Pandas

      ```python

      pd_df = pd.read_csv("data/earthquakes.csv")

          pd_df["month"] = pd_df["month"].apply(convert_month)

          avg_death_by_month = pd_df.groupby("month")["deaths"].mean()

      ```

      #### Polars

      ```python

      pl_df = pl.read_csv("data/earthquakes.csv", null_values="NA")

          pl_df = pl_df.with_columns(pl.col("month").replace_strict(month_map))

          avg_death_by_month = pl_df.group_by("month").agg(pl.col("deaths").mean())

      ```

      El timepo medio de esta ejecución con **Pandas** fue de 4.453 ms mientras que con **Polars** fue solo de 1.600 ms.

      # RESULTADOS #

      Se ha comprobado que en procesos separados, en los que se consultan datos de manera constante, la *ganadora* se puede considerar **Pandas**, ya que aunque en la ordenación obtuvo un tiempo mínimamente peor, sí que obtuvo mejores marcas en el resto de procesos.

      Sin embargo, cuando las operaciones se concatenan sin la necesidad de consultar los datos, o se realizan operaciones más complejas con muchos datos, **Polars** es claramente superior, llegando incluso a ser 2.78 veces más rápido que **Pandas**.

      # CONCLUSIONES #

      Ambas librerías tienen sus beneficios en comparación con la otra. **Pandas** tiene una curva de aprendizaje rápida, que permite crear pequeños proyectos o funciones de manera rápida y sencilla. Es idóneo para aquellas personas que están empezando o tienen poca experiencia en el mundo de los datos. Como contraposición, si alguien ya tiene experiencia, conoce lenguajes como SQL, o tiene acceso a un cluster que permita la paralelización de procesamientos, debe elegir **Polars**. La documentación de esta última es bastante menor, y los ejemplos prácticos son más escasos, lo cual hace que la curva de aprendizaje sea más difícil, pero las capacidades son mucho mayores.

      Y tú, ¿has probado o oído hablar de alguna de estas librerías? ¿Cuál prefieres? ¿Por qué? Estamos deseando conocer tus opiniones y experiencias.

      Read more
      PC-programacion-Software

      Programación Reactiva en Backend con Spring Boot

      Programación Reactiva en Backend con Spring Boot

      Más Allá del Modelo Imperativo
      Aroa Nieto Rodríguez | Mario Rodrigo Marcos | Julia García Vega
      uXcale Backend and APIs Developers

      En el mundo del desarrollo de backend, la necesidad de manejar grandes volúmenes de datos y solicitudes concurrentes de manera eficiente ha llevado a explorar paradigmas como la programación reactiva. En este artículo, exploraremos qué es la programación reactiva, cómo se aplica en backend con Spring Boot y qué ventajas ofrece frente al enfoque tradicional imperativo.

      Pero... ¿Qué es la Programación Reactiva?

      Programación Reactiva

      La programación reactiva es un paradigma de desarrollo basado en flujos de datos asíncronos y dirigidos por eventos. En lugar de procesar cada solicitud de manera secuencial y bloquear recursos hasta que se complete, este enfoque permite manejar miles de solicitudes simultáneamente sin saturar los recursos del sistema.

      Algunos conceptos clave de la programación reactiva son:

      • Asincronía: Las operaciones no bloquean el hilo mientras esperan datos.
      • Backpressure: Los sistemas pueden manejar la velocidad de emisión de datos para evitar la sobrecarga.
      • Flujos de datos: Los datos se procesan como streams, permitiendo transformaciones en tiempo real.

      En el ecosistema de Java, Reactor, la biblioteca reactiva utilizada en Spring WebFlux, implementa el estándar Reactive Streams, que es la base de la programación reactiva.

      Spring Boot y la Programación Reactiva: ¿Qué es WebFlux?

      Spring WebFlux es el módulo de Spring Boot diseñado específicamente para construir aplicaciones reactivas. A diferencia de Spring MVC, que sigue un modelo basado en hilos bloqueantes, WebFlux utiliza un modelo no bloqueante, ideal para sistemas que manejan:

      • Grandes cantidades de datos en tiempo real.
      • Altas cargas de solicitudes concurrentes.
      • Integración con servicios externos de forma eficiente.

      ¿Qué diferencia a WebFlux de Spring MVC?

      Cómo Implementar Programación Reactiva con Spring Boot

      1. Configuración Básica. Para empezar, necesitas agregar las dependencias necesarias en tu proyecto Spring Boot:
      <dependency>

          <groupId>org.springframework.boot</groupId>

          <artifactId>spring-boot-starter-webflux</artifactId>

      </dependency>
      1. Controladores Reactivos. En lugar de devolver un objeto tradicional, los controladores en WebFlux devuelven tipos reactivos como Mono y Flux:
      • Mono: Representa un único valor o ningún valor (similar a Optional).
      • Flux: Representa un flujo de 0 a N elementos.

      Ejemplo de un controlador:

      @RestController
      @RequestMapping("/api/reactivo")

      public class ReactiveController {

         @GetMapping("/mono")
         public Mono<String> getMono() {
             return Mono.just("¡Hola desde Mono!").map(String::toUpperCase);
         }

          @GetMapping("/flux")
         public Flux<Integer> getFlux() {
             return Flux.range(1, 10).filter(i -> i % 2 == 0); // Solo números pares
         }
      }

      1. Acceso a Datos Reactivo. Spring Data Reactive incluye soporte para bases de datos no bloqueantes como MongoDB o Cassandra. Por ejemplo, usando una base de datos reactiva:
      @Repositorypublic interface ReactiveUserRepository extends ReactiveCrudRepository<User, String> {
          Flux<User> findByRole(String role);
      }
      1. Gestión de Backpressure. El manejo de backpressure es esencial para controlar la velocidad del flujo de datos. Con Reactor, puedes usar operadores como limitRate para limitar el número de elementos procesados a la vez:
      Flux.range(1, 1000)
          .onBackpressureBuffer().limitRate(50)
          .subscribe(System.out::println);

      Ventajas de la Programación Reactiva en Spring Boot

      1. Mayor Escalabilidad. Las aplicaciones reactivas pueden manejar muchas conexiones simultáneamente sin saturar los recursos del servidor.
      2. Mejor Uso de Recursos. Al evitar bloqueos, los hilos están disponibles para procesar más tareas.
      3. Integración Eficiente con Servicios Externos. Al trabajar con APIs, bases de datos o colas de mensajes no bloqueantes, la programación reactiva asegura una comunicación fluida y rápida.
      4. Compatibilidad con Tiempo Real. Ideal para aplicaciones que requieren actualizaciones instantáneas, como chats o paneles en vivo.

      Casos de Uso de la Programación Reactiva

      • Aplicaciones de IoT. Procesamiento de grandes volúmenes de datos de sensores en tiempo real.
      • Plataformas de Streaming. Manejo de flujos de video o audio.
      • Sistemas Financieros. Procesamiento en tiempo real de transacciones.

      La programación reactiva con Spring Boot y WebFlux ofrece un enfoque moderno y eficiente para construir aplicaciones backend que pueden escalar de manera impresionante y manejar escenarios complejos de concurrencia. Si bien requiere un cambio de mentalidad respecto al modelo imperativo, los beneficios en rendimiento y escalabilidad lo convierten en una poderosa herramienta en el arsenal del desarrollador.

      🌟¿Estás listo para dar el salto al mundo reactivo? 🌟

      Read more

      Remplaza el Switch ya

      ¡Remplaza el Switch ya!

      Fabián Gómez Campo
      uXcale Frontend Developer

      Cuando comenzamos a programar, el switch suele ser una de las primeras estructuras de control que aprendemos. Es fácil de entender: un conjunto de condiciones organizadas que nos ayudan a manejar diferentes casos. Sin embargo, a medida que nuestros proyectos crecen y los requisitos se vuelven más complejos, este enfoque puede empezar a jugar en nuestra contra.

      El switch funciona bien para manejar un número limitado de opciones, pero ¿qué pasa cuando los casos empiezan a multiplicarse? De repente, ese bloque de código sencillo se transforma en un volumen difícil de leer y mantener. Además, si tu proyecto requiere actualizaciones frecuentes o ajustes dinámicos, el switch no es la herramienta más flexible

      ¿Que problemas vemos y que alternativas consideramos a tener en cuenta?

      Los problemas del switch

      PC-programacion-Software

      Imagina que estás desarrollando una aplicación con un proceso por pasos, y necesitas mostrar diferentes mensajes según el paso en el que se encuentre el usuario. La solución tradicional sería usar un switch. Aunque funcional, este enfoque puede llevar a varios problemas:

      1. Dificultad para mantener el código: Si necesitas agregar o modificar casos, cada cambio implica tocar la estructura directamente, aumentando el riesgo de cometer errores.
      2. Falta de claridad: Un switch con muchos casos puede parecer interminable y dificultar que otros (o incluso tú mismo, dentro de unas semanas) entiendan rápidamente qué está haciendo el código.
      3. Rígido ante cambios dinámicos: Si los mensajes dependen de datos externos o necesitan cambiarse frecuentemente, el switch no se adapta bien

      Una alternativa: Mapear casos en un objeto

      Aquí es donde los objetos entran al rescate. En lugar de usar un switch, puedes crear un objeto donde cada caso esté mapeado como un par clave-valor. Este enfoque es más limpio, legible y escalable, y permite mantener la lógica separada de la estructura.

      Vamos a verlo en acción.

      Ejemplo 1: Usando un switch

      tsx

      Copiar código

      const renderStep = (step: number): string => {
          switch (step) {
              case 0:
                  return "Paso 1: Introducir datos";
              case 1:
                  return "Paso 2: Confirmar información";
              case 2:
                  return "Paso 3: Finalizar";
              default:
                  return "Error: Paso no válido";
          }
      };

      Este código cumple su función, pero imagina que el proceso crece a 10, 20 o incluso más pasos. La estructura rápidamente se vuelve poco manejable. Además, si necesitas cambiar un mensaje o agregar un nuevo paso, tendrás que modificar directamente el bloque del switch.

      Ejemplo 2: Usando un objeto

      tsx

      Copiar código

      const stepMessages: Record<number, string> = {
          0: "Paso 1: Introducir datos",
          1: "Paso 2: Confirmar información",
          2: "Paso 3: Finalizar",
      };

      const renderStep = (step: number): string => {
          return stepMessages[step] ?? "Error: Paso no válido";
      };

      Con este enfoque, las claves del objeto representan cada paso, y los valores contienen los mensajes correspondientes. Si necesitas agregar o cambiar algo, solo tienes que modificar el objeto sin preocuparte de la lógica principal.

      Conclusión: ¿Por qué el enfoque con objetos es mejor?

      1. Mayor claridad: La estructura de los casos es más sencilla de leer. No necesitas recorrer un switch para entender qué hace cada paso.
      2. Fácil mantenimiento: Agregar, eliminar o modificar un caso es tan simple como ajustar una clave o valor en el objeto.
      3. Reutilización: Puedes usar el mismo objeto en diferentes partes del código. Por ejemplo, si tienes que mostrar los mensajes en otro lugar, solo tienes que importar el objeto.
      4. Escalabilidad: Este enfoque se adapta mejor si los casos son dinámicos o necesitan actualizarse desde una fuente externa.

      El switch cumple su función, pero no escala bien en proyectos complejos. Usar un objeto ofrece una solución más limpia, clara y fácil de mantener. Simplifica el código desde el inicio.

      Read more