Blog

Técnicas avançadas de RAG, parte 1: Processamento de dados

Discutir e implementar técnicas que possam aumentar o desempenho do RAG. Parte 1 de 2, com foco no componente de processamento e ingestão de dados de um pipeline RAG avançado.

Esta é a Parte 1 da nossa exploração das Técnicas Avançadas RAG. Clique aqui para a Parte 2!

O artigo recente "Searching for Best Practices in Retrieval-Augmented Generation" avalia empiricamente a eficácia de várias técnicas de aprimoramento de RAG (Geração Aumentada de Recuperação), com o objetivo de convergir para um conjunto de melhores práticas para RAG.

Gasoduto RAG recomendado por Wang

O protocolo RAG recomendado por Wang e seus colegas.

Implementaremos algumas dessas boas práticas propostas, principalmente aquelas que visam melhorar a qualidade da busca (Fragmentação de Sentenças, HyDE, Empacotamento Reverso).

Por uma questão de brevidade, omitiremos as técnicas focadas na melhoria da eficiência (Classificação e Sumarização de Consultas).

Implementaremos também algumas técnicas que não foram abordadas, mas que eu pessoalmente considero úteis e interessantes (Inclusão de Metadados, Incorporação Composta de Múltiplos Campos, Enriquecimento de Consultas).

Por fim, realizaremos um breve teste para verificar se a qualidade dos nossos resultados de pesquisa e das respostas geradas melhorou em comparação com a linha de base. Vamos lá!

Visão geral do RAG

O RAG visa aprimorar os LLMs (Modelos de Aprendizagem Baseados em Aprendizagem) recuperando informações de bases de conhecimento externas para enriquecer as respostas geradas. Ao fornecer informações específicas do domínio, os LLMs podem ser rapidamente adaptados para casos de uso fora do escopo de seus dados de treinamento; sendo significativamente mais baratos do que o ajuste fino e mais fáceis de manter atualizados.

As medidas para melhorar a qualidade do RAG normalmente se concentram em duas vertentes:

  1. Aprimorar a qualidade e a clareza da base de conhecimento.

  2. Melhorar a abrangência e a especificidade das consultas de pesquisa.

Essas duas medidas alcançarão o objetivo de aumentar as chances de o mestrando em Direito ter acesso a fatos e informações relevantes e, portanto, ser menos propenso a ter alucinações ou recorrer ao seu próprio conhecimento, que pode estar desatualizado ou ser irrelevante.

A diversidade de métodos é difícil de esclarecer em poucas frases. Vamos direto à implementação para que as coisas fiquem mais claras.

Gasoduto RAG avançado

Figura 1: O pipeline RAG utilizado pelo autor.

Índice

Configurar

Todo o código pode ser encontrado no repositório Searchlabs.

Em primeiro lugar, o mais importante. Você precisará do seguinte:

  1. Implantação na Nuvem Elástica

  2. Uma API LLM - Neste notebook, estamos usando uma implantação do GPT-4o no Azure OpenAI.

  3. Python versão 3.12.4 ou posterior

Executaremos todo o código do notebook main.ipynb.

Faça o clone do repositório usando o Git, navegue até supporting-blog-content/advanced-rag-techniques e execute os seguintes comandos:

# Create a new virtual environment named 'rag_env'
python -m venv rag_env

# Activate the virtual environment (for Unix-based systems)
source rag_env/bin/activate

# (For Windows)
.\rag_env\Scripts\activate

# Install packages listed in requirements.txt
pip install -r requirements.txt

Feito isso, crie um arquivo .env. Abra o arquivo e preencha os seguintes campos (Referenciado em .env.example). Agradecimentos ao meu coautor, Claude-3.5, pelos comentários úteis.

# Elastic Cloud: Found in the 'Deployment' page of your Elastic Cloud 
# console
ELASTIC_CLOUD_ENDPOINT=""
ELASTIC_CLOUD_ID=""

# Elastic Cloud: Created during deployment setup or in 'Security' 
# settings
ELASTIC_USERNAME=""
ELASTIC_PASSWORD=""

# Elastic Cloud: The name of the index you created in Kibana or via API
ELASTIC_INDEX_NAME=""

