Blog

Cómo ingerir datos en Elasticsearch a través de LlamaIndex

Un paso a paso sobre cómo ingerir datos y buscar usando RAG con LlamaIndex.

En este artículo, implementaremos un motor de búsqueda para las preguntas frecuentes empleando LlamaIndex para indexar los datos. Elasticsearch servirá como nuestra base de datos vectorial, permitiendo la búsqueda vectorial, mientras que RAG (Generación Aumentada por Recuperación) enriquecerá el contexto, proporcionando respuestas más precisas.

LlamaIndex y Elasticsearch: Ingestiendo documentos y construyendo búsqueda de preguntas frecuentes

¿Qué es LlamaIndex?

LlamaIndex es un marco que facilita la creación de agentes y flujos de trabajo impulsados por Grandes Modelos de Lenguaje (LLMs) para interactuar con datos específicos o privados. Permite la integración de datos de diversas fuentes (APIs, PDFs, bases de datos) con LLMs, facilitando tareas como investigación, extracción de información y generación de respuestas contextualizadas.

Conceptos clave:

  • Agentes: Asistentes inteligentes que emplean LLMs para realizar tareas, desde respuestas simples hasta acciones complejas.

  • Flujos de trabajo: Procesos de varios pasos que combinan agentes, conectores de datos y herramientas para tareas avanzadas.

  • Aumento de contexto: Una técnica que enriquece el LLM con datos externos, superando sus limitaciones de entrenamiento.

Integración de LlamaIndex con Elasticsearch:

Elasticsearch puede usar de varias formas con LlamaIndex:

  • Fuente de datos: Emplea el Elasticsearch Reader para extraer documentos.

  • Modelo de incrustaciones: Codificar datos en vectores para búsquedas semánticas.

  • Almacenamiento vectorial: Emplea Elasticsearch como repositorio para buscar documentos vectorizados.

  • Almacenamiento avanzado: Configurar estructuras como resúmenes de documentos o grafos de conocimiento.

Uso de LlamaIndex y Elasticsearch para crear una búsqueda de preguntas frecuentes

Preparación de datos

Usaremos como ejemplo las preguntas frecuentes de Elasticsearch Service . Cada pregunta se extraía de la web y se almacenaba en un archivo de texto individual. Puedes usar cualquier método para organizar los datos; En este ejemplo, elegimos almacenar los archivos localmente.

Archivo de ejemplo:

File Name: what-is-elasticsearch-service.txt
Content: Elasticsearch Service is hosted and managed Elasticsearch and Kibana brought to you by the creators of Elasticsearch. Elasticsearch Service is part of Elastic Cloud and ships with features that you can only get from the company behind Elasticsearch, Kibana, Beats, and Logstash. Elasticsearch is a full text search engine that suits a range of uses, from search on websites to big data analytics and more.

Luego de almacenar todas las preguntas, el directorio se verá así:

Instalación de dependencias

Implementaremos la ingestión y búsqueda usando el lenguaje Python, la versión que usé fue la 3.9. Como requisito previo, será necesario instalar las siguientes dependencias:

llama-index-vector-stores-elasticsearch
llama-index
openai

Elasticsearch y Kibana se crearán con Docker, configurados mediante docker-compose.yml para ejecutar la versión 8.16.2. Esto facilita la creación del entorno local.

version: '3.8'
services:

 elasticsearch:
   image: docker.elastic.co/elasticsearch/elasticsearch:8.16.2
   container_name: elasticsearch-8.16.2
   environment:
     - node.name=elasticsearch
     - xpack.security.enabled=false
     - discovery.type=single-node
     - "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"
   ports:
     - 9200:9200
   networks:
     - shared_network

 kibana:
   image: docker.elastic.co/kibana/kibana:8.16.2
   container_name: kibana-8.16.2
   restart: always
   environment:
     - ELASTICSEARCH_URL=http://elasticsearch:9200
   ports:
     - 5601:5601
   depends_on:
     - elasticsearch
   networks:
     - shared_network

