RAG (Retrieval Augmented Generation) com LlamaIndex, Elasticsearch e Mistral

Aprenda como implementar um sistema RAG (Retrieval Augmented Generation) usando LlamaIndex, Elasticsearch e o Mistral em execução local.

O Elasticsearch conta com integrações nativas com as principais ferramentas e provedores de IA generativa do setor. Confira nossos webinars sobre como ir além do básico do RAG ou criar aplicativos prontos para produção com o banco de dados vetorial da Elastic.

Para criar as melhores soluções de busca para seu caso de uso, inicie um teste gratuito na nuvem ou experimente o Elastic em sua máquina local agora mesmo.

Neste blog, discutiremos como implementar uma experiência de perguntas e respostas usando a técnica RAG (Retrieval Augmented Generation) com o Elasticsearch como banco de dados vetorial. Usaremos o LlamaIndex e uma instância local do Mistral LLM.

Antes de começarmos, vamos analisar alguns termos.

Terminologia

LlamaIndex é uma estrutura de dados líder para a construção de aplicações LLM (Large Language Model). O LlamaIndex fornece abstrações para vários estágios de construção de um aplicativo RAG (Retrieval Augmented Generation). Frameworks como LlamaIndex e LangChain fornecem abstrações para que os aplicativos não fiquem fortemente acoplados às APIs de nenhum LLM específico.

O Elasticsearch é oferecido pela Elastic. A Elastic é líder do setor e está por trás do Elasticsearch, um mecanismo de busca e análise que oferece suporte à busca de texto completo para precisão, busca vetorial para compreensão semântica e busca híbrida para o melhor dos dois mundos. O Elasticsearch é um banco de dados vetorial e um repositório de dados escalável. As funcionalidades do Elasticsearch que utilizamos neste blog estão disponíveis na versão gratuita e de código aberto do Elasticsearch.

A Geração Aumentada de Recuperação (RAG, na sigla em inglês) é uma técnica/padrão de IA em que os Modelos de Aprendizagem Baseados em Aprendizagem (LLMs, na sigla em inglês) recebem conhecimento externo para gerar respostas às consultas do usuário. Isso permite que as respostas do LLM sejam adaptadas a contextos específicos e sejam mais detalhadas.

A Mistral oferece modelos LLM de código aberto e modelos otimizados para uso empresarial. Neste tutorial, usaremos o modelo de código aberto mistral-7b , que funciona no seu computador portátil. Se você não quiser executar o modelo no seu laptop, como alternativa, pode usar a versão em nuvem. Nesse caso, você precisará modificar o código deste blog para usar as chaves de API e os pacotes corretos.

O Ollama ajuda a executar LLMs localmente no seu laptop. Usaremos o Ollama para executar o modelo de código aberto Mistral-7b localmente.

Os embeddings são representações numéricas do significado de um texto/mídia. São representações de menor dimensão de informações de alta dimensão.

Construindo uma aplicação RAG com LlamaIndex, Elasticsearch e Mistral: Visão geral do cenário

Cenário:

Temos um conjunto de dados de amostra (em formato JSON) de conversas de call center entre agentes e clientes de uma empresa fictícia de seguros residenciais. Vamos construir um aplicativo RAG simples que possa responder a perguntas como:

Give me summary of water related issues.

Fluxo de alto nível

Temos o Mistral LLM instalado e funcionando localmente usando o Ollama.

Em seguida, carregamos as conversas do arquivo JSON como Documents no ElasticsearchStore (que é um VectorStore com suporte do Elasticsearch). Ao carregar os documentos, criamos embeddings usando o modelo Mistral executado localmente. Armazenamos esses embeddings juntamente com as conversas no LlamaIndex Elasticsearch vector Store (ElasticsearchStore).

Configuramos um pipeline de ingestão LlamaIndex e o abastecemos com o LLM local que utilizamos, neste caso o Mistral executado via Ollama.

Quando fazemos uma pergunta como "Dê-me um resumo dos problemas relacionados à água", O Elasticsearch realiza uma busca semântica e retorna conversas relacionadas a questões hídricas. Essas conversas, juntamente com a pergunta original, são enviadas ao LLM local para gerar uma resposta.

Etapas para construir o aplicativo RAG

Execute o Mistral localmente

Baixe e instale o Ollama. Após instalar o Ollama, execute este comando para baixar e executar o mistral.

