DEV Community

Wesley de Morais
Wesley de Morais

Posted on

RAG na prática: transformando PDFs em respostas inteligentes com LLMs

Sumário

  • Entendendo o que são Embeddings
  • Entendendo o que são Vector Store
  • Fluxo de uma aplicação normal com RAG
  • Fluxo da nossa aplicação usando RAG
  • Criando nossa aplicação

Entendendo o que são Embeddings

Modelos de inteligência artificial não compreendem diretamente a linguagem natural como os humanos. Em vez disso, eles trabalham melhor com representações matemáticas. É aí que entram os embeddings — vetores gerados por modelos de embedding que capturam a essência de um texto, imagem ou áudio.

Esses vetores tornam possível comparar conteúdos de forma rápida e precisa, permitindo que sistemas de IA reconheçam similaridades mesmo quando a linguagem usada não é exatamente a mesma.

Entendendo o que são Vector Store

Os vector stores são bancos de dados especializados em armazenar esses vetores (embedding vectors).Quando um novo vetor é consultado, o vector store retorna os vetores armazenados mais semelhantes a ele, com base em medidas matemáticas de similaridade.

Esses vetores recuperados podem então ser usados como contexto para um modelo de linguagem (LLM), que gera uma resposta em linguagem natural com base nas informações mais relevantes.

Fluxo de uma aplicação normal com RAG(Retrieval-augmented generation)

Aqui temos um fluxo de uma aplicação básica usando RAG

  1. Um Vector Store é preenchido previamente com dados que foram convertidos em embedding vectors e armazenados para busca eficiente.
  2. Quando um usuário faz uma pergunta, essa pergunta é convertida em um embedding vector correspondente.
  3. Esse vetor da pergunta é enviado ao Vector Store, que utiliza algoritmos de similaridade para recuperar os embedding vectors mais similares.
  4. Os vetores recuperados são mapeados de volta para seus respectivos Documents (textos originais).
  5. Esses Documents relevantes são inseridos em um prompt estruturado, junto com a pergunta do usuário.
  6. O prompt é enviado como contexto para uma LLM (Large Language Model), que utiliza as informações para gerar uma resposta.
  7. A LLM gera o texto (recurso) como resposta ao usuário, combinando a informação do contexto com sua capacidade de linguagem natural.

flow_rag_normal

Fluxo da nossa aplicação usando RAG

No nosso RAG, vamos seguir uma abordagem parecida, mas com uma diferença: em vez de a aplicação apenas ficar esperando perguntas baseadas em informações previamente carregadas, vamos permitir que o usuário envie um arquivo junto com a pergunta

Dessa forma, a aplicação poderá entender o contexto diretamente a partir do conteúdo enviado, respondendo com base naquele documento.

flow_our_app

O arquivo em pdf vai ser dividido em partes usando a classe RecursiveCharacterTextSplitter e para cada parte vai ser criada um embedding vector relacionado.

Criando nossa aplicação

Antes de começar nossa aplicação é necessário que crie um arquivo .env contendo essas informações

OPENAI_API_KEY=<your_openai_api_key>
OPENAI_ORGANIZATION_KEY=<your_openai_api_key>
Enter fullscreen mode Exit fullscreen mode

Você pode pegar essas informações acessando o link https://platform.openai.com/docs/overview

Dependências

Com o ambiente virtual criado instale as seguintes dependências

langchain==0.3.25
gradio==5.33.2
langchain-openai==0.3.22
langchain-community==0.3.25
pypdf==5.6.0
python-dotenv==1.1.0
Enter fullscreen mode Exit fullscreen mode

Nossa aplicação vai seguir 2 etapas:

  1. Criação da interface demo usando a biblioteca gradio
  2. Criação da caso de uso para executar nossa aplicação

Criação da interface demo

Em um arquivo chamado app.py podemos adicionar seguinte código:

import gradio as gr

def handle_file(text_input, file):
    return text_input


with gr.Blocks() as demo:
    gr.Markdown("## Verificador de PDFs com RAG")
    text_input = gr.Textbox(label="Entre com sua questão aqui", placeholder="Faça uma pergunta sobre o PDF carregado")
    upload = gr.File(type="filepath")
    output = gr.Textbox(label="Output")
    button = gr.Button("Submit")

    button.click(
        fn=handle_file,
        inputs=[text_input, upload],
        outputs=output
    )