# Azure AI Studio: Found in 'Keys and Endpoint' section of your Azure 
# OpenAI resource
AZURE_OPENAI_KEY_1=""
AZURE_OPENAI_KEY_2=""
AZURE_OPENAI_REGION=""
AZURE_OPENAI_ENDPOINT=""

# Azure AI Studio: Found in 'Deployments' section of your Azure OpenAI 
# resource
AZURE_OPENAI_DEPLOYMENT_NAME=""

# Using BAAI/bge-small-en-v1.5 because I think it is a good balance of 
# resource efficiency and performance. 
HUGGINGFACE_EMBEDDING_MODEL="BAAI/bge-small-en-v1.5"

Em seguida, selecionaremos o documento a ser importado e o colocaremos na pasta de documentos. Para este artigo, usaremos o Relatório Anual da Elastic NV de 2023. É um documento bastante complexo e denso, perfeito para testar a resistência das nossas técnicas RAG.

Relatório Anual da Elastic 2023

Relatório Anual da Elastic 2023

Agora que está tudo pronto, vamos à ingestão. Abra o arquivo main.ipynb e execute as duas primeiras células para importar todos os pacotes e inicializar todos os serviços.

Voltar ao topo

Ingestão, processamento e incorporação de documentos

Ingestão de dados

  • Nota pessoal: Estou impressionado com a praticidade do LlamaIndex. Antigamente, antes dos mestrados em direito e do LlamaIndex, importar documentos de vários formatos era um processo doloroso de coletar pacotes esotéricos de todos os cantos. Agora, tudo se resume a uma única chamada de função. Selvagem.

O SimpleDirectoryReader carregará todos os documentos em directory_path. Para arquivos .pdf , ele retorna uma lista de objetos de documento, que eu converto em dicionários Python porque acho mais fácil trabalhar com eles.

# llamaindex_processor.py
from llama_index.core import SimpleDirectoryReader

class LlamaIndexProcessor:
   def __init__(self):
       pass 
   
   def load_documents(self, directory_path):
       ''' 
       Load all documents in directory
       '''
       reader = SimpleDirectoryReader(input_dir=directory_path)
       return reader.load_data()

# main.ipynb
llamaindex_processor=LlamaIndexProcessor()
documents=llamaindex_processor.load_documents('./documents/')
documents=[dict(doc_obj) for doc_obj in documents]

Cada dicionário contém o conteúdo da chave no campo text . Também contém metadados úteis, como número da página, nome do arquivo, tamanho do arquivo e tipo.

{
  'id_': '5f76f0b3-22d8-49a8-9942-c2bbab14f63f',
  'metadata': {'page_label': '5',
   'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_path': '/Users/han/Desktop/Projects/truckasaurus/documents/Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_type': 'application/pdf',
   'file_size': 3724426,
   'creation_date': '2024-07-27',
   'last_modified_date': '2024-07-27'},
   'text': 'Table of Contents\nPage\nPART I\nItem 1. Business 3\n15 Item 1A. Risk Factors\nItem 1B. Unresolved Staff Comments 48\nItem 2. Properties 48\nItem 3. Legal Proceedings 48\nItem 4. Mine Safety Disclosures 48\nPART II\nItem 5. Market for Registrant's Common Equity, Related Stockholder Matters and Issuer Purchases of \nEquity Securities49\nItem 6. [Reserved] 49\nItem 7. Management's Discussion and Analysis of Financial Condition and Results of Operations 50\nItem 7A. Quantitative and Qualitative Disclosures About Market Risk 64\nItem 8. Financial Statements and Supplementary Data 66\nItem 9. Changes in and Disagreements With Accountants on Accounting and Financial Disclosure 100\n100\n101Item 9A. Controls and Procedures\nItem 9B. Other Information\nItem 9C. Disclosure Regarding Foreign Jurisdictions That Prevent Inspections 101\nPART III\n102\n102\n102\n102Item 10. Directors, Executive Officers and Corporate Governance\nItem 11. Executive Compensation\nItem 12. Security Ownership of Certain Beneficial Owners and Management, and Related Stockholder Matters  \nItem 13. Certain Relationships and Related Transactions, and Director Independence\nItem 14. Principal Accountant Fees and Services 102\nPART IV\n103\n105Item 15. Exhibits and Financial Statement Schedules  \nItem 16. Form 10-K Summary\nSignatures 106\ni',
   ...
}

