Neste artigo, você aprenderá:
- O que são Retrieval-Augmented Generation (RAG) e ChromaDB e o que cada um oferece.
- Por que combinar a API Web Unlocker da Bright Data com o ChromaDB é uma forma prática de fundamentar um modelo de linguagem em dados reais e atualizados.
- Como construir um pipeline completo que coleta conteúdo da web, gera embeddings localmente e responde perguntas sobre ele, tudo na sua própria máquina.
Antes de entrarmos nas ferramentas e no código, é útil definir os conceitos e ver como eles se encaixam em um fluxo de trabalho RAG.
O Que É Retrieval-Augmented Generation (RAG)?
Um modelo de linguagem de grande escala só conhece o que foi treinado. Pergunte sobre uma página publicada na semana passada, uma wiki interna ou um catálogo de produtos de nicho, e ele vai adivinhar ou dizer que não sabe. O Retrieval-Augmented Generation fecha essa lacuna.
A ideia é simples. Em vez de depender da memória do modelo, você armazena seus próprios documentos em um índice pesquisável. Quando uma pergunta chega, você primeiro recupera os trechos de texto mais relevantes, depois aumenta o prompt colando esses trechos como contexto e, por fim, deixa o modelo gerar uma resposta fundamentada nesse material. O modelo ainda faz a escrita, mas os fatos vêm de dados que você controla.
Isso importa por dois motivos. As respostas permanecem atuais porque você decide o que entra no índice e quando atualizá-lo. E as respostas permanecem precisas porque o modelo raciocina sobre texto-fonte real em vez de preencher lacunas com dados de treinamento. Para bases de conhecimento, assistentes de suporte, ferramentas de pesquisa e tudo construído sobre informações proprietárias ou em rápida mudança, o RAG tornou-se a abordagem padrão.
Com o lado da recuperação coberto, vamos ver onde esses documentos realmente ficam armazenados.
O Que É o ChromaDB?
O ChromaDB (geralmente chamado apenas de Chroma) é um banco de dados vetorial de código aberto criado para aplicações de IA. Um banco de dados comum corresponde a valores exatos; um banco de dados vetorial corresponde a significados. Ele faz isso armazenando embeddings, que são representações numéricas de texto onde ideias similares ficam próximas no espaço vetorial. Ao consultá-lo, o Chroma retorna os trechos armazenados cujos embeddings estão mais próximos da sua pergunta, mesmo quando nenhuma palavra exata coincide.
O que torna o Chroma uma boa opção para um primeiro projeto RAG é o pouco que ele exige de você. Instala-se com um único pip install, roda embutido no seu processo Python sem servidor separado para gerenciar, e persiste tudo em disco via PersistentClient para que seu índice sobreviva a uma reinicialização. Você pode conectar qualquer modelo de embedding, das APIs da OpenAI ou Google a um modelo local que nunca acessa a rede. Para um projeto que precisa rodar em um laptop e manter privacidade, essa combinação é difícil de superar.
RAG vs Fine-Tuning: Qual É a Diferença?
Quem está começando nessa área frequentemente compara RAG com fine-tuning como se fossem opções concorrentes. Eles resolvem problemas diferentes:
- Fine-tuning altera o próprio modelo. Você o retreina com exemplos para que ele adapte seu tom, formato ou comportamento. É a ferramenta certa quando você quer que o modelo aja de forma diferente, mas é lento, caro e o conhecimento incorporado fica desatualizado assim que seus dados mudam.
- RAG mantém o modelo intocado e muda o que ele vê no momento da pergunta. Você injeta contexto relevante no prompt a cada requisição. É a ferramenta certa quando você quer que o modelo saiba algo específico e atual, e atualizá-lo é tão simples quanto adicionar um documento ao seu índice.
A regra geral que a maioria das equipes adota: se você precisa de novo conhecimento, use RAG; se precisa de novo comportamento, considere fine-tuning. Muitos sistemas em produção usam os dois. Neste tutorial focamos inteiramente em RAG, pois o objetivo é responder perguntas sobre conteúdo web que muda com muita frequência para retreinar um modelo.
Por Que Integrar a Bright Data em um Pipeline RAG + ChromaDB?
Um sistema RAG é tão bom quanto os documentos que você alimenta nele. Lixo entra, lixo sai com confiança. Portanto, o verdadeiro gargalo na maioria dos pipelines não é a busca vetorial ou o modelo, é obter material-fonte limpo, confiável e atualizado em primeiro lugar.
Isso é simples quando seus dados já estão em uma pasta de PDFs. Fica difícil quando seu conhecimento precisa vir da web aberta. Páginas públicas se escondem atrás de detecção de bots, renderizam conteúdo com JavaScript, servem resultados diferentes por região e lançam CAPTCHAs em tudo que parece automatizado. Manter seus próprios scrapers contra tudo isso é um trabalho por si só, e que quebra toda vez que um site-alvo muda sua marcação.
É aqui que a API Web Unlocker da Bright Data se encaixa. Você envia uma URL de destino e ela retorna o conteúdo da página, gerenciando medidas anti-bot, rotação de proxies, renderização JavaScript e geolocalização nos bastidores. Você pode até pedir que retorne a página como Markdown limpo, o que é quase ideal para RAG, pois remove a navegação e o conteúdo padrão, deixando texto legível para fragmentar e gerar embeddings. Sem automação de navegador, sem pool de proxies para gerenciar.
Combinado com o armazenamento vetorial local do Chroma e um modelo rodando localmente, isso fornece um pipeline que é atual onde precisa ser e privado em todo o resto: apenas a etapa de coleta de dados acessa a web, enquanto embedding, armazenamento, recuperação e geração ficam na sua máquina.
Esse padrão é especialmente útil para:
- Assistentes de pesquisa que respondem perguntas sobre os artigos, papers ou documentações mais recentes, em vez de um snapshot de treinamento do modelo.
- Ferramentas de inteligência competitiva que rastreiam páginas de produtos, preços ou funcionalidades de sites concorrentes.
- Bots de suporte interno baseados em documentação ao vivo, para que as respostas reflitam os documentos atuais em vez de uma versão de meses atrás.
- Sistemas de monitoramento de mercado que coletam listagens ou notícias recentes e permitem que um analista as consulte em linguagem natural.
Ao deixar a infraestrutura de dados web da Bright Data gerenciar a coleta e o Chroma gerenciar a recuperação, você obtém um motor RAG em formato de produção sem escrever uma única linha de código de scraping.
Como Construir um Pipeline RAG Local com Bright Data e ChromaDB
Nesta seção guiada, você construirá um pipeline com três etapas:
- Coletar conteúdo web: Um script chama a API Web Unlocker da Bright Data para buscar um conjunto de páginas e retornar cada uma como Markdown.
- Gerar embeddings e armazenar: Um segundo script fragmenta esse conteúdo, gera embeddings localmente com um modelo sentence-transformer e grava os vetores no ChromaDB.
- Recuperar e gerar: Um script de consulta gera o embedding da sua pergunta, recupera os trechos mais relevantes do Chroma e os passa a um modelo local para produzir uma resposta fundamentada com fontes.
Nota: Este é um dos muitos designs possíveis. Você pode trocar o modelo local por uma chamada de API para obter respostas de maior qualidade, adicionar uma etapa de re-ranking entre recuperação e geração, ou apontar a etapa de coleta para centenas de URLs em vez de três. A estrutura permanece a mesma.
Siga os passos abaixo para construir um pipeline RAG totalmente local alimentado pela API Web Unlocker da Bright Data e pelo ChromaDB.
Pré-requisitos
Para acompanhar, você precisa de:
- Uma conta Bright Data com uma zona Web Unlocker ativa. Faça login no seu painel, vá em Configurações da Conta e copie seu Token de API (estará em formato UUID). Anote também o nome da sua zona; você precisará de ambos.
- Python 3.10+ instalado localmente.
- Ollama instalado, com um modelo baixado para a etapa de geração (este tutorial usa
llama3.1, mas qualquer modelo de chat funciona). Se preferir usar um modelo hospedado, o Passo 5 mostra onde substituir por uma chamada de API.

