Como usar o Jev para moderar conteúdo enviado por usuários (passo a passo)

esteira de Jev moderação de conteúdo classificando textos enviados por usuários
Resposta rápida

Usar o Jev na moderação de conteúdo enviado por usuários é montar uma esteira em três partes: classificar o texto com perguntas tipadas (choice para categoria, score para severidade, noul para checagens sim/não), usar o campo confidence para separar bloqueio automático de revisão humana e salvar a distribuição de probabilidades como justificativa da decisão. O Jev é o primeiro modelo System One da TypeSafe AI, está em early access e devolve decisão probabilística em vez de texto livre. O modelo atual é o jev-1.13.0 e a entrada custa US$ 0,042 por milhão de tokens, com saída gratuita

Fala aí! Moderar conteúdo de usuário pedindo pra um LLM "responder se isso é tóxico" é receita pra dor de cabeça

Tu recebe um texto solto, parseia na unha, torce pra não vir um "Claro! Analisando o comentário…" na frente do JSON, e no fim não tem nenhum número pra dizer o quanto o modelo estava seguro daquilo

O Jev ataca exatamente esse ponto: é o primeiro modelo público da classe System One da TypeSafe AI, está em early access, e devolve decisão tipada e probabilística em vez de texto

Ou seja: tu pergunta "qual categoria de violação?" e recebe a opção escolhida, a probabilidade de cada opção e um número de confiança. É isso que transforma moderação em esteira automatizável

Neste post a gente monta essa esteira inteira: classificação em categorias tipadas, portão de confiança separando bloqueio automático de revisão humana, e registro do motivo da decisão pra auditoria. Bora? 🙂

O que você precisa antes de começar

Antes do código, o básico:

  • Chave da API da TypeSafe exposta na variável de ambiente TYPESAFE_API_KEY (o cliente oficial lê ela sozinho)
  • SDK Python: pip install typesafe-sdk (ou uv add typesafe-sdk), exigindo Python 3.10 ou superior
  • SDK JavaScript/TypeScript: npm install @typesafe-ai/sdk
  • Ou nenhum SDK: dá pra chamar direto com um POST em https://api.typesafe.ai/v1/systemone, com header Authorization: Bearer <chave> e Content-Type: application/json

O modelo atual é o jev-1.13.0, e existem dois aliases: jev-latest (release estável, padrão do SDK) e jev-preview

E aqui vem a limitação que define o escopo do seu projeto de moderação: o campo state, que é onde tu manda o conteúdo a ser avaliado, aceita só texto

Pode ser uma string, um objeto JSON ou um array de valores de texto. Imagem, áudio e vídeo não são suportados

Traduzindo pra vida real: tu não joga o arquivo do upload no Jev. Tu moderna o que existe em texto ao redor dele (nome do arquivo, legenda, descrição, título, transcrição que outro serviço gerou)

Tome cuidado com isso na hora de prometer "moderação de imagem" pro time de produto 😛

Passo a passo: montando a esteira de moderação

A lógica é sempre a mesma: você descreve o conteúdo no state, escreve as perguntas no objeto questions (cada uma com um id escolhido por você), e lê as respostas de volta por esse mesmo id

Domine o Jev e coloque decisões de IA dentro do seu sistema
Pré-inscrição Curso Jev

Domine o Jev e coloque decisões de IA dentro do seu sistema

Você vai aprender a usar o Jev, o System One Model da TypeSafe AI, pra automatizar decisões com resposta tipada e confiança medida, sem depender de chat nem de alguém revisando cada passo. Entre na lista de espera para garantir a condição de lançamento!

  1. Modelar o conteúdo do usuário no campo state

Não manda só o texto cru. Como o state aceita objeto JSON, aproveita e manda o contexto que um moderador humano olharia

state = {
    "tipo": "comentario",
    "autor_id": "u_8213",
    "texto": "vc é um lixo de dev, some daqui e não volta mais",
    "contexto": "resposta em thread de post sobre carreira",
    "idioma": "pt-BR"
}