demo.launch(share=True)
Enter fullscreen mode Exit fullscreen mode

O código acima é responsável por construir uma interface onde teremos :

  • Um campo para o usuário adicionar a sua questão
  • Um campo para adicionar o arquivo
  • Um campo que será adicionado a resposta da llm
  • Um botão de submissão, sendo responsável por quando for feito o evento de clique, então ser chamado a função handle_file.

No final ele expõe essa interface para ser acessado externamente.

Criação da caso de uso para executar nossa aplicação

No mesmo arquivo podemos criar um caso de uso onde inicialmente ele vai pegar o arquivo enviado e carregar na variável self.docs

class HandleLLMUseCase:

    def handle_pdf_load(self):
        """
        Carrega o PDF enviado e extrai os documentos.
        Se nenhum arquivo for enviado, levanta um erro.
        """
        if self.file is None:
            raise ValueError("Nenhum arquivo enviado.")

        loader = PyPDFLoader(self.file)
        self.docs = loader.load()
Enter fullscreen mode Exit fullscreen mode

Com o arquivo carregado, podemos dividir seu conteúdo em pequenos trechos (chunks), o que facilita a criação dos embedding vectors posteriormente.

def handle_text_split(self):
        """
        Divide os documentos carregados em chunks menores de 
        500 caracteres com sobreposição de 50 caracteres.
        Se nenhum documento for carregado, levanta um erro.
        """
        if not hasattr(self, 'docs'):
            raise ValueError("Nenhum documento carregado.")

        text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
        self.chunks = text_splitter.split_documents(self.docs)
Enter fullscreen mode Exit fullscreen mode

Com os chunks de texto já criados, podemos convertê-los em embedding vectors utilizando o modelo OpenAIEmbeddings. Esses vetores serão então armazenados em um Vector Store, que permite realizar buscas por similaridade de forma eficiente. Para fins de simplicidade e praticidade, vamos utilizar o InMemoryVectorStore, uma opção gratuita e fácil de usar.

No entanto, o Langchain oferece suporte a diversos outros vector stores, como FAISS, Chroma, Weaviate, Pinecone e outros — você pode conferir a lista completa na Documentação oficial.

def handle_embeddings(self):
        """
        Cria embeddings dos chunks de texto usando OpenAIEmbeddings .
        Se nenhum chunk de texto estiver disponível, levanta um erro.
        """
        if not hasattr(self, 'chunks'):
            raise ValueError("Nenhum chunk de texto disponível.")

        embeddings = OpenAIEmbeddings(openai_api_key=os.environ.get("OPENAI_API_KEY"))
        vectorstore = InMemoryVectorStore.from_documents(self.chunks, embeddings)
        self.retriever = vectorstore.as_retriever()
Enter fullscreen mode Exit fullscreen mode

Com o Vector Store já criado, podemos agora construir uma cadeia de RAG que combina os documentos recuperados do Vector Store com uma LLM. Essa cadeia será responsável por buscar os trechos mais relevantes no conteúdo armazenado e usá-los como contexto para a LLM gerar uma resposta em linguagem natural.

def handle_chain_creation(self):
        """
        Cria uma cadeia de perguntas e respostas (RetrievalQA) usando o modelo LLM.
        """
        llm = ChatOpenAI(
            api_key=os.environ.get("OPENAI_API_KEY"),
            organization=os.environ.get("OPENAI_ORGANIZATION_KEY"),
            model="gpt-4o-mini",
        )

        self.qa_chain = RetrievalQA.from_chain_type(
            llm=llm,
            retriever=self.retriever
        )
Enter fullscreen mode Exit fullscreen mode
def handle_query(self):
        """
        Executa a consulta na cadeia de perguntas e respostas e retorna o resultado.
        """
        response = self.qa_chain.invoke({"query": self.text_input})
        return response["result"]
Enter fullscreen mode Exit fullscreen mode

No final, o código fica estruturado de forma que é possível fazer perguntas e receber respostas contextualizadas com base no conteúdo do arquivo fornecido.