Passo 1: Configuração do Projeto
Crie um diretório de trabalho, configure um ambiente virtual e instale as dependências:
mkdir local-rag-pipeline && cd local-rag-pipeline
python -m venv venv
source venv/bin/activate # No Windows: venv\Scripts\activate
pip install requests chromadb sentence-transformers
Na primeira execução do pipeline, o sentence-transformers baixará o modelo de embedding (algumas centenas de megabytes). Depois disso, carrega do cache e roda offline.
Baixe um modelo para a etapa de geração, se ainda não o fez:
ollama pull llama3.1
Passo 2: Crie a Estrutura do Projeto
Crie as pastas para as quais o pipeline escreve:
mkdir -p data/raw data/chroma
A estrutura do seu projeto ficará assim:
local-rag-pipeline/
├── data/
│ ├── raw/ # Markdown bruto coletado da web
│ └── chroma/ # Índice ChromaDB persistido
├── collect.py # Etapa 1: buscar páginas via Bright Data
├── ingest.py # Etapa 2: fragmentar, gerar embeddings e armazenar
└── rag.py # Etapa 3: recuperar e gerar
Passo 3: Colete Dados da Web com a Bright Data
Crie o arquivo collect.py:
import json
import requests
from pathlib import Path
API_KEY = "your-brightdata-api-token-here"
ZONE = "web_unlocker1"
BASE_URL = "https://api.brightdata.com/request"
RAW_DATA_PATH = "data/raw/pages.json"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
TARGETS = [
"https://en.wikipedia.org/wiki/Retrieval-augmented_generation",
"https://en.wikipedia.org/wiki/Vector_database",
"https://en.wikipedia.org/wiki/Large_language_model",
]
def fetch_pages():
results = []
for url in TARGETS:
print(f"Fetching: {url}")
response = requests.post(
BASE_URL,
headers=HEADERS,
json={
"zone": ZONE,
"url": url,
"format": "raw",
"data_format": "markdown",
},
timeout=60,
)
response.raise_for_status()
results.append({"url": url, "content": response.text})
print(f" -> {len(response.text)} chars")
Path(RAW_DATA_PATH).parent.mkdir(parents=True, exist_ok=True)
with open(RAW_DATA_PATH, "w") as f:
json.dump(results, f, indent=2)
print(f"Saved {len(results)} pages to {RAW_DATA_PATH}")
if __name__ == "__main__":
fetch_pages()
Substitua your-brightdata-api-token-here pelo seu token de API real e atualize ZONE para corresponder ao nome da sua zona Web Unlocker.
Veja o que cada parte faz:
API_KEYeZONE: Suas credenciais da Bright Data. O token de API é o token em formato UUID das configurações da sua conta, não uma senha de zona.TARGETS: As páginas a serem ingeridas. Os três artigos da Wikipedia aqui fornecem um corpus coerente para fazer perguntas. Substitua pelas suas próprias URLs; é exatamente aqui que o Web Unlocker justifica sua presença, pois sites de notícias, páginas de produtos e aplicativos com muito JavaScript que bloqueiam requisições comuns retornam limpos pela API.fetch_pages: Percorre cada URL e envia uma requisição POST ao endpoint do Web Unlocker. A opçãodata_format: "markdown"instrui a Bright Data a retornar Markdown legível em vez de HTML bruto, economizando uma etapa de parsing. Os resultados são gravados em um único arquivo JSON para a próxima etapa ler.
Nota: Algumas páginas podem retornar com uma mensagem
bad_endpointse um site for restrito no modo de acesso imediato da Bright Data. Este é o comportamento esperado; a Bright Data exibe o erro na resposta em vez de falhar silenciosamente. Entre em contato com seu Gerente de conta se precisar de acesso total a um destino restrito.