Pode levar alguns minutos para baixar e executar o modelo localmente pela primeira vez. Verifique se o mistral está ativo fazendo uma pergunta como a seguinte: "Escreva um poema sobre nuvens" e veja se você gostou do poema. Mantenha o Ollama em execução, pois precisaremos interagir com o modelo Mistral posteriormente por meio de código.

Instale o Elasticsearch

Instale e execute o Elasticsearch criando uma implantação na nuvem (instruções aqui) ou executando-o em um contêiner Docker (instruções aqui). Você também pode criar uma implantação auto-hospedada de nível de produção do Elasticsearch, começando aqui.

Supondo que você esteja usando a implantação em nuvem, obtenha a chave da API e o ID da nuvem para a implantação, conforme mencionado nas instruções. Vamos usá-los mais tarde.

aplicação RAG

Para referência, o código completo pode ser encontrado neste repositório do Github. Clonar o repositório é opcional, pois analisaremos o código a seguir.

Em sua IDE favorita, crie uma nova aplicação Python com os 3 arquivos abaixo.

  • index.py Onde fica o código relacionado à indexação de dados.
  • query.py Onde fica o código relacionado a consultas e interação com o LLM.
  • .env onde ficam as propriedades de configuração, como chaves de API.

Precisamos instalar alguns pacotes. Começamos criando um novo ambiente virtual Python na pasta raiz da sua aplicação.

Ative o ambiente virtual e instale os pacotes necessários listados abaixo.

Dados de indexação

Baixe o arquivo conversations.json , que contém conversas entre clientes e atendentes de call center da nossa fictícia empresa de seguros residenciais. Coloque o arquivo no diretório raiz da aplicação, junto com os dois arquivos Python e o arquivo .env. arquivo que você criou anteriormente. Segue abaixo um exemplo do conteúdo do arquivo.

Definimos uma função chamada get_documents_from_file em index.py que lê o arquivo json e cria uma lista de Documentos. Os objetos de documento são a unidade básica de informação com a qual o LlamaIndex trabalha.

Criar Pipeline de Ingestão

Primeiramente, adicione o CloudID do Elasticsearch e as chaves de API que você obteve na seção Install Elasticsearch ao arquivo .env . Seu arquivo .env deve ter a aparência abaixo (com valores reais).

O LlamaIndex IngestionPipeline permite compor um pipeline usando vários componentes. Adicione o código abaixo ao arquivo index.py .

