EventoObtenha 50% de desconto no seu ingresso para MongoDB.local Londres em outubro 2. Use o código WEB50Saiba mais >>
Desenvolvedor MongoDB
Central de desenvolvedor do MongoDBchevron-right
Produtoschevron-right
Atlaschevron-right

Como escolher a estratégia de chunking certa para seu aplicativo LLM

Apoorva Joshi15 min read • Published Jun 17, 2024 • Updated Jun 17, 2024
IAPythonAtlas
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Na parte 1 desta série sobre Recuperação de Geração Aumentada (RAG), analisamos como escolher o modelo de incorporação correto para seu aplicativo RAG. Embora a escolha do modelo de incorporação seja uma consideração importante para garantir a recuperação de boa qualidade do RAG, há uma decisão chave a ser tomada antes do estágio de incorporação que pode ter um impacto significativo a seguir: escolher a estratégia de chunking correta para seus dados.
Neste tutorial, abordaremos o seguinte:
  • O que é chunking e por que ele é importante para o RAG?
  • Escolhendo a estratégia de chunking correta para seu aplicativo RAG
  • Avaliando diferentes metodologias de chunking em um conjunto de dados

RAG — uma atualização muito rápida

Arquitetura básica do RAG
Em um aplicativo RAG, o objetivo é obter respostas mais precisas de um grande modelo de linguagem (LLM) sobre assuntos que podem não estar bem representados em seu conhecimento paramétrico — por exemplo, os dados da sua organização. Isso normalmente é feito recuperando informações relevantes de uma base de conhecimento usando pesquisa semântica. Essa técnica usa um modelo de incorporação para criar representações vetoriais da query do usuário e das informações na base de conhecimento.
Dado uma consulta do usuário e sua incorporação, ele recupera os documentos mais relevantes da base de conhecimento usando a pesquisa de similaridade de vetores. Os documentos recuperados, a query do usuário e quaisquer prompts do usuário são então passados como contexto para um LLM, para gerar uma resposta para a pergunta do usuário.

O que é chunking e por que ele é importante para o RAG?

A maioria dos casos de uso do RAG atualmente gira em torno do uso de LLMs para responder a perguntas sobre grandes repositórios de dados, como documentação técnica, documentos de integração etc. Embora os fornecedores de modelos LLM estejam constantemente aumentando as Windows de contexto de seus modelos, eles também cobram pelo número de tokens de entrada. Isso significa que tentar ajustar documentos grandes na janela de contexto do modelo pode ser caro.
Passar documentos grandes como entrada para o LLM também significa que o LLM precisa analisar as informações mais relevantes do documento para responder às queries do usuário. Isso pode ser um desafio, pois os LLMs são projetados para processar entrada sequencialmente, token por token. Embora eles possam capturar dependências e contexto de longo alcance até certo ponto, sua capacidade de fazer isso efetivamente diminui à medida que a sequência de entrada se torna mais longa. É aqui que o chunking ajuda.
A fragmentação é o processo de dividir grandes partes de texto em segmentos ou partes menores. No contexto do RAG, incorporar partes menores em vez de documentos inteiros para criar a base de conhecimento significa que, dada uma consulta do usuário, você só precisa recuperar as partes mais relevantes do documento, resultando em menos tokens de entrada e um contexto mais direcionado para o LLM trabalhar.

Escolhendo a estratégia de chunking correta para seu aplicativo RAG

Não existe uma solução “one size fits all” quando se trata de escolher uma estratégia de chunking para o RAG — ela depende da estrutura dos documentos que estão sendo usados para criar a base de conhecimento e terá uma aparência diferente dependendo se você estiver trabalhando com texto bem formatado documentos ou documentos com trechos de código, tabelas, imagens etc. Os três componentes principais de uma estratégia de chunking são os seguintes:
  • Técnica de divisão: determina onde os limites dos blocos serão colocados — com base nos limites dos parágrafos, separadores específicos da linguagem de programação, tokens ou até mesmo limites semânticos
  • Tamanho do bloco: o número máximo de caracteres ou tokens permitidos para cada bloco
  • Sobreposiçãode blocos: número de caracteres ou tokens sobrepostos entre blocos; blocos sobrepostos podem ajudar a preservar o contexto entre blocos; o grau de sobreposição é normalmente especificado como uma porcentagem do tamanho do bloco