networks:
 shared_network:

Ingesta de documentos usando LlamaIndex

Los documentos se indexarán en Elasticsearch usando LlamaIndex. Primero, cargamos los archivos con SimpleDirectoryReader, que permite cargar archivos desde un directorio local. Luego de cargar los documentos, los indexaremos usando el VectorStoreIndex.

documents = SimpleDirectoryReader("./faq").load_data()

storage_context = StorageContext.from_defaults(vector_store=es)
index = VectorStoreIndex(documents, storage_context=storage_context, embed_model=embed_model)

Los Vector Stores en LlamaIndex son responsables de almacenar y gestionar las incrustaciones de documentos. LlamaIndex soporta diferentes tipos de Vector Stores, y en este caso, usaremos Elasticsearch. En el StorageContext, configuramos la instancia de Elasticsearch. Dado que el contexto es local, no se requerían parámetros adicionales. Para configuraciones en otros entornos, consulte la documentación para comprobar los parámetros necesarios: ElasticsearchStore Configuration.

Por defecto, LlamaIndex emplea el modelo OpenAI text-embedding-ada-002 para generar embeddings. Sin embargo, en este ejemplo, usaremos el modelo text-embedding-3-small . Es importante señalar que se requerirá una clave API de OpenAI para usar el modelo.

A continuación se muestra el código completo para la ingestión de documentos.

import openai
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.vector_stores.elasticsearch import ElasticsearchStore

openai.api_key = os.environ["OPENAI_API_KEY"]

es = ElasticsearchStore(
   index_name="faq",
   es_url="http://localhost:9200"
)

def format_title(filename):
   filename_without_ext = filename.replace('.txt', '')
   text_with_spaces = filename_without_ext.replace('-', ' ')
   formatted_text = text_with_spaces.title()

   return formatted_text


embed_model = OpenAIEmbedding(model="text-embedding-3-small")

documents = SimpleDirectoryReader("./faq").load_data()

for doc in documents:
   doc.metadata['title'] = format_title(doc.metadata['file_name'])

storage_context = StorageContext.from_defaults(vector_store=es)
index = VectorStoreIndex(documents, storage_context=storage_context, embed_model=embed_model)

Tras la ejecución, los documentos se indexarán en el índice de preguntas frecuentes como se muestra a continuación:

Búsqueda con RAG

Para realizar búsquedas, configuramos el cliente ElasticsearchStore , configurando los campos index_name y es_url con la URL de Elasticsearch. En retrieval_strategy, definimos la AsyncDenseVectorStrategy para búsquedas vectoriales. También están disponibles otras estrategias, como AsyncBM25Strategy (búsqueda por palabras clave) y AsyncSparseVectorStrategy (vectores dispersos). Se pueden encontrar más detalles en la documentación oficial.

es = ElasticsearchStore(
   index_name="faq",
   es_url="http://localhost:9200",
   retrieval_strategy=AsyncDenseVectorStrategy(
   )
)

A continuación, se creará un objeto VectorStoreIndex , donde configuramos el vector_store usando el objeto ElasticsearchStore. Con el método as_retriever , realizamos la búsqueda de los documentos más relevantes para una consulta, estableciendo el número de resultados devueltos a 5 mediante el parámetro similarity_top_k .

   index = VectorStoreIndex.from_vector_store(vector_store=es)
   retriever = index.as_retriever(similarity_top_k=5)
   results = retriever.retrieve(query)

El siguiente paso es RAG. Los resultados de la búsqueda vectorial se incorporan en un prompt formateado para el LLM, permitiendo una respuesta contextualizada basada en la información recuperada.

En la PromptPlantilla, definimos el formato del prompt, que incluye:

  • Contexto ({context_str}): documentos recuperados por el recuperador.

  • Consulta ({query_str}): la pregunta del usuario.

  • Instrucciones: pautas para que el modelo responda según el contexto, sin depender de conocimientos externos.