Voltar ao topo

Segmentação por tokens e em nível de frase

A primeira coisa a fazer é reduzir nossos documentos a blocos de tamanho padrão (para garantir consistência e facilidade de gerenciamento). Os modelos de incorporação possuem limites de token únicos (tamanho máximo de entrada que podem processar). Os tokens são as unidades básicas de texto que modelam o processo. Para evitar a perda de informações (truncamento ou omissão de conteúdo), devemos fornecer textos que não excedam esses limites (dividindo textos mais longos em segmentos menores).

O particionamento (chunking) tem um impacto significativo no desempenho. Idealmente, cada bloco representaria uma informação autossuficiente, capturando informações contextuais sobre um único tópico. Os métodos de fragmentação incluem a fragmentação ao nível da palavra, em que os documentos são divididos pela contagem de palavras, e a fragmentação semântica, que utiliza um modelo de lógica de divisão (LLM) para identificar pontos de quebra lógicos.

A segmentação em nível de palavra é barata, rápida e fácil, mas apresenta o risco de dividir frases e, assim, quebrar o contexto. A fragmentação semântica torna-se lenta e dispendiosa, especialmente se estivermos lidando com documentos como o Relatório Anual da Elastic, com 116 páginas.

Vamos optar por uma abordagem intermediária. A segmentação em nível de frase ainda é simples, mas pode preservar o contexto de forma mais eficaz do que a segmentação em nível de palavra, além de ser significativamente mais barata e rápida. Além disso, implementaremos uma janela deslizante para capturar parte do contexto circundante e atenuar o impacto da divisão de parágrafos.

# chunker.py 

import uuid
import re


class Chunker: 
    def __init__(self, tokenizer):
        self.tokenizer = tokenizer 
    
    def split_into_sentences(self, text):
        """Split text into sentences."""
        return re.split(r'(?<=[.!?])\s+', text)
 
    def sentence_wise_tokenized_chunk_documents(self, documents, chunk_size=512, overlap=20, min_chunk_size=50):
        '''
        1. Split text into sentences.
        2. Tokenize using the provided tokenizer method.
        3. Build chunks up to the chunk_size limit.
        4. Create an overlap based on tokens - to preserve context.
        5. Only keep chunks that meet the minimum token size requirement.
        '''
        chunked_documents = []

        for doc in documents:
            sentences = self.split_into_sentences(doc['text'])
            tokens = []
            sentence_boundaries = [0]

            # Tokenize all sentences and keep track of sentence boundaries
            for sentence in sentences:
                sentence_tokens = self.tokenizer.encode(sentence, add_special_tokens=True)
                tokens.extend(sentence_tokens)
                sentence_boundaries.append(len(tokens))

            # Create chunks
            chunk_start = 0
            while chunk_start < len(tokens):
                chunk_end = chunk_start + chunk_size

                # Find the last complete sentence that fits in the chunk
                sentence_end = next((i for i in sentence_boundaries if i > chunk_end), len(tokens))
                chunk_end = min(chunk_end, sentence_end)

                # Create the chunk
                chunk_tokens = tokens[chunk_start:chunk_end]

                # Check if the chunk meets the minimum size requirement
                if len(chunk_tokens) >= min_chunk_size:
                    # Create a new document object for this chunk
                    chunk_doc = {
                        'id_': str(uuid.uuid4()),
                        'chunk': chunk_tokens,
                        'original_text': self.tokenizer.decode(chunk_tokens),
                        'chunk_index': len(chunked_documents),
                        'parent_id': doc['id_'],
                        'chunk_token_count': len(chunk_tokens)
                    }

                    # Copy all other fields from the original document
                    for key, value in doc.items():
                        if key != 'text' and key not in chunk_doc:
                            chunk_doc[key] = value

                    chunked_documents.append(chunk_doc)

                # Move to the next chunk start, considering overlap
                chunk_start = max(chunk_start + chunk_size - overlap, chunk_end - overlap)

        return chunked_documents

# main.ipynb 
# Initialize Embedding Model
HUGGINGFACE_EMBEDDING_MODEL = os.environ.get('HUGGINGFACE_EMBEDDING_MODEL')
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