Assim como na escolha de modelos de incorporação ou conclusão para o RAG, recomendamos escolher algumas opções diferentes para cada componente; avaliá-los em um pequeno conjunto de dados selecionado manualmente de queries de usuários antecipadas para seu aplicativo; e escolher aquele que oferece o melhor desempenho em termos de precisão de recuperação e recuperação nesse conjunto de dados de avaliação. Como alternativa, você pode começar criando um conjunto de dados sintético gerado pelo LLM com base em sua melhor estimativa de como os usuários usarão seu aplicativo.
Neste tutorial, avaliaremos diferentes combinações de tamanhos de chunks, sobreposições e técnicas de divisão disponíveis no LangChain. Escolhemos o LangChain aqui por uma questão de continuação da parte 2 desta série de tutoriais. A maioria dessas técnicas também é suportada no LlamaIndex, que é outra estrutura popular para criar aplicativos RAG.
O LangChain e o LlamaIndex suportam várias técnicas de divisão de texto, mas os divisores de texto podem não funcionar bem se você estiver trabalhando com documentos contendo tabelas, imagens etc. Unstructured é uma biblioteca que suporta fragmentação de documentos contendo dados semiestruturados , multimodais , puramente tabulares, etc.

Antes de começarmos

Dados

Para este tutorial, vamos presumir que estamos criando um aplicativo de bate-papo para novos desenvolvedores Python. Usaremos o Python Enhancement Proposals (PEP) para construir a base de conhecimento para o sistema RAG. OsPEPs são documentos que fornecem informações sobre as convenções de codificação e as melhores práticas de codificação do Python para a comunidade do Python.

Ferramentas

Usaremos o LangChain para divisão de texto e para criar componentes de um aplicativo RAG conforme necessário. Usaremos a estruturaRagas que introduzimos na Parte 2 desta série de tutoriais para avaliar a melhor estratégia de agrupamento para nossos dados.

Onde está o código?

O Jupyter Notebook para este tutorial pode ser encontrado no GitHub.

Estratégias de chunking

Analisando os documentos PEP, notamos que eles contêm principalmente texto e alguns trechos de código Python. Com isso em mente, testaremos as seguintes estratégias de chunking:

Token fixo sem sobreposição

Nesta técnica, dividimos os documentos em blocos com um número fixo de tokens, sem sobreposição de tokens entre blocos. Isso pode funcionar bem se houver limites contextuais rígidos entre chunks — ou seja, o contexto varia drasticamente entre chunks adjacentes. Na verdade, esse raramente é o caso, mas manteremos isso como linha de base.
Uma representação visual desta estratégia é a seguinte:
Segmentação de token fixa sem sobreposição

Token fixo com sobreposição

Nessa técnica, dividimos os documentos em partes com um número fixo de tokens, com alguma sobreposição de tokens entre as partes. A sobreposição de chunks garante que as informações contextuais nos limites dos chunks não sejam perdidas durante o chunking, melhorando assim as chances de as informações corretas serem recuperadas durante a pesquisa semântica, mesmo que se estendam por vários chunks.
Uma representação visual desta estratégia é a seguinte:
Corrigido fragmentação de token com sobreposição

Recursivo com sobreposição

Nessa técnica, primeiro dividimos os documentos por uma lista parametrizada de caracteres como \n\n, \netc. e, em seguida, mesclamos recursivamente os caracteres em tokens usando um tokenizador desde o tamanho do bloco (em termos do número de tokens) é menor que o tamanho de chunk especificado. Isso tem o efeito de tentar manter todos os número s (e depois frases e depois palavras) juntos o maior tempo possível, pois esses parecem genericamente ser os pedaços de texto semanticamente mais fortes relacionados.
Uma representação visual desta estratégia é a seguinte:
Chunking recursivo com sobreposição