Passo 4: Gere Embeddings do Conteúdo no ChromaDB
Crie o arquivo ingest.py:
import json
import chromadb
from chromadb.utils import embedding_functions
RAW_DATA_PATH = "data/raw/pages.json"
CHROMA_PATH = "data/chroma"
COLLECTION_NAME = "web_knowledge"
def chunk_text(text, size=800, overlap=100):
words = text.split()
chunks, i = [], 0
while i < len(words):
chunks.append(" ".join(words[i:i + size]))
i += size - overlap
return chunks
def main():
with open(RAW_DATA_PATH) as f:
pages = json.load(f)
client = chromadb.PersistentClient(path=CHROMA_PATH)
embed_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="all-MiniLM-L6-v2"
)
collection = client.get_or_create_collection(
name=COLLECTION_NAME,
embedding_function=embed_fn,
)
documents, metadatas, ids = [], [], []
for page in pages:
for idx, chunk in enumerate(chunk_text(page["content"])):
documents.append(chunk)
metadatas.append({"source": page["url"], "chunk": idx})
ids.append(f"{page['url']}#{idx}")
collection.upsert(documents=documents, metadatas=metadatas, ids=ids)
print(f"Indexed {len(documents)} chunks from {len(pages)} pages")
print(f"Collection now holds {collection.count()} chunks")
if __name__ == "__main__":
main()
Esta etapa faz o trabalho pesado de transformar texto bruto em algo pesquisável:
chunk_text: Divide cada página em janelas sobrepostas de aproximadamente 800 palavras. A fragmentação importa porque você quer recuperar passagens focadas, não páginas inteiras, e a sobreposição de 100 palavras evita que uma frase no limite de um fragmento seja cortada ao meio.SentenceTransformerEmbeddingFunction: Carrega oall-MiniLM-L6-v2, um modelo de embedding pequeno e rápido que roda localmente. O Chroma o chama automaticamente sempre que você adiciona ou consulta documentos, então você nunca manipula vetores manualmente.get_or_create_collection: Abre a coleção persistente em disco, criando-a na primeira execução e reutilizando-a depois.collection.upsert: Grava os fragmentos, seus metadados e IDs estáveis no Chroma. Usarupsertem vez deaddsignifica que você pode reexecutar o script após coletar novo conteúdo sem erros de ID duplicado.
Passo 5: Construa a Etapa de Recuperação e Geração
Crie o arquivo rag.py:
import sys
import requests
import chromadb
from chromadb.utils import embedding_functions
CHROMA_PATH = "data/chroma"
COLLECTION_NAME = "web_knowledge"
OLLAMA_URL = "http://localhost:11434/api/generate"
MODEL = "llama3.1"
PROMPT_TEMPLATE = """You are a research assistant. Answer the question using only the context below.
If the context does not contain the answer, say you don't have enough information.
Context:
{context}
Question: {question}
Answer:"""
def retrieve(question, n_results=4):
client = chromadb.PersistentClient(path=CHROMA_PATH)
embed_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="all-MiniLM-L6-v2"
)
collection = client.get_collection(
name=COLLECTION_NAME, embedding_function=embed_fn
)
results = collection.query(query_texts=[question], n_results=n_results)
chunks = results["documents"][0]
sources = [m["source"] for m in results["metadatas"][0]]
return chunks, sources
def generate(question, chunks):
prompt = PROMPT_TEMPLATE.format(context="\n\n".join(chunks), question=question)
response = requests.post(
OLLAMA_URL,
json={"model": MODEL, "prompt": prompt, "stream": False},
timeout=120,
)
response.raise_for_status()
return response.json()["response"]
def main(question):
chunks, sources = retrieve(question)
answer = generate(question, chunks)
print("\n=== Answer ===")
print(answer.strip())
print("\n=== Sources ===")
for src in dict.fromkeys(sources): # de-duplicate, keep order
print(f" - {src}")
if __name__ == "__main__":
main(" ".join(sys.argv[1:]))
Aqui está o fluxo:
retrieve: Gera o embedding da sua pergunta com o mesmo modelo usado na ingestão (essa consistência é o que faz a busca por similaridade funcionar) e pede ao Chroma os quatro fragmentos mais próximos. Retorna tanto o texto quanto a URL de origem de cada fragmento.generate: Constrói um prompt que ancora o modelo ao contexto recuperado e instrui-o a admitir quando a resposta não está disponível, que é a proteção mais eficaz contra alucinações. Em seguida, chama um modelo local pela API REST do Ollama.main: Une os dois e imprime a resposta junto com a lista de fontes sem duplicatas, para que cada resposta seja rastreável até as páginas de origem.
Se preferir um modelo hospedado, esta é a única função que muda. Substitua a chamada requests.post ao Ollama por uma chamada à API da Anthropic ou OpenAI e passe o mesmo prompt; a metade de recuperação permanece exatamente igual.
Passo 6: Execute o Pipeline
Execute as três etapas em ordem. Primeiro, colete as páginas:
python collect.py
Em seguida, fragmente e gere embeddings delas no Chroma:
python ingest.py

