Como usar o Jev para categorizar o catálogo de produtos do seu e-commerce

Fluxo do Jev categorizando catálogo de produtos com Choice, Noul e Score
Resposta rápida

O Jev é o primeiro modelo System One da TypeSafe AI: em vez de texto livre, ele devolve decisão tipada com probabilidade por opção, e os valores válidos saem do schema da pergunta. Pra categorizar catálogo de produtos com o Jev, a receita é quebrar a decisão em pedaços: um Choice com a lista completa de categorias (até 255 opções, mais uma de escape), Noul pras flags sim/não e Score pras rubricas ordenadas, tudo numa chamada só. Aí você lê response.answers[id] e grava tipado no banco, mandando os itens de baixa confiança pra revisão humana.

Todo catálogo de e-commerce tem aquele título que ninguém quer categorizar na mão: "kit 3 camiseta masculina algodão premium tam G"

É kit ou item unitário? É vestuário masculino ou é "kits e combos"? O tamanho é atributo ou faz parte do nome? 😅

Categorizar errado custa caro: o produto some do filtro, cai na busca errada, vai pro frete errado e ninguém descobre até o cliente reclamar

O Jev é o primeiro modelo System One da TypeSafe AI, e ele resolve exatamente esse tipo de problema: em vez de cuspir um parágrafo que você depois tem que parsear, ele devolve decisões tipadas com probabilidades calibradas, e os valores válidos são definidos ANTES, no schema da pergunta

Ou seja: o modelo não inventa categoria nova, ele escolhe entre as suas

Neste post vou montar o passo a passo de categorização de catálogo, da montagem do state até a saída tipada que entra direto no banco, incluindo o que fazer com os itens que o modelo não teve certeza

Bora?

O que você precisa antes de começar

Antes do código, o básico:

  • Acesso ao modelo: o Jev saiu do stealth em 15/09/2026 e segue em early access, com lista de espera em typesafe.ai. A TypeSafe vai liberando desenvolvedores em ondas, então esse é o primeiro passo mesmo
  • Chave de API exportada na variável de ambiente TYPESAFE_API_KEY, que é o que o cliente lê sozinho
  • SDK: em Python é pip install typesafe-sdk (requer Python >= 3.10). Em JavaScript/TypeScript é npm install @typesafe-ai/sdk, com Node 20 ou superior
  • Modelo: o padrão do cliente Python é o alias jev-latest, e a versão corrente é a jev-1.13, publicada em 18/09/2026. Dá pra fixar um ID versionado no campo model se você quiser estabilidade

Os repositórios oficiais são o typesafe-sdk-python e o typesafe-ai/typesafe-sdk-js, e o pacote publicado no PyPI é o typesafe-sdk

Essas duas aqui valem ler DUAS vezes antes de arquitetar o pipeline

Primeira: o state aceita só texto. String, objeto JSON ou array de textos

Imagem, áudio e vídeo não são suportados

Traduzindo: a foto do produto não entra na decisão. Se o título é ruim e a descrição é vazia, o Jev tem pouco com o que trabalhar, e isso não é culpa do modelo

Segunda: o orçamento de contexto é de 64.000 tokens para state mais todas as perguntas, com um limite separado de 32.000 tokens para o state mais a pergunta mais longa

Então despejar a descrição HTML inteira do fornecedor no state é pedir dor de cabeça

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!

Passo a passo: categorizando o catálogo com o Jev

A ideia central é uma só: não faça uma pergunta gigante, faça várias pequenas

Se você conhece aquele papo de função pequena que faz uma coisa só, é exatamente a mesma cabeça aqui, só que pra decisão

  1. Monte o state a partir da linha do produto

O state é o contexto que o modelo lê. Pra catálogo, o natural é montar um JSON com os campos que você já tem no banco

import os
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

client = TypeSafeClient()  # lê TYPESAFE_API_KEY do ambiente

produto = {
    "titulo": "kit 3 camiseta masculina algodao premium tam G",
    "descricao": "Kit com 3 camisetas basicas 100% algodao penteado 30.1...",
    "marca": "Generica",
    "fornecedor_categoria": "MODA / MASC / BASICOS",
}