Divisor de Python recursivo com sobreposição

Como os PEPs contêm alguns trechos de código Python, também incluiremos uma técnica de chunking específica do Python em nossa avaliação. Essa técnica é a mesma que recursiva com sobreposição, exceto que a lista de caracteres para divisão também inclui separadores específicos do Python, como \nclass, \ndef, etc.

Semântico

Nesta técnica, os documentos são divididos com base na similaridade semântica. Os documentos são primeiro divididos em grupos de frases de três frases usando uma janela deslizante. As incorporações são geradas para cada grupo de frases, e grupos semelhantes no espaço de incorporações são mesclados para formar blocos. O limite de similaridade para fusão é determinado usando métricas como percentil, desvio padrão e distância interquartil. Como resultado, o tamanho do pedaço pode variar entre os pedaços.
Embora esse método seja mais dispendioso em termos de computação do que os anteriores, ele pode ser útil para a fragmentação de documentos em que os limites contextuais não são óbvios - por exemplo, redações que podem não ter títulos para indicar quebras contextuais lógicas.
Uma representação visual desta estratégia é a seguinte:
Fragmentação semântica

Etapa 1: instalar as bibliotecas necessárias

Vamos precisar das seguintes bibliotecas para este tutorial:
  • langchain: biblioteca Python para desenvolver aplicativos LLM usando LangChain
  • langchain-openai: pacote Python para usar modelos OpenAI no LangChain
  • langchain-mongodb: pacote Python para usar o MongoDB Atlas como armazenamento de vetores com LangChain
  • langchain-experimental: pacote Python para os recursos experimentais do LangChain, como chunking semântica
  • ragas: Biblioteca Python para a estrutura Ragas
  • pymongo: driver Python para interagir com o MongoDB
  • tqdm: módulo Python para mostrar um medidor de progresso para loops

Etapa 2: configurar pré-requisitos

Neste tutorial, usaremos o MongoDB Atlas para criar a base de conhecimento (armazenamento de vetores) para nosso aplicativo RAG . Mas, primeiro, você precisará de uma conta MongoDB Atlas com um cluster de banco de dados. Você também precisará obter a string de conexão para se conectar ao cluster. Siga estas etapas para configurar:
Não se lembre de adicionar o IP da sua máquina host à lista de acesso IP do seu cluster.
Usaremos os modelos de incorporação e conclusão de chat do OpenAI, então você também precisará obter uma chave de API do OpenAI e defini-la como uma variável de ambiente para o cliente do OpenAI usar:

Etapa 3: carregar o conjunto de dados

Conforme mencionado anteriormente, usaremos PEPs para construir a base de conhecimento para nosso aplicativo RAG. Usaremos o carregador de documentosWebBaseLoader da LangChain, que carrega todo o texto de páginas da web em HTML em um formato de documento mais legível, removendo tags HTML etc.
O métodoload dos carregadores de documentos no LangChain cria uma lista de objetos de documento LangChain (vamos nos referir a eles como "documents " neste tutorial), cada um consistindo em dois atributos - a saber, page_content e metadata. page_content, como o nome sugere, corresponde ao conteúdo do documento, e metadata são alguns metadados básicos extraídos pelo carregador de documentos, como origem, título, descrição, idioma etc.
Segue um exemplo de objeto de documento:
Se você estiver utilizando LlamaIndex, utilize a classeSimpleWebPageReader para carregar texto de páginas da web em HTML.

Etapa 4: defina as funções de fragmentação