qa_prompt = PromptTemplate(
   "You are a helpful and knowledgeable assistant."
   "Your task is to answer the user's query based solely on the context provided below."
   "Do not use any prior knowledge or external information.\n"
   "---------------------\n"
   "Context:\n"
   "{context_str}\n"
   "---------------------\n"
   "Query: {query_str}\n"
   "Instructions:\n"
   "1. Carefully read and understand the context provided.\n"
   "2. If the context contains enough information to answer the query, provide a clear and concise answer.\n"
   "3. Do not make up or guess any information.\n"
   "Answer: "
)

Finalmente, el LLM procesa el prompt y devuelve una respuesta precisa y contextual.

llm = OpenAI(model="gpt-4o")
context_str = "\n\n".join([n.node.get_content() for n in results])
response = llm.complete(
   qa_prompt.format(context_str=context_str, query_str=query)
)

print("Answer:")
print(response)

El código completo se encuentra a continuación:

es = ElasticsearchStore(
   index_name="faq",
   es_url="http://localhost:9200",
   retrieval_strategy=AsyncDenseVectorStrategy(
   )
)


def print_results(results):
   for rank, result in enumerate(results, start=1):
       title = result.metadata.get("title")
       score = result.get_score()
       text = result.get_text()
       print(f"{rank}. title={title} \nscore={score} \ncontent={text}")


def search(query: str):
   index = VectorStoreIndex.from_vector_store(vector_store=es)

   retriever = index.as_retriever(similarity_top_k=10)
   results = retriever.retrieve(QueryBundle(query_str=query))
   print_results(results)

   qa_prompt = PromptTemplate(
       "You are a helpful and knowledgeable assistant."
       "Your task is to answer the user's query based solely on the context provided below."
       "Do not use any prior knowledge or external information.\n"
       "---------------------\n"
       "Context:\n"
       "{context_str}\n"
       "---------------------\n"
       "Query: {query_str}\n"
       "Instructions:\n"
       "1. Carefully read and understand the context provided.\n"
       "2. If the context contains enough information to answer the query, provide a clear and concise answer.\n"
       "3. Do not make up or guess any information.\n"
       "Answer: "
   )

   llm = OpenAI(model="gpt-4o")
   context_str = "\n\n".join([n.node.get_content() for n in results])
   response = llm.complete(
       qa_prompt.format(context_str=context_str, query_str=query)
   )

   print("Answer:")
   print(response)


question = "Elastic services are free?"
print(f"Question: {question}")
search(question)

Ahora podemos realizar nuestra búsqueda, por ejemplo, "¿Los servicios de Elastic son gratis?" y obtener una respuesta contextualizada basada en los datos de las preguntas frecuentes en sí.

Question: Elastic services are free?
Answer:
Elastic services are not entirely free. However, there is a 14-day free trial available for exploring Elastic solutions. After the trial, access to features and services depends on the subscription level.

Para generar esta respuesta, se emplearon los siguientes documentos:

1. title=Can I Try Elasticsearch Service For Free 
score=1.0 
content=Yes, sign up for a 14-day free trial. The trial starts the moment a cluster is created.
During the free trial period get access to a deployment to explore Elastic solutions for Enterprise Search, Observability, Security, or the latest version of the Elastic Stack.

2. title=Do You Offer Elastic S Commercial Products 
score=0.9941274512218439 
content=Yes, all Elasticsearch Service customers have access to basic authentication, role-based access control, and monitoring.
Elasticsearch Service Gold, Platinum and Enterprise customers get complete access to all the capabilities in X-Pack: Security, Alerting, Monitoring, Reporting, Graph Analysis & Visualization. Contact us to learn more.

3. title=What Is Elasticsearch Service 
score=0.9896776845746571 
content=Elasticsearch Service is hosted and managed Elasticsearch and Kibana brought to you by the creators of Elasticsearch. Elasticsearch Service is part of Elastic Cloud and ships with features that you can only get from the company behind Elasticsearch, Kibana, Beats, and Logstash. Elasticsearch is a full text search engine that suits a range of uses, from search on websites to big data analytics and more.

