
Aprende a implementar RAG para criar chatbots inteligentes que respondem com base nos teus próprios documentos.
Já te aconteceu perguntar algo ao ChatGPT e ele responder com informação desatualizada ou simplesmente inventada? Isso acontece porque o modelo só sabe o que "estudou" durante o treino - não tem acesso aos teus documentos, à tua empresa ou às novidades de ontem. O RAG (Retrieval Augmented Generation) resolve exatamente este problema: ensina a IA a "consultar os livros" antes de responder. É a técnica mais importante para quem quer usar IA no mundo real, e neste guia vamos construí-la passo a passo.
Analogia principal: Imagina que precisas de responder a perguntas sobre as regras de um condomínio. Tens duas opções:
Opção A (memorizar tudo): Sentas-te, lês todas as atas, regulamentos e comunicados, e memorizas tudo. Quando alguém te pergunta algo, respondes de memória. O problema? Se o regulamento mudar, precisas de "estudar" tudo de novo. E a tua memória pode falhar - podes misturar regras ou inventar coisas que não existem. Isto é o que o ChatGPT faz com fine-tuning: decora os dados, mas pode "esquecer" ou inventar.
Opção B (consultar antes de responder): Quando alguém te faz uma pergunta, vais ao arquivo, procuras os documentos relevantes e respondes com o documento à frente. É mais lento, mas nunca inventas porque tens a fonte à vista. Se o regulamento mudar, basta atualizar o arquivo. Isto é RAG: a IA não decora os teus dados - pesquisa-os em tempo real cada vez que recebe uma pergunta.
Na prática, RAG é a abordagem preferida em 90% dos casos empresariais em 2026. É mais barata, mais fácil de manter atualizada e muito mais fiável. Empresas como a Notion, a Slack e o GitHub usam RAG nos seus assistentes de IA internos.
O RAG funciona como um bibliotecário inteligente. Entras numa biblioteca e perguntas: "Quais são as melhores práticas para gestão de projetos ágeis?". O bibliotecário não responde de memória. Em vez disso: (1) vai às estantes, procura os livros mais relevantes; (2) abre-os nas páginas certas; (3) lê os excertos relevantes e formula uma resposta clara para ti. O RAG faz exatamente isto, mas com os teus documentos digitais.
Passo 1 - Indexação (Organizar a Biblioteca): Antes de poder pesquisar, precisas de organizar os documentos. Os textos são divididos em fragmentos pequenos (como separar um livro em fichas de estudo) e cada fragmento é convertido num "código numérico" chamado embedding - uma espécie de "coordenada GPS do significado". Textos sobre temas semelhantes ficam com coordenadas próximas.
Passo 2 - Retrieval (Encontrar os Livros Certos): Quando o utilizador faz uma pergunta, essa pergunta também é convertida num embedding. Depois, procuramos os fragmentos cujas "coordenadas" estão mais próximas da pergunta - ou seja, os mais relevantes. É como procurar no GPS os restaurantes mais próximos da tua localização.
Passo 3 - Generation (Formular a Resposta): Os fragmentos encontrados são incluídos no prompt junto com a pergunta do utilizador. A IA lê tudo e gera uma resposta que combina o seu conhecimento geral com a informação específica dos teus documentos. O resultado? Uma resposta fundamentada, precisa e com fontes.
Antes de cozinhar, precisas de montar a cozinha. Vamos instalar todas as ferramentas (bibliotecas Python) que vamos usar. Não te assustes com a lista - cada uma tem um papel específico e só precisas de correr estes comandos uma vez.
# Criar ambiente virtual
python -m venv rag-env
source rag-env/bin/activate # Linux/macOS
# Instalar dependências
pip install langchain langchain-community langchain-openai
pip install chromadb
pip install sentence-transformers
pip install pypdf
pip install tiktoken
# Para interface web
pip install streamlit
# Para LLMs locais via Ollama
pip install langchain-ollama
# Verificar
python -c "import langchain; import chromadb; print('Tudo instalado!')"
Imagina que recebeste 500 páginas de documentos e precisas de os organizar para consulta rápida. Não vais meter as 500 páginas numa única pasta - vais separá-las por temas, recortar os trechos importantes e etiquetar cada um. O chunking (divisão em fragmentos) faz exatamente isto com os teus documentos digitais.
A estratégia de chunking é um dos fatores mais críticos para a qualidade do RAG. Fragmentos demasiado pequenos são como notas post-it com meia frase - perdem o contexto. Fragmentos demasiado grandes são como capítulos inteiros - difíceis de pesquisar e gastam recursos. O ideal é um meio-termo: fragmentos de 500-1000 caracteres, com alguma sobreposição entre eles (para que o início de um fragmento repita o fim do anterior, mantendo continuidade).
from langchain_community.document_loaders import (
PyPDFLoader, TextLoader, WebBaseLoader, DirectoryLoader
)
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Carregar PDFs
pdf_loader = PyPDFLoader("documentos/manual_empresa.pdf")
pdf_docs = pdf_loader.load()
print(f"PDF: {len(pdf_docs)} páginas carregadas")
# Carregar ficheiros de texto
txt_loader = TextLoader("documentos/politicas.txt", encoding="utf-8")
txt_docs = txt_loader.load()
# Carregar páginas web
web_loader = WebBaseLoader([
"https://vibecodingacademy.pt/sobre",
"https://vibecodingacademy.pt/cursos",
])
web_docs = web_loader.load()
# Carregar diretório inteiro
dir_loader = DirectoryLoader(
"documentos/", glob="**/*.txt",
loader_cls=TextLoader,
loader_kwargs={"encoding": "utf-8"}
)
dir_docs = dir_loader.load()
# Combinar tudo
todos_docs = pdf_docs + txt_docs + web_docs + dir_docs
print(f"Total: {len(todos_docs)} documentos")
# Dividir em fragmentos
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=800, # Tamanho máximo
chunk_overlap=200, # Sobreposição para continuidade
separators=["\n\n", "\n", ". ", " ", ""],
)
fragmentos = text_splitter.split_documents(todos_docs)
print(f"Divididos em {len(fragmentos)} fragmentos")
for i, frag in enumerate(fragmentos[:3]):
print(f"\n--- Fragmento {i+1} ({len(frag.page_content)} chars) ---")
print(frag.page_content[:150] + "...")Dica de chunking: Começa com chunk_size=800 e overlap=200. Para documentos técnicos em português, testa também 500 e 1000. Mede a qualidade das respostas para cada tamanho e ajusta. O RecursiveCharacterTextSplitter é o melhor ponto de partida.
Cada fragmento de texto recebe umas espécie de "coordenadas GPS". Mas em vez de indicarem uma localização geográfica, indicam o significado do texto. Textos sobre "como treinar um modelo de IA" e "tutorial de machine learning" teriam coordenadas muito próximas (significado semelhante), enquanto um texto sobre "receita de bacalhau à Brás" ficaria muito longe.
Estes "GPS do significado" chamam-se embeddings - são listas de números que representam o sentido do texto. A Vector Store é a base de dados que guarda estes embeddings e permite pesquisar rapidamente "quais fragmentos estão mais próximos desta pergunta?". É como ter o Google Maps, mas para significados em vez de localizações.
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
# Embeddings locais GRATUITOS (sem API externa)
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2",
model_kwargs={'device': 'cpu'}, # ou 'cuda' com GPU
encode_kwargs={'normalize_embeddings': True}
)
# Para melhor qualidade em português:
# model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
# Criar vector store com ChromaDB
vectorstore = Chroma.from_documents(
documents=fragmentos,
embedding=embeddings,
persist_directory="./chroma_db",
collection_name="documentos_empresa"
)
print(f"Vector store criado com {vectorstore._collection.count()} fragmentos")
# Pesquisar por semelhança
query = "Quais cursos de Machine Learning estão disponíveis?"
resultados = vectorstore.similarity_search(query, k=4)
print(f"\nPergunta: {query}")
for i, doc in enumerate(resultados):
print(f"\n--- Resultado {i+1} ---")
print(doc.page_content[:200])
# Pesquisa com score
resultados_score = vectorstore.similarity_search_with_score(query, k=4)
for doc, score in resultados_score:
print(f"Score: {score:.4f} | {doc.page_content[:100]}...")Até agora construímos as peças individuais: a biblioteca (vector store), o sistema de pesquisa (embeddings) e os livros organizados (fragmentos). Agora vamos juntar tudo com o bibliotecário (o LLM). É como montar um puzzle: cada peça sozinha não faz muito, mas quando encaixam, o resultado é impressionante.
from langchain_community.llms import Ollama
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# LLM local via Ollama (GRATUITO)
llm = Ollama(model="llama3.1", temperature=0.3)
# Prompt template em português
prompt_template = PromptTemplate(
template="""Usa o seguinte contexto para responder à pergunta.
Se não encontrares a resposta no contexto, diz que não tens informação.
Não inventes. Responde em português de Portugal.
Contexto:
{context}
Pergunta: {question}
Resposta útil e detalhada:""",
input_variables=["context", "question"]
)
# Retriever
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 5}
)
# Chain RAG
rag_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True,
chain_type_kwargs={"prompt": prompt_template}
)
# Fazer perguntas
def perguntar(pergunta: str):
resultado = rag_chain.invoke({"query": pergunta})
print(f"Pergunta: {pergunta}")
print(f"\nResposta:\n{resultado['result']}")
print(f"\nFontes ({len(resultado['source_documents'])}):")
for doc in resultado['source_documents']:
fonte = doc.metadata.get('source', 'N/A')
print(f" - {fonte}")
perguntar("Quais cursos de IA estão disponíveis e qual o preço?")
print("\n" + "=" * 60 + "\n")
perguntar("Qual é a política de cancelamento?")O prompt template é crítico: Um prompt bem escrito em português, com instruções claras sobre quando admitir desconhecimento, faz uma diferença enorme na qualidade. Investe tempo a testar diferentes versões.
"RAG is not just about retrieving documents — it is about building systems that can reason over your organization's entire knowledge base in real time."
O RAG básico que construímos acima já funciona bem, mas existem técnicas para o tornar ainda melhor. É como a diferença entre um carro de série e um carro afinado - ambos funcionam, mas o segundo tem melhor desempenho.
Quando procuras um livro numa biblioteca, podes procurar por tema ("quero algo sobre culinária portuguesa" - pesquisa semântica) ou por palavra exata ("quero o livro que menciona bacalhau à Brás" - pesquisa por palavras-chave). A pesquisa híbrida combina as duas: encontra documentos que correspondem tanto ao significado como às palavras específicas da tua pergunta.
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
# Retriever BM25 (keywords)
retriever_bm25 = BM25Retriever.from_documents(fragmentos, k=5)
# Retriever semântico
retriever_semantico = vectorstore.as_retriever(search_kwargs={"k": 5})
# Combinar com pesos
retriever_hibrido = EnsembleRetriever(
retrievers=[retriever_semantico, retriever_bm25],
weights=[0.6, 0.4] # 60% semântico, 40% keywords
)
resultado = retriever_hibrido.invoke("Preço do curso de Machine Learning")
for doc in resultado:
print(f"- {doc.page_content[:150]}...")Quando pesquisas no Google, obtens 10 resultados - alguns relevantes, outros nem por isso. Agora imagina que um especialista revê esses 10 resultados e reordena-os do mais para o menos útil. Isso é o re-ranking: uma segunda fase de avaliação que usa um modelo mais preciso (mas mais lento) para reordenar os resultados da primeira pesquisa. Recuperas 10 fragmentos inicialmente, e o re-ranker escolhe os 3 melhores.
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
cross_encoder = HuggingFaceCrossEncoder(
model_name="cross-encoder/ms-marco-MiniLM-L-6-v2"
)
compressor = CrossEncoderReranker(model=cross_encoder, top_n=3)
retriever_reranked = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=retriever_semantico # recupera 10, re-rank para 3
)Agora vamos ao grande final: construir uma aplicação web completa com interface de chat, memória de conversa e referências às fontes. É como abrir o teu próprio balcão de atendimento virtual, onde as pessoas fazem perguntas e recebem respostas fundamentadas nos teus documentos, com um link para a fonte original. Usamos o Streamlit, uma ferramenta Python que transforma scripts em aplicações web bonitas com muito pouco código.
"""
Chatbot RAG com Streamlit
Executa: streamlit run chatbot_rag.py
"""
import streamlit as st
from langchain_community.llms import Ollama
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferWindowMemory
st.set_page_config(page_title="Assistente RAG", page_icon="🤖")
st.title("Assistente Inteligente com RAG")
@st.cache_resource
def inicializar_rag():
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2"
)
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings
)
llm = Ollama(model="llama3.1", temperature=0.3)
return vectorstore, llm
vectorstore, llm = inicializar_rag()
if "memory" not in st.session_state:
st.session_state.memory = ConversationBufferWindowMemory(
memory_key="chat_history", return_messages=True,
output_key="answer", k=5
)
if "mensagens" not in st.session_state:
st.session_state.mensagens = []
qa_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": 4}),
memory=st.session_state.memory,
return_source_documents=True
)
# Mostrar histórico
for msg in st.session_state.mensagens:
with st.chat_message(msg["role"]):
st.markdown(msg["content"])
# Input
if pergunta := st.chat_input("Faz uma pergunta..."):
st.session_state.mensagens.append({"role": "user", "content": pergunta})
with st.chat_message("user"):
st.markdown(pergunta)
with st.chat_message("assistant"):
with st.spinner("A pesquisar..."):
resultado = qa_chain.invoke({"question": pergunta})
st.markdown(resultado["answer"])
if resultado.get("source_documents"):
with st.expander("Ver fontes"):
for doc in resultado["source_documents"]:
st.caption(doc.metadata.get("source", "N/A"))
st.text(doc.page_content[:200] + "...")
st.session_state.mensagens.append({
"role": "assistant", "content": resultado["answer"]
})
with st.sidebar:
st.metric("Docs indexados", vectorstore._collection.count())
if st.button("Limpar conversa"):
st.session_state.mensagens = []
st.session_state.memory.clear()
st.rerun()Para executar: Guarda como chatbot_rag.py, cria o vector store (secção anterior) e corre streamlit run chatbot_rag.py. Abre em localhost:8501.
Depois de ajudar dezenas de equipas a implementar RAG, estes são os erros que vemos constantemente. Evita-os e poupas semanas de frustração:
Chunks demasiado grandes: É como dar um livro inteiro ao bibliotecário quando só precisas de um parágrafo. Diluem a informação relevante e gastam tokens desnecessários. Mantém os chunks entre 500-1000 caracteres.
Sem overlap entre chunks: Imagina cortar uma frase a meio entre dois fragmentos - perdes o sentido. O overlap (sobreposição de 100-200 caracteres) garante que o contexto não se perde nas "costuras" entre fragmentos.
Embeddings fracos em português: Muitos modelos de embeddings foram treinados principalmente em inglês. Para textos em português, usa o modelo paraphrase-multilingual-MiniLM-L12-v2 que compreende múltiplas línguas incluindo o português - a diferença de qualidade é enorme.
Recuperar demasiados fragmentos (K alto): Mais nem sempre é melhor. Se recuperares 20 fragmentos, a maioria será ruído que confunde o modelo. Começa com k=4 e ajusta. É como pedir 20 opiniões sobre um assunto simples - mais confunde do que ajuda.
Ignorar metadata: Os teus documentos têm informação extra valiosa (data, autor, tipo, departamento). Usa filtros de metadata para refinar a pesquisa. Por exemplo: "procura apenas em documentos de 2026" ou "procura apenas nas políticas de RH".
Queres dominar RAG e NLP? O curso de NLP com Transformers da Vibe Coding Academy inclui módulos dedicados a RAG, embeddings, bases de dados vetoriais e chatbots em produção.
Docente universitário com vasta experiência em formação e ensino de tecnologias de IA.
Seguir no LinkedInExplora o nosso curso relacionado e leva as tuas competências ao próximo nível.
Ver CursosRecebe artigos como este diretamente no teu email. Sem spam.

Um guia completo para iniciantes que querem começar a aprender Machine Learning. Descobre os conceitos fundamentais e os melhores recursos.

Aprende técnicas avançadas como Chain-of-Thought, Few-Shot Learning e Tree of Thoughts para maximizar os resultados com LLMs.

Uma comparação detalhada entre as duas ferramentas de programação assistida por IA mais populares do momento.