Em seguida, vamos definir funções para dar suporte a cada uma das estratégias de chunking que queremos experimentar.
No código acima, a funçãofixed_token_split utiliza a classeTokenTextSplitter em LangChain para dividir documentos em partes, cada um consistindo em um número fixo de tokens. A classeTokenTextSplitter usa os seguintes argumentos:
  • chunk_size: Número de tokens em cada pedaço
  • chunk_overlap: Número de tokens sobrepostos entre partes adjacentes
  • encoding_name: o modelo a ser usado para gerar tokens
Usaremos esta função para criar chunks com e sem sobreposição. Para criar chunks sem sobreposição, definiremos o argumento chunk_overlapcomo0 ao chamar a função. Para criar chunks com sobreposição, vamos defini-lo para um valor diferente de zero.
Se você estiver usando o LlamaIndex, use a classeTokenTextSplitter para agrupamento de token fixo.
Em seguida, vamos criar uma função para agrupamento recursivo:
No código acima, a funçãorecursive_split usa o método tiktoken_encoderda classeRecursiveCharacterTextSplitter em LangChain para primeiro dividir documentos por uma lista de caracteres e, em seguida, mesclar recursivamente as divisões em tokens usando o tokenizadortiktoken .
Além de chunk_size e chunk_overlap, a função usa um parâmetrolanguageadicional para suportar a divisão com base em separadores específicos de linguagem para linguagens de programação suportadas no enumLanguage em LangChain. Se nenhum idioma ou um idioma não suportado for especificado, a lista padrão de separadores - ou seja, ["\n\n", "\n", " ", ""] - será usada para a divisão. Caso contrário, uma lista específica do idioma será usada. Esta lista é obtida utilizando o método get_separators_for_languageda classeRecursiveCharacterTextSplitter. Para Python, definiremos o parâmetro de idioma como Language.PYTHON na chamada de função mais tarde.
Se você estiver usando o LlamaIndex, use a classeSentenceSplitter para agrupamento recursivo e divisão com base em separadores específicos da linguagem de programação.
Finalmente, vamos definir uma função para a divisão semântica:
No código acima, definimos a funçãosemantic_split que utiliza a classeSemanticChunker no LangChain para dividir documentos com base na similaridade semântica. Usamos um modelo de incorporação OpenAI (text-embedding-ada-002 por padrão) para incorporar grupos de frases e definimos o parâmetro breakpoint_threshold_typecomopercentile. Nesse método, as distâncias de incorporação entre grupos de frases adjacentes são calculadas e as divisões são criadas em pontos em que a distância de incorporação é maior que o 95percentil de distâncias. Outras opções para breakpoint_threshold_type incluem standard_deviation e interquartile.
Se você estiver usando o LlamaIndex, use a classeSemanticSplitterNodeParser para chunking semântica.

Etapa 5: gere o conjunto de dados de avaliação

Conforme mencionado anteriormente, você pode usar um conjunto de dados com curadoria manual de consultas antecipadas de usuários para avaliação ou começar usando um conjunto de dados gerado pelo LLM. Vamos explorar a última opção usando os recursos de geração de dados sintéticos da bibliotecaragas.
O código acima:
  • Cria uma configuração de tempo de execução para que o Ragas substitua as configurações padrão de simultaneidade e de repetição — isso é feito para evitar atingir os limites de taxa da OpenAI , mas isso pode não ser um problema dependendo do seu nível de uso ou se você não estiver usando modelos da OpenAI para teste geração de definir.
  • Especifica o generator_llm — ou seja, o modelo a ser usado para gerar as perguntas, as respostas e as informações básicas para avaliação.
  • Especifica o critic_llm que é o modelo a ser usado para validar os dados gerados pelo generator_llm.
  • Especifica embeddings — ou seja, o modelo de incorporação para construir o armazenamento de vetor para o gerador de conjunto de teste.
  • Cria um gerador de conjunto de testes utilizando os modelos especificados. Usamos o métodofrom_langchainda TestsetGenerator, pois estamos usando modelos OpenAI por meio do LangChain.
  • Especifica a distribuição dos tipos de perguntas a serem incluídas no conjunto de testes: simple corresponde a perguntas diretas que podem ser facilmente respondidas usando os dados de origem. multi_context significa perguntas que exigiriam informações de várias seções ou partes relacionadas para formularuma resposta. As perguntas reasoningsão aquelas que exigiriam que um LLM raciocinasse para responder efetivamente à pergunta. Você desejará definir essa distribuição com base em sua melhor estimativa do tipo de perguntas que você esperaria de seus usuários.
  • Gera um conjunto de teste a partir do conjunto de dados (pages) que criamos na etapa 3. Usamos o métodogenerate_with_langchain_docs, pois nosso conjunto de dados é uma lista de documentos LangChain.
