Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

1
resposta

[Projeto] Introduzi modificações e fiz outros experimentos

A pergunta final sobre pergunta_pdf = "Qual foi a receita com laptops?" não veio muito satisfatório, eu sei que o melhor seria enriquecer um prompt para um LLM inferir melhor, porém resolvi experimentar alterações em parametros, tamnanho de chunk e overlap, e explorar como estava a busca de K e fazendo um rerank. Também tive de alterar algum libs que estão defasadas. O resultado legal ficou com o UnstructuredPDFLoader, o docling foi mais lento em chunks muito maiores.

from langchain_community.document_loaders import UnstructuredPDFLoader

loader = UnstructuredPDFLoader("relatorio_vendas.pdf", mode='elements') # mode define o nível de granularidade com que um doc é dividido ao ser carregado para a memória

docs_unstructured = loader.load()

print(f"Total de elementos extraídos: {len(docs_unstructured)}\n")

for doc in docs_unstructured:
  print(f"---- TIPO DE ELEMENTO: {doc.metadata.get('category')} ---")
  print(doc.page_content)
  print("\n")
print(set(doc.metadata.get("category") for doc in docs_unstructured)) # Mostrar quais categorias unicas existem no pdf
from langchain_core.documents import Document

# Lista para armazenar os documentos com metadados

docs_com_metados = []

for doc in docs_unstructured:

  novos_metadados = doc.metadata.copy()

  novos_metadados['source'] = 'relatorio_vendas.pdf'
  novos_metadados['ingestion_date'] = datetime.now().strftime('%Y-%m-%d')
  novos_metadados['data_owner'] = 'Departamento de Vendas'

  docs_com_metados.append(
      Document(page_content=doc.page_content, metadata=novos_metadados)
  )

print(f"Total de documentos com metadados: {len(docs_com_metados)}\n")
print(docs_com_metados[-1])
import json

ultimo_doc = docs_com_metados[-1]

print(f"Total de documentos com metadados: {len(docs_com_metados)}\n")

# Converte o objeto Document para Dicionário
doc_dict = {
    "page_content": ultimo_doc.page_content,
    "metadata": ultimo_doc.metadata
}

# Exibe formatado com identação
print(json.dumps(doc_dict, indent=2, ensure_ascii=False))
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size = 1200,
    chunk_overlap  = 200,
)

chunks = text_splitter.split_documents(docs_com_metados)

print(f"Numero de documentos original: {len(docs_com_metados)}")
print(f"Numero de chunks gerados: {len(chunks)}\n")

print(chunks[2])
# Faça a busca pedindo mais documentos (ex: k=5)
resultados = vector_store.similarity_search("Qual foi a receita com laptops?", k=10)

for i, doc in enumerate(resultados, 1):
    print(f"=== CHUNK {i} (Categoria: {doc.metadata.get('category')}) ===")
    # Exibe a string EXATA que o embedding leu
    print(repr(doc.page_content)) 
    print("\n" + "="*40 + "\n")

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

from flashrank import Ranker, RerankRequest

# 1. Carrega o modelo super leve do FlashRank (baixa rápido no Colab)
ranker = Ranker()

pergunta_pdf = "Qual foi a receita com laptops?"

# 2. Busca os TOP 10 no ChromaDB (a tabela estará lá na posição 9 como você viu!)
resultados_brutos = vector_store.similarity_search(pergunta_pdf, k=10)

# 3. Formata os chunks para o Flashrank processar
passagens = [
    {"id": idx, "text": doc.page_content, "meta": doc.metadata}
    for idx, doc in enumerate(resultados_brutos)
]

# 4. Rerank: O FlashRank avalia a pergunta x conteúdo e reorganiza a lista
requisicao = RerankRequest(query=pergunta_pdf, passages=passagens)
resultados_reordenados = ranker.rerank(requisicao)

# 5. Exibe os 3 primeiros após o Re-Ranking
print(f"Pergunta: '{pergunta_pdf}'\n")
print("=== RESULTADOS APÓS RE-RANKING ===\n")

for i, item in enumerate(resultados_reordenados[:3], 1):
    score = item.get('score', 0)
    texto = item.get('text')
    categoria = item.get('meta', {}).get('category', 'N/A')
    
    print(f"--- [POSIÇÃO {i}] (Score de Relevância: {score:.4f}) | Categoria: {categoria} ---")
    print(f"{texto}")
    print("-" * 60)

acredito que fazendo o rerank para este caso embora tenha mais tokens ajudaria o llm no prompt final ao agregar o contexto dar uma resposta mais assertiva. Também observo que existem outras formas de fazer a leitura do PDF com outras libs que convertem em markdown, ou como estamos usando gemini, fazer isso de um prompt com gemini vision, bom mas daí gera mais custos.
Insira aqui a descrição dessa imagem para ajudar na acessibilidade

1 resposta

Olá, Marcelo! Tudo bem?

Que bacana ver você testando novas alternativas e compartilhando os resultados do seu pipeline de dados robusto na comunidade!

Alterar parâmetros de chunk, explorar a busca de K e aplicar o rerank são estratégias muito válidas para refinar a recuperação de informações em documentos complexos como PDFs. Dica: como você mencionou a variação no tamanho dos blocos, uma boa prática é testar divisores baseados em estrutura semântica ou ajustar a sobreposição (chunk overlap) de forma proporcional para garantir que o contexto das tabelas não se perca durante a divisão.

Qual outra biblioteca de conversão de arquivos você tem vontade de testar nos próximos projetos para comparar o desempenho com o Unstructured?

Alura Conte com o apoio da comunidade Alura na sua jornada. Abraços e bons estudos!