O erro comum deste passo: mandar o texto sem contexto nenhum e depois reclamar que o modelo classificou errado uma ironia entre amigos. O Jev só conhece o estado que você envia, ele não busca nada por fora e não tem histórico do seu produto

  1. Escrever a pergunta de categoria com type: choice

No choice, o criteria é um mapa de opção para descrição da rubrica. É a sua política de moderação virando código

from typesafe_sdk import Choice, TypeSafeClient

client = TypeSafeClient()

categoria = Choice(
    instructions="Qual a principal violação de política neste conteúdo enviado por usuário?",
    criteria={
        "toxicidade": "Ofensa direta, xingamento ou linguagem degradante",
        "assedio": "Ataque repetido ou direcionado a uma pessoa específica",
        "spam": "Divulgação em massa, link promocional ou texto repetido",
        "fraude": "Golpe, promessa de ganho fácil ou identidade falsa",
        "conselho_perigoso": "Orientação que pode causar dano físico, financeiro ou de saúde",
        "dado_pessoal": "Expõe telefone, e-mail, documento ou endereço de alguém",
        "ok": None
    }
)

Repara no None na última opção: o criteria aceita null quando a opção dispensa detalhe. E o limite é de 255 opções por Choice, então o teto tá bem longe da sua política

O erro comum deste passo: escrever rubricas que se sobrepõem ("ofensivo" e "agressivo" como opções separadas, por exemplo). Se você não consegue separar as duas em uma frase, o modelo também não vai conseguir, e isso aparece depois como confiança baixa em tudo

  1. Adicionar severidade com type: score

Categoria diz o que é, severidade diz o quanto dói. No score o criteria é um array ordenado de níveis descritos em texto, do extremo baixo pro alto

"severidade": {
  "type": "score",
  "instructions": "Qual a gravidade desta violação para a política da plataforma?",
  "criteria": [
    "Sem violação: conteúdo normal da comunidade",
    "Leve: tom ácido ou rude, mas dentro da política",
    "Média: ofensa clara, merece aviso ao usuário",
    "Alta: ataque direcionado, golpe ou dano potencial imediato"
  ]
}

São de 2 a 10 níveis, e o número do nível é a posição no array começando em 0. No exemplo acima, "Alta" é o score 3, não 4

O erro comum deste passo: montar a escala e depois comparar com >= 1 achando que o primeiro nível é 1. Já vi gente perder uma tarde nisso

  1. Adicionar checagens binárias com noul

O noul responde uma pergunta sim/não devolvendo um único número entre 0 e 1: a probabilidade de a resposta ser sim

"tem_dado_pessoal": {
  "type": "noul",
  "instructions": "O conteúdo expõe dado pessoal de terceiro (telefone, e-mail, documento ou endereço)?"
}

E a resposta vem assim:

{ "type": "noul", "noul": 0.95 }

Atenção que o noul não carrega o campo confidence. Faz sentido: o próprio número já é a probabilidade. Se o seu roteamento depende de confidence, ele não funciona em resposta de noul, e isso é um KeyError/AttributeError esperando pra acontecer em produção

Resumo dos três tipos de pergunta:

Tipo Pergunta que responde Formato do criteria O que volta
choice Qual categoria é? mapa opção para descrição (null permitido), até 255 opções opção escolhida, probabilities por opção e confidence
score Qual o nível disso? array ordenado, de 2 a 10 níveis, numerados a partir de 0 score, probabilities por nível e confidence
noul Sim ou não? pergunta sim/não um número de 0 a 1, sem confidence
  1. Agrupar tudo na mesma chamada

Você pode mandar várias perguntas na mesma requisição. Todas veem o mesmo state, são avaliadas em paralelo e de forma independente, e os três tipos podem ser misturados numa chamada só

