Criação de um agente local de pesquisa profunda com dados brilhantes
Criação de um agente local de pesquisa profunda com dados brilhantes
Como combinar as APIs da Bright Data, a interface do Streamlit e modelos de IA rodando na sua própria máquina para construir um assistente de pesquisa particular — privado, auditável e sob seu controle.
Introdução: a era dos agentes que pesquisam por nós
Durante décadas, a pesquisa na web foi uma tarefa essencialmente humana: formulávamos uma consulta, abríamos dez abas, liamos o que dava tempo, descartávamos o ruído e escrevíamos uma síntese. Nos últimos anos, porém, surgiu uma nova categoria de software que promete fazer esse trabalho pesado no nosso lugar: os agentes de pesquisa profunda (deep research agents).
A proposta é tentadora. Em vez de receber uma resposta instantânea e superficial, você entrega um objetivo — “mapeie o mercado brasileiro de fintechs de crédito em 2025”, “qual é a expectativa do IPCA para este ano?” ou “compare os preços dos concorrentes no setor de logística” — e o agente planeja as etapas, dispara dezenas de buscas, visita páginas, extrai dados, cruza fontes e só então devolve um relatório estruturado, com citações.
O problema é que a maioria dessas soluções vive na nuvem de alguém. Suas perguntas, seus dados internos e sua estratégia competitiva passam por servidores de terceiros. Para quem trabalha com inteligência de mercado, jurídico, saúde ou finanças, isso é um obstáculo sério.
Este guia mostra o caminho oposto: como construir um agente local de pesquisa profunda, que usa a Bright Data para acessar a web em escala, o Streamlit como interface e um modelo de linguagem rodando na sua própria máquina para raciocinar sobre os dados coletados. Nada de dados sensíveis saindo do seu computador além das consultas públicas de busca.
O que é, afinal, um agente de pesquisa profunda?
É importante separar o hype da engenharia. Um agente de pesquisa profunda não é “um chatbot que navega”. Ele combina quatro capacidades distintas:
- Planejamento: decompor um objetivo amplo em subperguntas pesquisáveis.
- Coleta: executar buscas, acessar páginas, lidar com bots de bloqueio, paginação e conteúdo dinâmico.
- Curadoria: limpar, deduplicar, ranquear e resumir as fontes encontradas, mantendo a rastreabilidade.
- Síntese: transformar dezenas de trechos em um texto coerente, com atribuição de origem.
A parte mais frágil — e a que costuma fazer projetos fracassarem — é a coleta. Basta o agente bater em um site protegido por Cloudflare, um e-commerce com conteúdo renderizado em JavaScript ou uma página que exige JavaScript para carregar os preços, e o pipeline inteiro quebra. É exatamente aqui que entra uma camada de dados como a Bright Data.
Por que rodar o agente localmente?
Antes de entrar no código, vale justificar a arquitetura. Três argumentos sustentam a escolha por uma execução local:
- Privacidade e conformidade. Com modelos locais (via Ollama, llama.cpp ou LM Studio), nenhuma consulta interna, documento proprietário ou hipótese de negócio sai da sua rede. Isso simplifica enormemente discussões de LGPD e de políticas internas de segurança da informação.
- Custo previsível. APIs de LLM cobram por token. Um agente de pesquisa profunda consome muito token — facilmente centenas de milhares por execução, entre páginas lidas e reflexões intermediárias. Um modelo local tem custo marginal próximo de zero depois do hardware.
- Controle e auditabilidade. Você define prompts, limites de profundidade, fontes permitidas e formatos de saída. Pode inspecionar cada etapa, o que é essencial quando o resultado alimenta uma decisão de negócio.
A contrapartida é honesta: modelos locais de porte médio (7B a 32B parâmetros) são menos capazes que os frontier models. A solução é compensar com arquitetura: dividir o problema em etapas simples, dar contexto limpo ao modelo e usar ferramentas externas para tudo que exija precisão factual.
A arquitetura em quatro camadas
O agente que vamos descrever se organiza assim:
| Camada | Tecnologia | Responsabilidade |
|---|---|---|
| Interface | Streamlit | Entrada da pergunta, acompanhamento do progresso, exibição do relatório final |
| Orquestração | LangGraph / Python puro | Planejar subperguntas, decidir quais ferramentas chamar, iterar |
| Coleta web | Bright Data (SERP API, Web Unlocker, Browser API) | Buscar, contornar bloqueios, renderizar JavaScript, extrair HTML limpo |
| Raciocínio | Ollama (Llama 3.1, Qwen 2.5 ou Mistral) | Resumir, classificar relevância, sintetizar o relatório |
O fluxo é circular, não linear. O agente planeja, coleta, avalia se o material é suficiente e, se não for, refina as subperguntas e coleta de novo. Esse laço — e não a sofisticação do prompt — é o que produz a “profundidade”.
Passo 1 — Preparando o ambiente
Comece com Python 3.11 ou superior e um ambiente virtual isolado:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install streamlit requests beautifulsoup4 langchain langchain-community \
langgraph chromadb ollama pandas python-dotenv
Instale também o Ollama e baixe um modelo. Para máquinas com 16 GB de RAM, um 8B quantizado é o ponto de equilíbrio:
ollama pull llama3.1:8b
ollama pull nomic-embed-text # modelo de embeddings para o RAG local
Guarde suas credenciais da Bright Data em um arquivo .env — e nunca as comite no repositório:
BRIGHTDATA_API_KEY=seu_token_aqui
BRIGHTDATA_SERP_ZONE=serp_zone
BRIGHTDATA_UNLOCKER_ZONE=web_unlocker
Passo 2 — Coleta: busca e acesso com Bright Data
A primeira etapa do agente é transformar uma pergunta em resultados de busca confiáveis. Usar scraping direto do HTML do Google é frágil e, frequentemente, viola termos de uso. Uma SERP API resolve isso entregando resultados estruturados em JSON:
import os, requests
from dotenv import load_dotenv
load_dotenv()
def buscar_web(consulta: str, pais: str = "br", limite: int = 10):
"""Executa uma busca e retorna título, link e snippet estruturados."""
url = "https://api.brightdata.com/request"
payload = {
"zone": os.getenv("BRIGHTDATA_SERP_ZONE"),
"url": f"https://www.google.com/search?q={consulta}&gl={pais}&num={limite}",
"format": "json",
}
headers = {
"Authorization": f"Bearer {os.getenv('BRIGHTDATA_API_KEY')}",
"Content-Type": "application/json",
}
resposta = requests.post(url, json=payload, headers=headers, timeout=90)
resposta.raise_for_status()
dados = resposta.json()
return [
{
"titulo": item.get("title"),
"link": item.get("link"),
"resumo": item.get("description"),
}
for item in dados.get("organic", [])[:limite]
]
Com a lista de URLs em mãos, o próximo obstáculo é abrir essas páginas. Boa parte delas vai bloquear requisições simples, exigir fingerprint de navegador real ou carregar conteúdo via JavaScript. O Web Unlocker cuida desse trabalho e devolve o HTML já renderizado:
from bs4 import BeautifulSoup
def extrair_texto(url: str) -> str:
"""Baixa uma página driblando bloqueios e devolve texto limpo."""
payload = {
"zone": os.getenv("BRIGHTDATA_UNLOCKER_ZONE"),
"url": url,
"format": "raw",
}
headers = {
"Authorization": f"Bearer {os.getenv('BRIGHTDATA_API_KEY')}",
"Content-Type": "application/json",
}
resposta = requests.post(
"https://api.brightdata.com/request",
json=payload, headers=headers, timeout=120
)
resposta.raise_for_status()
sopa = BeautifulSoup(resposta.text, "html.parser")
for tag in sopa(["script", "style", "nav", "footer", "aside"]):
tag.decompose()
texto = " ".join(sopa.get_text(separator=" ").split())
return texto
Para páginas que dependem fortemente de interação — filtros, painéis, tabelas carregadas sob demanda — a Browser API permite controlar um navegador remoto via Playwright ou Puppeteer, executando cliques e capturando o DOM final. Em pipelines de pesquisa de mercado, isso costuma ser indispensável para portais de dados econômicos e comparadores de preço.
Boa prática: sempre trate a coleta como um recurso caro. Faça cache local do HTML por URL e por data, respeite robots.txt quando aplicável e limite a profundidade de rastreamento. Um agente bem projetado coleta dez páginas excelentes, não duzentas medianas.
Passo 3 — Curadoria: transformando HTML em conhecimento
Texto bruto é ruído. Antes de enviar qualquer coisa ao modelo, vale passar por três filtros:
- Deduplicação: remover páginas com conteúdo quase idêntico (sindicado, republicado).
- Chunking: dividir em blocos de 500 a 1.000 tokens com sobreposição de ~15%, preservando o cabeçalho de origem.
- Indexação vetorial: gerar embeddings com
nomic-embed-texte armazenar no ChromaDB, junto com metadados de URL, título e data de coleta.
import chromadb
from ollama import Client
cliente_ollama = Client()
chroma = chromadb.PersistentClient(path="./base_pesquisa")
colecao = chroma.get_or_create_collection("pesquisa")
def indexar(fontes: list[dict]):
for i, fonte in enumerate(fontes):
trechos = dividir_em_chunks(fonte["texto"], tamanho=800, sobreposicao=120)
for j, trecho in enumerate(trechos):
embedding = cliente_ollama.embeddings(
model="nomic-embed-text", prompt=trecho
)["embedding"]
colecao.add(
ids=[f"{i}-{j}"],
embeddings=[embedding],
documents=[trecho],
metadados=[{"url": fonte["link"], "titulo": fonte["titulo"]}],
)
Esse índice local é o que dá ao agente uma memória persistente. Pesquisas futuras sobre o mesmo tema reaproveitam o material já coletado, e o relatório final pode citar trechos com precisão.
Passo 4 — O laço de raciocínio
Aqui está o coração do agente. Em vez de um único prompt gigante, o fluxo se divide em três funções pequenas e verificáveis:
- Planejador: recebe o objetivo e devolve de 3 a 6 subperguntas.
- Avaliador: lê os trechos recuperados e decide se são suficientes ou se é preciso buscar mais.
- Sintetizador: escreve o relatório final, citando as fontes.
def planejar(objetivo: str) -> list[str]:
prompt = f"""Você é um pesquisador sênior.
Divida o objetivo abaixo em no máximo 5 subperguntas específicas e pesquisáveis.
Responda APENAS com a lista, uma por linha, sem numeração.
Objetivo: {objetivo}"""
resposta = cliente_ollama.generate(model="llama3.1:8b", prompt=prompt)
return [l.strip() for l in resposta["response"].split("\n") if l.strip()]
def sintetizar(objetivo: str, trechos: list[dict]) -> str:
contexto = "\n\n".join(
f"[FONTE {i+1} — {t['url']}]\n{t['texto']}" for i, t in enumerate(trechos)
)
prompt = f"""Com base exclusivamente nos trechos abaixo, escreva um relatório analítico
em português sobre: {objetivo}
Regras:
- Sempre cite a fonte no formato [FONTE n] após cada afirmação factual.
- Se os trechos não sustentarem uma conclusão, diga explicitamente que faltam dados.
- Estruture em: Panorama, Evidências, Contradições, Lacunas, Conclusão.
TRECHOS:
{contexto}"""
return cliente_ollama.generate(model="llama3.1:8b", prompt=prompt)["response"]
O laço completo fica assim: planejar → buscar → extrair → indexar → recuperar trechos relevantes → avaliar suficiência → repetir se necessário → sintetizar. Um limite de três iterações costuma ser suficiente para produzir profundidade sem cair em loops infinitos de custo.
Passo 5 — A interface com Streamlit
Streamlit transforma o pipeline em uma ferramenta utilizável por quem não escreve código. A interface mínima cabe em poucas linhas:
import streamlit as st
st.set_page_config(page_title="Agente de Pesquisa Profunda", page_icon="🔎")
st.title("🔎 Agente Local de Pesquisa Profunda")
with st.sidebar:
st.markdown("### Configurações")
profundidade = st.slider("Iterações de pesquisa", 1, 3, 2)
modelo = st.selectbox("Modelo local", ["llama3.1:8b", "qwen2.5:14b", "mistral:7b"])
st.caption("Execução 100% local. Nada sai da sua máquina.")
objetivo = st.text_area(
"O que você quer investigar?",
placeholder="Ex.: Qual é a expectativa de mercado para o IPCA em 2025 e quais setores são mais sensíveis?",
)
if st.button("Iniciar pesquisa") and objetivo:
barra = st.progress(0, text="Planejando subperguntas...")
subperguntas = planejar(objetivo)
st.write("**Plano de pesquisa:**", subperguntas)
fontes = []
for i in range(profundidade):
barra.progress((i + 1) / (profundidade + 1),
text=f"Coletando dados — rodada {i+1}")
for pergunta in subperguntas:
for resultado in buscar_web(pergunta):
resultado["texto"] = extrair_texto(resultado["link"])
fontes.append(resultado)
indexar(fontes)
barra.progress(1.0, text="Sintetizando relatório...")
trechos = recuperar_trechos(objetivo, k=12)
relatorio = sintetizar(objetivo, trechos)
st.markdown("## Relatório")
st.markdown(relatorio)
st.download_button("Baixar relatório (.md)", relatorio, file_name="relatorio.md")
Detalhes que fazem diferença na experiência real: mostrar as subperguntas enquanto são geradas (transparência do raciocínio), permitir pausar e revisar as fontes antes da síntese, e oferecer exportação em Markdown ou PDF. Um bom agente de pesquisa é inspecionável — se você não consegue ver de onde veio uma afirmação, não confie nela.
Aplicações práticas
Inteligência de mercado
Mapear concorrentes, preços, posicionamento e lançamentos recentes de um setor. O agente varre comparadores, marketplaces, blogs de indústria e releases, e devolve um panorama com citações — reduzindo de dias para horas o trabalho de um analista.
Dados econômicos
Perguntas em linguagem natural como “qual é a expectativa do IPCA para 2025?” podem ser respondidas consultando portais de bancos centrais, institutos de pesquisa e séries históricas. A tecnologia de agentes está democratizando o acesso a dados econômicos complexos, tornando-os acessíveis mesmo para quem não domina APIs estatísticas.
Monitoramento regulatório e jurídico
Acompanhar diários oficiais, resoluções e mudanças normativas em setores específicos, com resumo periódico e alertas baseados em palavras-chave.
Due diligence e verificação
Antes de fechar um contrato, o agente cruza notícias, processos públicos e registros empresariais, sinalizando contradições entre o que foi declarado e o que aparece em fontes abertas.
Limites, custos e considerações éticas
Seria desonesto vender esse projeto como solução livre de atritos. Alguns pontos merecem atenção:
- Alucinação em modelos locais. Modelos de 8B parâmetros erram mais. A mitigação é arquitetural: contexto recuperado, citação obrigatória e instrução explícita para declarar lacunas.
- Qualidade da fonte. Um agente rápido em sites ruins produz relatórios ruins com mais velocidade. Mantenha uma lista de domínios de confiança e penalize agregadores de conteúdo.
- Custo de coleta. As APIs da Bright Data cobram por requisição bem-sucedida. Faça cache agressivo, limite profundidade e monitore o consumo por execução.
- Compliance. Respeite termos de uso, direitos autorais e legislação de proteção de dados. Pesquisa de fontes públicas é diferente de coleta de dados pessoais sem base legal.
- Hardware. Um 8B quantizado roda confortavelmente em 16 GB de RAM. Modelos de 30B+ pedem GPU com pelo menos 24 GB de VRAM para latência aceitável.
Próximos passos: tornando o agente melhor
Uma vez que o fluxo básico funciona, os ganhos vêm de refinamentos incrementais:
- Memória de longo prazo: mantenha um banco vetorial por domínio de pesquisa, em vez de recomeçar do zero a cada consulta.
- Roteamento de modelos: use um modelo pequeno para triagem e classificação, e um maior apenas para a síntese final. Economiza tempo sem sacrificar qualidade onde importa.
- Agendamento: rode o agente periodicamente sobre um conjunto fixo de temas e entregue um boletim semanal por e-mail ou Slack.
- Verificação cruzada: uma etapa dedicada a confrontar afirmações do relatório contra as fontes originais, marcando o que não foi possível confirmar.
- Rastreabilidade total: grave cada URL visitada, timestamp e trecho utilizado. Sem isso, o relatório é opinião com aparência de dado.
Conclusão
A promessa dos agentes de pesquisa profunda só se sustenta sobre duas pernas: acesso confiável à web e raciocínio controlado sobre o que foi coletado. A primeira é resolvida por uma camada de dados robusta, capaz de lidar com bloqueios, JavaScript e escala — território em que a Bright Data se consolidou como infraestrutura. A segunda pode, hoje, viver inteiramente na sua máquina.
O resultado é uma combinação poderosa: a abrangência de um sistema que varre dezenas de fontes em minutos, com a privacidade de um processo que nunca envia seus dados para fora. Não é uma solução mágica — exige curadoria, limites de iteração e disciplina de citação. Mas para equipes que lidam com informação sensível e precisam de respostas rastreáveis, é um caminho consideravelmente melhor do que entregar suas perguntas a uma caixa-preta na nuvem.
Comece pequeno: um objetivo, cinco subperguntas, dez fontes. Ajuste o prompt de síntese até que as citações estejam precisas. Depois, escale a profundidade. O agente melhora na mesma medida em que você aprende a interrogá-lo.