Agora faça uma pergunta:
python rag.py "What problem does retrieval-augmented generation solve?"
Passo 7: Inspecione os Resultados
A consulta retorna uma resposta fundamentada seguida das fontes utilizadas:
=== Answer ===
Retrieval-augmented generation addresses the fact that a language model only
knows what it was trained on. By retrieving relevant passages from an external
index at query time and adding them to the prompt, the model can answer using
current, specific information it was never trained on, which also reduces
hallucination because the response is anchored to real source text.
=== Sources ===
- https://en.wikipedia.org/wiki/Retrieval-augmented_generation
- https://en.wikipedia.org/wiki/Large_language_model

Sugestão de captura de tela: a saída do terminal de uma consulta rag.py, mostrando a resposta gerada e a lista de fontes abaixo.
Experimente mais algumas perguntas para ter uma ideia da qualidade da recuperação:
python rag.py "How does a vector database differ from a relational database?"
python rag.py "What are common limitations of large language models?"
Se uma resposta parecer superficial, dois parâmetros valem a pena ajustar primeiro. Aumentar n_results em rag.py fornece mais contexto ao modelo, o que ajuda em perguntas amplas ao custo de um prompt mais longo. Ajustar size e overlap em ingest.py muda como o texto é dividido; fragmentos menores aprimoram buscas precisas, fragmentos maiores preservam mais contexto ao redor. Reexecute ingest.py após qualquer mudança na fragmentação para que o índice a reflita.
Todo o ciclo rodou sem uma única linha de código de scraping ou proxy. A Bright Data entregou Markdown limpo da web, o Chroma gerenciou os embeddings e a busca por similaridade localmente, e um modelo local produziu a resposta, tudo fundamentado nas páginas que você escolheu coletar.
Indo Além
Este pipeline é uma base funcional e você pode expandi-lo em várias direções:
- Troque o modelo local por uma API hospedada como Anthropic ou OpenAI quando precisar de raciocínio mais robusto ou contexto mais longo, mantendo toda a metade de recuperação intocada.
- Adicione uma etapa de descoberta com a API SERP da Bright Data para que o pipeline possa encontrar páginas relevantes a partir de uma consulta de busca, em vez de trabalhar com uma lista fixa de URLs.
- Extraia registros estruturados em vez de texto livre usando a API Web Scraper da Bright Data, que cobre mais de 120 domínios. O tutorial de RAG agêntico mostra esse padrão de ponta a ponta.
- Use a filtragem de metadados do Chroma (o argumento
whereemquery) para limitar a recuperação a uma fonte, data ou categoria específica. - Adicione uma camada de re-ranking ou busca híbrida se seus documentos contiverem muitos substantivos próprios e códigos com os quais a busca semântica pura tem dificuldades.
- Agende atualizações periódicas ou exponha tudo a um assistente via servidor MCP para que uma ferramenta como o Claude Desktop possa coletar e consultar sob demanda.
- Migre do Chroma local para um armazenamento vetorial gerenciado como Pinecone ou pgvector quando sua coleção superar uma única máquina; a lógica de ingestão e recuperação se transfere com mudanças mínimas.
As possibilidades são praticamente infinitas.
Conclusão
Neste artigo, você construiu um pipeline RAG local funcional combinando a API Web Unlocker da Bright Data com o ChromaDB.
O Chroma gerencia embedding, armazenamento e busca por similaridade localmente, para que seu índice e suas consultas nunca saiam da sua máquina. Um modelo local transforma o contexto recuperado em uma resposta fundamentada com fontes citadas. E a Bright Data remove a parte mais difícil de todo o processo: coletar conteúdo de página fresco e limpo da web aberta sem gerenciar proxies, escrever scrapers ou combater sistemas anti-bot.
Ao contrário de um assistente sem código, esta stack oferece controle total sobre cada camada: quais páginas você coleta, como as fragmenta e gera embeddings, quantas recupera e qual modelo escreve a resposta. Integra-se naturalmente a qualquer plataforma maior de dados ou IA e escala conforme suas necessidades.
Para construir pipelines mais ricos, explore o conjunto completo de ferramentas de dados web da Bright Data, incluindo o Web Unlocker para páginas protegidas por bot, a API SERP para dados de busca e conjuntos de dados prontos para casos de uso comuns.
Crie uma conta gratuita na Bright Data hoje e comece a fundamentar seus modelos em dados reais da web.