Blog

Como ingerir dados no Elasticsearch através do LlamaIndex

Um guia passo a passo sobre como ingerir dados e realizar buscas usando RAG com LlamaIndex.

Neste artigo, implementaremos um mecanismo de busca para FAQs usando o LlamaIndex para indexar os dados. O Elasticsearch servirá como nosso banco de dados de vetores, permitindo a busca vetorial, enquanto o RAG (Retrieval-Augmented Generation) enriquecerá o contexto, fornecendo respostas mais precisas.

LlamaIndex e Elasticsearch: Ingestão de documentos e criação de uma busca de FAQs

O que é o LlamaIndex?

O LlamaIndex é uma estrutura que facilita a criação de agentes e fluxos de trabalho baseados em Modelos de Linguagem de Grande Porte (LLMs, na sigla em inglês) para interagir com dados específicos ou privados. Permite a integração de dados de diversas fontes (APIs, PDFs, bases de dados) com LLMs, possibilitando tarefas como pesquisa, extração de informações e geração de respostas contextualizadas.

Conceitos-chave:

  • Agentes: Assistentes inteligentes que utilizam LLMs para executar tarefas, desde respostas simples até ações complexas.

  • Fluxos de trabalho: Processos de várias etapas que combinam agentes, conectores de dados e ferramentas para tarefas avançadas.

  • Aumento de contexto: uma técnica que enriquece o modelo de aprendizagem linear (LLM) com dados externos, superando suas limitações de treinamento.

Integração do LlamaIndex com o Elasticsearch:

O Elasticsearch pode ser usado de diversas maneiras com o LlamaIndex:

  • Fonte de dados: Utilize o Elasticsearch Reader para extrair os documentos.

  • Modelo de embeddings: Codifica dados em vetores para buscas semânticas.

  • Armazenamento vetorial: Utilize o Elasticsearch como repositório para busca de documentos vetorizados.

  • Armazenamento avançado: configure estruturas como resumos de documentos ou grafos de conhecimento.

Utilizando LlamaIndex e Elasticsearch para construir uma busca de perguntas frequentes (FAQ).

Preparação de dados

Usaremos as Perguntas Frequentes do Elasticsearch Service como exemplo. Cada pergunta foi extraída do site e salva em um arquivo de texto individual. Você pode usar qualquer abordagem para organizar os dados; neste exemplo, optamos por salvar os arquivos localmente.

Arquivo de exemplo:

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.

Após salvar todas as perguntas, o diretório ficará assim:

Instalação de dependências

Implementaremos a ingestão e a pesquisa usando a linguagem Python, sendo a versão utilizada a 3.9. Como pré-requisito, será necessário instalar as seguintes dependências:

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

O Elasticsearch e o Kibana serão criados com o Docker, configurados via docker-compose.yml para executar a versão 8.16.2. Isso facilita a criação do ambiente 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:

Ingestão de documentos usando LlamaIndex

Os documentos serão indexados no Elasticsearch usando o LlamaIndex. Primeiro, carregamos os arquivos com SimpleDirectoryReader, que permite carregar arquivos de um diretório local. Após carregar os documentos, iremos indexá-los usando o 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)

Os Vector Stores no LlamaIndex são responsáveis por armazenar e gerenciar incorporações de documentos. O LlamaIndex suporta diferentes tipos de armazenamentos vetoriais e, neste caso, usaremos o Elasticsearch. No StorageContext, configuramos a instância do Elasticsearch. Como o contexto é local, não foram necessários parâmetros adicionais. Para configurações em outros ambientes, consulte a documentação para verificar os parâmetros necessários: Configuração do ElasticsearchStore.

Por padrão, o LlamaIndex usa o modelo text-embedding-ada-002 da OpenAI para gerar embeddings. No entanto, neste exemplo, usaremos o modelo text-embedding-3-small . É importante ressaltar que será necessária uma chave de API da OpenAI para usar o modelo.

Abaixo está o código completo para ingestão 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)