curl -X POST https://api.typesafe.ai/v1/systemone \
  -H "Authorization: Bearer $TYPESAFE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "state": {"tipo": "comentario", "texto": "vc é um lixo de dev, some daqui"},
    "model": "jev-latest",
    "questions": {
      "categoria": { "type": "choice", "instructions": "...", "criteria": { "toxicidade": "...", "ok": null } },
      "severidade": { "type": "score", "instructions": "...", "criteria": ["...", "..."] },
      "tem_dado_pessoal": { "type": "noul", "instructions": "..." }
    }
  }'

Só não esqueça do orçamento de contexto do jev-1.13 quando tu agrupa: 64k cobrindo o state mais todas as perguntas somadas, e 32k aplicados ao state mais a pergunta mais longa

O erro comum deste passo: rubrica gigante. Se cada uma das suas 10 perguntas tem meia página de critério, o teto de 64k chega bem mais rápido do que você imagina, ainda mais se o state for uma avaliação longa

  1. Ler a resposta pelo id

Cada resposta volta sob o id que você escolheu. Com o SDK Python fica direto assim:

resposta = client.system_one(
    state=state,
    questions={"categoria": categoria}
)

print(resposta.answers["categoria"].choice)
print(resposta.answers["categoria"].confidence)
print(resposta.answers["categoria"].probabilities)

E o payload completo da API traz três coisas: o model que atendeu, o objeto answers e um objeto usage com a contagem de tokens

{
  "model": "jev-1.13.0",
  "answers": { "categoria": { "type": "choice", "choice": "toxicidade", "confidence": 0.78, "probabilities": { "toxicidade": 0.85, "assedio": 0.15, "ok": 0.0 } } },
  "usage": { "input_tokens": 296, "output_tokens": 20 }
}

O erro comum deste passo: ler só o .choice e jogar o resto fora. O probabilities e o confidence são justamente o que faz a esteira funcionar sem humano no meio de tudo…

Como usar o confidence para separar bloqueio automático de revisão humana

Agora a parte que muda o jogo

O que é o confidence? É um número de 0 a 1 calculado a partir de como a probabilidade se distribui entre as opções da própria resposta

Se a distribuição tem um pico claro em uma opção, a confiança é alta. Se ela tá espalhada entre várias opções, a confiança é baixa

É como olhar pra cara de um revisor humano: ele cravou na hora ou ficou 20 segundos coçando a cabeça? O confidence é esse "coçar a cabeça" virando número

E lembra: resposta de noul não tem esse campo, porque o próprio valor já é a probabilidade

O padrão de portão de confiança

A documentação oficial descreve esse padrão no roteamento com portão de confiança: cada ação tem seu próprio limiar, conforme o risco de agir sobre uma classificação errada

Os números que aparecem por lá: piso de confiança de 0,6 para mandar caso incerto a um humano, e ações de alto risco exigindo confiança acima de 0,85

Tem também um exemplo de código na doc que roteia pra revisão humana quando answer.confidence < 0.8, e segue pro handler automático caso contrário

Na prática, combinando com a severidade, a esteira fica assim:

resposta = client.system_one(state=state, questions=questions)

categoria = resposta.answers["categoria"]
severidade = resposta.answers["severidade"]

if categoria.choice == "ok" and categoria.confidence > 0.85:
    publicar(state)
elif severidade.score >= 3 and categoria.confidence > 0.85:
    bloquear(state, resposta)          # ação de alto risco: só com confiança alta
elif categoria.confidence < 0.6:
    fila_humana(state, resposta)       # incerto demais pra automatizar
else:
    avisar_usuario(state, resposta)    # ação de baixo risco no meio da curva

Repara que bloquear é a ação cara. Bloquear errado gera ticket de suporte, print no Twitter e usuário nervoso. Avisar errado gera um dar de ombros

Por isso o limiar de bloqueio é o mais alto e o de aviso é frouxo

Quanto isso melhora de verdade?

Tem um dado bem concreto no cookbook de classificação por confiança

Um corte de confiança em 0,9 dividiu 60 documentos ao meio: a metade confiante acertou 90% das vezes e a outra metade acertou 40%

Ou seja: a média geral escondia dois mundos completamente diferentes. Um automatizável e um que precisava de gente