state = produto

Repara que eu mandei até a categoria porca do fornecedor. Ela é bagunçada, mas é sinal, e sinal ruim ainda é sinal 🙂

O erro comum deste passo: jogar a descrição completa com HTML, tabela de medidas e política de troca dentro do state. Você estoura orçamento de contexto à toa e ainda enche a decisão de ruído. Limpe o HTML e corte o que é boilerplate da loja

  1. Quebre a categorização em decisões menores

Categorizar produto não é UMA pergunta, são várias:

  • qual categoria
  • é kit ou unitário
  • é infantil
  • precisa de bateria
  • quão detalhado está o anúncio

Cada uma dessas vira uma primitiva. E tudo que você pergunta ao Jev é uma de três: Choice, Score e Noul

Choice pra escolher entre opções nomeadas, Score pra posição numa rubrica ordenada e Noul pra afirmação sim/não

O erro comum deste passo: tentar resolver categoria, subcategoria, atributos e flags num Choice só com 900 combinações. Além de estourar o limite de opções, você perde a probabilidade individual de cada decisão, que é justamente o que dá o controle depois

  1. Primeiro Choice: a categoria, com a lista COMPLETA

Um Choice aceita até 255 opções e cada opção custa poucos tokens

Por isso a própria doc recomenda passar a lista completa de categorias em vez de montar um shortlist antes. Deixa o modelo ver todo mundo

E tem um detalhe que salva catálogo: a doc recomenda incluir uma opção de escape, tipo other ou "nenhuma das anteriores", quando a lista pode não cobrir toda entrada

Além disso, instructions, opções de Choice, níveis de Score e critérios de Noul aceitam estrutura JSON. A descrição de uma opção pode ser um objeto dizendo o que ela cobre e o que NÃO cobre

Isso é ouro pra desambiguar fronteira de categoria

categoria = Choice(
    criteria="Em qual categoria do catalogo este produto se encaixa?",
    options={
        "vestuario_masculino": {
            "cobre": "camisetas, camisas, calcas e bermudas adulto masculino",
            "nao_cobre": "roupa infantil, calcados, acessorios, moda intima",
        },
        "vestuario_infantil": {
            "cobre": "roupas para bebes e criancas",
            "nao_cobre": "roupa adulta tamanho P",
        },
        "calcados": {
            "cobre": "tenis, sapatos, sandalias, chinelos",
            "nao_cobre": "meias, palmilhas, produtos de limpeza de calcado",
        },
        # ... o resto da sua taxonomia aqui
        "outros": "nao se encaixa em nenhuma das opcoes acima",
    },
)

O Choice devolve uma probabilidade para cada opção, mais a opção de maior pontuação, mais um campo de confiança derivado da concentração da distribuição

O erro comum deste passo: esquecer a opção de escape. Sem ela, o produto esquisito é empurrado à força pra categoria menos errada, e você nunca descobre que ele existe

  1. Atributos e flags: Noul e Score

Noul recebe uma afirmação sim/não e devolve um único número: a probabilidade de ela ser verdadeira

Ele não tem campo de confiança separado, porque a própria probabilidade JÁ é a resposta. Perto de 0,5 é o modelo dizendo "não faço ideia"

Score devolve uma posição nos níveis que você define, calculada como média ponderada por probabilidade

perguntas = {
    "categoria": categoria,
    "eh_kit": Noul(
        criteria="O anuncio vende mais de uma unidade do mesmo item em um pacote (kit/combo)"
    ),
    "eh_infantil": Noul(
        criteria="O produto e destinado a bebes ou criancas"
    ),
    "precisa_bateria": Noul(
        criteria="O produto precisa de pilha ou bateria para funcionar"
    ),
    "qualidade_anuncio": Score(
        criteria="Quao completo esta o anuncio para publicar sem revisao",
        levels=[
            "so o titulo, sem atributos",
            "titulo e descricao curta, faltam atributos",
            "titulo, descricao e alguns atributos",
            "completo: titulo, descricao, material, tamanho e composicao",
        ],
    ),
}

