Criação de um agente local de pesquisa profunda com dados brilhantes
Criação de um agente local de pesquisa profunda com dados brilhantes
Aprenda a automatizar a pesquisa na web integrando APIs de coleta de dados, Streamlit e IA rodando na sua própria máquina — e entenda como essa arquitetura se aplica a pesquisa de mercado, auditoria de SEO e inteligência competitiva.
Introdução: a pesquisa profunda saiu do laboratório
Durante anos, "pesquisa profunda" (deep research) foi sinônimo de um processo caro e artesanal: analistas abrindo dezenas de abas, copiando trechos para planilhas, perdendo o fio da meada entre a décima e a vigésima fonte. Os grandes modelos de linguagem deram o primeiro salto ao resumir documentos soltos, mas ainda faltava o essencial — autonomia para navegar, decidir o próximo passo e consolidar um relatório com fontes rastreáveis.
O que mudou agora é a combinação de três peças que, juntas, derrubam a barreira de entrada:
- Infraestrutura de coleta de dados na web (como as APIs e proxies da Bright Data), que resolvem o problema mais chato de qualquer agente: acessar páginas que bloqueiam, limitam ou camuflam conteúdo;
- Modelos de linguagem locais (Ollama, llama.cpp, vLLM), que rodam na sua GPU ou até na sua CPU sem enviar uma única linha de dado sensível para fora;
- Interfaces rápidas de prototipagem (Streamlit), que transformam um script Python em um aplicativo usável em minutos.
Neste guia, vamos construir um agente local de pesquisa profunda do zero. Depois, mostramos como a mesma arquitetura sustenta dois casos de uso que já estão virando rotina em agências e times de produto: a automatização de pesquisa de mercado e a auditoria de SEO em escala.
O que é, afinal, um agente de "pesquisa profunda"?
Um agente de pesquisa profunda não é um chatbot que responde de memória. Ele é um sistema que planeja, busca, lê, avalia e itera até ter material suficiente para responder a uma pergunta complexa. A diferença fundamental em relação a um RAG tradicional está no loop:
- Decomposição: a pergunta original ("como está o mercado de carregadores veiculares no Brasil?") é quebrada em subperguntas.
- Busca: cada subpergunta vira uma consulta em buscadores e fontes específicas.
- Leitura seletiva: o agente abre as páginas mais promissoras e extrai apenas o conteúdo relevante.
- Reflexão: o modelo avalia se o que encontrou é suficiente. Se não for, gera novas consultas — é aqui que a "profundidade" acontece.
- Síntese com citação: o relatório final aponta a fonte de cada afirmação.
Esse ciclo é caro e lento. Um relatório decente pode exigir 30 a 80 requisições HTTP e dezenas de milhares de tokens. É justamente por isso que rodar localmente faz tanto sentido: você troca custo por token por custo fixo de hardware e ganha controle total sobre o pipeline.
Por que rodar o agente na sua própria máquina?
Antes de partir para o código, vale entender as três motivações que aparecem com mais frequência entre quem adota essa abordagem:
1. Privacidade e soberania do dado
Se o agente vai ler contratos, e-mails, relatórios internos ou dados de clientes, enviá-los a uma API de terceiros é um problema jurídico antes de ser um problema técnico. Modelos locais eliminam essa etapa da cadeia.
2. Previsibilidade de custo
Pesquisa profunda queima tokens rápido. Um agente local tem custo marginal próximo de zero depois do investimento inicial em GPU. O gasto variável fica concentrado na camada de coleta de dados — que, em muitos projetos, é a única que vale a pena pagar.
3. Controle sobre o comportamento
Você pode ajustar temperatura, prompt de sistema, número de iterações, quais domínios ignorar e como as citações são montadas. Em APIs fechadas, esse controle é sempre parcial.
O trade-off honesto: modelos locais de porte médio (7B a 14B parâmetros) ainda ficam atrás dos modelos de fronteira em raciocínio longo. A saída é arquitetural — use o modelo local para orquestração, extração e sumarização, e reserve chamadas externas apenas para as etapas que realmente exigem raciocínio pesado.
Arquitetura do agente: as cinco camadas
Um agente de pesquisa profunda bem construído se organiza em camadas independentes. Isso facilita trocar peças sem reescrever tudo:
| Camada | Função | Exemplos de tecnologia |
|---|---|---|
| Coleta | Acessar páginas, contornar bloqueios, buscar em SERPs | Bright Data (Web Unlocker, SERP API, proxies residenciais) |
| Extração | Converter HTML em texto limpo e metadados | Trafilatura, BeautifulSoup, Readability |
| Memória | Armazenar trechos e embeddings para consulta | ChromaDB, FAISS, Qdrant |
| Raciocínio | Planejar, avaliar e redigir | Ollama (Llama 3.1, Qwen 2.5, Mistral) |
| Interface | Entrada de perguntas e saída de relatórios | Streamlit |
Passo 1 — Preparando o ambiente local
Comece instalando o Ollama e baixando dois modelos: um para raciocínio e um para embeddings.
# instalação (Linux/macOS)
curl -fsSL https://ollama.com/install.sh | sh
# modelo de raciocínio (bom equilíbrio entre qualidade e VRAM)
ollama pull llama3.1:8b-instruct-q4_K_M
# modelo dedicado a embeddings
ollama pull nomic-embed-text
Em seguida, o ambiente Python:
python -m venv .venv && source .venv/bin/activate
pip install streamlit requests trafilatura chromadb ollama python-dotenv
Guarde suas credenciais em um arquivo .env — nunca no código:
BRIGHT_DATA_API_KEY=seu_token_aqui
BRIGHT_DATA_SERP_ZONE=serp_api1
BRIGHT_DATA_UNLOCKER_ZONE=web_unlocker1
Passo 2 — A camada de coleta com dados "brilhantes"
Aqui está o gargalo histórico de qualquer scraper: CAPTCHAs, rate limiting, bloqueio por fingerprint de navegador e conteúdo carregado via JavaScript. A proposta das APIs de coleta gerenciada é abstrair isso. Em vez de você manter um exército de proxies e navegadores headless, a requisição sai por uma infraestrutura que já lida com rotação de IP, resolução de desafios e renderização.
Busca em mecanismos de pesquisa (SERP)
import os, requests
from urllib.parse import quote
API_KEY = os.getenv("BRIGHT_DATA_API_KEY")
SERP_ZONE = os.getenv("BRIGHT_DATA_SERP_ZONE")
def buscar_serp(consulta: str, pais: str = "br") -> str:
"""Retorna o HTML da página de resultados para uma consulta."""
url_alvo = f"https://www.google.com/search?q={quote(consulta)}&hl=pt-BR&gl={pais}"
resposta = requests.post(
"https://api.brightdata.com/request",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"zone": SERP_ZONE,
"url": url_alvo,
"format": "raw",
"country": pais,
},
timeout=60,
)
resposta.raise_for_status()
return resposta.text
Leitura de páginas protegidas
UNLOCKER_ZONE = os.getenv("BRIGHT_DATA_UNLOCKER_ZONE")
def ler_pagina(url: str) -> str:
"""Baixa o HTML renderizado de uma URL, contornando bloqueios."""
resposta = requests.post(
"https://api.brightdata.com/request",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"zone": UNLOCKER_ZONE, "url": url, "format": "raw"},
timeout=90,
)
resposta.raise_for_status()
return resposta.text
Ponto de atenção jurídico e ético: coletar dados publicamente acessíveis é diferente de raspar dados pessoais ou contornar paywalls de forma indevida. Documente a finalidade da coleta, respeite termos de uso aplicáveis, a LGPD e mantenha um registro de auditoria das URLs acessadas. Um agente autônomo sem limites de domínio é um risco, não uma vantagem.
Passo 3 — Extração limpa e memória vetorial
HTML bruto é ruído. A extração transforma a página em texto utilizável e guarda de onde ele veio — a rastreabilidade é o que separa um relatório confiável de uma alucinação bem formatada.
import trafilatura
from datetime import datetime
def extrair_conteudo(html: str, url: str) -> dict | None:
texto = trafilatura.extract(html, include_comments=False, include_tables=True)
if not texto or len(texto) < 400:
return None
return {
"url": url,
"texto": texto,
"coletado_em": datetime.utcnow().isoformat(),
"caracteres": len(texto),
}
Para a memória, chunking com sobreposição e embeddings locais:
import chromadb, ollama
cliente = chromadb.PersistentClient(path="./memoria_agente")
colecao = cliente.get_or_create_collection("pesquisa")
def indexar(documento: dict, tamanho: int = 1200, sobreposicao: int = 200):
texto = documento["texto"]
for i in range(0, len(texto), tamanho - sobreposicao):
trecho = texto[i:i + tamanho]
if len(trecho) < 200:
continue
emb = ollama.embeddings(model="nomic-embed-text", prompt=trecho)["embedding"]
colecao.add(
ids=[f'{documento["url"]}#{i}'],
embeddings=[emb],
documents=[trecho],
metadatas=[{"url": documento["url"], "posicao": i}],
)
Esse detalhe é frequentemente subestimado: guardar a URL junto de cada chunk permite que o relatório final traga citações verificáveis, não apenas afirmações soltas.
Passo 4 — O loop de raciocínio do agente
O coração do sistema é um laço com três verbos: planejar, buscar, avaliar. Uma implementação enxuta, sem frameworks pesados, é mais fácil de depurar:
import json, ollama
MODELO = "llama3.1:8b-instruct-q4_K_M"
PROMPT_PLANEJADOR = """Você é um pesquisador sênior. Dada a pergunta do usuário,
gere de 3 a 5 consultas de busca distintas e complementares.
Responda APENAS com um JSON no formato {{"consultas": ["...", "..."]}}.
Pergunta: {pergunta}"""
PROMPT_AVALIADOR = """Com base nos trechos coletados, avalie se a pergunta abaixo
já pode ser respondida com confiança.
Responda APENAS com JSON: {{"suficiente": true/false, "lacunas": "..."}}
Pergunta: {pergunta}
Trechos: {trechos}"""
def planejar(pergunta: str) -> list[str]:
saida = ollama.generate(
model=MODELO,
prompt=PROMPT_PLANEJADOR.format(pergunta=pergunta),
format="json",
)["response"]
return json.loads(saida).get("consultas", [])
def avaliar(pergunta: str, trechos: str) -> dict:
saida = ollama.generate(
model=MODELO,
prompt=PROMPT_AVALIADOR.format(pergunta=pergunta, trechos=trechos[:12000]),
format="json",
)["response"]
return json.loads(saida)
O laço completo fica assim:
def pesquisa_profunda(pergunta: str, max_iteracoes: int = 3) -> dict:
consultas = planejar(pergunta)
visitadas = set()
for iteracao in range(max_iteracoes):
for consulta in consultas:
html_serp = buscar_serp(consulta)
for url in extrair_urls(html_serp)[:5]: # função auxiliar
if url in visitadas:
continue
visitadas.add(url)
doc = extrair_conteudo(ler_pagina(url), url)
if doc:
indexar(doc)
contexto = recuperar_trechos(pergunta, k=12) # busca vetorial
veredito = avaliar(pergunta, contexto)
if veredito.get("suficiente"):
break
consultas = planejar(f"{pergunta}\n\nLacunas: {veredito.get('lacunas')}")
return redigir_relatorio(pergunta, recuperar_trechos(pergunta, k=20))
Três parâmetros merecem ajuste fino:
- Limite de iterações: 3 é um bom ponto de partida. Sem teto, o agente entra em loop e queima recursos.
- Número de fontes por consulta: 5 é razoável; 10 gera muito ruído para modelos locais.
- Janela de contexto enviada ao avaliador: truncar agressivamente reduz alucinação e acelera a resposta.
Passo 5 — A interface em Streamlit
Poucas linhas transformam o script em ferramenta de equipe:
import streamlit as st
st.set_page_config(page_title="Agente de Pesquisa Profunda", page_icon="🔎")
st.title("Agente local de pesquisa profunda")
st.caption("Coleta via API · Raciocínio 100% local · Relatório com fontes")
pergunta = st.text_area(
"O que você quer investigar?",
placeholder="Ex.: quais são os principais desafios regulatórios do mercado de saúde digital no Brasil?",
)
with st.sidebar:
st.header("Parâmetros")
max_it = st.slider("Iterações máximas", 1, 6, 3)
dominios_bloqueados = st.text_input("Domínios a evitar (separados por vírgula)")
st.divider()
st.metric("Fontes indexadas", len(colecao.get()["ids"]))
if st.button("Pesquisar", type="primary") and pergunta:
with st.status("Planejando consultas...", expanded=True) as status:
st.write("Buscando em mecanismos de pesquisa...")
st.write("Lendo e indexando páginas...")
st.write("Avaliando lacunas e redigindo relatório...")
resultado = pesquisa_profunda(pergunta, max_it)
status.update(label="Pesquisa concluída", state="complete")
st.markdown(resultado["relatorio"])
with st.expander("Fontes consultadas"):
for url in resultado["fontes"]:
st.markdown(f"- {url}")
Vale acrescentar um botão de exportação para Markdown ou PDF e um cache (@st.cache_data) nas chamadas de coleta — evita rebaixar a mesma página duas vezes na mesma sessão.
Caso de uso 1 — Pesquisa de mercado automatizada
Times de produto e agências usam esse tipo de agente para responder, de forma recorrente, perguntas que antes exigiam dias de trabalho manual:
- Inteligência competitiva: monitorar mudanças de posicionamento, lançamentos e preços de concorrentes a partir de páginas públicas.
- Análise de percepção de marca: consolidar avaliações, fóruns e menções públicas em temas recorrentes.
- Mapeamento regulatório: acompanhar publicações oficiais e traduzir mudanças em impactos operacionais.
- Tamanho de mercado aproximado: cruzar dados de associações setoriais, relatórios públicos e notícias recentes.
O ganho real não está em substituir o analista, e sim em entregar a ele um primeiro rascunho com fontes no lugar de uma tela em branco. Segundo avaliações públicas de ferramentas desse tipo, os ganhos de produtividade relatados giram em torno de 40% a 70% no tempo de produção do primeiro draft — desde que haja revisão humana obrigatória.
Um ajuste importante para pesquisa de mercado é versionar os relatórios. Rodando o agente semanalmente e guardando os resultados, você constrói uma série temporal capaz de revelar tendências que uma leitura isolada jamais mostraria.
Caso de uso 2 — Auditoria de SEO local em escala
Praticamente toda agência tem alguém abrindo uma planilha e visitando, uma por uma, as URLs de cada cliente para conferir title, meta description, H1, canibalização e dados estruturados. É trabalho repetitivo, mecânico e caro — o candidato perfeito para automação.
A arquitetura é a mesma que construímos acima, com uma camada de leitura em navegador para capturar o DOM já renderizado por JavaScript:
- O agente recebe uma lista de URLs (sitemap do cliente ou lista de páginas-alvo).
- A camada de coleta baixa o HTML renderizado de cada página.
- Um parser extrai os elementos técnicos:
<title>,meta description, hierarquia de headings,canonical,hreflang, esquema de dados estruturados, links internos quebrados. - Regras determinísticas marcam violações objetivas (título acima de 60 caracteres, H1 duplicado, meta ausente).
- O modelo local entra apenas onde há julgamento: avaliar se o título descreve bem a página, se o conteúdo responde à intenção de busca e se há canibalização entre páginas.
O resultado é um relatório priorizado por impacto, com capturas de evidência e sugestões acionáveis. Um processo que consumia uma tarde inteira passa a rodar em poucos minutos, e — mais importante — deixa de depender de alguém lembrar de checar.
Boas práticas, custos e limitações
Hardware: o que esperar
- 8 GB de VRAM: confortável para modelos 7B–8B em quantização Q4. Suficiente para orquestração.
- 12–16 GB de VRAM: abre espaço para 14B quantizados, com salto perceptível na qualidade da síntese.
- Macs com memória unificada (16 GB+): rodam bem o suficiente para uso individual.
- Somente CPU: funciona, mas espere algo entre 5 e 20 tokens por segundo. Viável para lotes noturnos, frustrante para interação.
Erros comuns que arruínam um agente de pesquisa
- Confiar no resumo do modelo em vez do texto-fonte. Guarde sempre o trecho original ao lado da síntese.
- Não limitar iterações. Agentes sem teto gastam tempo e dinheiro perseguindo irrelevâncias.
- Indexar tudo sem filtro de qualidade. Conteúdo duplicado e SEO-spam contaminam a base vetorial.
- Ignorar a data de coleta. Informação de 2019 sobre um mercado volátil é pior que nenhuma informação.
- Não registrar o que foi acessado. Sem trilha de auditoria, você não consegue defender o relatório diante de um cliente.
Onde o modelo local ainda sofre
Raciocínio multi-etapas muito longo, matemática financeira precisa e síntese de dezenas de documentos simultâneos continuam sendo pontos fracos de modelos na faixa de 8B–14B. Estratégias que funcionam: dividir o relatório em seções independentes, usar o modelo local para gerar estrutura e um modelo maior apenas para a redação final, e aplicar validação por regras sempre que a resposta puder ser checada programaticamente.
Conclusão: o diferencial não é o modelo, é o encanamento
A corrida pelos melhores modelos esconde o fato de que, na prática, o que separa um agente de pesquisa medíocre de um realmente útil é a camada de dados. Um modelo excepcional com conteúdo bloqueado, desatualizado ou mal extraído produz relatórios bonitos e inúteis. Um modelo modesto operando sobre dados limpos, atualizados e rastreáveis entrega valor real.
E é exatamente aí que a combinação proposta faz sentido: coleta robusta na nuvem, raciocínio e memória na sua máquina, interface em Streamlit. Você paga pela parte difícil (acessar a web de verdade), mantém sob controle a parte sensível (seus dados e seu raciocínio) e prototipa rápido o suficiente para descobrir, em uma sexta-feira, se a ideia funciona.
Se este texto serviu de ponto de partida, o próximo passo é pequeno e concreto: suba o Ollama, indexe dez páginas de um tema que você já domina e compare o relatório do agente com o que você escreveria manualmente. A diferença entre os dois vai dizer, em minutos, exatamente quais peças do seu encanamento precisam ser trocadas.