E tem um truque legal no mesmo cookbook: reportando um nível acima na hierarquia (uma categoria mais ampla em vez da específica), aqueles 40% viram 70%

Faz sentido, né? O modelo pode estar em dúvida entre "assédio" e "toxicidade" mas ter certeza absoluta de que não é ok. Se a sua ação só precisa do nível amplo, a dúvida do nível específico não te atrapalha

O alerta que ninguém pode pular

Calibração não é acurácia

As probabilidades são otimizadas contra resultados pra refletir incerteza de verdade: resultados com probabilidade 0,8 devem ocorrer cerca de 80% das vezes ao longo de muitas previsões

Mas isso vale para grupos de previsões, não pra uma resposta isolada. Não existe garantia de que aquele bloqueio específico, com confiança 0,92, está certo

É estatística, não oráculo. Trate os limiares como política de risco do seu produto, não como verdade absoluta

Como registrar o motivo da decisão de moderação

Aqui mora a diferença entre uma esteira que você defende numa reunião e uma que você reza pra ninguém perguntar

Pra cada item moderado, salve no seu banco:

  • o id da pergunta e as instructions daquela versão da rubrica
  • o choice escolhido (ou o score, ou o valor do noul)
  • o mapa completo de probabilities
  • o confidence
  • o campo model retornado (não o alias que você mandou: o alias jev-latest muda, o jev-1.13.0 que voltou na resposta não)
  • o objeto usage com input_tokens e output_tokens
log_moderacao.insert({
    "item_id": item_id,
    "model": resposta.model,
    "rubrica_versao": "politica-v3",
    "categoria": categoria.choice,
    "categoria_probabilities": categoria.probabilities,
    "categoria_confidence": categoria.confidence,
    "severidade": severidade.score,
    "acao": acao_tomada,
    "usage": resposta.usage,
    "criado_em": agora()
})

Por que a distribuição inteira e não só a decisão? Porque a distribuição é a justificativa

O Jev não gera texto livre e não explica o raciocínio dele. Ele não escreve um parecer bonitinho dizendo "bloqueei porque o usuário usou o termo X"

O motivo, no mundo System One, é o formato da probabilidade: 0,85 em toxicidade contra 0,15 em assédio conta uma história bem diferente de 0,40 contra 0,38

Um caso desses você defende. O outro você reconhece na hora que deveria ter ido pro humano

E tem o uso mais valioso do log: recalibrar os cortes depois

Com umas semanas de histórico, você cruza os casos que o humano reverteu com o confidence que eles tinham, e descobre o ponto exato onde a automação começa a errar no SEU conteúdo. Aí você mexe no limiar com dado, não com achismo

O erro comum aqui: guardar só "bloqueado: true". Três meses depois chega a pergunta "por que esse comentário foi derrubado?" e você não tem absolutamente nada pra responder

Outro ponto de honestidade: a responsabilidade desse registro é da sua aplicação. Monte o log do seu lado e não conte com nada além do que a resposta da API te devolve

Onde essa esteira encaixa: comentários, avaliações e uploads

Moderação e trust and safety estão no mapa oficial de casos de uso do Jev, e a receita descrita por lá é exatamente essa: detectar toxicidade, assédio, spam, fraude, conselho perigoso, exposição de dados pessoais, pedido de descadastramento e alegação que viola política, combinando severidade e confiança pra decidir entre permitir, avisar, revisar ou bloquear

Na prática, três recortes bem diferentes:

Comentários

Volume alto, texto curto, briga rápida. Aqui a categoria toxicidade e assédio faz o trabalho pesado

Bloqueio automático só na faixa de alta confiança. O resto vai pra fila ou leva aviso, porque comentário curto é cheio de ironia e gíria regional, que é onde a confiança naturalmente cai

Avaliações de produto

Muda o inimigo: aqui é spam, fraude e alegação que viola política (aquela avaliação prometendo cura milagrosa, por exemplo)

Avaliação carrega dinheiro junto, então derrubar avaliação legítima machuca o vendedor e derrubar avaliação falsa protege o comprador. Cenário clássico de fila humana no meio da curva, com automação só nas duas pontas