Esse Score de "qualidade do anúncio" é de graça em termos de arquitetura e paga sozinho: ele vira a sua fila de enriquecimento de catálogo

A mesma cabeça de rubrica ordenada aparece quando você usa o Jev para qualificar leads, só muda o objeto da decisão

O erro comum deste passo: escrever Noul com afirmação ambígua, tipo "o produto é bom". Noul é afirmação verificável no texto, não opinião

  1. Junte tudo numa chamada só

Aqui tá o pulo do gato

Todas as perguntas de uma mesma chamada são avaliadas em paralelo e isoladamente contra o mesmo state. O state é lido UMA vez, e adicionar perguntas quase não muda o tempo de resposta

response = client.system_one(
    state=state,
    questions=perguntas,
)

No cookbook oficial de perguntas paralelas, juntar 13 perguntas em uma chamada levou 0,27 s contra 2,71 s de 13 chamadas separadas, sem mudar as respostas: 12,2x mais barato e 10,0x mais rápido

O erro comum deste passo: fazer um for em cima das perguntas e disparar uma requisição por atributo. Funciona, mas você paga a leitura do state N vezes e ainda se aproxima do rate limit sem necessidade

  1. Leia tipado e grave tipado

As respostas saem em response.answers, indexadas pelo ID da pergunta, com a propriedade choice, score ou noul conforme a primitiva

categoria_final = response.answers["categoria"].choice
confianca_categoria = response.answers["categoria"].confidence

eh_kit = response.answers["eh_kit"].noul > 0.8
qualidade = response.answers["qualidade_anuncio"].score

linha = {
    "sku": produto_id,
    "categoria": categoria_final,
    "categoria_confianca": confianca_categoria,
    "flag_kit": eh_kit,
    "score_qualidade": qualidade,
}

Não tem regex de saída, não tem "o modelo respondeu com markdown de novo", não tem json.loads dentro de try/except rezando pra dar certo

A categoria já vem como uma das chaves que VOCÊ definiu, o que significa que um INSERT direto é seguro

O erro comum deste passo: gravar só o rótulo e jogar a confiança fora. Sem esse número você não consegue calibrar nada depois, e vai ter que reprocessar o catálogo inteiro. Guarda a confiança, sempre

Catálogo sério não tem 30 categorias planas, tem árvore

Departamento, categoria, subcategoria, tipo de produto

O jeito de navegar isso é em loop: depois de escolher o departamento, você faz o próximo Choice com os FILHOS daquele nó como opções e as subárvores como valores, repetindo até chegar na folha

O criteria de cada passo é o nó atual

def descer_taxonomia(client, state, arvore):
    caminho = []
    no = arvore
    while isinstance(no, dict) and no:
        pergunta = Choice(
            criteria=f"Dentro de '{caminho[-1] if caminho else 'raiz'}', qual o melhor no para este produto?",
            options={nome: {"cobre": nome} for nome in no.keys()},
        )
        r = client.system_one(state=state, questions={"nivel": pergunta})
        escolha = r.answers["nivel"].choice
        caminho.append(escolha)
        no = no.get(escolha, {})
    return caminho

Essa versão aí em cima é gulosa: ela escolhe o melhor de cada nível e segue

E guloso tem um problema clássico: errou o departamento, errou tudo pra baixo, sem volta

Por que beam search é melhor aqui:

Existe um cookbook oficial de classificação hierárquica que percorre a taxonomia com beam search em cima das probabilidades do Choice

A ideia: em vez de manter só 1 caminho, você mantém os K melhores, e pontua cada caminho pela média geométrica das probabilidades de aresta, normalizada pelo comprimento:

score = product(edge_probabilities) ** (1 / decisions)

A normalização pelo número de decisões é o que impede que caminho curto ganhe de caminho longo só por ter multiplicado menos números

No comparativo do próprio cookbook, o resultado foi esse:

Estratégia Folhas corretas
Beam search 4 de 4
Busca gulosa 2 de 4

O cookbook usa, entre outras taxonomias, a de produtos de varejo da Shopify (versão 2026-02), indo de departamento de loja até o tipo específico de produto

Ou seja: é exatamente o formato de árvore que um catálogo de e-commerce de verdade tem

O erro comum deste passo: montar a lista de opções de cada nível com nome de nó e mais nada. Lembra que a descrição de opção aceita JSON com o que cobre e o que não cobre? É nos níveis intermediários da árvore que isso rende mais, porque é ali que "Acessórios" e "Roupas" brigam pelo mesmo produto

Extraindo atributos numéricos sem depender do modelo para contar

Agora a parte que muita gente erra feio

Voltagem, tamanho, quantidade de peças, medidas em cm, peso

A página de limitações do jev-1.13 é bem clara: o modelo não conta de forma confiável. Caracteres, ocorrências de um termo, itens de uma lista longa, e o erro cresce com o tamanho do que está sendo contado

A mesma página avisa que ele lê datas como texto, não como quantidades ordenadas, e que os níveis do Score são fracos em calibração numérica, então não dá pra reconstruir um número exato interpolando entre dois níveis

Tome cuidado! Se você pedir "quantas peças tem o kit" como Score, você vai levar número errado com cara de número certo

A solução: regex acha, Jev escolhe, código normaliza

Existe um cookbook oficial de extração de valores pré-parseados que resolve isso de um jeito elegante

O regex encontra TODOS os candidatos no texto, o Jev só escolhe qual deles é o trecho pedido, e a normalização do valor literal fica com o código

O modelo faz o que ele é bom (decidir), o regex faz o que ele é bom (achar padrão), e a matemática fica com a linguagem, que nunca erra conta

import re

texto = produto["titulo"] + " " + produto["descricao"]
candidatos = re.findall(r"\d+[.,]?\d*\s?(?:pecas|un|unidades|x)", texto, flags=re.I)

opcoes = {c: {"trecho": c} for c in set(candidatos)}
opcoes["nenhum"] = "nenhum dos trechos indica a quantidade de pecas do kit"

qtd = Choice(
    criteria="Qual destes trechos informa a QUANTIDADE DE PECAS incluidas no kit?",
    options=opcoes,
)

r = client.system_one(state=state, questions={"qtd": qtd})
trecho = r.answers["qtd"].choice

# a conversao fica com o Python, nao com o modelo
quantidade = int(re.search(r"\d+", trecho).group()) if trecho != "nenhum" else None

Repara que a opção de escape voltou. Ela é obrigatória aqui, porque metade dos títulos de catálogo não tem a informação

O erro comum deste passo: mandar o modelo "extrair o número" em vez de "escolher o trecho". São coisas diferentes: escolher é decisão tipada, extrair é geração de texto, e o Jev não gera texto livre

O que fazer com os itens de baixa confiança

Confiança não é erro, é parte do fluxo

Relembrando: Choice e Score trazem um campo de confiança derivado da concentração da distribuição. Noul não tem campo separado, porque a própria probabilidade é a resposta

Sintoma: metade do catálogo saiu categorizada de um jeito esquisito

Causa: você tratou todas as respostas como iguais e gravou tudo automaticamente

O modelo respondeu a mesma coisa nos dois casos: o item óbvio e o item que ele chutou entre duas categorias parecidas. A diferença estava na distribuição, e você ignorou ela

Tem número público sobre isso. No cookbook de classificação por confiança, um corte em 0,9 divide um conjunto de 60 documentos ao meio: a metade confiante acerta 90%, e a outra metade acerta 40%

Ou seja: com UM campo que você já recebe de graça, dá pra separar o que é seguro do que é aposta

Solução: três faixas de comportamento, que é o que a doc de confiança recomenda:

  • Alta: agir automaticamente. Grava a categoria no banco e segue a vida
  • Média: prosseguir com cautela. Confirmar, sinalizar para revisão ou buscar mais contexto (puxar a descrição completa do fornecedor, por exemplo)
  • Baixa: mandar para humano, pedir esclarecimento ou cair em outro sistema