# Initialize Chunker
chunker=Chunker(embedder.tokenizer)

A classe Chunker recebe o tokenizador do modelo de incorporação para codificar e decodificar o texto. Agora vamos construir blocos de 512 tokens cada, com uma sobreposição de 20 tokens. Para isso, vamos dividir o texto em frases, tokenizar essas frases e, em seguida, adicionar as frases tokenizadas ao nosso bloco atual até que não possamos adicionar mais sem ultrapassar nosso limite de tokens.

Finalmente, decodifique as frases de volta ao texto original para incorporação, armazenando-o em um campo chamado original_text. Os blocos são armazenados em um campo chamado chunk. Para reduzir o ruído (ou seja, documentos inúteis), descartaremos quaisquer documentos com menos de 50 tokens de comprimento.

Vamos executar o programa em nossos documentos:

chunked_documents=chunker.sentence_wise_tokenized_chunk_documents(documents, chunk_size=512)

E receba trechos de texto com a seguinte aparência:

print(chunked_documents[4]['original_text'])

[CLS] the aggregate market value of the ordinary shares held by non - affiliates of the registrant, 
based on the closing price of the shares of ordinary shares on the new york stock exchange on 
october 31, 2022 ( the last business day of the registrant 's second fiscal quarter ), was 
approximately $ 6. 1 billion. [SEP] [CLS] as of may 31, 2023, the registrant had 97, 390, 886 
ordinary shares, par value €0. 01 per share, outstanding. [SEP] [CLS] documents incorporated by 
reference portions of the registrant 's definitive proxy statement relating to the registrant 's 2
023 annual general meeting of shareholders are incorporated by reference into part iii of this annual 
...
...

Voltar ao topo

Inclusão e geração de metadados

Dividimos nossos documentos em partes menores. Agora é hora de enriquecer os dados. Desejo gerar ou extrair metadados adicionais. Esses metadados adicionais podem ser usados para influenciar e melhorar o desempenho da busca.

Vamos definir uma classe DocumentEnricher , cuja função é receber uma lista de documentos (dicionários Python) e uma lista de funções de processamento. Essas funções serão executadas na coluna original_text dos documentos e armazenarão suas saídas em novos campos.

Primeiro, extraímos as palavras-chave usando o TextRank. O TextRank é um algoritmo baseado em grafos que extrai frases e sentenças-chave de um texto, classificando sua importância com base nas relações entre as palavras.

Em seguida, vamos gerar perguntas potenciais usando o GPT-4o.

Por fim, vamos extrair as entidades usando o Spacy.

Como o código para cada um deles é bastante extenso e complexo, vou evitar reproduzi-lo aqui. Caso tenha interesse, os arquivos estão marcados nos exemplos de código abaixo.

Vamos executar o enriquecimento de dados:

# documentenricher.py
from tqdm import tqdm

class DocumentEnricher:

    def __init__(self):
        pass 

    def enrich_document(self, documents, processors, text_col='text'):
        for doc in tqdm(documents, desc="Enriching documents using processors: "+str(processors)): 
            for (processor, field) in processors: 
                metadata=processor(doc[text_col])
                if isinstance(metadata, list):
                    metadata='\n'.join(metadata)
                doc.update({field: metadata})
 
# main.ipynb
# Initialize processor classes 
nltkprocessor=NLTKProcessor() // nltk_processor.py
entity_extractor=EntityExtractor() // entity_extractor.py
gpt4o = LLMProcessor(model='gpt-4o') // llm.py

# Initialize LLM
documentenricher=DocumentEnricher()

# Create new fields in the documents - These are the outputs of the processor functions.
processors=[
    (nltkprocessor.textrank_phrases, "keyphrases"),
    (gpt4o.generate_questions, "potential_questions"),
    (entity_extractor.extract_entities, "entities")
    ]

# .enrich_document() will modify chunked_docs in place. 
# To view the results, we'll print chunked_docs in the next few cells!
documentenricher.enrich_document(chunked_docs, text_col='original_text', processors=processors)

E veja os resultados:

Palavras-chave extraídas pelo TextRank

Essas palavras-chave representam os tópicos principais do bloco. Se a consulta estiver relacionada à segurança cibernética, a pontuação desse segmento será aumentada.

print(chunked_documents[25]['keyphrases'])

'elastic agent stop', 'agent stop malware', 
'stop malware ransomware', 'malware ransomware environment', 
'ransomware environment wide', 'environment wide visibility', 
'wide visibility threat', 'visibility threat detection', 
'sep cl key', 'cl key feature'

Possíveis perguntas geradas pelo GPT-4o

Essas perguntas em potencial podem corresponder diretamente às consultas do usuário, oferecendo um aumento na pontuação. Solicitamos ao GPT-4o que gere perguntas que possam ser respondidas usando as informações encontradas no bloco atual.

print(chunked_documents[25]['potential_questions'])

1. What are the primary functions that Elastic Agent provides in terms of cybersecurity?
2. Describe how Logstash contributes to data management within an IT environment.
3. List and explain any key features of Logstash mentioned in the document.
4. How does Elastic Agent enhance environment-wide visibility in threat detection?
5. What capabilities does Logstash offer for handling data beyond simple collection?
6. In what ways does the document suggest that Elastic Agent stops malware and ransomware?
7. Can you identify any relationships between the functionalities of Elastic Agent and Logstash in an integrated environment?
8. What implications might the advanced threat detection capabilities of Elastic Agent have for organizational security policies?
9. Compare and contrast the roles of Elastic Agent and Logstash based on their described functions.
10. How might the centralized collection ability of Logstash support the threat detection capabilities of Elastic Agent?

Entidades extraídas pelo Spacy

Essas entidades têm uma finalidade semelhante à das palavras-chave, mas capturam os nomes de organizações e indivíduos, que a extração por palavras-chave pode não incluir.

print(chunked_documents[29]['entities'])

'appdynamics', 'apm data', 'azure sentinel', 
'microsoft', 'mcafee', 'broadcom', 'cisco', 
'dynatrace', 'coveo', 'lucidworks'

Voltar ao topo

Incorporações compostas de múltiplos campos

Agora que enriquecemos nossos documentos com metadados adicionais, podemos aproveitar essas informações para criar representações vetoriais mais robustas e sensíveis ao contexto.

Vamos recapitular o ponto em que nos encontramos no processo. Cada documento contém quatro áreas de interesse.

{
    "chunk": "...",
    "keyphrases": "...", 
    "potential_questions": "...", 
    "entities": "..." 
}

Cada campo representa uma perspectiva diferente sobre o contexto do documento, podendo destacar uma área-chave para o mestrado em Direito (LLM) se concentrar.

Pipeline de enriquecimento de metadados no RAG

Pipeline de enriquecimento de metadados

O plano é incorporar cada um desses campos e, em seguida, criar uma soma ponderada das incorporações, conhecida como Incorporação Composta.

Com sorte, essa incorporação composta permitirá que o sistema se torne mais sensível ao contexto, além de introduzir mais um hiperparâmetro ajustável para controlar o comportamento de busca.

Primeiro, vamos incorporar cada campo e atualizar cada documento no local, usando nosso modelo de incorporação definido localmente e importado no início do notebook main.ipynb.

# EmbeddingModel defined in embedding_model.py
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

cols_to_embed=['keyphrases', 'potential_questions', 'entities']

embedding_cols=[]
for col in cols_to_embed:
    # Works on text input
    embedding_col=embedder.embed_documents_text_wise(chunked_documents, text_field=col)
    embedding_cols.append(embedding_col)
# Works on token input
embedding_col=embedder.embed_documents_token_wise(chunked_documents, token_field="chunk")
embedding_cols.append(embedding_col)

Cada função de incorporação retorna o campo da incorporação, que é simplesmente o campo de entrada original com um sufixo _embedding .

Vamos agora definir os pesos da nossa incorporação composta:

embedding_cols=[
                'keyphrases_embedding',
                'potential_questions_embedding',
                'entities_embedding',
                'chunk_embedding']
combination_weights=[
                    0.1,
                    0.15,
                    0.05,
                    0.7
                ]

Os pesos permitem atribuir prioridades a cada componente, com base no seu caso de uso e na qualidade dos seus dados. Intuitivamente, a magnitude dessas ponderações depende do valor semântico de cada componente. Como o texto em si é de longe o mais rico, atribuo uma ponderação de 70%. Como as entidades são as menores, sendo apenas uma lista de nomes de organizações ou pessoas, atribuo a elas uma ponderação de 5%. A configuração precisa desses valores deve ser determinada empiricamente, caso a caso, para cada situação específica.

Finalmente, vamos escrever uma função para aplicar as ponderações e criar nossa representação composta. Também excluiremos todos os elementos incorporados dos componentes para economizar espaço.

from tqdm import tqdm 
def combine_embeddings(objects, embedding_cols, combination_weights, primary_embedding='primary_embedding'):
    # Ensure the number of weights matches the number of embedding columns
    assert len(embedding_cols) == len(combination_weights), "Number of embedding columns must match number of weights"
    
    # Normalize weights to sum to 1
    weights = np.array(combination_weights) / np.sum(combination_weights)
    
    for obj in tqdm(objects, desc="Combining embeddings"):
        # Initialize the combined embedding
        combined = np.zeros_like(obj[embedding_cols[0]])
        
        # Compute the weighted sum
        for col, weight in zip(embedding_cols, weights):
            combined += weight * np.array(obj[col])
        
        # Add the new combined embedding to the object
        obj.update({primary_embedding:combined.tolist()})
        
        # Remove the original embedding columns
        for col in embedding_cols:
            obj.pop(col, None)

combine_embeddings(chunked_documents, embedding_cols, combination_weights)

Com isso, concluímos o processamento de nossos documentos. Agora temos uma lista de objetos de documento com a seguinte aparência:

{ 'id_': '7fe71686-5cd0-4831-9e79-998c6dbeae0c', 'chunk': [2312, 14613, ...], 'original_text': 'if an emerging growth company, indicate by check mark if the registrant has elected not to use the extended ...', 'chunk_index': 3, 'chunk_token_count': 399, 'metadata': {'page_label': '3', 'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf', ... 'keyphrases': 'sep cl unk\ncheck mark registrant\ncl unk indicate\nunk indicate check\nindicate check mark\nprincipal executive office\naccelerate filer unk\ncompany unk emerge\nunk emerge growth\nemerge growth company', 'potential_questions': '1. What are the different types of registrant statuses mentioned in the document?\n2. Under what section of the Sarbanes-Oxley Act must registrants file a report on the effectiveness of their internal ...', 'entities': 'the effe ctiveness of\nsection 13\nSEP\nUNK\nsection 21e\n1934\n1933\nu. s. c.\nsection 404\nsection 12\nal', 'primary_embedding': [-0.3946287803351879, -0.17586839850991964, ...] }

Indexação para Elastic

Vamos fazer o upload em massa de nossos documentos para o Elastic Search. Para esse propósito, há muito tempo defini um conjunto de funções auxiliares elásticas em elastic_helpers.py. É um trecho de código muito extenso, então vamos nos ater à análise das chamadas de função.

es_bulk_indexer.bulk_upload_documents Funciona com qualquer lista de objetos de dicionário, aproveitando os convenientes mapeamentos dinâmicos do Elasticsearch.

# Initialize Elasticsearch
ELASTIC_CLOUD_ID = os.environ.get('ELASTIC_CLOUD_ID')
ELASTIC_USERNAME = os.environ.get('ELASTIC_USERNAME')
ELASTIC_PASSWORD = os.environ.get('ELASTIC_PASSWORD')
ELASTIC_CLOUD_AUTH = (ELASTIC_USERNAME, ELASTIC_PASSWORD)
es_bulk_indexer = ESBulkIndexer(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)
es_query_maker = ESQueryMaker(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)

# Define Index Name
index_name=os.environ.get('ELASTIC_INDEX_NAME')


# Create index and bulk upload 
index_exists = es_bulk_indexer.check_index_existence(index_name=index_name)
if not index_exists:
    logger.info(f"Creating new index: {index_name}")
    es_bulk_indexer.create_es_index(es_configuration=BASIC_CONFIG, index_name=index_name)

success_count = es_bulk_indexer.bulk_upload_documents(
    index_name=index_name, 
    documents=chunked_documents, 
    id_col='id_',
    batch_size=32
)

Acesse o Kibana e verifique se todos os documentos foram indexados. Deveriam ser 224. Nada mal para um documento tão grande!

Índice Kibana

Documentos de Relatório Anual Indexados no Kibana

Voltar ao topo

Pausa para o gato

Vamos fazer uma pausa, o artigo está um pouco denso, eu sei. Vejam só o meu gato:

Gasoduto Han

Veja como ela está furiosa.

Adorável. O chapéu sumiu e eu suspeito que ela o roubou e escondeu em algum lugar :(

Parabéns por ter chegado até aqui :)

Junte-se a mim na Parte 2 para testes e avaliação do nosso pipeline RAG!

Apêndice

Definições

1. Segmentação de Frases

  • Uma técnica de pré-processamento usada em sistemas RAG para dividir o texto em unidades menores e significativas.

  • Processo:

    1. Entrada: Bloco grande de texto (ex.: documento, parágrafo)

    2. Saída: Segmentos de texto menores (normalmente frases ou pequenos grupos de frases)

  • Propósito:

    • Cria segmentos de texto granulares e específicos ao contexto.

    • Permite uma indexação e recuperação mais precisas.

    • Melhora a relevância das informações recuperadas em sistemas RAG.

  • Características:

    • Os segmentos têm significado semântico.

    • Podem ser indexados e recuperados independentemente.

    • Frequentemente preserva algum contexto para garantir a compreensibilidade de forma independente.

  • Benefícios:

    • Aumenta a precisão de recuperação

    • Permite uma ampliação mais focada em pipelines RAG.

2. HyDE (Incorporação Hipotética de Documentos)

  • Uma técnica que utiliza um modelo de lógica latente (LLM) para gerar um documento hipotético para expansão de consultas em sistemas RAG.

  • Processo:

    1. Inserir consulta em um LLM

    2. O LLM gera um documento hipotético que responde à pergunta.

    3. Incorpore o documento gerado

    4. Use o embedding para busca vetorial

  • Principal diferença:

    • RAG tradicional: relaciona a consulta aos documentos.

    • HyDE: Correspondência entre documentos.

  • Propósito:

    • Melhorar o desempenho de recuperação de dados, especialmente para consultas complexas ou ambíguas.

    • Captura um contexto semântico mais rico do que uma consulta curta.

  • Benefícios:

    • Aproveita o conhecimento do LLM para ampliar as consultas.

    • Pode potencialmente melhorar a relevância dos documentos recuperados.

  • Desafios:

    • Requer inferência LLM adicional, aumentando a latência e o custo.

    • O desempenho depende da qualidade do documento hipotético gerado.

3. Reembalagem reversa

  • Uma técnica utilizada em sistemas RAG para reordenar os resultados da pesquisa antes de passá-los para o LLM.

  • Processo:

    1. O mecanismo de busca (por exemplo, Elasticsearch) retorna documentos em ordem decrescente de relevância.

    2. A ordem é invertida, colocando o documento mais relevante por último.

  • Propósito:

    • Explora o viés de recência dos mestrados em direito, que tendem a se concentrar mais nas informações mais recentes em seu contexto.

    • Garante que as informações mais relevantes sejam as mais "atualizadas" na janela de contexto do LLM.

  • Exemplo: Ordem original: [Mais relevante, Segundo mais relevante, Terceiro mais relevante, ...] Ordem inversa: [..., Terceiro mais relevante, Segundo mais relevante, Mais relevante]

4. Classificação de consultas

  • Uma técnica para otimizar a eficiência do sistema RAG, determinando se uma consulta requer RAG ou se pode ser respondida diretamente pelo LLM.

  • Processo:

    1. Desenvolver um conjunto de dados personalizado específico para o LLM em uso.

    2. Treinar um modelo de classificação especializado

    3. Utilize o modelo para categorizar as consultas recebidas.

  • Propósito:

    • Melhore a eficiência do sistema evitando o processamento desnecessário de RAG (raiz, grafite e agregação).

    • Direcione as consultas para o mecanismo de resposta mais apropriado.

  • Requisitos:

    • Conjunto de dados e modelo específicos para LLM

    • Aperfeiçoamento contínuo para manter a precisão.

  • Benefícios:

    • Reduz a sobrecarga computacional para consultas simples.

    • Potencialmente melhora o tempo de resposta para consultas que não sejam RAG.

5. Resumo

  • Uma técnica para condensar documentos recuperados em sistemas RAG.

  • Processo:

    1. Recuperar documentos relevantes

    2. Gere resumos concisos de cada documento.

    3. Utilize resumos em vez de documentos completos no pipeline RAG.

  • Propósito:

    • Melhore o desempenho RAG concentrando-se em informações essenciais.

    • Reduzir o ruído e a interferência de conteúdo menos relevante

  • Benefícios:

    • Potencialmente melhora a relevância das respostas do LLM

    • Permite a inclusão de mais documentos dentro dos limites do contexto.

  • Desafios:

    • Risco de perder detalhes importantes na sumarização.

    • Sobrecarga computacional adicional para geração de resumos.

6. Inclusão de Metadados

  • Uma técnica para enriquecer documentos com informações contextuais adicionais.

  • Tipos de metadados:

    • Palavras-chave

    • Títulos

    • Datas

    • Detalhes da autoria

    • Resumos

  • Propósito:

    • Aumentar a informação contextual disponível para o sistema RAG

    • Proporcionar aos alunos de mestrado em Direito uma compreensão mais clara do conteúdo e da relevância dos documentos.

  • Benefícios:

    • Potencialmente melhora a precisão da recuperação.

    • Aumenta a capacidade do LLM de avaliar a utilidade dos documentos.

  • Implementação:

    • Pode ser feito durante o pré-processamento do documento.

    • Pode exigir etapas adicionais de extração ou geração de dados.

7. Incorporações compostas de múltiplos campos

  • Uma técnica avançada de incorporação para sistemas RAG que cria incorporações separadas para diferentes componentes do documento.

  • Processo:

    1. Identifique os campos relevantes (ex.: título, palavras-chave, sinopse, conteúdo principal).

    2. Gere embeddings separados para cada campo.

    3. Combine ou armazene esses embeddings para uso na recuperação de informações.

  • Diferença em relação à abordagem padrão:

    • Tradicional: Incorporação única para todo o documento.

    • Composição: Incorporação múltipla para diferentes aspectos do documento

  • Propósito:

    • Criar representações de documentos mais matizadas e sensíveis ao contexto.

    • Capturar informações de uma variedade maior de fontes em um documento.

  • Benefícios:

    • Potencialmente melhora o desempenho em consultas ambíguas ou multifacetadas.

    • Permite uma ponderação mais flexível de diferentes aspectos do documento na recuperação.

  • Desafios:

    • Aumento da complexidade na incorporação de processos de armazenamento e recuperação

    • Pode exigir algoritmos de correspondência mais sofisticados.

8. Enriquecimento de consultas

  • Uma técnica para expandir a consulta original com termos relacionados, a fim de melhorar o alcance da pesquisa.

  • Processo:

    1. Analise a consulta original

    2. Gere sinônimos e frases semanticamente relacionadas.

    3. Aprimore a consulta com estes termos adicionais.

  • Propósito:

    • Aumentar o leque de correspondências potenciais no conjunto de documentos.

    • Melhorar o desempenho de recuperação de dados para consultas com linguagem específica ou técnica.

  • Benefícios:

    • Pode recuperar documentos relevantes que não correspondam exatamente aos termos da consulta original.

    • Pode ajudar a superar a incompatibilidade de vocabulário entre consultas e documentos.

  • Desafios:

    • Risco de desvio de consulta se não for implementado com cuidado.

    • Pode aumentar a sobrecarga computacional no processo de recuperação.

Voltar ao topo

Conteúdo relacionado

LINQ para Elasticsearch ES|QL: escreva Consultas em C# e Consulte o Elasticsearch

Florian Bernd

Aprimorando as capacidades de chatbots com PNL e busca vetorial no Elasticsearch.

Priscilla Parodi

Elasticsearch x OpenSearch: comparação de desempenho da busca vetorial

Ugo Sangiorgi

Detecção de plágio por IA: usando o Elasticsearch

Priscilla Parodi

Técnicas avançadas de RAG, parte 2: Consultas e testes

Han Xiang Choong

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)