DEV Community

Cover image for Do PDF ao Discord com RAG: Como construí um agente RAG para eliminar interrupções operacionais na empresa
Cleber Lucas
Cleber Lucas

Posted on

Do PDF ao Discord com RAG: Como construí um agente RAG para eliminar interrupções operacionais na empresa

Como construí um agente RAG para eliminar interrupções operacionais na empresa

Projeto open source com Python, LangChain, ChromaDB, FastAPI e Discord — do problema real ao deploy em produção.


Toda empresa tem aquele ciclo silencioso que drena tempo sem que ninguém perceba.

Um funcionário tem uma dúvida sobre um procedimento. Não encontra a resposta nos documentos. Interrompe alguém mais experiente. Essa pessoa para o que estava fazendo, responde, e volta ao trabalho — já com o raciocínio quebrado. Multiplique isso por 10, 20, 50 vezes por semana.

Foi observando esse padrão que decidi construir o POPS AI: um agente de RAG (Retrieval-Augmented Generation) capaz de responder perguntas sobre os Procedimentos Operacionais Padrão de uma empresa, direto pelo Discord ou via API REST.


O problema que motivou o projeto

A empresa tinha dezenas de POPs documentados em PDF. O problema não era a falta de documentação — era o atrito para acessá-la. Ninguém abre uma pasta de rede, procura o arquivo certo e lê 15 páginas para responder uma dúvida pontual.

A pergunta que me fiz foi simples: e se a documentação pudesse responder sozinha?


A arquitetura em três etapas

O sistema funciona em três fases distintas, cada uma com responsabilidade clara.

1. Extração

O script extrair_texto.py lê os PDFs da pasta pops_originais/, extrai o texto completo com PyMuPDF e salva em .txt. Imagens das páginas também são extraídas para uso futuro.

import fitz  # PyMuPDF

def extrair_texto_pdf(caminho_pdf):
    doc = fitz.open(caminho_pdf)
    texto_completo = ""
    for pagina in doc:
        texto_completo += pagina.get_text()
    return texto_completo
Enter fullscreen mode Exit fullscreen mode

Simples, mas importante: a qualidade da extração determina a qualidade das respostas. PDFs escaneados sem OCR são o inimigo número um aqui.

2. Geração de embeddings

Com os textos extraídos, o gerar_embeddings.py divide o conteúdo em chunks usando o RecursiveCharacterTextSplitter da LangChain, gera os vetores e persiste no ChromaDB.

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200
)
chunks = splitter.split_text(texto)
Enter fullscreen mode Exit fullscreen mode

O chunk_overlap=200 foi uma decisão deliberada: ele garante que o contexto não seja cortado abruptamente entre um chunk e o próximo, o que melhorou visivelmente a coerência das respostas.

O projeto suporta dois modelos de embedding via config.py:

  • Gemini models/embedding-001 — qualidade alta, requer API key e gera custo por volume
  • SBERT local (paraphrase-multilingual-mpnet-base-v2) — roda offline, ótimo para evitar custos ou limites de requisição

Essa flexibilidade foi uma das decisões de design que mais agregou valor, especialmente para quem quer experimentar o projeto sem gastar nada.

3. Consulta (RAG)

Quando o usuário faz uma pergunta, o sistema:

  1. Converte a pergunta em vetor usando o mesmo modelo de embedding
  2. Busca os chunks mais semanticamente similares no ChromaDB
  3. Monta um prompt com os trechos recuperados como contexto
  4. Envia para o Gemini 2.0 Flash gerar a resposta final
resultados = collection.query(
    query_embeddings=[embedding_pergunta],
    n_results=5
)

contexto = "\n\n".join(resultados['documents'][0])

prompt = f"""Você é um assistente especializado nos POPs da empresa.
Use apenas as informações abaixo para responder.

Contexto:
{contexto}

Pergunta: {pergunta}
"""
Enter fullscreen mode Exit fullscreen mode

As interfaces: Discord e API

O projeto expõe a base de conhecimento de duas formas.

Bot do Discord com slash commands:

  • /pop <pergunta> — consulta a base vetorial e retorna a resposta
  • /addpop <arquivo.txt> — permite que administradores adicionem novos POPs em tempo real, sem precisar reprocessar toda a base

API FastAPI com endpoint POST /ask, pensada para integrar com outros sistemas internos:

// Request
{ "question": "Como configurar o scanner da impressora Samsung?" }

// Response
{
  "answer": "Para configurar o scanner, siga os passos:\n1. Ligue a impressora...\n[Fonte: POP-ConfiguraçãoScanner.txt]"
}
Enter fullscreen mode Exit fullscreen mode

O desafio que ninguém menciona: custo de tokens

Construir o RAG foi a parte divertida. O desafio real veio depois: como controlar o custo em produção?

Algumas decisões que fizeram diferença:

Usar SBERT para embeddings em vez da API do Gemini reduz o custo de indexação para zero — o modelo roda localmente. O custo só existe na geração de resposta, que é onde o valor real está.

Limitar n_results=5 na busca vetorial evita passar contexto desnecessário para o modelo. Mais contexto = mais tokens = mais custo, sem necessariamente melhorar a resposta.

Gemini 2.0 Flash foi escolhido intencionalmente sobre o Pro: para perguntas objetivas sobre procedimentos, a diferença de qualidade é mínima e a diferença de custo é expressiva.


Deploy: um container, dois processos

Uma decisão que me custou algumas horas foi rodar o bot do Discord e a API FastAPI no mesmo container Docker. A solução foi o Supervisor, que gerencia ambos os processos de forma leve e auto-recuperável.

# supervisord.conf
[program:api]
command=uvicorn api_bot:app --host 0.0.0.0 --port 8000

[program:discord]
command=python bot_discord.py

autostart=true
autorestart=true
Enter fullscreen mode Exit fullscreen mode

O resultado é um container único, leve, que sobe os dois serviços em paralelo e reinicia automaticamente qualquer um que falhe. Para uma VPS de entrada, isso faz toda a diferença.


O que aprendi que não estava no plano

Chunking é uma arte. O tamanho e o overlap dos chunks afetam mais a qualidade das respostas do que o modelo em si. Passei mais tempo ajustando isso do que qualquer outra coisa.

Segurança desde o início. O .gitignore precisou ser configurado antes do primeiro commit público para garantir que nenhum PDF com dados confidenciais da empresa fosse parar no repositório. Um erro aqui é difícil de reverter.

O problema real não era técnico. A parte mais complexa foi entender que tipo de pergunta os usuários fariam e como estruturar os POPs para que o modelo conseguisse recuperar as informações certas. Garbage in, garbage out vale dobrado em RAG.


O projeto é open source

O POPS AI está disponível no GitHub com README completo, .env.example, Docker Compose configurado e passo a passo de instalação tanto local quanto via container.

Você pode clonar, adaptar para sua própria base de conhecimento e usar com seus próprios documentos — seja para POPs, wikis internas, manuais de produto ou qualquer documentação em PDF.

🔗 github.com/obelucca/POPS_AI


Stack utilizada

Python 3.10 LangChain ChromaDB FastAPI Discord.py Google Gemini 2.0 Flash SBERT Docker Supervisor PyMuPDF


Se você chegou até aqui e tem curiosidade sobre alguma decisão de arquitetura, custo de tokens em produção ou como adaptar para um caso de uso diferente — deixa nos comentários. Bora trocar ideia.

Top comments (0)