1

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.

Carregando publicação patrocinada...
1

Sobre os blocos de Markdown: se a chamada ao Gemini usa responseMimeType: "application/json" junto com o responseSchema, a resposta já vem como JSON puro, sem os ``` em volta. Então quando aparecer resposta cercada no log, eu olharia primeiro se aquele caminho tá mandando essa config, antes de culpar o modelo.

Pra parte do conteúdo tem um meio-termo barato antes da revisão humana: pedir que cada dúvida ou objeção venha com o trecho exato da mensagem a que ela se refere, e conferir no servidor se esse trecho existe mesmo na mensagem. Não prova que a objeção é boa, mas pega objeção sobre coisa que a mensagem nem diz, tipo reclamar de preço num texto que não fala de preço.

0

Excelente reflexão, Alexandre. A distinção que você faz entre 'sintaticamente processável' e 'semanticamente confiável' é, na minha opinião, o maior gargalo da IA generativa em produção hoje.
Trabalho com IA aplicada a conversas (chamadas e WhatsApp) na Action Tec, e esbarramos exatamente nesse muro. É muito fácil fazer um LLM devolver um JSON válido com {"risco": "baixo"}. O desafio real é garantir que essa afirmação não seja uma alucinação e que a conversa de fato não violou uma regra de compliance (ex: o atendente prometeu algo fora do protocolo).
Para lidar com isso no FalaAI, seguimos uma filosofia similar à sua: a validação de formato (JSON/Zod) é apenas a primeira camada de defesa. A segunda camada é uma auditoria semântica específica, onde cruzamos o diálogo com frameworks de risco (como COPC ou regras customizadas) para gerar um score de conformidade e detectar alucinações, em vez de confiar cegamente na saída bruta do modelo.
Seu ponto sobre 'uma resposta pode ser aceita pelo esquema e continuar errada' é a razão pela qual nossa camada de risk_audit retorna não só o JSON, mas um relatório estruturado de por que aquele risco foi atribuído (com trechos da fala como evidência). Isso permite que o sistema tome a decisão com base em dados, não só em um booleano ou string solta.
Como você vê a evolução dessa validação semântica em escala? Estamos curiosos para saber se, na sua experiência com o DoesItClick, encontrar padrões de 'afirmações infundadas' via prompt tem sido mais eficaz do que tentar validar contra uma base de conhecimento externa (RAG).
Parabéns pelo preprint e pela transparência sobre as limitações. É esse tipo de rigor que falta em muitos 'wrappers' de IA por aí.
(Para quem quiser ver como aplicamos essa auditoria semântica em conversas reais, deixamos o playground aberto: https://falaai.action.tec.br/pt/api)