En amplio mundo de la inteligencia artificial, la recuperación-generación aumentada (RAG) se destaca como un enfoque útil en el mundo de la programación. El RAG combina la fuerza de la recuperación de información con el poder de los modelos de lenguaje generativo. Este enfoque mejora la capacidad de los modelos para generar respuestas más precisas, informativas y contextualmente relevantes. Esto se consigue al incorporar fuentes de conocimiento externas durante el proceso de generación.
Para mostrar este sistema implementaremos una aplicación capaz de cargar documentos en un almacén de vectores, previo cálculo de embeddings. Nuestra interfaz proporcionará un chat interactivo que atenderá nuestra consulta y, mediante búsqueda semántica, recuperará los documentos más relevantes disponibles en nuestra tienda de vectores. Finalmente, el mensaje resultante compuesto tanto por la consulta inicial como por la información recuperada se envía al LLM para formar una respuesta informada.
Nos ayudaremos en el desarrollo de esta aplicación con el marco Langchain. Este proporciona herramientas e interfaces para operaciones de almacenamiento de vectores, construcción rápida e interacción LLM.
Infraestructura
Nuestra infraestructura será tan sencilla como parece. Implementaremos nuestra aplicación Streamlit en Elastic Container Service en nuestra subred privada. Esta será liderada por un Application Load Balancer que permitirá la conectividad desde Internet. Nuestra base de datos vectorial de elección será OpenSearch, ya que AWS le proporciona un servicio administrado.
Profundizando en el código de Terraform, comenzamos con el clúster OpenSearch. Para reducir costos, nos quedaremos con una instancia t3.small.search sin nodos maestros, ya que será suficiente para fines de prueba.
La aplicación Streamlit en la carpeta src se empaquetará en una imagen acoplable y se cargará en Elastic Container Registry (ECR). A partir de ahí estará listo para implementarse como un servicio en Elastic Container Service (ECS).
Para simplificar, tanto la aplicación como la infraestructura se construirán e implementarán juntas. La compilación de Docker se realiza en la operación de aplicación de terraform llamando a un script bash a través del recurso build_image.
Haremos referencia a la imagen cargada en el registro en nuestra Definición de tarea de ECS; siendo Fargate el proveedor de capacidad elegido para esta aplicación de exhibición. Nos aseguramos de asignar el puerto correcto para Streamlit (siendo 8501 el predeterminado). Y además proporcionar las variables de entorno necesarias para conectarnos a nuestro clúster OpenSearch.
Con la definición de tarea eliminada, podemos crear el servicio ECS. Como se mencionó anteriormente, hemos optado por ejecutar nuestra aplicación en Fargate y solo una instancia de contenedor para fines de demostración. El servicio contará con dos grupos de seguridad adscritos; uno para permitir el acceso a Internet (para llamar a la API de OpenAI) y el grupo de seguridad de usuarios de OpenSearch.
Finalmente, el servicio estará encabezado por un Application Load Balancer que nos dará una IP pública y un nombre DNS para acceder a nuestra aplicación desde Internet.
Solicitud
En cuanto a nuestra aplicación, estamos desarrollando un sistema RAG barebone. Nos permitirá cargar documentos para los cuales se calcularán los embeddings. Luego, nuestra aplicación también proporcionará un chatbot, que calculará los embeddings de nuestras consultas para realizar una búsqueda semántica en la base de datos vectorial. Con los documentos recuperados construiremos un mensaje que permitirá al LLM de elección brindar una respuesta más confiable.
Nuestra aplicación primero recuperará la configuración de las variables de entorno de AWS, el proveedor de nube que utilizamos. Además, creará una instancia del modelo de incrustaciones y del almacén de vectores. Finalmente creará los componentes de la interfaz Streamlit.
Nuestra aplicación, siendo independiente de la plataforma, se conectará a la tienda de vectores según el proveedor de nube usado. En esta arquitectura, el proveedor será OpenSearch.
En nuestro panel lateral le brindaremos al usuario la opción de cargar archivos en la tienda de vectores. Por diversión, también incluimos un pequeño panel de control para modificar los parámetros de fragmentación de texto. Es decir, en caso de que queramos jugar con diferentes configuraciones.
Profundizando un poco más en los detalles del marco Langchain, necesitamos convertir los archivos cargados en Documentos. Este es un tipo proporcionado por Langchain y eliminarlo nos permitirá trabajar sin problemas con el resto de funcionalidades. Inmediatamente después de eso, podemos fragmentar nuestros documentos con los parámetros proporcionados.
La parte crítica aquí es la creación de nuestro canal Langchain con build_rag_chain. Aquí es donde reside la lógica de nuestro sistema RAG.
Como podemos ver, el pipeline consta de algunas partes. Primero construimos un mensaje compuesto con los elementos contexto y pregunta. Langchain le permite utilizar el almacén de vectores como VectorStoreRetriever para la recuperación de documentos relevantes. Estos dos elementos se insertan en los avisos parametrizados que hemos creado, dependiendo de si estamos usando RAG o no.
La última parte del proceso pasa el mensaje resultante al modelo de idioma grande, para el que hemos optado por GPT 3.5 Turbo. Este pipeline se ejecutará cada vez que consultemos al chatbot, proporcionando al modelo el contexto necesario para responder nuestras preguntas.
Próximos pasos
Aunque se trata de un sistema relativamente simple, sienta las bases y los conceptos para cualquier arquitectura RAG más avanzada que podamos tener la tentación de diseñar. En próximos artículos cubriremos muchos matices y mejoras que nos quedan sobre la mesa.
Fragmentación de texto. Aunque realizamos fragmentación de texto en nuestra canalización, este es un componente crítico de cualquier RAG y un arte en sí mismo. Pronto exploraremos técnicas de fragmentación más avanzadas, como la fragmentación semántica, que pueden mejorar el rendimiento de nuestro chatbot.
Ampliación de consultas. Hoy en día contamos con LLM lo suficientemente potentes como para que el mayor cuello de botella para el rendimiento de nuestro sistema RAG no sea la generación, proporcionarle un contexto relevante es generalmente el mayor desafío. Con Query Expansion generamos versiones alternativas de nuestro mensaje inicial para realizar una búsqueda más exhaustiva y exitosa en nuestra base de datos de vectores.
Reclasificación. Básicamente, utilizar un LLM para discernir cuáles de los documentos que hemos recuperado de nuestra búsqueda de vectores son los más relevantes. Un buen enfoque es lanzar una red lo más amplia posible durante nuestra recuperación de la base de datos de vectores y luego filtrar los documentos con un procedimiento de reclasificación.
Evaluación. En la carrera por el desarrollo de cada vez más aplicaciones GenAI, es esencial evaluar y cuantificar nuestros productos para garantizar que realmente estamos avanzando. En las siguientes entradas profundizaremos en marcos como RAGAS para ayudarnos a lograr exactamente eso.