E o mais importante: o limiar não é um número único

A orientação é testar os limiares plotando confiança contra acurácia nos SEUS dados, e escalar os casos incertos para uma pessoa ou para um modelo de raciocínio mais caro

O 0,9 do cookbook é o corte do cookbook, não o seu. Seu catálogo, seus dados, seu corte

Sintoma: a fila de revisão humana ficou gigante

Causa: você está tratando "incerto" como binário. Ou o item tem categoria folha, ou ele vai pra fila

Solução: o truque do fallback hierárquico

No mesmo cookbook, quando o rótulo é reportado um nível ACIMA na hierarquia, aqueles 40% de acerto viram 70%

Na mesma resposta, sem segunda chamada

Faz todo sentido: o modelo pode não ter certeza se é "camiseta manga curta" ou "camiseta regata", mas ele tem bastante certeza de que é "camiseta"

O cookbook fecha com uma função classify() que devolve o rótulo mais o quão específico ele é, com uma requisição por documento. Quando o modelo não tem certeza do grupo, ele reporta a divisão acima

Na prática do catálogo, isso significa: o produto entra publicado em "Camisetas" em vez de ficar invisível na fila esperando alguém decidir se é regata

E aí a revisão humana vira refinamento, não desbloqueio

A doc lista e-commerce e marketplaces entre os casos de uso oficiais, especificamente:

  • classificar e normalizar anúncios de catálogos inconsistentes
  • extrair atributos de produto de títulos e descrições
  • rotear anúncios incertos para revisão humana

O pipeline que você montou aí em cima já cobre os três. O que sobra é derivar mais perguntas em cima do MESMO state, e isso é quase de graça em tempo de resposta

Algumas extensões que caem bem:

  • Flags de moderação no anúncio: promessa proibida, termo de marca registrada usado indevidamente, contato fora da plataforma. O raciocínio é o mesmo de quando você usa o Jev para moderar conteúdo de usuários, só que o "usuário" aqui é o seller
  • Enriquecimento de filtro de busca: material, ocasião de uso, público, estilo. Cada um vira um Choice com a lista fechada de valores que o seu filtro aceita
  • Detecção de duplicata de sentido: flags de "este anúncio é o mesmo produto do fornecedor X" como Noul

A conta de custo e velocidade:

O preço público do Jev cobra só entrada: US$ 0,042 por 1 milhão de tokens de entrada e US$ 0,00 por 1 milhão de tokens de saída

Saída zero muda o desenho, porque a parte cara de LLM normalmente é a geração, e aqui não tem geração

A latência medida em cookbook da própria TypeSafe pra ida e volta de um Choice é de 114 ms em média com jev-latest

E o ganho de juntar perguntas, de novo, pra fixar:

Formato Tempo (13 perguntas)
1 chamada com 13 perguntas 0,27 s
13 chamadas separadas 2,71 s

Os números de workflow que a TypeSafe divulga na imprensa são bem maiores (até 193,6x mais rápido e 444,6x mais barato, segundo a própria empresa), mas isso é comparação do fornecedor, então trata como marketing até você medir no seu caso

Pra escala de catálogo, fica o alerta: os limites de taxa são medidos em tokens por segundo E requisições por minuto, e passar de qualquer um dos dois retorna 429 Too Many Requests

Os limites mudam sem aviso durante o early access, então backoff e retry na sua rotina de batch, sempre

Detalhe: a Vercel afirmou que o Jev virou o modelo de adoção mais rápida da história do AI Gateway deles, o que dá uma ideia de quanta gente entrou na fila de uma vez

Vídeo: e-commerce montado com IA em 28 minutos

Pra quem tá começando do zero e quer ver o outro lado da moeda, a loja em si sendo montada com IA, esse vídeo do canal mostra o ANTIGRAVITY criando um e-commerce inteiro a partir de um prompt só, em 28 minutos

Próximo passo

