Cuando la mayoría de la gente piensa en una búsqueda, piensa en el típico cuadro de texto que aparece en los buscadores. Escribe las palabras, pulsa el botón de búsqueda y aparecen páginas de deliciosos resultados.

Pero la búsqueda también está enterrada en otros lugares. ¿Esos chatbots de atención al cliente que conoces y adoras? Hacen búsquedas. Pero en lugar de devolver páginas de resultados, sólo devuelven el resultado más relevante, y lo hacen en una interfaz de usuario conversacional.

Hoy veremos cómo podemos construir nuestro propio chatbot utilizando el ecosistema Jina, y cómo desplegarlo fácilmente en la nube.

Este tutorial se basa en nuestro repo de chatbot de ejemplo y puedes jugar con una demo en vivo. Que a su vez se basa en el chatbot jina hello de Jina< /code>.

Ten en cuenta que el código aquí es mucho más simple que en el repo, ya que no estamos demasiado interesados en el despliegue real o la eficiencia, sólo en la construcción de nuestro chatbot. Sin embargo, el repo contiene un docker-compose.yml para facilitar el despliegue en la nube.

Tabla de contenidos

¿Qué problema queremos resolver?

Cierra los ojos y retrocede en el tiempo. No, no tan lejos: los tazos (tambien conocidos como pogs) nunca volverán a estar de moda.

Pero retrocedamos en el tiempo hasta el nebuloso año 2020, justo al comienzo de la pandemia de COVID-19 (algunos creen que todavía estamos allí). Todo el mundo tenía preguntas, y se oían muchas respuestas que quizá no se sostenían demasiado bien cuando miramos atrás.

Vamos a construir un chatbot utilizando un conjunto de datos de preguntas y respuestas de COVID-19 de 2019. Por el amor de todo lo que es sagrado, no confíes en conjuntos de datos aleatorios que encuentres en Internet para obtener consejos médicos. Esto es sólo para construir una aplicación de ejemplo y no para sustituir a tu médico.

¿Cómo funcionará para el usuario final?

Desde el punto de vista del usuario final, todo lo que tiene que hacer es escribir una pregunta en la interfaz de usuario y se le devolverá una respuesta en una interfaz conversacional. Como cualquier otro chatbot.

¿Cómo funcionará bajo el capó?

Podemos dividir el código de nuestro chatbot en varios pasos:

  • Procesar nuestros datos para cargarlos en nuestra aplicación de búsqueda.
  • Construir y ejecutar un flujo para indexar nuestros datos (codificándolos y almacenando esos incrustaciones y metadatos en el disco).
  • Ejecutar el mismo flujo para permitir a los usuarios buscar con preguntas como entrada.
  • Abrir una bonita GUI en el navegador.

¡Vamos a construirlo!

Puedes referirte al repo para todo el código que discutimos aquí. No vamos a profundizar demasiado en la interfaz de usuario, excepto para mostrar dónde estamos interactuando con el ecosistema Jina.

Descargar los datos

Si clonas el repo, ya tendrás los datos allí. Alternativamente, puedes encontrar el conjunto de datos COVID-QA en Kaggle.

Vamos a utilizar el archivo community.csv, que tiene un montón de campos. Para nuestro caso de uso, sólo nos interesa question y answer.

Configuración básica

Crearemos un archivo llamado config.py y pondremos algunos valores allí para la configuración básica. Esto nos ahorra tener que rebuscar en el archivo principal de nuestra aplicación cada vez que queramos cambiar algo:

PORT = 23456 # which port will we run the REST interface on? NUM_DOCS = 30000 # how many rows of the CSV do we want to index? DATA_FILE = "./data/community.csv" # where can we find the CSV

También necesitaremos instalar DocArray y Jina:

pip install docarray jinag

Convertir los datos en  DocumentArray

Casi todo el ecosistema de Jina funciona con Documentos, el tipo de datos primitivo de Jina. Estos pueden contener texto, imagen, audio, vídeo o cualquier otro tipo de datos. Un grupo de Documentos puede combinarse en un DocumentArray. Toda esta funcionalidad está en el paquete DocArray.

En nuestro caso, trataremos cada fila del archivo CSV como un único Documento, y luego combinaremos todos esos Documentos en un DocumentArray que luego podremos procesar.

