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
- Um Vector Store é preenchido previamente com dados que foram convertidos em embedding vectors e armazenados para busca eficiente.
- Quando um usuário faz uma pergunta, essa pergunta é convertida em um embedding vector correspondente.
- Esse vetor da pergunta é enviado ao Vector Store, que utiliza algoritmos de similaridade para recuperar os embedding vectors mais similares.
- Os vetores recuperados são mapeados de volta para seus respectivos Documents (textos originais).
- Esses Documents relevantes são inseridos em um prompt estruturado, junto com a pergunta do usuário.
- O prompt é enviado como contexto para uma LLM (Large Language Model), que utiliza as informações para gerar uma resposta.
- A LLM gera o texto (recurso) como resposta ao usuário, combinando a informação do contexto com sua capacidade de linguagem natural.
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.
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>
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
Nossa aplicação vai seguir 2 etapas:
- Criação da interface demo usando a biblioteca gradio
- 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)
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()
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)
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()
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
)
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"]
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)
Referencias
https://python.langchain.com/docs/introduction/
https://datawaybr.medium.com/guia-definitivo-para-vector-databases-bbeeb8f0d802
Top comments (2)
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
muito legal