O Ragas também suporta a geração de conjuntos de teste usando o LlamaIndex. Consulte a documentaçãodeles para obter mais informações.

Etapa 6: Avalie estratégias de chunking

Agora que definimos nossas funções de agrupamento e criamos nosso conjunto de dados de avaliação, estamos prontos para avaliar as diferentes estratégias de agrupamento para encontrar aquela que nos oferece a melhor qualidade de recuperação em nosso conjunto de dados de avaliação.
Vamos começar criando uma collection MongoDB com um índice de armazenamento de vetor para nossa avaliação. Para fazer isso, navegue até a 2 Collectionsdo cluster que você criou na etapa . Clique em Create Database para criar um banco de dados chamado evals com uma coleção chamada chunking.
Criando um banco de dados na IU do MongoDB Atlas
Depois de fazer isso, crie um índice de vetor chamado vector_index na collectionchunking com a seguinte definição de índice:
O número de dimensões de incorporação na definição de índice é 1536 , pois usaremos o modelo text-embedding-3-small do OpenAI para gerar incorporações para nosso armazenamento de vetores.
Agora, vamos definir uma função para criar um armazenamento de vetores do MongoDB Atlas usando a coleção que criamos:
O código acima:
  • Cria um PyMongo (client) para se conectar ao nosso MongoDB Atlas cluster.
  • Especifica o banco de dados (DB_NAME) e a coleção (MONGODB_COLLECTION) aos quais se conectar.
  • Especifica o índice de pesquisa vetorial (ATLAS_VECTOR_SEARCH_INDEX_NAME``) a ser usado para pesquisa vetorial.
  • Define uma create_vector_store função que recebe uma lista de documentos e retorna um objeto de armazenamento de vetor do MongoDB Atlas. Ele usa o from_documents método da MongoDBAtlasVectorSearch classe da langchain-mongodb integração para criar um armazenamento de vetor do MongoDB Atlas diretamente usando documentos do LangChain.
Em seguida, vamos definir uma função para avaliar diferentes estratégias de chunking no conjunto de dados de teste que criamos na etapa 5:
O código acima define uma funçãoperform_evalque recebe como entrada uma lista de documentos fragmentados (docs) produzidos usando uma determinada estratégia de fragmentação e retorna um dicionário que consiste em métricas de qualidade de recuperação para essa estratégia, em nosso conjunto de dados de avaliação. A seguir, apresentamos um detalhamento do que a função faz:
  • Cria um dicionário (eval_data) com question, ground_truthe contexts como chaves, correspondentes às perguntas no conjunto de dados de avaliação, suas respostas de verdade fundamental e contextos recuperados
  • Exclui quaisquer documentos existentes da coleção do MongoDB que estamos usando para teste
  • Cria um armazenamento de vetores do MongoDB Atlas usando a lista atual de documentos em partes
  • Usa o métodosimilarity_search para recuperar os três blocos mais relevantes do armazenamento de vetores para cada pergunta no conjunto de dados da avaliação e os adiciona à lista contexts no dicionárioeval_data
  • Converte o dicionárioeval_data em um objeto do conjunto de dados
  • Usa o evaluate método da bibliotecaragas para calcular as context_precision context_recall métricas e para o conjunto de dados de avaliação