Uploads

Lembra da limitação: o state não aceita mídia. O que dá pra moderar é o texto associado ao upload

Nome do arquivo, título, descrição, legenda, tags, e transcrição gerada por outro serviço. Dá pra pegar muita coisa só por aí, mas seja honesto no escopo: isso não é análise do arquivo em si

Testando os casos de fronteira

O cookbook de autoconsistência com choices usa justamente um caso de moderação: um post limítrofe passa por uma rubrica repetida várias vezes pra medir a estabilidade da decisão

O setup de lá: rubrica de 8 perguntas do tipo Choice respondidas em uma única chamada, com 15 repetições por condição (modelo mais configuração)

É um teste que vale ouro antes de ligar bloqueio automático: pega os 20 conteúdos mais ambíguos que seu time já discutiu, roda repetido, e vê se a decisão balança. Se balançar muito, o problema tá na rubrica, não no modelo

E se as suas categorias têm nível amplo e nível específico (tipo "violência" contendo "ameaça direta" e "apologia"), existe um cookbook oficial de classificação hierárquica na documentação da TypeSafe pra esse desenho

Levando a esteira para produção: custo, limites e integrações

Custo

O Jev cobra por token de entrada, e os tokens de saída são gratuitos: US$ 0,042 por 1 milhão de tokens de entrada e US$ 0,00 por milhão de tokens de saída

O mesmo preço aparece listado em provedor externo para o jev-1.13: $0.042/M input e $0.00/M output

Pra estimar, use o usage que volta em cada resposta: pega o input_tokens médio dos seus itens, multiplica pelo volume que você modera no mês e joga esse total contra o preço por milhão de tokens de entrada

Faça a sua conta com o seu input_tokens real, que a rubrica pesa: cada critério que você escreve entra na conta em toda chamada

Limites de taxa

Os limites são medidos em tokens por segundo e requisições por minuto. Estourar qualquer um dos dois devolve 429 Too Many Requests

Então trate 429 como caminho esperado, não como exceção: retry com backoff e fila. Moderação costuma vir em rajada (post viralizou, chuva de comentário), e é bem aí que o 429 aparece

Versão assíncrona

O SDK Python tem cliente assíncrono, que é o que você quer num worker processando fila:

from typesafe_sdk import AsyncTypeSafeClient, Choice

client = AsyncTypeSafeClient()

async def moderar(state, questions):
    resposta = await client.system_one(state=state, questions=questions)
    return resposta.answers

Outras portas de entrada

O Jev também pode ser chamado pelo AI Gateway da Vercel, com o identificador typesafe-ai/jev, usado pela API de evaluation do AI SDK

E se você vibe coda com agente, a TypeSafe distribui uma agent skill oficial com o contexto da API. No Claude Code:

claude plugin marketplace add typesafe-ai/skills
claude plugin install typesafe@typesafe-ai

Depois é só invocar com /typesafe:typesafe-ai

Em outros agentes, o caminho é:

npx skills add typesafe-ai/skills --skill typesafe-ai

Isso ajuda bastante, porque o agente passa a conhecer o formato de questions e para de alucinar campo que não existe 🙂

E aqueles números gigantes de performance?

A TypeSafe reporta até 193,6x mais rápido e 444,6x mais barato que LLMs

Só que esses números vêm de avaliações internas da própria empresa, sobre workflows montados pelo time deles. Serve como indicação da proposta do modelo, não como benchmark neutro do seu caso

Medir no seu volume, com o seu conteúdo, continua sendo tarefa sua

Vídeo: manipulando conteúdo na tela com JavaScript

Depois que a decisão sai da esteira, alguém precisa mostrar isso na interface: o aviso, a tarja de bloqueio, o badge de "em revisão"

Pra começar do zero com manipulação de conteúdo na tela, este vídeo do canal mostra textContent e innerHTML na prática:

Conclusão: comece pela rubrica, depois ajuste os limiares

