Como escolher a estratégia de chunking certa para seu aplicativo LLM
Avalie esse Tutorial
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
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.
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.
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.
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.
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.
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:
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:
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:
Nessa técnica, primeiro dividimos os documentos por uma lista parametrizada de caracteres como
\n\n
, \n
etc. 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:
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.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:
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
1 ! pip install -qU langchain langchain-openai langchain-mongodb langchain-experimental ragas pymongo tqdm
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:
1 import getpass 2 MONGODB_URI = getpass.getpass("Enter your MongoDB connection string:")
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:
1 import os 2 from openai import OpenAI 3 os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API Key:") 4 openai_client = OpenAI()
Conforme mencionado anteriormente, usaremos PEPs para construir a base de conhecimento para nosso aplicativo RAG. Usaremos o carregador de documentos
WebBaseLoader
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.1 from langchain_community.document_loaders import WebBaseLoader 2 3 web_loader = WebBaseLoader( 4 [ 5 "https://peps.python.org/pep-0483/", 6 "https://peps.python.org/pep-0008/", 7 "https://peps.python.org/pep-0257/", 8 ] 9 ) 10 11 pages = web_loader.load()
O método
load
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:
1 Document( 2 page_content="\nThe goal of this PEP is to propose such a systematic way of defining types\nfor type annotations of variables and functions using PEP 3107 syntax.", 3 metadata= {'source': 'https://peps.python.org/pep-0483/', 'title': 'PEP 483 – The Theory of Type Hints | peps.python.org', 'description': 'Python Enhancement Proposals (PEPs)', 'language': 'en'} 4 )
Se você estiver utilizando LlamaIndex, utilize a classeSimpleWebPageReader para carregar texto de páginas da web em HTML.
Em seguida, vamos definir funções para dar suporte a cada uma das estratégias de chunking que queremos experimentar.
1 from langchain.text_splitter import TokenTextSplitter 2 3 def fixed_token_split( 4 docs: List[Document], chunk_size: int, chunk_overlap: int 5 ) -> List[Document]: 6 """ 7 Fixed token chunking 8 9 Args: 10 docs (List[Document]): List of documents to chunk 11 chunk_size (int): Chunk size (number of tokens) 12 chunk_overlap (int): Token overlap between chunks 13 14 Returns: 15 List[Document]: List of chunked documents 16 """ 17 splitter = TokenTextSplitter( 18 encoding_name="cl100k_base", chunk_size=chunk_size, chunk_overlap=chunk_overlap 19 ) 20 return splitter.split_documents(docs)
No código acima, a função
fixed_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_overlap
como0 ao chamar a função. Para criar chunks com sobreposição, vamos defini-lo para um valor diferente de zero.Em seguida, vamos criar uma função para agrupamento recursivo:
1 from langchain.text_splitter import ( 2 Language, 3 RecursiveCharacterTextSplitter, 4 ) 5 6 def recursive_split( 7 docs: List[Document], 8 chunk_size: int, 9 chunk_overlap: int, 10 language: Optional[Language] = None, 11 ) -> List[Document]: 12 """ 13 Recursive chunking 14 15 Args: 16 docs (List[Document]): List of documents to chunk 17 chunk_size (int): Chunk size (number of tokens) 18 chunk_overlap (int): Token overlap between chunks 19 language (Optional[Language], optional): Programming language enum. Defaults to None. 20 21 Returns: 22 List[Document]: List of chunked documents 23 """ 24 separators = ["\n\n", "\n", " ", ""] 25 26 if language is not None: 27 try: 28 separators = RecursiveCharacterTextSplitter.get_separators_for_language( 29 language 30 ) 31 except (NameError, ValueError) as e: 32 print(f"No separators found for language {language}. Using defaults.") 33 34 splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( 35 encoding_name="cl100k_base", 36 chunk_size=chunk_size, 37 chunk_overlap=chunk_overlap, 38 separators=separators, 39 ) 40 return splitter.split_documents(docs)
No código acima, a função
recursive_split
usa o método tiktoken_encoder
da 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âmetrolanguage
adicional 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_language
da 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:
1 from langchain_experimental.text_splitter import SemanticChunker 2 from langchain_openai.embeddings import OpenAIEmbeddings 3 4 def semantic_split(docs: List[Document]) -> List[Document]: 5 """ 6 Semantic chunking 7 8 Args: 9 docs (List[Document]): List of documents to chunk 10 11 Returns: 12 List[Document]: List of chunked documents 13 """ 14 splitter = SemanticChunker( 15 OpenAIEmbeddings(), breakpoint_threshold_type="percentile" 16 ) 17 return splitter.split_documents(docs)
No código acima, definimos a função
semantic_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_type
comopercentile
. 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.
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.
1 from ragas import RunConfig 2 from ragas.testset.generator import TestsetGenerator 3 from ragas.testset.evolutions import simple, reasoning, multi_context 4 from langchain_openai import ChatOpenAI, OpenAIEmbeddings 5 6 RUN_CONFIG = RunConfig(max_workers=4, max_wait=180) 7 8 # generator with openai models 9 generator_llm = ChatOpenAI(model="gpt-3.5-turbo-16k") 10 critic_llm = ChatOpenAI(model="gpt-4") 11 embeddings = OpenAIEmbeddings() 12 13 generator = TestsetGenerator.from_langchain(generator_llm, critic_llm, embeddings) 14 15 # Change resulting question type distribution 16 distributions = {simple: 0.5, multi_context: 0.4, reasoning: 0.1} 17 18 testset = generator.generate_with_langchain_docs( 19 pages, 10, distributions, run_config=RUN_CONFIG 20 )
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 pelogenerator_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étodo
from_langchain
daTestsetGenerator
, 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 perguntasreasoning
sã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.
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
.Depois de fazer isso, crie um índice de vetor chamado
vector_index
na collectionchunking
com a seguinte definição de índice:1 { 2 "fields": [ 3 { 4 "numDimensions": 1536, 5 "path": "embedding", 6 "similarity": "cosine", 7 "type": "vector" 8 } 9 ] 10 }
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:
1 from langchain_mongodb import MongoDBAtlasVectorSearch 2 from pymongo import MongoClient 3 4 client = MongoClient(MONGODB_URI) 5 DB_NAME = "evals" 6 COLLECTION_NAME = "chunking" 7 ATLAS_VECTOR_SEARCH_INDEX_NAME = "vector_index" 8 MONGODB_COLLECTION = client[DB_NAME][COLLECTION_NAME] 9 10 def create_vector_store(docs: List[Document]) -> MongoDBAtlasVectorSearch: 11 """ 12 Create MongoDB Atlas vector store 13 14 Args: 15 docs (List[Document]): List of documents to create the vector store 16 17 Returns: 18 MongoDBAtlasVectorSearch: MongoDB Atlas vector store 19 """ 20 vector_store = MongoDBAtlasVectorSearch.from_documents( 21 documents=docs, 22 embedding=OpenAIEmbeddings(model="text-embedding-3-small"), 23 collection=MONGODB_COLLECTION, 24 index_name=ATLAS_VECTOR_SEARCH_INDEX_NAME, 25 ) 26 27 return vector_store
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 ofrom_documents
método daMongoDBAtlasVectorSearch
classe dalangchain-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:
1 from tqdm import tqdm 2 from datasets import Dataset 3 from ragas import evaluate 4 from ragas.metrics import context_precision, context_recall 5 import nest_asyncio 6 7 # Allow nested use of asyncio (used by Ragas) 8 nest_asyncio.apply() 9 10 # Disable tqdm locks 11 tqdm.get_lock().locks = [] 12 13 QUESTIONS = testset.question.to_list() 14 GROUND_TRUTH = testset.ground_truth.to_list() 15 16 def perform_eval(docs: List[Document]) -> Dict[str, float]: 17 """ 18 Perform Ragas evaluation 19 20 Args: 21 docs (List[Document]): List of documents to create the vector store 22 23 Returns: 24 Dict[str, float]: Dictionary of evaluation metrics 25 """ 26 eval_data = { 27 "question": QUESTIONS, 28 "ground_truth": GROUND_TRUTH, 29 "contexts": [], 30 } 31 32 print(f"Deleting existing documents in the collection {DB_NAME}.{COLLECTION_NAME}") 33 MONGODB_COLLECTION.delete_many({}) 34 print(f"Deletion complete") 35 vector_store = create_vector_store(docs) 36 37 # Getting relevant documents for the evaluation dataset 38 print(f"Getting contexts for evaluation set") 39 for question in tqdm(QUESTIONS): 40 eval_data["contexts"].append( 41 [doc.page_content for doc in retriever.similarity_search(question, k=3)] 42 ) 43 # RAGAS expects a Dataset object 44 dataset = Dataset.from_dict(eval_data) 45 46 print(f"Running evals") 47 result = evaluate( 48 dataset=dataset, 49 metrics=[context_precision, context_recall], 50 run_config=RUN_CONFIG, 51 raise_exceptions=False, 52 ) 53 return result
O código acima define uma função
perform_eval
que 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
) comquestion
,ground_truth
econtexts
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étodo
similarity_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 à listacontexts
no dicionárioeval_data
- Converte o dicionário
eval_data
em um objeto do conjunto de dados - Usa o
evaluate
método da bibliotecaragas para calcular ascontext_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:
1 for chunk_size in [100, 200, 500, 1000]: 2 chunk_overlap = int(0.15 * chunk_size) 3 print(f"CHUNK SIZE: {chunk_size}") 4 print("------ Fixed token without overlap ------") 5 print(f"Result: {perform_eval(fixed_token_split(pages, chunk_size, 0))}") 6 print("------ Fixed token with overlap ------") 7 print( 8 f"Result: {perform_eval(fixed_token_split(pages, chunk_size, chunk_overlap))}" 9 ) 10 print("------ Recursive with overlap ------") 11 print(f"Result: {perform_eval(recursive_split(pages, chunk_size, chunk_overlap))}") 12 print("------ Recursive Python splitter with overlap ------") 13 print( 14 f"Result: {perform_eval(recursive_split(pages, chunk_size, chunk_overlap, Language.PYTHON))}" 15 ) 16 print("------ Semantic chunking ------") 17 print(f"Result: {perform_eval(semantic_split(pages))}")
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 chunking | Tamanho do bloco | Precisão do contexto | Lembrete de contexto |
---|---|---|---|
Token fixo sem sobreposição | 100 | 0.8583 | 0.7833 |
200 | 0.9 | 0.9 | |
500 | 0.8833 | 0.95 | |
1000 | 0.9 | 0.8909 | |
Token fixo com sobreposição | 100 | 0.9 | 0.95 |
200 | 1.0 | 0.9383 | |
500 | 0.7 | 0.9 | |
1000 | 0.7833 | 0.8909 | |
Recursivo com sobreposição | 100 | 0.9 | 0.9833 |
200 | 0.9 | 0.9008 | |
500 | 0.5667 | 0.8236 | |
1000 | 0.7833 | 0.88 | |
Divisor de Python recursivo com sobreposição | 100 | 0.9833 | 0.9833 |
200 | 1.0 | 0.8583 | |
500 | 0.6 | 0.88 | |
1000 | 0.8 | 0.8709 | |
Fragmentação semântica | N/A | 0.9 | 0.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.
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
Scheersh_SexenaScheersh Saxena3 meses atrás
no final do código, estou encontrando um erro de handshake SSL.