Pitch: DoesItClick — como validamos respostas de personas sintéticas
Sou o criador do DoesItClick, uma aplicação que usa modelos de linguagem para explorar como uma mensagem pode ser interpretada por personas sintéticas. Quero compartilhar um recorte da implementação: como separamos uma resposta que o programa consegue processar de uma conclusão em que alguém deveria confiar.
O problema tem duas partes. O modelo pode devolver uma explicação convincente em um formato que quebra o processamento. E pode devolver um JSON perfeitamente válido contendo uma afirmação sem fundamento. Resolver a primeira parte não resolve a segunda.
A pergunta de pesquisa que ajuda a examinar a implementação
No meu preprint Do Synthetic Personas Predict Real Audience Response?, comparei um painel de personas com uma avaliação sem personas, usando variantes de títulos e dados de cliques do Upworthy. Naquele protocolo, a alternativa sem personas ordenou as variantes melhor. O artigo está no arXiv; cito como autor, não como uma avaliação independente do meu produto.
Esse resultado não demonstra que uma persona consegue encontrar objeções úteis. Também não autoriza pegar uma nota produzida pelo modelo e apresentá-la como probabilidade de compra de um visitante. É uma limitação que precisamos manter visível ao interpretar a saída da aplicação.
Uma referência complementar é Persona Generators, de Paglieri e colegas, que distingue explorar uma variedade de perfis de reproduzir a distribuição de uma população. Essa distinção ajuda a formular perguntas; os resultados daquele trabalho não são resultados do DoesItClick.
Não estou afirmando que essas leituras comprovam a arquitetura. Uso as referências para examinar o que cada parte dela permite concluir.
O caminho da mensagem até o resultado
O projeto tem uma interface em Next.js, uma API em Fastify e uma integração com Gemini. O trecho relevante pode ser resumido assim:
Mensagem e configuração da simulação
|
v
API prepara a solicitação ao modelo
|
v
Gemini devolve uma resposta estruturada
|
v
Servidor lê o JSON e valida seus campos
|
+--------+--------+
| |
Resposta válida Erro explícito
|
v
Resultados da simulação e agregação
O desenho acima omite autenticação, armazenamento e outros caminhos da aplicação para destacar a validação. Não é um mapa completo do sistema nem uma confirmação de qual revisão está em produção.
A resposta de uma persona tem campos separados para interpretação da mensagem, dúvidas, objeções e sugestões. No código revisado, o caminho da demonstração e o executor das simulações usam o mesmo módulo de validação. Isso evita manter dois parsers com critérios diferentes para o mesmo tipo de resposta.
Por que validar no servidor se o modelo já recebe um formato?
Na chamada ao provedor, descrevemos a estrutura esperada. No retorno, o servidor ainda aplica JSON.parse e um esquema escrito com Zod, biblioteca de validação de dados. São responsabilidades diferentes: orientar a geração e decidir se a saída recebida pode ser consumida.
O parser aceita JSON estrito. Uma resposta cercada por marcações de Markdown, uma explicação antes do objeto ou aspas incompatíveis com JSON gera uma falha. Os testes também rejeitam valores fracionários em escalas que exigem inteiros.
Uma alternativa seria tentar extrair o primeiro objeto encontrado no texto ou consertar a resposta automaticamente. Esse caminho pode ser útil em outros produtos, mas torna necessário definir o que cada reparo preserva e o que altera. Neste parser, a escolha foi manter a falha explícita em vez de aceitar essas respostas como se tivessem cumprido o contrato.
O custo dessa escolha é rejeitar algumas respostas que uma pessoa conseguiria interpretar. O benefício é ter uma fronteira verificável: o próximo componente recebe o tipo esperado ou recebe uma falha. Isso ainda não garante que o conteúdo seja verdadeiro.
Um exemplo reduzido para executar localmente
Este código é uma versão didática, reescrita para rodar sem dependências. Ele não é o esquema completo da aplicação, não usa Zod e não chama um modelo. Salve como leitura-persona.cjs e rode node leitura-persona.cjs:
const assert = require('node:assert/strict');
function parseLeitura(raw) {
let value;
try { value = JSON.parse(raw); }
catch { return { ok: false, motivo: 'JSON inválido' }; }
const camposValidos = value !== null && !Array.isArray(value)
&& typeof value === 'object'
&& typeof value.leitura === 'string'
&& Array.isArray(value.duvidas)
&& value.duvidas.every(item => typeof item === 'string')
&& Number.isInteger(value.nota)
&& value.nota >= 1 && value.nota <= 5;
return camposValidos
? { ok: true, value }
: { ok: false, motivo: 'Campos inválidos' };
}
const leitura = { leitura: 'Prepara rascunhos para suporte.',
duvidas: ['Quem revisa antes do envio?'], nota: 3 };
const raw = JSON.stringify(leitura);
assert.equal(parseLeitura(raw).ok, true);
assert.equal(parseLeitura('```json\n' + raw + '\n```').ok, false);
assert.equal(parseLeitura(JSON.stringify({ ...leitura, nota: 3.5 })).ok, false);
assert.equal(parseLeitura(JSON.stringify({ ...leitura, duvidas: 'Preço?' })).ok, false);
// A estrutura passa, embora a afirmação abaixo não tenha evidência.
const inventada = { ...leitura, leitura: 'Todos os clientes vão comprar.' };
assert.equal(parseLeitura(JSON.stringify(inventada)).ok, true);
console.log('5 verificações passaram; a última mostra o limite do formato.');
As primeiras verificações examinam estrutura: JSON estrito, tipo dos campos e uma escala inteira. A última é propositalmente incômoda: “Todos os clientes vão comprar” passa porque é uma string. O parser não tem dados sobre clientes para sustentar ou rejeitar essa afirmação.
Acrescentar uma lista de frases proibidas não resolveria a questão geral. Uma afirmação igualmente infundada poderia ser escrita de outra forma. Para examinar relevância e sustentação, precisamos revisar o conteúdo contra a mensagem e, quando a pergunta envolve comportamento de pessoas, coletar evidência adequada com pessoas reais.
O que conferi nesta revisão
Executei os sete testes focados do módulo de validação da aplicação. Todos passaram: um caso válido, três rejeições de notas fracionárias e três rejeições de formatos incompatíveis com JSON estrito. Executei também as cinco verificações do exemplo didático acima.
Esses testes não usaram modelo, banco de dados ou tráfego de visitantes. Demonstram o comportamento desses parsers nos casos descritos; não medem qualidade das sugestões, representatividade das personas, conversão nem o funcionamento completo da aplicação publicada.
No agregador revisado, apenas respostas aceitas pelo parser entram no cálculo; quando nenhuma é válida, ele retorna ausência de agregado. Mesmo quando existe uma média, seu significado continua sendo o de saídas daquela simulação. Repetir chamadas ao modelo não cria uma amostra de compradores reais.
O que ainda precisa ser investigado
Uma pergunta que quero estudar é se as dúvidas geradas com personas ajudam mais na revisão de uma mensagem do que as dúvidas geradas sem elas. Uma comparação possível manteria a mensagem e o modelo fixos e pediria a revisores que examinassem as dúvidas sem conhecer sua origem. Seria necessário definir antes o que conta como uma contribuição útil. É uma proposta, não um estudo concluído.
A lição de implementação que consigo compartilhar agora é mais limitada: uma resposta pode ser aceita pelo esquema e continuar errada. Separar essas duas perguntas ajuda a escrever testes mais honestos e evita usar uma verificação de formato como prova de qualidade do modelo.
Para quem quiser conhecer o projeto citado, o site é doesitclick.ai. O exemplo e a discussão acima podem ser usados sem cadastro. Texto preparado com assistência de IA, conferido contra a implementação e as referências; os exemplos são sintéticos.