Creación de una aplicación GenAI de extremo a extremo con Ruby y Milvus
La introducción de frameworks GenAI especializados como LangChain nos ha permitido crear aplicaciones de IA sofisticadas de forma rápida y sencilla aprovechando potentes modelos de lenguaje grandes (LLMs) como ChatGPT y LLaMA. LangChain, por ejemplo, nos permite crear una potente aplicación de Generación Aumentada por Recuperación (RAG) en solo unas pocas líneas de código sin requerir profundos conocimientos teóricos de IA.
Esta tendencia significa que hoy en día, los científicos de datos y los ingenieros de aprendizaje automático ya no son los únicos capaces de crear aplicaciones GenAI. Los ingenieros full-stack o desarrolladores de software ahora pueden crear apps GenAI usando LangChain.
Sin embargo, estos frameworks GenAI suelen estar escritos en Python, y sabemos que algunos ingenieros full-stack y desarrolladores de software rara vez usan Python en sus proyectos. Por lo tanto, existe la necesidad de extensiones de estos frameworks GenAI en otros lenguajes de programación para que estos ingenieros full-stack puedan aprovechar potentes LLMs para crear aplicaciones GenAI en sus proyectos de software.
En una charla reciente, Andrei Bondarev, arquitecto de soluciones en Source Labs LLC, presentó una extensión de Ruby de LangChain llamada LangChain.rb para facilitar que los ingenieros full-stack creen aplicaciones GenAI en sus proyectos de software.
Pero antes de analizar cómo crear una aplicación GenAI con Ruby, exploremos brevemente el funcionamiento interno de la Generación Aumentada por Recuperación (RAG), un caso de uso popular de GenAI.
Cómo funciona RAG
No es ningún secreto que los datos son la mina de oro de cualquier app GenAI. Sirven como la fuente de información utilizada por GenAI para generar respuestas fácticas y precisas. De todos los datos disponibles en este momento, el 80% puede clasificarse como datos no estructurados.
Los datos no estructurados se refieren a datos que no se ajustan a un formato de datos predefinido. Este tipo de datos incluye imágenes, texto, sonido y videos. Para que las máquinas puedan interpretar estos tipos de datos no estructurados, necesitamos transformarlos en un formato numérico llamado embeddings vectoriales.
Conceptos fundamentales de los embeddings vectoriales
Un embedding consiste en un vector n-dimensional, donde n se refiere a la dimensionalidad del embedding. La dimensionalidad depende del modelo de aprendizaje profundo que transforma los datos en un embedding. Un embedding contiene el significado semántico de los datos que representa.
Podemos usar modelos de aprendizaje profundo para transformar diversas modalidades de datos en embeddings. Por ejemplo, si tenemos datos de texto, podemos usar modelos de OpenAI o Sentence Transformer para transformar estos datos de texto en un embedding. Si tenemos datos de imagen, podemos usar modelos preentrenados especializados capaces de extraer características de imágenes, como Vision Transformer, como modelo de embedding.
Dado que un embedding contiene el significado semántico de los datos que representa, podemos calcular la similitud de ese embedding con otros embeddings en el llamado espacio vectorial. Los embeddings con significados semánticos similares se colocarán cerca unos de otros en el espacio vectorial, como puedes ver en la visualización a continuación:
Embeddings vectoriales en un espacio vectorial.png
Embedding de palabras relacionadas en el espacio vectorial
Como se muestra en la imagen anterior, los embeddings de "reina" y "rey" están ubicados cerca uno del otro, y lo mismo ocurre con "mujer" y "hombre". La distancia euclidiana entre "reina-rey" y "mujer-hombre" también sería aproximadamente la misma, ya que tienen significados similares.
Este concepto es la base de una operación de búsqueda vectorial, en la que calculamos la similitud entre un embedding y varios embeddings.
El papel de la base de datos vectorial en la búsqueda vectorial y las aplicaciones RAG
Implementar una búsqueda vectorial es sencillo si solo tratamos con una pequeña cantidad de embeddings. Sin embargo, comúnmente tratamos con miles, millones o incluso miles de millones de embeddings en casos del mundo real. Por lo tanto, necesitamos una solución para almacenar eficientemente los embeddings y realizar búsquedas vectoriales rápidas sobre ellos.
Aquí es donde entra en juego una base de datos vectorial como Milvus. Milvus es una base de datos vectorial de código abierto donde puedes almacenar cantidades masivas de embeddings y realizar búsquedas vectoriales sobre estos embeddings en una fracción de segundo.
El flujo de trabajo de transformar datos no estructurados en embeddings y almacenarlos en Milvus
El flujo de trabajo de transformar datos no estructurados en embeddings y almacenarlos en Milvus
Las bases de datos vectoriales también desempeñan un papel crucial en aplicaciones populares de GenAI como RAG. Como quizá ya sepas, el objetivo principal de RAG es mejorar la precisión de las respuestas generadas por LLMs como ChatGPT y LLaMA proporcionándoles contexto que puede ser útil para responder a la consulta del usuario.
En una aplicación RAG, una vez que se recibe la consulta del usuario, esta se transforma en un embedding usando un modelo de embeddings. A continuación, se realiza una búsqueda vectorial, donde el embedding de la consulta se compara con embeddings de contexto almacenados dentro de la base de datos vectorial como Milvus. Luego, los datos de contexto más similares se recuperan y se pasan junto con la consulta al LLM. El LLM puede entonces usar la información del contexto para generar una respuesta contextualizada que responda a la consulta del usuario.
RAG
Flujo de trabajo de RAG
LangChain como un framework popular de GenAI
LangChain es un framework que facilita la creación y el desarrollo de aplicaciones GenAI usando modelos LLM de última generación. Se integra fácilmente con proveedores populares de LLM como OpenAI, Anthropic y Google, así como con proveedores de bases de datos vectoriales como Zilliz.
LangChain también ofrece abstracciones flexibles para desarrollar aplicaciones de IA impulsadas por LLM, lo que facilita a los científicos de datos y desarrolladores de software construir sistemas sofisticados como RAG con solo unas pocas líneas de código.
Por ejemplo, supongamos que queremos resumir el contenido de esta publicación de blog usando GPT-4. Podemos completar esta tarea con el siguiente código:
import os
from langchain.chains.summarize import load_summarize_chain
from langchain_community.document_loaders import WebBaseLoader
from langchain_openai import ChatOpenAI
os.environ["LANGCHAIN_TRACING_V2"] = "True"
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
docs = loader.load()
llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo-1106")
chain = load_summarize_chain(llm, chain_type="stuff")
result = chain.invoke(docs)
"""
Salida: El artículo analiza el concepto de agentes autónomos impulsados por LLM, con un enfoque en los componentes de planificación, memoria y uso de herramientas. Incluye estudios de caso y ejemplos de prueba de concepto, así como desafíos y referencias a investigaciones relacionadas. El autor enfatiza el potencial de los LLM para crear agentes potentes de resolución de problemas, al tiempo que destaca limitaciones como la longitud finita del contexto y la fiabilidad de las interfaces de lenguaje natural.
"""
Como puedes ver, con aproximadamente solo 10 líneas de código, podemos aprovechar el modelo GPT-4 para resumir con precisión una publicación larga de blog.
Con LangChain, también puedes realizar tareas más complejas. Por ejemplo, puedes dividir un texto largo de un documento PDF en fragmentos, transformar cada fragmento en un embedding utilizando un modelo de embedding de tu elección, almacenar el embedding de esos fragmentos dentro de una base de datos vectorial y realizar RAG después.
import getpass
import os
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores.milvus import Milvus
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain.embeddings import SentenceTransformerEmbeddings
from langchain_openai import ChatOpenAI
# Set the API key
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")
llm = ChatOpenAI(model="gpt-4")
# Text to be processed
texts = "This is a very long text.....:"
# Split text into chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100) # Example chunk size and overlap
chunk_texts = text_splitter.split_text(texts)
# Instantiate embedding model
embeddings = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
# Store embeddings of chunks inside Milvus DB
vector_db = Milvus.from_texts(texts=chunk_texts, embedding=embeddings, collection_name="rag_milvus")
retriever = vector_db.as_retriever()
# Define function to format documents
def format_docs(docs):
return "\\n\\n".join(doc.page_content for doc in docs)
# Perform RAG (Retrieval Augmented Generation)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| llm
| StrOutputParser()
)
# Example question
question = "What is the main idea of the text?"
# Execute RAG chain and print the result
for chunk in rag_chain.stream(question):
print(chunk, end="", flush=True)
Además de las demostraciones proporcionadas anteriormente, LangChain ofrece una amplia gama de funcionalidades. Por ejemplo, integra LLM y API de fuentes externas como aplicaciones meteorológicas, calculadoras o Google Search. Este enfoque permite a los LLM utilizar información de estas fuentes para generar respuestas más precisas y contextuales. Veremos la implementación detallada de este enfoque en las próximas secciones.
También puedes explorar todas las funcionalidades de LangChain en su página de documentación.
Desarrollo de aplicaciones GenAI con Ruby y Milvus
Python se ha convertido en el lenguaje de programación de facto para los marcos de investigación y desarrollo de IA, incluido LangChain. Mientras tanto, Ruby sigue siendo popular para el desarrollo rápido de software y aplicaciones web.
Sin embargo, como has visto en la sección anterior, la introducción de LangChain abre posibilidades para que los desarrolladores de software integren el poder de los LLM en sus aplicaciones web sin conocer las teorías detalladas de los LLM y la IA en general.
Esta capacidad ha creado una demanda creciente para extender estos marcos de desarrollo GenAI a otros lenguajes que resultan más familiares para los desarrolladores full-stack, como Ruby. Para satisfacer esta demanda, Andrei Bondarev introdujo LangChain.rb, que es la extensión en Ruby del marco original LangChain.
LangChain.rb permite a los desarrolladores Ruby full-stack crear aplicaciones web impulsadas por LLM sin la molestia de incorporar múltiples lenguajes de programación en sus proyectos. Con él, puedes integrar fácilmente bases de datos vectoriales populares, LLMs y recursos externos en tus aplicaciones web con LLM.
LangChain.rb tiene las mismas funcionalidades generales que el LangChain original, tales como:
Gestión de prompts: crear, cargar y guardar plantillas de prompts para los LLMs de tu elección
Validación de longitud de contexto: validar la longitud de contexto de las entradas según la longitud de contexto de los LLMs y modelos de embeddings de tu elección
Fragmentación de datos: dividir datos en fragmentos con reglas predefinidas antes de ingerirlos en las bases de datos vectoriales de tu elección
Memoria de conversación: persistir un chat con un LLM en una memoria
En las siguientes secciones, demostraremos el desarrollo de aplicaciones simples impulsadas por LLM con la ayuda de LangChain.rb.
Aplicaciones RAG generales con LangChain.rb
En este primer ejemplo, construiremos una aplicación RAG simple y rápida usando LangChain.rb. Antes de poder usar LangChain.rb para tu proyecto Ruby, asegúrate de instalar la gema ejecutando el siguiente comando:
gem install langchainrb
En este proyecto, usaremos Milvus como base de datos vectorial y modelos de OpenAI tanto como modelos LLM como de embeddings. Para iniciar Milvus, necesitamos instalar Milvus en Docker e iniciar el contenedor con el siguiente comando:
# Descargar el script de instalación
curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh
# Iniciar el contenedor Docker
bash standalone_embed.sh start
Ahora que iniciamos el contenedor Docker, vamos a instanciar Milvus y los modelos que usaremos para nuestra aplicación RAG.
require 'langchain'
milvus = Langchain::Vectorsearch::Milvus.new(
url: ENV["MILVUS_URL"],
index_name: "Documents",
llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
)
Lo primero que debemos hacer es crear un esquema dentro de la base de datos vectorial Milvus y el método de indexación correspondiente. A continuación, necesitamos cargar ese esquema antes de poder realizar una búsqueda vectorial con él.
# Crear esquema predeterminado
milvus.create_default_schema
# Crear índice predeterminado
milvus.create_default_index
# Cargar esquema predeterminado
milvus.load_default_schema
Ahora podemos ingerir algunos datos en nuestro esquema. Digamos que tenemos un PDF en nuestro directorio local que contiene información sobre beneficios para empleados. Si queremos almacenar todos los textos de este PDF dentro de la base de datos Milvus, podemos hacerlo ejecutando los siguientes comandos:
pdf = Langchain.root.join("path/to/my.pdf")
# Añadir PDF dentro de Milvus
milvus.add_data(path: pdf)
Una vez que ejecutes los comandos anteriores, LangChain hará todo el preprocesamiento internamente. Analizará el texto dentro del archivo PDF, lo dividirá en varios fragmentos, transformará cada fragmento en embeddings y luego almacenará los embeddings dentro de la base de datos vectorial Milvus.
Después de almacenar nuestros datos dentro de la base de datos vectorial Milvus, podemos empezar a hacer preguntas relacionadas con nuestro documento PDF. Digamos que queremos preguntar: “¿Cuál es la política de vacaciones de la empresa? ¿Cuánto tiempo puedo tomar libre?” entonces podemos preguntar a nuestros LLMs en un sistema RAG simplemente ejecutando esta línea de código:
response = milvus.ask(question: "What’s the company’s vacation policy? How much can I take off?")
puts response
"""
Response:
=> The company's vacation policy allows employees to take any reasonable amount of time off with pay,
as long as they consult with their manager in advance and get their work done.
"""
¡Y eso es todo! Además de construir una aplicación RAG general, también podemos construir una aplicación RAG agéntica con LangChain.rb, lo cual discutiremos en la siguiente sección.
Utilizar agentes para interactuar con herramientas de terceros
La principal limitación de muchos LLM es su fecha de corte de conocimiento. GPT-4, por ejemplo, tiene una fecha de corte de abril de 2023. Esto significa que si queremos preguntar sobre eventos generales o factuales posteriores a abril de 2023, no obtendremos una respuesta precisa del LLM.
Para resolver este problema, LangChain.rb nos permite crear una aplicación RAG agéntica. Este tipo de aplicación RAG añade otra capa de inteligencia, que contiene un "agente" que actúa como responsable de la toma de decisiones. El agente analiza la consulta del usuario y luego decide cuáles son las herramientas de terceros más eficaces que pueden proporcionar el contexto más adecuado para responder a la consulta.
Supongamos que queremos preguntarle a nuestro LLM sobre el clima actual en Nueva York. Con un sistema RAG general, el LLM no puede conocer el clima en tiempo real en Nueva York. Lo más probable es que empiece a alucinar y nos dé algunas predicciones meteorológicas aleatorias.
The agentic RAG workflow (1).png
Flujo de trabajo de un RAG agéntico
El RAG agéntico resuelve este problema permitiéndonos usar herramientas o API, como la API de OpenWeather, en el sistema para obtener el clima en tiempo real en Nueva York. El agente primero procesará la consulta del usuario y luego decidirá qué herramientas pueden proporcionar contexto relevante para responder a la consulta antes de sintetizar el contexto en una respuesta precisa.
La siguiente demostración utilizará herramientas de terceros dentro de nuestro sistema RAG, como una calculadora, la aplicación OpenWeather y Google Search.
weather = Langchain::Tool::Weather.new(api_key: ENV["OPEN_WEATHER_API_KEY"])
google_search = Langchain::Tool::GoogleSearch.new(api_key: ENV["SERPAPI_API_KEY"])
calculator = Langchain::Tool::Calculator.new
A continuación, necesitamos añadir estas tres herramientas dentro de nuestro sistema RAG con el siguiente comando:
openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
agent = Langchain::Agent::ReActAgent.new(
llm: openai,
tools: [weather, google_search, calculator]
)
Ahora, podemos empezar a hacer preguntas a nuestro LLM. Supongamos que queremos hacer la siguiente pregunta: “Find current weather in Boston, MA, and Washington, D.C., and take an average.”
response = agent.run(question: "Find current weather in Boston, MA and Washington, D.C. and take an average")
Output of RAG with tools integration
Salida de RAG con integración de herramientas
Como puedes ver en la captura de pantalla anterior, nuestro sistema RAG agéntico pudo responder a la consulta con precisión. Desglosemos el flujo de trabajo de este sistema RAG:
La consulta se envió primero al LLM de OpenAI.
El agente reconoció la necesidad de utilizar la API de OpenWeather para obtener el clima actual en Boston y Washington D.C.
Después de recuperar los datos meteorológicos, el agente vio que la consulta requería promediar el clima de las dos ciudades.
Luego, el agente invocó una herramienta de calculadora para calcular el promedio del clima.
Finalmente, el LLM sintetizó los resultados en una respuesta coherente y se la devolvió al usuario.
Este ejemplo demuestra el poder del enfoque RAG agéntico. Al incorporar herramientas y API externas, el sistema supera las limitaciones de la fecha de corte de conocimiento del LLM y proporcionó una respuesta precisa y actualizada a la consulta del usuario.
Utilizar agentes para interactuar con una base de datos interna
También podemos usar RAG agéntico para interactuar con nuestras bases de datos internas. Esto es muy útil porque podemos pedir información sobre nuestros datos usando un lenguaje similar al humano en lugar de depender de consultas SQL tradicionales.
Supongamos que tenemos una tienda en línea y datos de usuarios almacenados en una base de datos. Normalmente, tendríamos que escribir consultas SQL para extraer información de esos datos. Con RAG agéntico, todo lo que necesitamos hacer es preguntarle al LLM sobre la información que queremos, y la respuesta se devolverá inmediatamente.
Por ejemplo, supongamos que queremos saber cuántos registros de usuarios están almacenados en la base de datos. Simplemente podemos preguntar: "¿Cuántos usuarios hay?" y ejecutar los siguientes comandos:
require 'langchain'
# Instantiate the database connection
database = Langchain::Tool::Database.new(connection_string: "postgres://localhost:5432/my_database")
# Create OpenAI LLM instance
openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
# Create SQLAgent with the LLM and database connection
agent = Langchain::Agent::SQLAgent.new(
llm: openai,
db: database
)
# Ask a question to the agent
response = agent.run("How many users are there?")
A continuación se muestra el resultado de ejemplo del comando:
Resultado de RAG con integración SQL
Resultado de RAG con integración SQL
Como puedes ver, nuestro sistema RAG agéntico pudo responder con precisión una pregunta específica relacionada con los datos de nuestra base de datos. El flujo de trabajo del agente es similar al ejemplo anterior:
La consulta se envió al LLM de OpenAI.
El agente analizó la consulta y determinó que se requiere una búsqueda en la base de datos para contar el número de usuarios.
El LLM generó una consulta SQL adecuada basada en el esquema de tablas de la base de datos.
La consulta SQL se ejecutó en la base de datos y devolvió el resultado.
La salida de la base de datos se envió de vuelta al LLM.
El LLM sintetizó el resultado de la base de datos en una respuesta coherente y legible para humanos, y la proporcionó como respuesta final.
Conclusión
La introducción de LangChain hace que los LLM sean accesibles para profesionales que quizá no tengan un conocimiento profundo de las teorías de IA y ciencia de datos. Podemos crear una potente aplicación RAG usando LangChain con solo unas pocas líneas de código.
Esta accesibilidad es la razón por la que Andrei Bondarev presentó LangChain.rb, una extensión de LangChain para Ruby. Este framework permite a los desarrolladores full-stack incorporar el potente rendimiento de los LLM en sus aplicaciones web sin necesitar una amplia experiencia en IA. Además, LangChain.rb elimina la molestia de que los desarrolladores full-stack tengan que cambiar a otro lenguaje de programación cuando quieren aprovechar los LLM en sus aplicaciones web.
Sigue leyendo

Introducing Functions and Model Inference on Zilliz Cloud: Automatic Embedding and Reranking with Hosted Models
Zilliz Cloud Functions auto-generate embeddings via OpenAI, Voyage AI, Cohere, or Zilliz Hosted Models. Built-in reranking — just insert text and search.

Introducing Customer-Managed Encryption Keys (CMEK) on Zilliz Cloud
We're announcing the general availability of Customer-Managed Encryption Keys (CMEK) on Zilliz Cloud.

Introducing Business Critical Plan: Enterprise-Grade Security and Compliance for Mission-Critical AI Applications
Discover Zilliz Cloud’s Business Critical Plan—offering advanced security, compliance, and uptime for mission-critical AI and vector database workloads.