O caminho prático é esse, na ordem:

  1. Roda o fluxo num lote PEQUENO de produtos, uns que você já sabe a resposta certa
  2. Guarda rótulo e confiança lado a lado e plota confiança contra acurácia nos seus dados
  3. Define suas três faixas com base nesse gráfico, não no número de cookbook de ninguém
  4. Só então liga a gravação automática no banco, e só pra faixa alta

De cabeça pra baixo (ligar o automático primeiro e calibrar depois) você vai passar a semana seguinte escrevendo script de rollback, e isso não é divertido 😅

E se você vive dentro do Claude Code, a TypeSafe publica uma agent skill oficial no repositório typesafe-ai/skills, com instalação própria:

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

A invocação é /typesafe:typesafe-ai

Pra carregar atualização, a doc indica reiniciar o Claude Code ou rodar /reload-plugins, e dá pra habilitar auto-update em /plugin > Marketplaces > typesafe-ai > Enable auto-update

Lembrando que o acesso ao Jev segue em early access com waitlist, então se você ainda não entrou, o passo zero é a fila mesmo

Até o próximo post! =)

Perguntas frequentes

Dá pra categorizar um catálogo inteiro em vários níveis, tipo categoria, subcategoria e tipo específico, numa chamada só?

Sim, existe um cookbook oficial de classificação hierárquica que percorre a taxonomia com beam search sobre as probabilidades do Choice, mantendo os melhores caminhos pela média geométrica das probabilidades de cada aresta. Um exemplo desse cookbook usa a taxonomia de produtos de varejo da Shopify, versão 2026-02, indo do departamento até o tipo específico de produto. No comparativo publicado, o beam search acertou 4 de 4 folhas esperadas contra 2 de 4 da busca gulosa.

O que fazer com os produtos que o Jev classifica com baixa confiança?

A doc recomenda três faixas de comportamento: confiança alta age automaticamente, confiança média prossegue com cautela confirmando, sinalizando pra revisão ou buscando mais contexto, e confiança baixa vai pra humano ou pede esclarecimento. Não existe um limiar único, o ideal é calibrar plotando confiança contra acurácia nos seus próprios dados. Um cookbook mostra um corte de 0,9 dividindo 60 documentos ao meio, onde a metade confiante acerta 90% e a outra 40%, e ao reportar um nível acima na hierarquia esses 40% sobem pra 70%.

Categorizar vários atributos do mesmo produto de uma vez sai mais caro?

Pelo contrário. Todas as perguntas de uma mesma chamada são avaliadas em paralelo contra o mesmo state, que é lido uma única vez. No cookbook de perguntas paralelas da TypeSafe, 13 perguntas numa chamada só levaram 0,27 s contra 2,71 s de 13 chamadas separadas, e o próprio cookbook registra que juntar tudo saiu mais barato e mais rápido, sem mudar as respostas.

O Jev consegue extrair o tamanho ou outro atributo direto do título bagunçado do produto?

Sim, existe um cookbook de extração de valores pré-parseados: regex acha os candidatos no texto, tipo ‘G’ ou ‘P’, e o Choice do Jev escolhe qual trecho é o correto. A normalização final do valor fica por conta do seu código, o modelo só aponta o span certo.

Quanto custa categorizar um catálogo grande com o Jev?

O preço público cobra só a entrada: US$ 0,042 por 1 milhão de tokens, e a saída sai a US$ 0,00. Como cada opção de Choice custa poucos tokens e as perguntas de uma chamada rodam em paralelo sobre o mesmo state, categorizar um catálogo inteiro tende a sair barato comparado a um modelo de texto livre respondendo pergunta por pergunta.

O Jev consegue contar quantas peças tem um kit, tipo ‘kit 3 camisetas’?

Não é o ponto forte dele. A página de limitações do jev-1.13 diz que o modelo não conta de forma confiável caracteres, ocorrências de um termo ou itens de uma lista, e o erro cresce com o tamanho do que está sendo contado. Pra confirmar se o ‘3’ do kit é a quantidade certa, o caminho é regex ou validação separada, não perguntar direto pro Jev.




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