DocArray contiene una buena función from_csv para procesar rápidamente los CSV, así que vamos a utilizarla en app.py:

from config import DATA_FILE, NUM_DOCS  docs = DocumentArray.from_csv(     DATA_FILE, field_resolver={"question": "text"}, size=NUM_DOCS )

Como puedes ver, hacemos referencia a DATA_FILE y NUM_DOCS que hemos configurado antes en config.py. A continuación, asignamos el atributo de text de cada documento al campo de la pregunta en la fila correspondiente. Los demás campos se añadirán automáticamente como metadatos en el atributo tags del Documento.

Construir nuestro flujo de indexación

Queremos tomar nuestro DocumentArray, y para cada Documento:

  1. Codificar su contenido en una incrustación vectorial
  2. Almacenarlos en un índice para facilitar la búsqueda más tarde

Haremos esto con nuestro Flow, que construimos usando el núcleo de Jina.

flow = (     Flow(protocol="http", port=PORT)     .add(         name="encoder",         uses="jinahub+docker://TransformerTorchEncoder",         uses_with={             "pretrained_model_name_or_path": "sentence-transformers/paraphrase-mpnet-base-v2"         },     )     .add(name="indexer", uses="jinahub://SimpleIndexer", install_requirements=True) )

Puedes ver que estamos usando un arreglo de Flow().add(...).add(...) para construir nuestro pipeline de procesamiento a partir de Executors:

  • Codificación: El primer .add() añade nuestro ejecutor codificador. Usaremos TransformerTorchEncoder de Jina Hub, que se ejecuta en un contenedor Docker para no tener que preocuparnos por las dependencias. También le diremos qué modelo queremos que utilice.
  • Indexación: Con el último .add() traeremos nuestro indexador Executor. Sólo necesitamos algo sencillo como el bien llamado SimpleIndexer. De nuevo, lo sacamos de Jina Hub pero no lo ejecutaremos en Docker ya que necesita acceso para escribir en el sistema de archivos local (lo que podríamos hacer con unas cuantas opciones de uses_with y volume, pero eso complica el código para un ejemplo sencillo)

También puedes ver que hemos habilitado una pasarela HTTP y hemos establecido el puerto en los argumentos de Flow.

Ejecutar nuestro flujo de indexación

Podemos abrir nuestro Flow con:

with flow:     flow.index(docs, show_progress=True)

Para ponerlo en marcha, ejecuta app.py en el terminal que prefieras. La primera ejecución puede tardar un poco, ya que tiene que descargar nuestros Ejecutores de Jina Hub y luego agitar todos los datos.

Es posible que veas algunas advertencias. No te preocupes por ellas. El Señor sabe que yo nunca lo hago.

Aquí sólo he indexado algunos documentos, no el lote completo

Después deberías ver un workspace de espacio de trabajo que contiene los datos indexados:

Y dentro de esa carpeta del espacio de trabajo verás index.db, una base de datos SQLite que almacena tus datos:

Si has indexado todo el conjunto de datos, el directorio del espacio de trabajo debería ocupar unos 5,4Mb.

Hacer una pregunta a nuestro chatbot

Esta vez utilizaremos el mismo flujo, pero en lugar de indexar, buscaremos la respuesta a una pregunta. Como dijimos antes, todo en el ecosistema de Jina es un Documento, así que lo haremos:

  • Envolver la pregunta en un Documento
  • Pasar ese documento a nuestro flujo
  • Obtener las preguntas más cercanas devueltas por el flujo
  • Imprimir el campo de respuesta de la pregunta más cercana
question = Document(text="Can I catch COVID from my cat?") with flow:     results = flow.search(question)  print(results[0].matches[0].tags["answer"])

Utilizamos los mismos dos Ejecutores que para la indexación, pero esta vez actúan de forma un poco diferente:

  • Codificación: Codifica la cadena de texto de la pregunta en una incrustación vectorial
  • Indexación: Busca en el índice la incrustación más cercana a la cadena de búsqueda codificada y devuelve los datos coincidentes

Ahora, si ejecutas app.py de nuevo, obtendrás una respuesta como:

El Covid-19 es el resultado de un brote zoonótico que pasa de los murciélagos a un huésped intermedio ( y no identificado ) y de ahí a los humanos. Se cree que los murciélagos no sufren la enfermedad por sí mismos, ya que son portadores de bajos niveles de virus, pero el huésped intermedio modifica el virus y lo amplifica, de modo que aumenta enormemente la cantidad de virus que puede verterse en el medio ambiente infectando a los humanos. Una posible hipótesis es que el coronavirus de los murciélagos se combine con un coronavirus de los peces dentro del huésped intermediario. Esto se basa en la observación de que el gen de la espiga del SARS-CoV-2 comparte una inserción de 39 bases con un tipo de pez soldado que nada en el Mar del Sur de China….

Si sigues leyendo, verás que la respuesta contiene información sobre los gatos más abajo:

El SARS-CoV-2 infecta a los humanos a través del receptor de superficie ACE2, pero este receptor no sólo se encuentra en los humanos. Otros animales tienen receptores ACE2 similares, pero no exactamente iguales, y varios perros y un gato han dado positivo en hisopos para el SARS-CoV-2. El gato desarrolló síntomas de covirus-19.

En un ejemplo más avanzado, dividiríamos la respuesta completa (larga) en trozos de frases y sólo devolveríamos al usuario el trozo más relevante.

Añadir una interfaz de usuario

No nos centraremos demasiado en la interfaz de usuario aquí, excepto en ciertas partes que se conectan al ecosistema de Jina. Puedes encontrar los archivos aquí.

Usaremos Streamlit para construir nuestro frontend y el maravilloso módulo Streamlit-Chat para añadir una interfaz tipo chatbot:

pip install streamlit-chat streamlit

Una vez más vamos a configurar un frontend_config.py básico:

PORT = 23456 SERVER = "0.0.0.0" TOP_K = 1

La parte más importante de frontend.py es la forma en que interactuamos con nuestro backend para obtener la mejor respuesta a la pregunta de un usuario. Hacemos esto usando Jina Client:

from jina import Client  def search_by_text(input, server=SERVER, port=PORT, limit=TOP_K):     client = Client(host=server, protocol="http", port=port)     response = client.search(         Document(text=input),         parameters={"limit": limit},         return_results=True,         show_progress=True,     )     match = response[0].matches[0].tags["answer"]      return match

El resto del código del frontend funciona básicamente para tomar la entrada del usuario, enviarla a esa función y mostrar la respuesta en una interfaz de chat. Dado que no es super-relevante para Jina no lo cubriremos aquí.

Conectando nuestra interfaz de usuario a nuestro flujo

Nuestro código de búsqueda de antes es genial para la creación de prototipos CLI, pero ahora tenemos que abrir (y mantener abierta) nuestra interfaz RESTful en nuestro app.py del backend. Vamos a comentar nuestra funcionalidad de búsqueda inicial, ya que ya no la estamos utilizando, y utilizaremos flow.block() para mantener un puerto abierto en su lugar:

# question = Document(text="Can I catch COVID from my cat?") # with flow: #     results = flow.search(question)  # print(results[0].matches[0].tags["answer"])  with flow:   flow.block()

A continuación, volveremos a ejecutar nuestro código de backend:
python app.py

Seguido de nuestro código del frontend en una nueva terminal:
streamlit frontend.py

Una vez que todo se haya puesto en marcha, ahí lo tienes, ¡un chatbot con búsqueda neuronal!:

Próximos pasos

  • Ya está hecho: Si fuéramos a correr en el mundo real, querríamos dividir las cosas en funciones, hacerlas correr en Docker, etc. Ya lo hemos hecho en nuestro repositorio, así que la ejecución y el despliegue es un paseo por el parque.
  • Romper nuestro conjunto de datos en trozos más pequeños: Nadie quiere leer respuestas larguísimas, por lo que sería bueno sentenciar todo y buscar a través de frases en lugar de texto completo.
  • Afinar nuestro modelo: Estamos utilizando un modelo preentrenado que no está especializado en preguntas médicas. Para mejorar el rendimiento, podemos afinar el modelo con el llamado Finetuner de Jina.

Únete a la comunidad

¿Tienes comentarios? ¿Quieres saber más sobre el ecosistema Jina y la búsqueda neuronal? Únete a nosotros en Slack y pásate por alguno de nuestros eventos.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *