← Blog

Decisão tipada com probabilidade: Jev, Clef e o que medimos

Modelos de decisão tipada devolvem uma probabilidade por opção em vez de texto. Medimos o Clef, da Cloudflare, contra dois LLMs generativos em 24 tickets: o que não muda e o que passa a ser possível.

Num lote de 24 tickets de suporte rotulados à mão, o Clef marcou como urgentes quatro tickets que não eram. Um deles: "Quero excluir minha conta e meus dados". Os quatro erros tinham algo em comum que não aparece olhando só a resposta: a certeza do modelo ficou abaixo de 0,9 em todos. Os tickets acima de 0,9 estavam todos certos. A resposta de um LLM com JSON mode, no mesmo teste, trouxe só o rótulo, sem nada que separasse o ticket seguro do duvidoso.

O que apareceu em setembro e outubro

Em 15/09/2026, a TypeSafe lançou o Jev, primeiro de uma classe que chama de "System One Models". Em vez de gerar texto token a token, o modelo recebe um estado e perguntas tipadas e devolve uma probabilidade para cada resposta permitida, numa passada só. Há três tipos de pergunta: choice (escolher entre opções), score (nota numa escala) e noul (sim ou não).

Em 01/10/2026, a Cloudflare publicou no Workers AI o Clef (@cf/cloudflare/clef, 27B) e o Clef-flash (@cf/cloudflare/clef-flash, 9B), com a mesma interface de perguntas tipadas. A Cloudflare declara que a API é compatível com a do Jev, que o Clef tem mediana de 209 ms no servidor e que supera o Jev em 7 de 10 benchmarks. O teste abaixo não verifica essas afirmações: o Jev não entrou (cadastro pausado na época e instabilidade no site depois), e a latência medida aqui inclui a rede.

O padrão é o que interessa, e ele cabe em qualquer modelo desse tipo.

O conceito, não o produto

Structured output em LLM generativo é uma restrição aplicada sobre um processo que continua gerando texto token a token. O modelo escreve a decisão como se escrevesse um parágrafo, e o formato é conferido depois ou imposto durante a geração. Um modelo de decisão tipada tem a decisão como saída direta: o contrato (opções, escala, sim ou não) faz parte da chamada, e o retorno traz uma probabilidade por opção. Na prática, isso muda duas coisas na integração.

A primeira é que o contrato fica na requisição, e não no prompt:

# bench.py (trecho). DEPARTAMENTOS e NIVEIS estão em tickets.py e no topo do arquivo.
def decidir_clef(modelo: str, ticket: str) -> tuple[dict, dict]:
    corpo = {
        "model": modelo.rsplit("/", 1)[-1],
        "state": ticket,
        "questions": {
            "departamento": {"type": "choice", "instructions": INSTRUCAO_DEPTO,
                             "criteria": DEPARTAMENTOS},
            "urgente": {"type": "noul", "instructions": INSTRUCAO_URGENTE},
            "frustracao": {"type": "score", "instructions": INSTRUCAO_FRUSTRACAO,
                           "criteria": NIVEIS},
        },
    }
    r = post(modelo, corpo)
    a = r["answers"]
    decisao = {
        "departamento": a["departamento"]["choice"],
        "urgente": a["urgente"]["noul"] >= 0.5,
        "frustracao": NIVEIS[round(a["frustracao"]["score"])],
    }
    return decisao, r["usage"]

A segunda é que a resposta traz números. Para o ticket "O checkout está falhando para todos os clientes há uma hora", o Clef devolveu:

"departamento": {"choice": "tecnico",
                 "probabilities": {"cobranca": 0.0982, "tecnico": 0.8898, "conta": 0.012},
                 "confidence": 0.7024},
"urgente": {"noul": 0.9715}

O que não mudou

O experimento usou 24 tickets em português, rotulados à mão (departamento certo e urgente ou não), cada um rodado 3 vezes por modelo. Os dois lados receberam a mesma tarefa: três decisões por ticket. Os generativos usaram JSON mode do Workers AI, com json_schema no Llama 3.3 70B. O Llama 3.1 8B não aceita json_schema (a API devolve "This model doesn't support JSON Schema"), então ele rodou com json_object e o contrato descrito no prompt.

modelop50p95acerto deptoacerto urgênciatokens entrada/saídacusto por 1000 decisões
Clef704 ms1382 ms1,000,80356 / 0US$ 0,086
Clef-flash575 ms1084 ms1,000,88356 / 0US$ 0,032
Llama 3.3 70B (json_schema)1118 ms2665 ms1,000,67164 / 27US$ 0,109
Llama 3.1 8B (json_object)2042 ms2869 ms1,000,79174 / 31US$ 0,035

Custo calculado com a tabela de preços do Workers AI na data da medição.

  • Contrato: zero respostas fora do contrato nos dois generativos, nas 72 chamadas de cada um, inclusive no 8B sem json_schema. Nesse teste, o formato não foi o problema. O Clef teve 1 erro HTTP 529 (sobrecarga) em 72 chamadas.
  • Departamento: 1,00 em todos. A tarefa é fácil demais para separar modelos.
  • Custo: Clef 20% abaixo do Llama 70B, Clef-flash 9% abaixo do 8B. Nada perto de uma ordem de grandeza.
  • Latência: o Clef foi 1,6x mais rápido que o 70B e o Clef-flash 3,5x mais que o 8B, medido da máquina do autor, pela internet. Os 209 ms e 39 ms que a Cloudflare declara são de servidor e não aparecem aqui.
  • Urgência: de 0,67 a 0,88, com 24 tickets e rótulos subjetivos. Com temperature: 0, as repetições do generativo não ampliam a amostra. Diferença dessa ordem não dá para tratar como ranking.

O que muda: decidir só o que passa de um limiar

Com a probabilidade, a pergunta deixa de ser "o modelo acertou?" e vira "quais tickets posso deixar o modelo decidir sozinho?". O script junta a probabilidade da pergunta de urgência de cada ticket e calcula, para cada limiar de certeza, o acerto entre os tickets que o passam:

# calibracao.py (trecho)
for lim in LIMIARES:
    decididos = [x for x in linhas if x["certeza"] >= lim]
    acertos = sum(x["previsto"] == x["esperado"] for x in decididos)
    acerto = acertos / len(decididos) if decididos else float("nan")

Saída para o Clef, em que certeza é max(p, 1 - p) da pergunta de urgência:

limiar_certeza  decididos  acerto_nos_decididos  vao_para_humano
           0.0         24                 0.833                0
           0.7         22                 0.818                2
           0.8         20                 0.850                4
           0.9         17                 1.000                7
          0.95         14                 1.000               10

E para o Clef-flash:

limiar_certeza  decididos  acerto_nos_decididos  vao_para_humano
           0.0         24                 0.917                0
           0.7         23                 0.913                1
           0.8         19                 1.000                5
           0.9         17                 1.000                7
          0.95          7                 1.000               17

No Clef, os quatro erros ficaram em certezas de 0,761 a 0,886. Com limiar de 0,9, 17 tickets saem decididos com acerto de 100% e 7 vão para uma pessoa. No Clef-flash, os dois erros ficaram abaixo de 0,8, e um limiar de 0,8 já deixa 19 decididos sem erro. O custo do limiar aparece na última coluna: quanto mais alto, mais trabalho humano. Em 0,95, o Clef-flash manda 17 de 24.

O LLM generativo, na configuração do teste, devolve só o rótulo. Isso não prova que ele não possa expressar dúvida: logprobs ou pedir um nível de confiança no próprio JSON são caminhos que este teste não mediu.

Quando essa medição não serve

Os limiares foram escolhidos olhando os mesmos 24 tickets. O valor 0,9 que zera os erros aqui é o resultado de uma amostra pequena e não um parâmetro pronto. Em outro conjunto, a faixa de certeza dos erros muda. O procedimento é o que se leva: rotular um lote que represente o tráfego real, medir a curva limiar versus acerto e escolher o ponto pelo custo de errar contra o custo de um revisor.

Os rótulos são do autor. Os quatro erros do Clef são todos falsos positivos de urgência. "Quero excluir minha conta e meus dados" é um pedido de privacidade que muita equipe trataria como prioritário, e o modelo pode estar certo onde o rótulo está errado.

Há variação entre rodadas. O acerto de urgência do Clef foi 0,80 no lote de 72 chamadas e 0,83 na rodada de calibração. Uma segunda rodada pode mudar quais tickets ficam abaixo do limiar.

O Jev não foi medido. Tudo acima vale para o Clef, o Clef-flash e os dois Llamas. Comparar com o Jev exige rodar o mesmo script contra a API da TypeSafe, e a interface compatível deve tornar isso uma troca de URL e de chave.

Poucas classes e saída fechada. Resposta ao cliente, resumo e decisões que dependem de raciocínio em várias etapas continuam sendo trabalho de LLM generativo. A decisão tipada serve a escolhas com opções conhecidas na hora da chamada, e as categorias entram no contrato da requisição.

O que levar para amanhã

Antes de trocar de modelo para decidir mais rápido, meça se a resposta traz uma probabilidade que separe acerto de erro: com ela, o limiar de certeza decide o que roda sozinho e o que vai para uma pessoa, e o resto da escolha de modelo vira detalhe de latência e custo.


A trilha AI Engineer (R$ 697) ensina structured output, model routing e os trade-offs de decisão automatizada em produção, com testes automatizados a cada etapa. O código deste artigo está em greenlitlabs/structured-vs-generative-decisions.