A precisão do contexto avalia a capacidade do retriever de classificar os itens recuperados em ordem de relevância para a resposta de verdade fundamental. A recordação de contexto mede até que ponto o contexto recuperado se alinha com a resposta de verdade básica.
Por fim, vamos executar a avaliação das cinco estratégias de chunking que listamos previamente para nosso conjunto de dados PEP:
Algumas coisas a observar sobre o código acima:
  • Estamos avaliando diferentes tamanhos de chunk para encontrar o tamanho ideal de chunk para cada estratégia de chunking.
  • Para estratégias de chunking com sobreposição de token, definimos a sobreposição para 15% do tamanho do chunk. Embora tenhamos mantido a porcentagem de sobreposição constante aqui, você pode experimentar valores diferentes, se necessário. Uma sobreposição de bloco entre 5% e 20% do tamanho do bloco é recomendada para a maioria dos conjuntos de dados.
  • Não existe um conceito de tamanho de bloco para o chunking semântico, pois ele usa um limite de distância de incorporação para determinar os limites do bloco.
Os resultados da avaliação para as diferentes estratégias de chunking têm a seguinte aparência em nosso conjunto de dados de avaliação:
Estratégia de chunkingTamanho do blocoPrecisão do contextoLembrete de contexto
Token fixo sem sobreposição1000.85830.7833
2000.90.9
5000.88330.95
10000.90.8909
Token fixo com sobreposição1000.90.95
2001.00.9383
5000.70.9
10000.78330.8909
Recursivo com sobreposição1000.90.9833
2000.90.9008
5000.56670.8236
10000.78330.88
Divisor de Python recursivo com sobreposição1000.98330.9833
2001.00.8583
5000.60.88
10000.80.8709
Fragmentação semânticaN/A0.90.8187
Com base nos resultados acima, uma técnica de divisão recursiva específica do Python com uma sobreposição de tokens 15 e um tamanho de chunk de 100 é a melhor estratégia de chunking para nosso conjunto de dados.
Lembre-se de que usamos um conjunto de dados de avaliação muito pequeno de amostras 10 para demonstração, portanto, os resultados acima não podem ser tratados como totalmente conclusivos. Na realidade, você desejará usar um conjunto de dados de avaliação maior e mais representativo.

Conclusão

Neste tutorial, aprendemos como escolher a estratégia certa de chunking para RAG. A divisão de documentos grandes em partes menores para o RAG resulta em menos tokens passados como entrada para os LLMs e um contexto mais direcionado para os LLMs trabalharem. As estratégias de chunking são compostas por três componentes principais – técnica de divisão, tamanho do chunk e sobreposição de chunks. Escolher a estratégia certa envolve experimentar diferentes combinações dos três componentes.
Agora que você tem um bom entendimento do chunking para RAG, aceite o desafio de avaliar diferentes estratégias de chunking em conjuntos de dados que foram usados em alguns de nossos tutoriais anteriores:
Se você tiver mais dúvidas sobre RAG, Vector Search ou AI em geral, entre em contato conosco em nossos fóruns da comunidade de AI generativa e fique atento ao último tutorial da série RAG. Os tutoriais anteriores da série podem ser encontrados abaixo:
Principais comentários nos fóruns
Avatar do Comentarista do Fórum
Sheersh_SaxenaSheersh Saxena3 weeks ago

no final do código, estou encontrando um erro de handshake SSL.

Veja mais nos fóruns

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Início rápido

Usar o Atlas Data API no Excel com Power Query


Aug 26, 2022 | 9 min read
Tutorial

Getting Started With MongoDB and C


Sep 17, 2024 | 12 min read
exemplo de código

EHRS-Peru


Sep 11, 2024 | 3 min read
Tutorial

Como implementar fluxos de trabalho do Databricks e o Atlas Vector Search para melhorar a precisão da pesquisa de comércio eletrônico


Sep 18, 2024 | 6 min read
Sumário