Pesquisa profunda com citações (Deep research with citations) é um estudo de caso real de sistema com LLM, do grupo Dados e documentos. Investigar uma pergunta aberta em dezenas de fontes e entregar um relatório citado.
Uma consultoria de estratégia faz dezenas de pesquisas de mercado por mês: tamanho de mercado, concorrentes, regulação, tendências. Cada uma leva dois a três dias de um analista júnior, a maior parte lendo fontes e organizando anotações.
O produto que o cliente paga é um relatório em que cada afirmação tem uma fonte que dá para abrir. Número sem fonte não entra. Trecho que não está na página citada não entra. O analista júnior hoje faz esse trabalho com dez abas no navegador, um documento de notas e muito copiar e colar.
O objetivo é um sistema que recebe uma pergunta aberta (“como está o mercado de energia solar residencial no Nordeste?”) e entrega, em minutos, um relatório em que toda afirmação tem uma fonte verificável. O analista passa a revisar e aprofundar, em vez de começar do zero. Nordeste, aqui, é a região Nordeste do Brasil: sol forte, programas estaduais e regras federais de geração distribuída que mudam o retorno de um telhado solar.
Isso não é um luxo de redação. Em Mata v. Avianca (2023), advogados nos Estados Unidos protocolaram uma petição com jurisprudência inventada pelo ChatGPT. O tribunal descobriu, e o episódio virou aula de citação falsa. Um relatório de mercado com fonte que não diz o que o texto afirma é o mesmo defeito, vendido por hora.
O que este cenário mede: afirmações com fonte válida, tempo por relatório e custo por relatório.
Versão 1
A equipe de inovação montou a primeira versão com um framework de agentes e cinco papéis. A configuração parecia uma equipe humana:
flowchart LR
Q(["1. Pergunta"]):::io --> P(["2. Pesquisador"]):::llm
P --> W["Busca web"]:::untrusted
W --> P
P --> A(["3. Analista"]):::llm
A --> R(["4. Redator"]):::llm
R --> V(["5. Revisor"]):::llm
V --> E(["6. Editor"]):::llm
E --> OUT(["7. Relatório"]):::io
A pergunta chega.
O pesquisador faz buscas em sequência e escreve um resumo com tudo que leu.
a 6. Analista, redator, revisor e editor reescrevem o texto em fila, cada um lendo o anterior.
O relatório sai.
Na demo, a pergunta era estreita (“qual a alíquota do ICMS na Bahia para GD?”). O pesquisador achou uma página, os outros quatro reescreveram, o relatório saiu em poucos minutos e soou profissional. Ninguém mediu uma pergunta ampla, ninguém conferiu se as URLs abriam, ninguém pôs teto de custo.
O guia “Building effective agents”, da Anthropic (dezembro de 2024), recomenda começar pelo fluxo mais simples que resolve a tarefa e só acrescentar agentes quando há um motivo técnico. Cinco cargos na fila é o contrário disso.
O que quebrou
Semana 1. Um relatório levava 25 minutos. O pesquisador fazia uma busca, lia, fazia outra, lia. Nada rodava em paralelo. “Tamanho de mercado” e “regulação da ANEEL” não dependem um do outro. A fila indiana somava as latências.
Semana 2. O custo por relatório era várias vezes o esperado. O trace mostrou que analista, redator e revisor reliam o texto inteiro e devolviam versões quase iguais. Remover três dos cinco agentes não mudou a qualidade no eval. O editor respondia “aprovado”. Você estava pagando quatro reescritas para um único trabalho de busca.
Semana 3. O pesquisador acumulava dezenas de páginas no mesmo contexto. Quando o redator recebia o resumo, fatos de fontes diferentes estavam misturados, e citações apontavam para a fonte errada. Uma frase sobre a Lei 14.300/2022 (marco legal da geração distribuída no Brasil) saía ligada a um post de blog de 2020. O analista júnior teria aberto os dois links. O sistema não abriu nenhum.
Semana 4. Uma pergunta ampla (“e o hidrogênio verde no mundo?”) fez o pesquisador buscar por mais de uma hora. Não havia limite de passos nem de custo. A pesquisa custou mais que o valor cobrado pelo relatório. O cliente recebeu um rascunho incompleto, e a fatura do provedor chegou inteira.
Os quatro incidentes não pedem um modelo melhor. Pedem menos agentes, mais estrutura e um teto.
Cinco cargos, três deles só reescrevendo. O YAML da versão 1 é o trecho que mostra o smell: papéis de empresa, fluxo sempre sequencial, ferramentas só no primeiro. Causou a semana 2.
Buscas independentes em sequência. O pesquisador só começava “concorrentes” depois de terminar “mercado”. Causou a semana 1. Paralelismo aqui não é otimização precoce: as subperguntas já eram independentes no briefing do cliente.
Todas as fontes no mesmo texto, sem estrutura. O pesquisador escrevia um resumo em prosa. O redator citava de memória do resumo. Causou as citações erradas da semana 3. Quem mistura páginas num parágrafo único perde o dono de cada frase.
Primeiro o teto: passos, tempo e custo por relatório, compartilhados entre todos os agentes. No limite, o relatório sai marcado como parcial, com as lacunas listadas. Parar com exceção muda deixa o analista sem texto e o financeiro sem teto.
O orçamento é um só para a árvore. Teto de 20 passos por subagente, com três subagentes que disparam outros três, não é teto. É o smell vizinho de subagentes em cascata.
Analista, revisor e editor saíram. Ficou um orquestrador que planeja e escreve, e a revisão de fontes virou checagem em código. Quem não tem ferramenta e só reescreve prosa não justifica uma chamada.
Cada subpergunta é investigada por um subagente com contexto próprio, que devolve afirmações estruturadas com a fonte de cada uma. As páginas lidas ficam com ele. O orquestrador não recebe HTML. Recebe linhas no quadro: afirmação, URL, trecho, data de acesso.
Isso é isolamento de contexto, não “mais um cargo”. O motivo técnico é a janela: dezenas de páginas no mesmo histórico misturam fontes e empurram o trecho certo para o meio, de novo o efeito de Liu et al. (2023).
Os subagentes rodam ao mesmo tempo. Mercado, concorrentes e regulação não esperam um ao outro. O tempo do relatório deixa de ser a soma das leituras e passa a ser a mais lenta, mais o plano e a escrita.
Arquitetura final
flowchart TD
Q(["1. Pergunta"]):::io --> O(["2. Orquestrador planeja"]):::llm
O --> S1(["3. Subagente: mercado"]):::llm
O --> S2(["3. Subagente: concorrentes"]):::llm
O --> S3(["3. Subagente: regulação"]):::llm
S1 --> B[("4. Quadro de afirmações com fonte")]:::store
S2 --> B
S3 --> B
B --> C["5. Checar citações"]:::tool
C --> O2{"6. Lacunas e orçamento?"}
O2 -->|"lacuna, há orçamento"| O
O2 -->|"pronto ou limite"| W(["7. Escrever relatório"]):::llm
W --> OUT(["8. Relatório citado"]):::io
A pergunta chega com um orçamento de passos, tempo e custo.
O orquestrador divide a pergunta em subperguntas e define o que conta como resposta suficiente. É Planejar e Executar mais Orquestrador e Trabalhadores: o plano depende da pergunta, não de cinco cargos fixos.
Subagentes pesquisam em paralelo, cada um num contexto próprio, com busca e leitura de páginas. Conteúdo da web entra como não confiável: a página pode pedir no HTML que o agente ignore o briefing.
Cada subagente grava afirmações estruturadas (texto curto, fonte, trecho que sustenta) num quadro compartilhado. O quadro tem schema e dono por linha. Não é um bloco de notas em prosa.
O código confere se o trecho citado existe na página e se a afirmação está nele, com uma checagem automática.
O orquestrador vê o quadro, decide se faltam pontos e se ainda há orçamento. Condições de Parada são três: suficiente, limite atingido, lacuna declarada.
O relatório é escrito a partir das afirmações conferidas, não das páginas. O escritor não visita a web.
O relatório sai com cada afirmação ligada à fonte, e marcado como parcial se parou no limite.
Quando o modelo de relatório é estável (sempre mercado, concorrentes, regulação, riscos), Seccionamento fixa as seções e as roda em paralelo sem o orquestrador decidir a divisão. Quando a pergunta muda a forma (“compare três editais da ANEEL”), o orquestrador monta o plano. Os dois padrões convivem: seções fixas para o produto recorrente, plano livre para o briefing atípico.
Map-Reduce aparece quando uma única fonte é enorme: um relatório da EPE de 200 páginas, lido por capítulos, cada capítulo devolvendo afirmações, o reduce juntando sem duplicar. Isso é varrer um documento. Não substitui subagentes em temas diferentes.
Exemplo trabalhado: solar residencial no Nordeste
A pergunta chega: “como está o mercado de energia solar residencial no Nordeste?”. O orçamento do job é um teto de passos, um teto de minutos e um teto de dinheiro, definidos pelo preço que a consultoria cobra daquele tipo de relatório. Os valores exatos ficam no código de referência, não nesta página.
O orquestrador devolve um plano, não um rascunho:
{ "suficiente_quando": [ "marco legal federal identificado com fonte primária", "pelo menos uma regra da ANEEL citada com trecho", "dois concorrentes ou programas estaduais com fonte", "uma tendência recente com data" ], "subperguntas": [ "Qual o marco legal da geração distribuída no Brasil?", "O que a ANEEL define hoje para micro e minigeração?", "Quais programas ou restrições aparecem em estados do Nordeste?", "Quem são os players visíveis no residencial?" ]}
Três subagentes sobem em paralelo. O de regulação busca “Lei 14.300 geração distribuída” e abre o texto em Planalto, não um resumo em blog. Ele grava:
{ "id": "c-014", "afirmacao": "A Lei 14.300/2022 instituiu o marco legal da microgeração e minigeração distribuída no Brasil.", "url": "https://www.planalto.gov.br/ccivil_03/_ato2019-2022/2022/lei/l14300.htm", "trecho": "Institui o marco legal da microgeração e minigeração distribuída", "acessado_em": "2026-10-02", "subagente": "regulacao"}
A Lei 14.300/2022 existe e o trecho acima está no ementa oficial. É o tipo de afirmação que o sistema deve preferir: fonte primária, trecho curto, data de acesso. O que o sistema não faz é inventar gigawatts instalados no Ceará ou um market share. Se a fonte com número não passou na checagem, o número não entra no relatório.
O verificador baixa a URL (ou usa o HTML que o subagente já guardou), procura o trecho como substring, e recusa a linha se não achar. Parafrase longa demais falha de propósito: o subagente precisa copiar um pedaço que dá para conferir.
O orquestrador olha o quadro. Falta “tendência recente com data”. Ainda há orçamento: ele dispara um quarto subagente só nisso. Se o teto estourar antes, o relatório sai parcial, com a lacuna explícita. O analista júnior vê o buraco. Ele não recebe um parágrafo fluido que esconde a ausência.
A escrita final só vê linhas com status: conferido. HTML da web não entra nesse contexto. Injeção indireta na página de um concorrente pode tentar mandar o subagente “ignorar o briefing”. O subagente não tem ferramenta de enviar email nem de alterar o relatório final. Ele só grava linhas no quadro, e o quadro só aceita o schema.
Como a citação é conferida
flowchart LR
L[("1. Linha do quadro")]:::store --> D["2. Baixar URL"]:::untrusted
D --> V{"3. Trecho na página?"}
V -->|não| X["4. Recusar linha"]:::tool
V -->|sim| K["4. Marcar conferido"]:::tool
K --> W(["5. Escritor lê só conferidas"]):::llm
A linha chega com URL e trecho literal.
O fetch é dado não confiável: a página pode ter mudado ou ser hostil.
Substring, em código. Não é um modelo dizendo “parece verdadeiro”.
Falhou: a linha não alimenta o relatório. Passou: status conferido.
O escritor nunca vê página. Vê afirmações já ancoradas.
Essa checagem é o antídoto de Mata v. Avianca. O modelo é bom em produzir texto que parece jurisprudência, paper ou nota da ANEEL. Abrir a URL é barato perto do prejuízo de um cliente que decide com número fantasma.
Armadilhas comuns
Organograma de agentes. Pesquisador, analista, redator, revisor, editor. Se só um tem ferramenta, você comprou reescrita cara. O post da Anthropic (dez. 2024) é direto: complexidade se justifica com ganho medido.
Resumo único com todas as páginas. É rascunho compartilhado. A citação escorrega. Afirmação estruturada é o contrato.
Pesquisa sem teto. “Até ficar bom” não é condição de parada. Bom precisa de critério no plano, e o cartão precisa de limite.
Juiz LLM no lugar do fetch. Um segundo modelo que dá nota à citação reproduz a fluência da invenção. A prova é o trecho na página.
Subagente que devolve prosa. Se a saída não cabe num schema, o quadro vira de novo um bloco de notas, e a semana 3 volta.
Paralelizar o que tem dependência. Não peça “compare com o número da seção 2” antes da seção 2 existir. Paralelismo é para subperguntas independentes.
O eval que segura as refatorações
O conjunto de perguntas tem briefing real anonimizado e, para cada um, uma lista de afirmações que um analista marcou como obrigatórias, com a URL que ele mesmo abriu. O eval não pede o relatório “bonito”. Pede: cada afirmação obrigatória apareceu com fonte que passa no verificador? O relatório saiu dentro do teto de tempo e de custo? As recusas (lacunas) estão declaradas, e não escondidas em prosa segura?
Um caso do eval tem esta cara:
{ "id": "solar-ne-014", "pergunta": "Como está o mercado de energia solar residencial no Nordeste?", "obrigatorias": [ { "tema": "marco legal GD", "fonte_tipo": "lei", "exemplo_aceitavel": "planalto.gov.br ... lei/l14300" } ], "proibidas": ["afirmação numérica sem URL conferida"]}
A versão 1 foi medida nesse conjunto antes das fusões. Sem isso, “remover três agentes” seria opinião. Com isso, foi o eval da semana 2.
Números
As métricas deste cenário são afirmações com fonte válida, tempo por relatório e custo por relatório, comparando a versão 1 com a arquitetura final num conjunto de perguntas de pesquisa com revisão humana das fontes.
As três se puxam. Tempo mínimo com um só agente sequencial reproduz a semana 1. Custo mínimo sem busca reproduz Mata v. Avianca: texto fluido, fonte nenhuma. Fonte válida em 100% das linhas, sem teto, reproduz a semana 4.
A leitura certa é: das afirmações que saíram no relatório, quantas passaram no verificador de trecho; quanto tempo até o PDF; quanto a execução custou em relação ao preço cobrado. Relatório parcial com lacuna declarada conta como parada correta, não como falha de qualidade.
Os números ainda não foram publicados. Eles virão do código de referência executável, rodando as duas versões contra as mesmas perguntas, e esta seção será atualizada com o resultado e o script que gera os números. Até lá, nenhum número aparece aqui.
O subagente decide a próxima busca a partir do que leu
A lista completa de padrões ligados a este cenário está em Relações, abaixo.
Código de referência
O sistema completo, com as duas versões, as perguntas do eval e o script que gera os números desta página, fica no repositório de código de referência do ai-guru, em scenarios/deep-research/. O repositório está sendo preparado, e o link entra aqui quando for publicado.
Lições
Divida por contexto, não por cargo. Os subagentes que ficaram têm motivo técnico: cada um isola a leitura de uma subpergunta. Analista e revisor na fila não isolavam nada.
Afirmação estruturada é melhor que resumo. Gravar “afirmação, fonte, trecho” acabou com a mistura de fontes. O relatório passou a ser um reduce de linhas conferidas.
Conferir a citação em código muda o jogo. O relatório deixou de ter fontes que não dizem o que o texto afirma. Mata v. Avianca é o que acontece quando a conferência é “parece verdadeiro”.
Teto de custo é requisito de produto. Uma pesquisa precisa caber no preço do relatório. Sem teto, a execução mais cara ganha da venda.
Paralelismo é onde o tempo cai. As subperguntas eram independentes desde o começo; só faltava rodar ao mesmo tempo. Fila de reescritores não é paralelismo.
Teste seu entendimento
Perguntas rápidas
O que é Pesquisa profunda com citações?
Pesquisa profunda com citações (Deep research with citations) é um estudo de caso real de sistema com LLM, do grupo Dados e documentos. Investigar uma pergunta aberta em dezenas de fontes e entregar um relatório citado.
Quais padrões de IA o cenário Pesquisa profunda com citações usa?