Recapitulando a esteira inteira:

  1. Classificar tipado: choice pra categoria, score pra severidade, noul pras checagens sim/não, tudo na mesma chamada contra o mesmo state
  2. Portar por confiança: limiar por ação conforme o risco, com bloqueio automático só na faixa alta e fila humana quando o modelo ficou na dúvida
  3. Registrar o motivo: distribuição completa, confidence, model e usage salvos do seu lado, porque o Jev não escreve parecer, ele devolve probabilidade

O próximo passo é bem chato e bem necessário: escreva a rubrica de categorias do seu produto

Depois pega um lote de conteúdo real que já foi moderado à mão, roda a esteira em cima dele e compara. É esse lote que te diz onde colocar os cortes de confiança

Só liga o bloqueio automático depois disso. Nunca antes

E lembra que o Jev está em early access, então vale acompanhar a documentação da API de perto, que coisa nova aparece

Até o próximo post! 😀

Perguntas frequentes

Quanto custa rodar moderação de conteúdo com o Jev em produção?

O Jev cobra US$ 0,042 por milhão de tokens de entrada, e os tokens de saída são gratuitos (US$ 0,00 por milhão). Esse mesmo valor aparece listado para o jev-1.13 em provedor externo. Como o state costuma ser curto (um comentário, uma legenda), o custo por item de moderação tende a ficar bem baixo.

Qual limiar de confiança usar pra decidir entre bloqueio automático e revisão humana?

Não existe um número único: a documentação oficial descreve um portão de confiança em que cada ação tem o seu próprio limiar, conforme o risco de agir sobre uma classificação errada. O piso é 0,6, ou seja, abaixo disso o caso incerto vai pra revisão humana. No topo ficam as ações de alto risco (como banir usuário), que só rodam com confiança acima de 0,85. Entre esses dois extremos ficam os cortes das ações intermediárias, que você define: o exemplo de código da documentação, por exemplo, manda pro humano quando answer.confidence < 0.8 e segue pro handler automático acima disso. A régua é sempre a mesma: quanto mais cara a ação, mais alto o limiar

Dá pra combinar categoria, severidade e checagem binária na mesma requisição de moderação?

Sim, você pode enviar várias perguntas no mesmo objeto questions, misturando choice, score e noul na mesma chamada. Todas as perguntas veem o mesmo state e são avaliadas em paralelo, de forma independente. Cada resposta volta sob o id que você escolheu pra pergunta, então dá pra ler categoria, severidade e dado_pessoal do mesmo resultado.

O que significa quando o Jev responde com confiança alta mas a decisão está errada?

Calibração não é a mesma coisa que acurácia. As probabilidades do Jev são otimizadas pra refletir incerteza ao longo de muitas previsões: um grupo de respostas com probabilidade 0,8 deve acertar cerca de 80% das vezes. Isso não garante que uma resposta isolada esteja certa, por isso o portão de confiança existe pra pegar os casos duvidosos antes de agir sozinho.

Dá pra usar o Jev na moderação sem instalar nenhum SDK?

Dá. A API System One é chamada com um POST em https://api.typesafe.ai/v1/systemone, com header Authorization: Bearer <chave> e Content-Type: application/json. No corpo você manda o state, o model e o objeto questions, e recebe de volta o model, o objeto answers e o usage com a contagem de tokens. Se preferir SDK, tem o Python (pip install typesafe-sdk, exigindo Python 3.10 ou superior) e o JavaScript/TypeScript (npm install @typesafe-ai/sdk)




Escrito por | Matheus Battisti

Matheus Battisti
Fundador da Hora de Codar

Programador apaixonado pelo mundo das tecnologias, sempre buscando em aprender e se aprofundar em linguagens, frameworks e o que mais for necessário para executar um bom trabalho. Agora tem uma nova missão que é de passar seu conhecimento adiante para formar novos programadores e especializar mais os que já são.

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Claude Code

Formação Claude Code

Domine Claude Code do absoluto zero até o avançado

  • 120 aulas
  • 4 projetos
  • 9h 45min

Blog | Mais populares