Conforme mencionado anteriormente, o LlamaIndex IngestPipeline pode ser composto por vários componentes. Estamos adicionando 3 componentes ao pipeline na linha pipeline = IngestionPipeline(....

  • SentenceSplitter: Como pode ser visto na definição de get_documents_from_file(), cada Documento tem um campo de texto que contém a conversa encontrada no arquivo json. Este campo de texto contém um texto longo. Para que a busca semântica funcione bem, ela precisa ser dividida em blocos de texto menores. A classe SentenceSplitter faz isso por nós. Esses fragmentos são chamados de Nós na terminologia do LlamaIndex. Existem metadados nos nós que apontam de volta para o Documento ao qual pertencem. Alternativamente, você pode usar o Elasticsearch Ingestpipeline para fragmentação, conforme mostrado neste blog.
  • OllamaEmbedding: Os modelos de incorporação convertem um texto em números (também chamados de vetores). A representação numérica nos permite realizar buscas semânticas, em que os resultados correspondem ao significado da palavra, em vez de apenas realizar uma busca textual. Fornecemos o IngestionPipeline com OllamaEmbedding("mistral"). Os trechos que dividimos usando o SentenceSplitter são enviados para o modelo Mistral que está sendo executado em sua máquina local por meio do Ollama. O Mistral então cria embeddings para esses trechos.
  • ElasticsearchStore: O armazenamento vetorial LlamaIndex ElasticsearchStore faz backup dos embeddings que estão sendo criados em um índice do Elasticsearch. O ElasticsearchStore se encarrega de criar e preencher o conteúdo do índice Elasticsearch especificado. Ao criar o ElasticsearchStore (referenciado por es_vector_store) fornecemos o nome do índice Elasticsearch que queremos criar (calls no nosso caso ), o campo no índice onde queremos que os embeddings sejam armazenados (conversation_vector no nosso caso ) e o campo onde queremos armazenar o texto (conversation no nosso caso ). Em resumo, com base na nossa configuração ElasticsearchStore cria um novo índice no Elasticsearch com conversation_vector e conversation como campos (entre outros campos criados automaticamente).

Unindo tudo, executamos o pipeline chamando pipeline.run(documents=documents).

Execute o script index.py para iniciar o pipeline de ingestão:

Assim que a execução do pipeline for concluída, devemos ver um novo índice no Elasticsearch chamado calls. Ao executar uma consulta simples no Elasticsearch usando o Console do Desenvolvedor, você deverá conseguir ver os dados carregados juntamente com os embeddings.

Resumindo o que fizemos até agora, criamos documentos a partir de um arquivo JSON, dividimos esses documentos em partes, criamos embeddings para essas partes e armazenamos os embeddings (e a conversa de texto) em um armazenamento vetorial (ElasticsearchStore).

Consultando

O llamaIndex VectorStoreIndex permite recuperar documentos relevantes e consultar dados. Por padrão, o VectorStoreIndex armazena embeddings na memória em um SimpleVectorStore. No entanto, armazenamentos vetoriais externos (como o ElasticsearchStore) podem ser usados para tornar os embeddings persistentes.

Abra o query.py e cole o código abaixo.

Definimos um LLM local (local_llm) para apontar para o modelo Mistral em execução no Ollama. Em seguida, criamos um VectorStoreIndex (index) a partir do armazenamento de vetores ElasticssearchStore que criamos anteriormente e, em seguida, obtemos um mecanismo de consulta do índice. Ao criar o mecanismo de consulta, fazemos referência ao LLM local que deve ser usado para responder, também fornecemos (similarity_top_k=10) para configurar o número de documentos que devem ser recuperados do armazenamento de vetores e enviados ao LLM para obter uma resposta.

Execute o script query.py para executar o fluxo RAG:

Enviamos a consulta Give me summary of water related issues (sinta-se à vontade para personalizar o query) e a resposta do LLM, que é fornecida com os documentos relacionados, deve ser algo como o abaixo.

No contexto apresentado, observamos diversos casos em que os clientes perguntaram sobre a cobertura para danos relacionados à água. Em dois casos, as inundações causaram danos aos porões e, em outro caso, o problema foram os vazamentos no telhado. Os agentes confirmaram que ambos os tipos de danos causados pela água estão cobertos pelas respectivas apólices. Portanto, problemas relacionados à água, incluindo inundações e vazamentos no telhado, geralmente são cobertos por apólices de seguro residencial.

Algumas ressalvas:

Este post do blog é uma introdução para iniciantes à técnica RAG com Elasticsearch e, portanto, omite a configuração de recursos que permitirão que você leve este ponto de partida para um ambiente de produção. Ao desenvolver soluções para uso em produção, você deve considerar aspectos mais sofisticados, como a capacidade de proteger seus dados com Segurança em Nível de Documento, dividir seus dados em partes menores como parte de um pipeline de ingestão do Elasticsearch ou até mesmo executar outras tarefas de aprendizado de máquina nos mesmos dados usados para casos de uso de GenAI/Chat/Perguntas e Respostas.

Você também pode considerar a possibilidade de obter dados e criar embeddings a partir de várias fontes externas (por exemplo, Azure Blob Storage, Dropbox, Gmail etc.) usando Elastic Connectors.

A Elastic torna tudo isso e muito mais possível, oferecendo uma solução abrangente de nível empresarial para casos de uso de GenAI e além.

O que vem a seguir?

  • Você deve ter notado que estamos enviando 10 conversas relacionadas, juntamente com a pergunta do usuário, para o LLM para que ele elabore uma resposta. Essas conversas podem conter informações de identificação pessoal (PII), como nome, data de nascimento, endereço etc. No nosso caso, o LLM é local, portanto o vazamento de dados não é um problema. No entanto, quando se deseja usar um LLM executado na nuvem (por exemplo, OpenAI), não é recomendável enviar textos que contenham informações de identificação pessoal. Em uma postagem de acompanhamento, veremos como realizar o mascaramento de informações PII antes de enviá-las a LLMs externos no fluxo RAG.
  • Neste post, usamos um LLM local. No próximo post sobre mascaramento de dados PII no RAG, veremos como podemos migrar facilmente de um LLM local para um LLM público.

Perguntas frequentes

O que é o LlamaIndex?

LlamaIndex é uma estrutura de dados líder para a construção de aplicações LLM (Large Language Model).

O que é Mistral?

A Mistral é uma empresa que fornece modelos LLM de código aberto e otimizados para uso empresarial.

Conteúdo relacionado

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)