4. title=Can I Run The Full Elastic Stack In Elasticsearch Service 
score=0.9880631561979476 
content=Many of the products that are part of the Elastic Stack are readily available in Elasticsearch Service, including Elasticsearch, Kibana, plugins, and features such as monitoring and security. Use other Elastic Stack products directly with Elasticsearch Service. For example, both Logstash and Beats can send their data to Elasticsearch Service. What is run is determined by the subscription level.

5. title=What Is The Difference Between Elasticsearch Service And The Amazon Elasticsearch Service 
score=0.9835054890793161 
content=Elasticsearch Service is the only hosted and managed Elasticsearch service built, managed, and supported by the company behind Elasticsearch, Kibana, Beats, and Logstash. With Elasticsearch Service, you always get the latest versions of the software. Our service is built on best practices and years of experience hosting and managing thousands of Elasticsearch clusters in the Cloud and on premise. For more information, check the following Amazon and Elastic Elasticsearch Service comparison page.
Please note that there is no formal partnership between Elastic and Amazon Web Services (AWS), and Elastic does not provide any support on the AWS Elasticsearch Service.

Conclusión

Empleando LlamaIndex, demostramos cómo crear un sistema eficiente de búsqueda de preguntas frecuentes con soporte para Elasticsearch como base de datos vectorial. Los documentos se ingieren e indexan mediante incrustaciones, lo que permite búsquedas vectoriales. A través de una PromptPlantilla, los resultados de la búsqueda se incorporan al contexto y se envían al LLM, que genera respuestas precisas y contextualizadas basadas en los documentos recuperados.

Este flujo de trabajo integra la recuperación de información con la generación contextualizada de respuestas para ofrecer resultados precisos y relevantes.

Referencias

https://www.elastic.co/guide/en/cloud/current/ec-faq-getting-started.html

https://docs.llamaindex.ai/en/stable/api_reference/readers/elasticsearch/

https://docs.llamaindex.ai/en/stable/module_guides/indexing/vector_store_index/

https://docs.llamaindex.ai/en/stable/examples/query_engine/custom_query_engine/

https://www.elastic.co/search-labs/integrations/llama-index

Preguntas frecuentes

¿Qué es LlamaIndex?

LlamaIndex es un marco que facilita la creación de agentes y flujos de trabajo impulsados por Grandes Modelos de Lenguaje (LLMs) para interactuar con datos específicos o privados.

¿Se puede integrar LlamaIndex con Elasticsearch?

Sí, Elasticsearch puede emplear de diversas formas con LlamaIndex, incluyendo como fuente de datos (usando el Elasticsearch Reader para extraer documentos), como modelo de incrustaciones (codificando datos en vectores para búsquedas semánticas), como almacenamiento vectorial (usando Elasticsearch como repositorio para buscar documentos vectorizados) y para almacenamiento avanzado (configuración de estructuras como resúmenes de documentos o grafos de conocimiento).

Contenido relacionado

Visualización de campos en un índice de Elasticsearch

Kofi Bartlett

Cómo ingerir datos en Elasticsearch a través de Kafka

Andre Luiz

Guion Ruby en Logstash

Dai Sugimori

Plantillas de índice en Elasticsearch: Cómo usar plantillas componibles

Kofi Bartlett

Cómo mostrar los campos de un índice de Elasticsearch

JD Armada

¿Estás listo para crear experiencias de búsqueda de última generación?

No se logra una búsqueda suficientemente avanzada con los esfuerzos de uno. Elasticsearch está impulsado por científicos de datos, operaciones de ML, ingenieros y muchos más que son tan apasionados por la búsqueda como tú. Conectemos y trabajemos juntos para crear la experiencia mágica de búsqueda que te dará los resultados que deseas.

Pruébalo tú mismo