Após a execução, os documentos serão indexados no índice de perguntas frequentes , conforme mostrado abaixo:

Pesquisar com RAG

Para realizar buscas, configuramos o cliente ElasticsearchStore , definindo os campos index_name e es_url com a URL do Elasticsearch. Em retrieval_strategy, definimos a AsyncDenseVectorStrategy para buscas vetoriais. Outras estratégias, como AsyncBM25Strategy (busca por palavra-chave) e AsyncSparseVectorStrategy (vetores esparsos), também estão disponíveis. Mais detalhes podem ser encontrados na documentação oficial.

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

Em seguida, será criado um objeto VectorStoreIndex , onde configuraremos o vector_store usando o objeto ElasticsearchStore. Com o método as_retriever , realizamos a busca pelos documentos mais relevantes para uma consulta, definindo o número de resultados retornados para 5 através do 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)

O próximo passo é o RAG. Os resultados da busca vetorial são incorporados a um prompt formatado para o LLM, permitindo uma resposta contextualizada com base nas informações recuperadas.

No PromptTemplate, definimos o formato do prompt, que inclui:

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

  • Consulta ({query_str}): a pergunta do usuário.

  • Instruções: diretrizes para o modelo responder com base no contexto, sem depender de conhecimento externo.

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: "
)

Por fim, o LLM processa a solicitação e retorna uma resposta precisa e contextualizada.

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)

O código completo está abaixo:

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)

Agora podemos realizar nossa busca, por exemplo, "Os serviços da Elastic são gratuitos?" e obter uma resposta contextualizada com base nos próprios dados das perguntas frequentes.

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 gerar esta resposta, foram utilizados os seguintes 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.

Conclusão

Utilizando o LlamaIndex, demonstramos como criar um sistema de busca de FAQs eficiente com suporte para o Elasticsearch como banco de dados vetorial. Os documentos são ingeridos e indexados usando incorporações, possibilitando buscas vetoriais. Por meio de um PromptTemplate, os resultados da pesquisa são incorporados ao contexto e enviados ao LLM, que gera respostas precisas e contextualizadas com base nos documentos recuperados.

Este fluxo de trabalho integra a recuperação de informações com a geração de respostas contextualizadas para fornecer resultados precisos e relevantes.

Referências

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

Perguntas frequentes

O que é o LlamaIndex?

O LlamaIndex é uma estrutura que facilita a criação de agentes e fluxos de trabalho baseados em Modelos de Linguagem de Grande Porte (LLMs, na sigla em inglês) para interagir com dados específicos ou privados.

É possível integrar o LlamaIndex com o Elasticsearch?

Sim, o Elasticsearch pode ser usado de várias maneiras com o LlamaIndex, incluindo como fonte de dados (usando o Elasticsearch Reader para extrair documentos), como modelo de incorporação (codificando dados em vetores para buscas semânticas), como armazenamento vetorial (usando o Elasticsearch como repositório para busca de documentos vetorizados) e para armazenamento avançado (configurando estruturas como resumos de documentos ou grafos de conhecimento).

Conteúdo relacionado

Exibindo campos em um índice do Elasticsearch

Kofi Bartlett

Como ingerir dados no Elasticsearch através do Kafka

Andre Luiz

Scripting em Ruby no Logstash

Dai Sugimori

Criação de modelos de índice no Elasticsearch: Como usar modelos compostos

Kofi Bartlett

Como exibir os campos de um índice do Elasticsearch

JD Armada

Pronto para criar buscas de última geração?

Uma pesquisa suficientemente avançada não se consegue apenas com o esforço de uma só pessoa. O Elasticsearch é impulsionado por cientistas de dados, especialistas em operações de aprendizado de máquina, engenheiros e muitos outros que são tão apaixonados por buscas quanto você. Vamos nos conectar e trabalhar juntos para construir a experiência de busca mágica que lhe trará os resultados desejados.

Experimente você mesmo(a)