Sintetizando reviews sem enviesar: como resumir sentimento real em meio a manipulação
Você abre a planilha. São 8.342 reviews do produto, três anos de histórico, distribuição puxada para 4 e 5 estrelas. O time de produto quer um resumo executivo até sexta. O caminho fácil é jogar tudo no Claude e pedir "me resume isso aí".
O resumo sai. Bonito. Bem escrito. E enganoso.
Neste post você vai construir um pipeline em quatro estágios para sintetizar centenas (ou milhares) de reviews respeitando a distribuição real de estrelas, detectando padrões de manipulação e separando crítica estrutural de ruído. Útil para o time de UX entender onde o produto dói de verdade, e para o time de compliance dormir tranquilo sob a Consumer Reviews and Testimonials Rule da FTC, que entrou em vigor em outubro de 2024 e já gerou as primeiras warning letters em dezembro de 2025.
TL;DR
- O que é: pipeline de síntese de reviews resistente a manipulação e a viés do próprio LLM.
- Stack/Modelos: Claude (Opus ou Sonnet), embedding model (Voyage, OpenAI ou local), Postgres com pgvector, classificadores leves para sinais comportamentais.
- Custo/Acesso: centavos por mil reviews em modelos médios; o gargalo é engenharia, não inferência.
- Referência técnica: paper Beyond Stars: Bridging the Gap Between Ratings and Review Sentiment with LLM (arXiv:2509.20953) e o guia effective context engineering for AI agents da Anthropic.
Por que "joga no LLM e resume" não funciona
Resumir review é um problema clássico que parece resolvido pela chegada do contexto longo. Não é.
Primeiro, LLM não é neutro como sumarizador. Trabalhos recentes mostram que modelos atuais supergeneralizam o conteúdo original em 26 a 73% dos casos ao resumir texto técnico. Quando o LLM atua como avaliador, 48,4% dos vereditos são revertidos só com a inversão da ordem das opções. Isso é positional bias puro. Tem também familiarity bias (preferência por texto de baixa perplexity), anchoring em multi-aspecto e scoring bias por ordem das rubricas. Joga 8 mil reviews no prompt e o resumo herda essas distorções.
Segundo, estrelas mentem. O paper Beyond Stars mostra empiricamente que rating numérico e sentimento textual divergem em apps reais, e que o gap é explorável tanto por quem manipula quanto por quem audita. Resumir só pelo texto perde a estrela. Resumir só pela estrela perde o porquê.
Terceiro, a base está contaminada. A Trustpilot removeu 4,5 milhões de reviews falsos em 2024, 7,4% do total submetido, 90% removidos automaticamente, com checagem de cerca de 200 mil reviews por dia. A Amazon bloqueou mais de 275 milhões de reviews suspeitos antes da publicação em 2024, usando deep graph neural networks para mapear grupos de bad actors. Se você está consumindo dados de qualquer marketplace ou agregador, parta do princípio: tem ruído manipulado dentro.
Quarto, e mais importante para quem opera no Brasil mas vende para fora: a regra da FTC pune até US$ 53.088 por violação ao usar reviews falsos, comprados ou gerados por IA sem ressalva. O texto da regra cobre AI-generated reviews explicitamente. "A gente só fez um resuminho com IA" não é defesa.
Conclusão: o problema não é "como resumir". É como construir um pipeline que filtra, estratifica, sintetiza e audita, nessa ordem.
Pré-requisitos e ferramentas
Antes de codar, garanta:
- [ ] Base de reviews com
id,author_id,rating,text,created_at,verified_purchase(ou equivalente). - [ ] Acesso a um LLM com structured output (Claude com tool use, ou OpenAI com response_format).
- [ ] Embedding model para detecção de similaridade (qualquer um serve; Voyage tem boa relação custo/qualidade).
- [ ] Postgres 15+ com extensão pgvector instalada, ou outro vector store.
- [ ] Ambiente Laravel/PHP 8.3+ ou Python 3.11+. Os exemplos abaixo usam pseudocódigo agnóstico, mas os snippets concretos vão em PHP/Laravel.
Sem verified_purchase ou equivalente, você ainda consegue rodar. Só perde um sinal forte.
O pipeline em quatro estágios
A regra é: nenhum estágio confia no anterior. Cada etapa registra evidência rastreável até o review original. Isso é o que diferencia "resumo bonito" de "resumo defensável".
Estágio 1: saneamento e detecção de manipulação
Antes de resumir, você precisa decidir o que não entra no resumo. Três famílias de sinais:
Sinais comportamentais (baratos, rápidos):
- Burst de reviews: vários reviews para o mesmo produto numa janela curta. O paper sobre time burst characteristics mostra que spammers contratados causam picos identificáveis estatisticamente.
- Autor com volume anômalo (N reviews em 24h, sempre 5 estrelas, sempre texto curto).
- Rating divergente da média do produto além de X desvios padrão, combinado com texto genérico.
-- Detecta janelas de burst em SQL puro: reviews dentro de 1h
-- com mais de 10 entradas e variancia de rating baixa
SELECT product_id,
date_trunc('hour', created_at) AS bucket,
COUNT(*) AS n,
VAR_POP(rating) AS rating_var,
AVG(rating) AS rating_avg
FROM reviews
GROUP BY product_id, bucket
HAVING COUNT(*) > 10 AND VAR_POP(rating) < 0.3
ORDER BY n DESC;
Sinais textuais (médio custo):
- Similaridade alta entre reviews (cosine > 0.92 em embeddings) sugere template ou copia-cola.
- Stylometric: a Trustpilot usa classificadores estilométricos para estimar probabilidade de geração por LLM, e diz claramente que o sinal isolado é moderado. Combinado com metadata, fica forte.
- Perplexity e burstiness: texto gerado por LLM tem distribuição estatística diferente do humano. Sinal ruidoso, não use sozinho.
LLM-as-a-judge (caro, só para borderline):
Para reviews que ficaram em zona cinza nos sinais acima, manda para o LLM com schema rígido. Cuidado com positional bias: randomize a ordem dos campos na entrada.
// Laravel + Prism para classificacao binaria com structured output
use Prism\Prism\Prism;
use Prism\Prism\Enums\Provider;
$schema = [
'type' => 'object',
'properties' => [
'authentic' => ['type' => 'boolean'],
'confidence' => ['type' => 'number'],
'reason' => ['type' => 'string'],
],
'required' => ['authentic', 'confidence', 'reason'],
];
$response = Prism::structured()
->using(Provider::Anthropic, 'claude-sonnet-4-6')
->withSchema($schema)
->withSystemPrompt(file_get_contents(resource_path('prompts/judge_authenticity.md')))
->withPrompt("REVIEW:\n{$review->text}\n\nMETADATA:\nrating={$review->rating}\nauthor_review_count={$author->review_count}")
->generate();
$verdict = $response->structured;
Output do estágio 1: cada review ganha um authenticity_score em [0,1] e flags. Quem fica abaixo do threshold sai do pool de síntese, mas não some. Vai para uma trilha de auditoria separada (você precisa explicar depois por que ignorou).
Estágio 2: amostragem estratificada
Aqui está o erro mais comum: pegar uma amostra aleatória de N reviews e mandar para o LLM resumir.
Se 70% dos seus reviews são 5 estrelas e 5% são 1 estrela, amostragem uniforme entrega exatamente o mesmo viés. Pior: as críticas estruturais, geralmente em reviews de 1 a 3 estrelas, viram minoria estatística no prompt. O resumo sai cor-de-rosa.
A solução é trivial e subutilizada: estratifique por rating, garantindo um piso por estrato. Trabalho recente sobre stratified sampling para validação de relevance judgements de LLM mostra que a estratégia mantém garantias estatísticas com fração do esforço humano.
// Amostragem estratificada com piso por estrato
function stratifiedSample(Collection $reviews, int $perStratum = 30): Collection {
return $reviews
->groupBy('rating')
->map(fn ($group) => $group->random(min($perStratum, $group->count())))
->flatten(1);
}
// Estratifique tambem por periodo (ultimos 90d vs historico)
// e por verified_purchase quando disponivel.
Para bases muito grandes, estratifique em duas dimensões: rating × período (últimos 90 dias vs. resto). Você quer captar regressão de qualidade que não aparece se diluir tudo no histórico.
Estágio 3: síntese map-reduce com calibração
Mesmo com amostra limpa e estratificada, jogar tudo num prompt único é frágil. Use map-reduce summarization: resume por estrato no map, agrega no reduce. Anthropic descreve a mesma ideia no guia de context engineering: compaction de chunks antes da agregação final.
Map (por estrato): extrai aspectos com sentimento e cita evidência.
// Prompt do estagio "map": extracao de aspectos por estrato
$mapPrompt = <<<MD
Voce esta analisando reviews de UM ESTRATO especifico (rating=$rating, periodo=$period).
NAO generalize para o produto inteiro. NAO infira o que "a maioria pensa".
Sua tarefa: extrair aspectos mencionados, sentimento por aspecto e os IDs dos reviews que sustentam cada afirmacao.
Aspectos esperados (nao exaustivo): preco, entrega, qualidade, durabilidade, suporte, instrucoes.
Saida JSON:
{
"stratum": {"rating": $rating, "period": "$period", "n_reviews": $n},
"aspects": [
{
"name": "string",
"sentiment": "positive|negative|mixed",
"frequency": int,
"evidence_ids": [int, int, ...],
"representative_quote": "string curta extraida literal"
}
]
}
REVIEWS:
$reviewsBlock
MD;
Reduce: agrega os outputs do map ponderando pelo tamanho real de cada estrato na população, não pelo tamanho da amostra. Isso é o que evita que 30 reviews 5★ amostrados pesem igual aos 30 reviews 1★ amostrados quando a população real é 70/5.
// Pseudo-reduce: pondere pela populacao, nao pela amostra
foreach ($mapOutputs as $stratum => $output) {
$weight = $populationCount[$stratum] / $totalPopulation;
foreach ($output['aspects'] as $aspect) {
$aggregated[$aspect['name']]['weighted_sentiment'] +=
sentimentScore($aspect['sentiment']) * $weight;
$aggregated[$aspect['name']]['evidence_ids'] =
array_merge($aggregated[$aspect['name']]['evidence_ids'] ?? [], $aspect['evidence_ids']);
}
}
O LLM no reduce não cria afirmação nova. Só agrega e prioriza. Toda claim final aponta para evidence_ids rastreáveis.
Estágio 4: auditoria
Esse estágio é o que diferencia engenharia de demo. Três checagens automáticas antes do resumo virar slide:
- Coerência de distribuição. A polaridade média do resumo deve bater com a distribuição de estrelas. Se a base é 35% 1-2★ mas o resumo final fala em "tom geralmente positivo", trava o pipeline. Isso é o gap que o Beyond Stars ataca em direção contrária; aqui você usa como sanity check.
- Ancoragem. Toda frase do resumo final aponta para pelo menos N IDs de review reais. Frase órfã é frase alucinada. Rejeita.
- Trilha de compliance. Loga: quais reviews foram filtrados, por qual sinal, com qual score; quais foram amostrados; qual foi o prompt do map e do reduce; qual modelo, qual versão, qual seed. A FTC não exige esse trail hoje, mas o histórico mostra que sempre que regulação aperta, quem tem log dorme melhor.
// Audit log minimo: grava na hora da sintese, nao depois
DB::table('review_synthesis_audit')->insert([
'synthesis_id' => $id,
'model' => 'claude-sonnet-4-6',
'filter_threshold' => 0.7,
'filtered_count' => $filteredCount,
'sampled_ids' => json_encode($sampledIds),
'population_distribution' => json_encode($popDist),
'sample_distribution' => json_encode($sampleDist),
'final_summary_hash' => hash('sha256', $summary),
'created_at' => now(),
]);
Limitações e pontos de atenção
Onde isso falha, e onde você se queima se não souber:
- Stylometric isolado é fraco. Reviews falsos bem escritos (humanos pagos) passam por classificador estilométrico. A própria Trustpilot diz que o sinal só vira forte quando combinado com metadata. Não confie em "detector de IA".
- LLM-as-a-judge tem positional bias forte. O paper sobre inconsistência de evaluators mostra 48,4% de inversão por troca de ordem. Randomize, faça self-consistency, ou use modelo separado do que gera o resumo.
- Burst nem sempre é fraude. Lançamento, viralização ou mídia ganham burst legítimo. Trate burst como sinal, não como sentença.
- Compliance vai além de FTC. No Brasil tem CDC e ANPD; na UE tem o Digital Services Act tratando de manipulação de plataforma. O pipeline aqui te dá rastreabilidade. Não te dá assessoria jurídica.
- Map-reduce não é grátis. O reduce ainda pode alucinar, principalmente em domínio especializado. Mantenha humano no loop para resumos que viram comunicação externa.
Quer construir produto de verdade com IA?
O dev que ganha mercado em 2026 não é o que sabe escrever prompt bonito. É o que sabe transformar IA em feature defensável: pipeline com filtro, amostragem, evidência e auditoria. Engenharia, não mágica.
Esse é o foco do Clã Beer & Code: formar dev PHP/Laravel para construir produto real com IA, RAG, agentes, busca semântica, recomendação, evals e síntese auditável. Se você caiu aqui porque já sentiu o problema na pele, entra para a comunidade e vamos discutir o seu caso concreto.
FAQ rápido
Como detectar reviews gerados por LLM em escala? Combine três sinais: estilometria (perplexity, burstiness), metadata (IP, device, timing) e similaridade entre reviews via embeddings. Sinal isolado é moderado; o conjunto é o que segura. Veja a arquitetura pública da Trustpilot como referência.
Vale rodar isso em tempo real ou em batch? Batch noturno resolve 95% dos casos e é dramaticamente mais barato. Real-time só para sinais óbvios (burst, autor já conhecido por fraude, similaridade > 0.95 com review existente). Síntese executiva é processo de produto, não evento de millisegundo.
Posso usar pgvector para detectar reviews quase idênticos?
Sim, e é o caminho mais barato. Embedde os reviews, indexe com vector_cosine_ops, faça ORDER BY embedding <=> $query LIMIT 10. Threshold prático: cosine distance < 0.08 indica template provável. Veja a doc do pgvector para o índice HNSW.
E se a base for pequena, tipo 200 reviews? Pula a amostragem estratificada e usa a base inteira no map-reduce. O estágio 1 (saneamento) e o estágio 4 (auditoria) continuam valendo. O 2 só faz sentido quando você não cabe num único prompt confortavelmente.
Conclusão
Resumir review virou commodity. Resumir review sem enviesar é o que diferencia time que vira produto de time que vira incidente jurídico. O pipeline aqui não é a única arquitetura possível. É uma base que separa as quatro responsabilidades que ninguém deveria misturar: filtrar manipulação, garantir representatividade, sintetizar com evidência, auditar a saída.
Próximo passo natural: trocar o LLM-as-a-judge por uma camada com evals automáticos rodando em CI toda vez que você muda o prompt do reduce. Resumo de review é feature de produto. Trate como código.
{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.
Você também pode gostar
LLM-as-a-Judge: avaliação automatizada do seu agente de ofertas sem abrir planilha
Como montar um juiz LLM que pontua cada resposta do agente contra uma rubrica objetiva: preço correto, link válido, sentimento de review coerente. Você sai do achismo e transforma iteração em ciclo mensurável.
Alucinação em e-commerce é caro: quando a IA inventa especificação, cupom e estoque
Air Canada, DPD e Chevrolet mostraram em escala global o custo de deixar o LLM virar fonte de verdade no atendimento. Especificação inventada, cupom que não existe, estoque que não bate — vira chargeback, processo e dano de marca. O caminho técnico passa por retrieval grounded e tool use validando cada promessa.
Chatbot não é agente: o teste dos 3 turnos que separa brinquedo de produto
Três perguntas simples sobre um produto real — preço hoje, reviews recentes, disponibilidade no CEP — quebram qualquer chatbot cru. O que separa brinquedo de produto não é o modelo. É o harness: a camada que transforma um LLM em agente confiável, com tool use, estado e validação contra o mundo real.
Top-10 da busca não é top-10 do usuário: por que a SERP bruta sabota seu agente
A primeira página do Google não foi feita pra alimentar agente de IA. Ela foi feita pra ranquear sites. E essas duas coisas, em 2026, não são mais a mesma coisa. Plugar a SERP bruta no seu agente é amplificar SEO spam, MFA e conteúdo gerado por IA na escala. Veja por que o top-10 da busca não é o top-10 do usuário e como montar um pipeline de filtros + rerank que devolve confiança ao seu agente.