JIZT empleará una arquitectura basada en microservicios (microservices architecture, en inglés). Este tipo de arquitectura se contrapone a las arquitecturas monolíticas, en las cuales los diferentes componentes están estrechamente interrelacionados.
En nuestro caso, requerimos de una arquitectura en la que las dependencias entre los diferentes componentes sean lo más flexibles posibles. Esto se debe a que existen numerosos modelos de generación de resúmenes, y queremos poder darle la opción al usuario de elegir qué modelo emplear.
Además, el campo del Procesamiento de Lenguaje Natural (NLP, por sus siglas en inglés) es un área bajo activo desarrollo, en el que aparecen modelos más y más potentes cada pocos meses. Por ello, nuestra arquitectura debe ser capaz de adaptarse a estos cambios de manera local, de forma que el reemplazo de un componente sea transparente al resto.
En un principio existirán los siguientes microservicios:
Dispatcher: se encargará de dar comienzo al procesado del texto, gestionando el asincronismo de la API REST (explicado más adelante). En un principio, redigirá todas las peticiones entrantes hacia el Pre-procesador de textos. No obstante, en un futuro podría añadirse otro microservicio, por ejemplo, un Pre-procesador de hilos de Twitter, que se encargaría de extraer el texto de un thread de Twitter y pre-procesarlo adecuadamente. En este caso, el Dispatcher debería ser capaz de detectar que la petición contiene un link a una conversación de Twitter, y la redirigiría hacia el Pre-procesador de hilos de Twitter, y no hacia el Pre-procesador de textos.
Pre-procesador de textos (Text Pre-processor): este microservicio se encarga de pre-procesar textos "planos" en formato UTF-8, a fin de adecuarlos para su posterior resumen por parte del motor de resumen.
Codificador de textos (Text Encoder): una vez el texto ha sido pre-procesado, es necesario "codificarlo". Este proceso consiste en, dicho de manera informal y simplificada, "traducir" las palabras del texto a vectores de números. Con estos vectores son los que trabaja a continuación por el motor de resumen, para generar, valga la redundancia, los resúmenes. Existen diferentes modelos que se pueden emplear en la codificación del texto, por lo que este microservicio, en un futuro, podría dividirse en varios. Por ahora solo se empleará el codificador t5-large de Hugging Face.
Motor de resumen (Text Summarizer): aquí es donde se genera el resumen en sí. De nuevo, al igual que para la codificación del texto, se pueden encontrar una gran variedad de modelos para la generación de resúmenes, por lo que este microservicio también se podría dividir en varios. De momento, solo se empleará el modelo t5-large de Hugging Face.
Post-procesador (Text post-processor): el texto de salida del motor de resumen puede no ser completamente adecuado para entregar al cliente como resultado final. Por ejemplo, el ya mencionado modelo t5-large genera los resúmenes en minúsculas, por lo que una de las tareas de post-procesado ha de consistir en poner mayúsculas allá donde se requiera.
Los anteriores microservicios se ejecutarán dentro de contenedores Docker orquestrados mediante Kubernetes, de forma que el número de contenedores en ejecución se pueda escalar de manera automatizada para adaptarse a las demandas de los clientes.
Existen dos cuestiones fundamentales a abordar a la hora de emplear este tipo de arquitectura:
La generación de resúmenes es una tarea que se puede dilatar varios segundos en el tiempo, dependiendo de factores como la longitud del texto o de los parámetros con los que se quiera generar el resumen. Por lo tanto, realizar peticiones HTTP síncronas queda descartado, puesto que estas no se pueden mantener activas durante periodos de tiempo tan prolongados. La pregunta es: ¿cómo se gestionará el asincronismo?
El proceso de resumen de los textos es principalmente secuencial, de forma que las salidas de unos microservicios será las entradas de los siguientes. Por tanto, la siguiente pregunta es: ¿cómo se llevará a cabo la comunicación entre ellos? Y, ¿cómo sabrá un microservicio a que otro microservicio debe enviarle su output?
1. Asincronismo
La solución para evitar llevar a cabo peticiones HTTP demasiado largas es simplemente no tener que hacerlas. Nuestra API proporcionará un mecanismo para ello. Veamos como funciona.
1.1 Petición HTTP POST
El cliente comienza realizando una petición POST incluyendo en el cuerpo de la petición el texto que quiere resumir. La API le responde con un identificador único del resumen, el summary_id:
1.2 Peticiones HTTP GET sucesivas
En ese momento, el cliente puede llevar a cabo peticiones HTTP GET con el iddel resumen de manera periódica a fin de consultar el estado del mismo. Los diferentes estados en los que un resumen puede estar son:
pre-processing: el texto está siendo pre-procesado.
encoding: el texto está siendo codificado.
summarizing: se está generando el resumen.
post-processing: el texto está siendo post-procesado.
completed: el resumen ha sido completado.
Una de las principales ventajas de poder consultar el estado del resumen es poder ofrecerle al usuario retroalimentación acerca del proceso de resumen, ya que de lo contrario no podría saber si el sistema se ha quedado "colgado" o sigue trabajando.
1.3 Petición HTTP GET final
En algún momento, el estado del resumen pasará a ser completed y la respuesta contendrá el resumen generado:
Los diferentes resúmenes se almacenarán en una base de datos gestionada por el Dispatcher, de manera que:
Cuando el Dispatcher recibe una petición POST, lo primero que hace es calcular un summary_id y ver si este ya existe en la base de datos. El summary_id se genera de tal forma que dos textos idénticos tendrán el mismo summary_id. Por tanto, si un texto ya hubiera sido resumido anteriormente, no se volvería a generar su resumen, si no que se recuperaría de la base de datos.
Si el texto no hubiera sido resumido previamente, el Dispatcher pasaría el texto a resumir al Pre-procesador de textos. Además, almacenaría el resumen en la base de datos. El estado de dicho resumen sería preprocessing, y su output (es decir, el resumen) null.
El Dispatcher iría actualizando el estado del resumen a medida que va pasando por las diferentes etapas (codificación, resumen, etc.).
Cada vez el cliente realizara una petición GET con el id del resumen, consultaría su estado en la base de datos, y respondería en consecuencia.
2. Arquitectura dirigida por eventos: Apache Kafka
Para resolver el segundo punto, es decir, cómo implementar la comunicación entre microservicios, emplearemos una Arquitectura dirigida por eventos (o Event-driven architecture, EDA, en inglés). En nuestro caso se considerará como un evento cada vez que un microservicio acabe su trabajo,
Para implementar este paradigma arquitectónico, haremos uso de Apache Kafka, una solución software que, si bien requiere cierta labor de configuración, facilita enormemente el despliegue y escalado de este tipo de arquitectura.
Kafka introduce el concepto de topic, que vendría a ser el lugar en el que se recogen los diferentes eventos. Existirán una serie de productores y de consumidores. Los productores se encargarán de publicar mensajes en los topics apropiados, y los consumidores de recoger dichos mensajes.
La figura incluida a continuación puede ayudar a comprender mejor estos conceptos:
En el anterior diagrama, los topics serían aquellos que aparecen incluidos dentro del Kafka Broker (que es el componente encargado de gestionarlos):
Text pre-processing: topic al cual produce el Dispatcher comenzando el procesado del texto, y del cual consume el Pre-procesador de textos. Los mensajes publicados en este topic incluyen el texto del usuario a resumir.
Text encoding: topic al cual produce el Pre-procesador de textos, y del cual consume el Codificador. Los mensajes publicados en este topic incluyen el texto pre-procesado, preparado para su codificación.
Text summarization: topic al cual produce el Codificador, y del cual consume el Motor de resumen. Los mensajes publicados en este topic incluyen el texto codificado, preparado para su resumen.
Text post-processing: topic al cual produce el Motor de resumen, y del cual consume el Post-procesador de textos. Los mensajes publicados en este topic incluyen el texto resumido, preparado para su post-procesado.
Ready: topic al cual produce el Post-procesador de textos. Los mensajes publicados en este topic incluyen el texto resumido y post-procesado, preparado para entregárselo al usuario.
El Dispatcher, por su parte, consume de todos los anteriores topics a fin de actualizar el estado del resumen en cuestión.
Una descripción muy precisa, muy bien explicada , presentada y justificada. La implementación de la computación dirigida por eventos con kafka es muy innovadora.
Me parece un diseño arquitectónico perfecto.
JIZT empleará una arquitectura basada en microservicios (microservices architecture, en inglés). Este tipo de arquitectura se contrapone a las arquitecturas monolíticas, en las cuales los diferentes componentes están estrechamente interrelacionados.
En nuestro caso, requerimos de una arquitectura en la que las dependencias entre los diferentes componentes sean lo más flexibles posibles. Esto se debe a que existen numerosos modelos de generación de resúmenes, y queremos poder darle la opción al usuario de elegir qué modelo emplear.
Además, el campo del Procesamiento de Lenguaje Natural (NLP, por sus siglas en inglés) es un área bajo activo desarrollo, en el que aparecen modelos más y más potentes cada pocos meses. Por ello, nuestra arquitectura debe ser capaz de adaptarse a estos cambios de manera local, de forma que el reemplazo de un componente sea transparente al resto.
En un principio existirán los siguientes microservicios:
t5-large
de Hugging Face.t5-large
de Hugging Face.t5-large
genera los resúmenes en minúsculas, por lo que una de las tareas de post-procesado ha de consistir en poner mayúsculas allá donde se requiera.Los anteriores microservicios se ejecutarán dentro de contenedores Docker orquestrados mediante Kubernetes, de forma que el número de contenedores en ejecución se pueda escalar de manera automatizada para adaptarse a las demandas de los clientes.
Existen dos cuestiones fundamentales a abordar a la hora de emplear este tipo de arquitectura:
La generación de resúmenes es una tarea que se puede dilatar varios segundos en el tiempo, dependiendo de factores como la longitud del texto o de los parámetros con los que se quiera generar el resumen. Por lo tanto, realizar peticiones HTTP síncronas queda descartado, puesto que estas no se pueden mantener activas durante periodos de tiempo tan prolongados. La pregunta es: ¿cómo se gestionará el asincronismo?
El proceso de resumen de los textos es principalmente secuencial, de forma que las salidas de unos microservicios será las entradas de los siguientes. Por tanto, la siguiente pregunta es: ¿cómo se llevará a cabo la comunicación entre ellos? Y, ¿cómo sabrá un microservicio a que otro microservicio debe enviarle su output?
1. Asincronismo
La solución para evitar llevar a cabo peticiones HTTP demasiado largas es simplemente no tener que hacerlas. Nuestra API proporcionará un mecanismo para ello. Veamos como funciona.
1.1 Petición HTTP POST
El cliente comienza realizando una petición POST incluyendo en el cuerpo de la petición el texto que quiere resumir. La API le responde con un identificador único del resumen, el
summary_id
:1.2 Peticiones HTTP GET sucesivas
En ese momento, el cliente puede llevar a cabo peticiones HTTP GET con el iddel resumen de manera periódica a fin de consultar el estado del mismo. Los diferentes estados en los que un resumen puede estar son:
pre-processing
: el texto está siendo pre-procesado.encoding
: el texto está siendo codificado.summarizing
: se está generando el resumen.post-processing
: el texto está siendo post-procesado.completed
: el resumen ha sido completado.Una de las principales ventajas de poder consultar el estado del resumen es poder ofrecerle al usuario retroalimentación acerca del proceso de resumen, ya que de lo contrario no podría saber si el sistema se ha quedado "colgado" o sigue trabajando.
1.3 Petición HTTP GET final
En algún momento, el estado del resumen pasará a ser
completed
y la respuesta contendrá el resumen generado:Los diferentes resúmenes se almacenarán en una base de datos gestionada por el Dispatcher, de manera que:
summary_id
y ver si este ya existe en la base de datos. Elsummary_id
se genera de tal forma que dos textos idénticos tendrán el mismosummary_id
. Por tanto, si un texto ya hubiera sido resumido anteriormente, no se volvería a generar su resumen, si no que se recuperaría de la base de datos.preprocessing
, y suoutput
(es decir, el resumen)null
.2. Arquitectura dirigida por eventos: Apache Kafka
Para resolver el segundo punto, es decir, cómo implementar la comunicación entre microservicios, emplearemos una Arquitectura dirigida por eventos (o Event-driven architecture, EDA, en inglés). En nuestro caso se considerará como un evento cada vez que un microservicio acabe su trabajo,
Para implementar este paradigma arquitectónico, haremos uso de Apache Kafka, una solución software que, si bien requiere cierta labor de configuración, facilita enormemente el despliegue y escalado de este tipo de arquitectura.
Kafka introduce el concepto de topic, que vendría a ser el lugar en el que se recogen los diferentes eventos. Existirán una serie de productores y de consumidores. Los productores se encargarán de publicar mensajes en los topics apropiados, y los consumidores de recoger dichos mensajes.
La figura incluida a continuación puede ayudar a comprender mejor estos conceptos:
En el anterior diagrama, los topics serían aquellos que aparecen incluidos dentro del Kafka Broker (que es el componente encargado de gestionarlos):
El Dispatcher, por su parte, consume de todos los anteriores topics a fin de actualizar el estado del resumen en cuestión.