import gradio as gr
import os
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.vectorstores import InMemoryVectorStore
from langchain.embeddings import OpenAIEmbeddings
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv

load_dotenv()  

class HandleLLMUseCase:

    def __init__(self, text_input, file):
        self.text_input = text_input
        self.file = file
        self.docs = None
        self.chunks = None
        self.retriever = None
        self.qa_chain = None

    def handle_pdf_load(self):
        """
        Carrega o PDF enviado e extrai os documentos.
        Se nenhum arquivo for enviado, levanta um erro.
        """
        if self.file is None:
            raise ValueError("Nenhum arquivo enviado.")

        loader = PyPDFLoader(self.file)
        self.docs = loader.load()

    def handle_text_split(self):
        """
        Divide os documentos carregados em chunks menores de 
        500 caracteres com sobreposição de 50 caracteres.
        Se nenhum documento for carregado, levanta um erro.
        """
        if not hasattr(self, 'docs'):
            raise ValueError("Nenhum documento carregado.")

        text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
        self.chunks = text_splitter.split_documents(self.docs)

    def handle_embeddings(self):
        """
        Cria embeddings dos chunks de texto usando OpenAIEmbeddings .
        Se nenhum chunk de texto estiver disponível, levanta um erro.
        """
        if not hasattr(self, 'chunks'):
            raise ValueError("Nenhum chunk de texto disponível.")

        embeddings = OpenAIEmbeddings(openai_api_key=os.environ.get("OPENAI_API_KEY"))
        vectorstore = InMemoryVectorStore.from_documents(self.chunks, embeddings)
        self.retriever = vectorstore.as_retriever()

    def handle_chain_creation(self):
        """
        Cria uma cadeia de perguntas e respostas (RetrievalQA) usando o modelo LLM.
        """
        llm = ChatOpenAI(
            api_key=os.environ.get("OPENAI_API_KEY"),
            organization=os.environ.get("OPENAI_ORGANIZATION_KEY"),
            model="gpt-4o-mini",
        )

        self.qa_chain = RetrievalQA.from_chain_type(
            llm=llm,
            retriever=self.retriever
        )

    def handle_query(self):
        """
        Executa a consulta na cadeia de perguntas e respostas e retorna o resultado.
        """
        response = self.qa_chain.invoke({"query": self.text_input})
        return response["result"]

    def execute(self):
        self.handle_pdf_load()
        self.handle_text_split()
        self.handle_embeddings()
        self.handle_chain_creation()
        response = self.handle_query()
        return response



def handle_file(text_input, file):
    instance = HandleLLMUseCase(text_input, file)
    return instance.execute()


with gr.Blocks() as demo:
    gr.Markdown("## Verificador de PDFs com RAG")
    text_input = gr.Textbox(label="Entre com sua questão aqui", placeholder="Faça uma pergunta sobre o PDF carregado")
    upload = gr.File(type="filepath")
    output = gr.Textbox(label="Output")
    button = gr.Button("Submit")

    button.click(
        fn=handle_file,
        inputs=[text_input, upload],
        outputs=output
    )
demo.launch(share=True)
Enter fullscreen mode Exit fullscreen mode

Referencias

https://python.langchain.com/docs/introduction/

https://datawaybr.medium.com/guia-definitivo-para-vector-databases-bbeeb8f0d802

https://www.gradio.app

Link do repositório

https://github.com/WesleyVitor/rag_pdf_openai_app

Top comments (2)

Collapse
 
walkingtree_technologies_ profile image
WalkingTree Technologies

Muito legal ver uma explicação tão detalhada e bem estruturada sobre o uso do RAG para trabalhar com PDFs e LLMs! É realmente interessante como você mapeou cada etapa do processo e explicou de forma clara o fluxo e a criação dos embeddings. Obrigado por compartilhar – conteúdos assim ajudam muito a comunidade! 🚀

Estamos também explorando muito o potencial do RAG em diferentes casos de uso. Se tiver interesse, estamos organizando um webinar sobre o tema – seria ótimo te ver lá!
🔗 Participe do webinar

Você também pode conhecer mais sobre nosso trabalho aqui:
🌐 walkingtree.tech

Collapse
 
jackson541 profile image
Jackson Alves

muito legal