Choice, score e noul: quais são os tipos de pergunta do Laya e quando usar cada um?

Os tipos de pergunta do Laya são três primitivas tipadas: choice, score e noul, todas avaliadas sobre um mesmo estado em uma única passagem de inferência. O choice escolhe uma chave dentro de um dicionário de critérios e devolve a distribuição entre as opções mais a confiança. O score posiciona o estado numa rubrica ordinal (0, 1, 2 e por aí vai) e devolve o nível esperado com a distribuição. O noul é booleano e devolve P(true) de 0.0 a 1.0, com P(false) = 1 – P(true). A acurácia reportada por primitiva é noul 0.857, choice 0.733 e score 0.723
Fala aí, beleza? O Laya não escreve texto, ele responde perguntas tipadas sobre um estado
E isso muda TUDO na hora de desenhar as perguntas
Em vez de pedir pro modelo gerar um JSON e torcer pra ele vir bonitinho, você declara o que quer saber e recebe probabilidades calibradas de volta, sem nenhum token gerado. São três primitivas: choice, score e noul, todas avaliadas em um único forward pass sobre o mesmo estado (um texto, um e-mail, um ticket ou um documento JSON)
Escolher a primitiva errada é o que quebra o design das perguntas, e é um erro silencioso: o modelo responde, o número sai, só que ele não significa o que você acha que significa
Contexto de maturidade antes de seguir: o modelo é da Convai Innovations, que publica os pesos pela própria conta no Hugging Face, em huggingface.co/convaiinnovations/laya
Já o código vive numa conta pessoal no GitHub, o github.com/NandhaKishorM/laya, então não estranha ver os dois nomes: pesos pela empresa, repositório nessa conta. A licença é Apache 2.0 e o release mais recente listado no GitHub é o v0.3.5
Bora entender cada tipo? 🙂
Choice, score e noul lado a lado
| Primitiva | Que pergunta responde | Formato da saída | Como se lê | Acurácia reportada |
|---|---|---|---|---|
choice |
Qual das opções é esta? | Chave escolhida + distribuição por opção + confiança | answers['department']['choice'] |
0.733 |
score |
Quanto disso tem aqui? | Nível esperado + distribuição sobre os níveis + confiança | answers['urgency']['score'] |
0.723 |
noul |
É verdade ou não? | P(true) de 0.0 a 1.0, com P(false) = 1 – P(true) | answers['churn_risk']['noul'] |
0.857 |
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 45min
Repara que o noul é o mais preciso dos três, com 0.857
Um aviso pra não te confundir mais pra frente: esses três números são acurácia POR primitiva, e não dá pra comparar direto com os números do benchmark typed-decisions que aparecem lá no fim do post. São recortes diferentes, medidos de jeito diferente
Faz sentido: ele só precisa separar duas margens de um eixo, enquanto o choice tem que distribuir massa de probabilidade entre várias opções que competem entre si e o score ainda carrega a ordem dos níveis por cima disso
Quanto mais espaço de resposta, mais lugar pro modelo errar, né?
Quando usar choice: roteamento e classificação por categoria
O choice escolhe UMA opção dentro de um dicionário de critérios
O exemplo do README é um ticket de suporte com a pergunta department e critérios billing, technical, sales e other. Clássico caso de roteamento: chegou a mensagem, pra qual time ela vai?
Só que a graça não está no rótulo vencedor
A saída traz a distribuição por opção e um score de confiança calibrado, e é aí que mora o valor real. Se billing deu 0.48 e technical deu 0.45, você não tem uma classificação, você tem um empate disfarçado. Esse é o ticket que merece revisão humana em vez de cair direto na fila errada
Com o rótulo cru você nunca enxergaria isso, porque billing é billing e pronto
Dois detalhes do v0.3.5 que vale ter na cabeça ao montar dicionários de opção:
- o tratamento da descrição das opções mudou: agora só
Nonee string vazia contam como ausência de descrição. Antes, qualquer valor falsy caía pra chave nua - o crash em perguntas
choicecom uma única opção foi corrigido nesse mesmo release
Ou seja: se você gera os critérios dinamicamente e às vezes o dicionário chega com uma opção só, atualize antes de sair culpando o seu código 😀
Quando usar score: rubricas ordinais com níveis
Use score quando as opções têm ordem natural
O score posiciona o estado em uma rubrica ordinal (níveis 0, 1, 2, …) e devolve o nível esperado, a distribuição sobre os níveis e a confiança. No exemplo do README, a pergunta é urgency, com níveis do tipo not urgent, soon e critical deadline or blocking issue
E qual a diferença prática de forçar isso num choice?
No choice, as três opções são estranhas entre si: errar de not urgent pra critical custa o mesmo que errar de soon pra critical. No score, a ordem existe, e a distribuição sobre os níveis te deixa tratar o meio termo de forma decente: um caso que fica espalhado entre soon e critical é um caso que sobe de prioridade, mesmo sem cravar o topo da rubrica
É isso que te permite definir limiar em cima de um eixo contínuo em vez de contar votos
Tome cuidado com um ponto: score é a primitiva com a MENOR acurácia reportada (0.723)
Então rubrica curta e com níveis bem descritos ajuda. Quanto mais parecido um nível é do vizinho, mais difícil pro modelo separar os dois
Quando usar noul: perguntas booleanas com probabilidade calibrada
O noul é a pergunta sim/não direta, tipo o churn_risk do exemplo do ticket
Mas atenção, porque aqui tem uma pegadinha de leitura: a saída não é um booleano
É uma probabilidade contínua, P(true) entre 0.0 e 1.0, com P(false) = 1 – P(true) por construção. Você recebe 0.62, não recebe True. Quem transforma isso em decisão é você, com o limiar que fizer sentido pro seu fluxo
E por que a calibragem importa tanto justamente aqui?
Porque o treinamento usa aprendizado por reforço contra strictly proper scoring rules, as tais regras de pontuação estritamente próprias, o que a documentação do modelo chama de RLCD. Traduzindo: o esquema de recompensa é montado de um jeito em que reportar a probabilidade honesta é o caminho de maior recompensa. Chutar 0.99 pra parecer confiante sai mais caro do que dizer 0.6 quando é 0.6
Se você conhece a ideia de um previsor de tempo bem calibrado (a chance que ele anuncia bate com a frequência real de chuva nos dias parecidos), é exatamente esse espírito
Dica de design: prefira noul a um choice de duas opções quando a pergunta é um gate
Você já tem um eixo contínuo pronto pra virar limiar, sem precisar comparar duas massas de probabilidade na mão
Como declarar as perguntas e ler as respostas no Laya
O fluxo é curtinho, se liga:
- Instale o pacote pelo PyPI
pip install laya
- Importe o Router, que é o ponto de entrada recomendado do pacote
from laya import Router
- Instancie com preload. O Router detecta script e idioma em menos de 1 ms e despacha pro checkpoint ideal
router = Router(preload=True)
O erro comum deste passo: instanciar dentro do handler da requisição e pagar carregamento toda hora. Suba o Router uma vez, no start da aplicação
- Monte o dicionário de perguntas, com o tipo, a instrução e os critérios em cada entrada
questions = {
"department": {
"type": "choice",
"instruction": "Qual time deve receber este ticket?",
"criteria": {
"billing": "cobrança, fatura, reembolso",
"technical": "erro, bug, indisponibilidade",
"sales": "proposta, upgrade, novo contrato",
"other": "qualquer outro assunto",
},
},
"urgency": {
"type": "score",
"instruction": "Qual o nível de urgência deste ticket?",
"criteria": {
0: "not urgent",
1: "soon",
2: "critical deadline or blocking issue",
},
},
"churn_risk": {
"type": "noul",
"instruction": "Este cliente demonstra risco de cancelar?",
},
}
O erro comum deste passo: escrever o tipo errado na entrada (declarar choice numa pergunta que é gradação, por exemplo) e depois tentar ler por outra chave de tipo lá na saída. A leitura é sempre por tipo, então tipo trocado vira KeyError ou, pior, vira número que não quer dizer nada
- Chame o predict passando o estado e as perguntas. O estado pode ser um texto, um e-mail, um ticket ou um documento JSON
answers = router.predict(state, questions)
- Leia as respostas por chave e por tipo
print(answers["department"]["choice"])
print(answers["urgency"]["score"])
print(answers["churn_risk"]["noul"])
O padrão é sempre answers[<pergunta>][<tipo>], sem exceção
- Visualize as distribuições rodando o servidor de demonstração em
examples/server.py, que renderiza choice, score e noul como barras de 0 a 100 e expõe uma API JSON. Ele depende do grupo opcional:
pip install "laya[server]"
O erro comum deste passo: rodar o exemplo sem o grupo opcional de servidor instalado e tomar erro de import achando que o pacote veio quebrado
Pra quem está começando do zero com a ideia de tipo e declaração antes de cair no dicionário de perguntas, este vídeo do canal mostra tipos de dados e declaração de variáveis na prática:
Por que seu choice piora quando o dicionário de opções cresce
Sintoma: você monta uma classificação com dezenas de rótulos e a acurácia despenca, mesmo com critérios bem escritos
Causa: as opções dividem um orçamento de 192 a 256 tokens. Com 77 opções, sobra coisa de 3 a 4 tokens por candidato, o que é basicamente nada pra descrever um rótulo. A degradação já começa a aparecer acima de umas 20 opções
Evidência: no Banking77 (77 rótulos) o Laya marcou 0.425, contra 0.870 do Jev, que é o baseline usado como comparação na própria documentação do modelo. Não é sutil, é metade do desempenho
Solução e prevenção: hierarquia em dois passos, o famoso coarse-to-fine
Quebre o espaço de rótulos: primeiro um choice grosso com poucos grupos, depois um segundo choice fino só dentro do grupo vencedor. Cada passagem fica com um dicionário pequeno, cada opção ganha tokens suficientes pra se descrever, e você para de brigar com o orçamento
Regra de bolso que dá pra tirar disso: se o seu choice passou de umas 20 opções, ele virou dois choice
O que esperar de acurácia antes de desenhar suas perguntas
Aqui vai a parte honesta, porque ela muda o seu planejamento
A acurácia zero-shot no benchmark typed-decisions é 0.362, ou seja, perto do acaso. Aquele 0.766 que circula por aí exige fine-tuning no split de treino do próprio benchmark. Não é plug and play, e fingir o contrário só gera frustração depois
Com o checkpoint ajustado, os resultados por fluxo ficam assim:
- processamento de notas fiscais: 0.804
- incidentes de segurança: 0.766
- atendimento ao cliente: 0.764
- observabilidade de traces de agente: 0.730
Do lado do custo, é onde a coisa fica interessante: a latência declarada é de 33 ms para uma pergunta e 7,2 ms por pergunta em lote, medida em GPU T4
Por checkpoint, em Tesla T4, dá cerca de 39,5 ms no modelo em inglês (ModernBERT-large, 421M de parâmetros e 512 tokens de contexto) e cerca de 32,8 ms no multilíngue (mmBERT-base, 322M de parâmetros, 1024 tokens e mais de 100 idiomas)
Veredito: faz sentido pra decisão tipada de alto volume, aquele fluxo que roda milhares de vezes por dia e precisa de número calibrado, não de texto bonito. Desde que você assuma o fine-tuning como parte do projeto, e não como um extra opcional
Se o plano era sair do zero-shot pra produção direto, melhor ajustar a expectativa agora 😛
Conclusão
O critério de escolha entre os tipos de pergunta do Laya dá pra resumir em três linhas:
- pergunta booleana vira
noul, e você trata a saída como probabilidade, não como sim/não - categoria sem ordem vira
choice, de preferência curto (e hierárquico quando o espaço de rótulos for grande) - gradação com ordem natural vira
score, com rubrica curta e níveis bem descritos
Próximo passo é simples: instala com pip install laya, reproduz o exemplo do ticket de suporte com department, urgency e churn_risk, e abre o examples/server.py pra ver as distribuições em barra em vez de olhar só o rótulo vencedor
Ver a massa de probabilidade se espalhando entre as opções é o que faz a ficha cair sobre qual primitiva você deveria ter usado
Os pesos estão em huggingface.co/convaiinnovations/laya e o código no repositório do projeto
Até o próximo post! 😀
Perguntas frequentes
Qual dos tipos de pergunta do Laya tem a maior acurácia reportada?
É o noul, com 0.857. Faz sentido: ele só precisa separar duas margens de um eixo (P(true) e P(false) = 1 – P(true)), enquanto choice (0.733) e score (0.723) lidam com mais opções competindo entre si. Só não confunda esses valores com os do benchmark typed-decisions: são recortes diferentes, reportados à parte.
Dá pra usar o tipo choice do Laya com muitas opções, tipo 77 rótulos?
Não é o ponto forte dessa primitiva. As opções dividem um orçamento de 192 a 256 tokens, e com 77 rótulos sobram só uns 3 a 4 tokens por candidato, o que derrubou o resultado no Banking77 pra 0.425, contra 0.870 do Jev, o baseline usado na comparação da documentação. A degradação já aparece acima de uns 20 rótulos, e a saída recomendada é uma hierarquia em dois passos (coarse-to-fine) em vez de um choice gigante.
O tipo noul do Laya devolve um true/false ou uma probabilidade?
Devolve uma probabilidade, não um booleano. É P(true) entre 0.0 e 1.0, com P(false) = 1 – P(true) por construção, então quem decide o limiar pra virar sim ou não é você.
Como fica a leitura de uma pergunta do tipo score no dicionário de resposta do Laya?
Vem pela chave da pergunta seguida de ‘score’, tipo answers[‘urgency’][‘score’]. Dentro dela está o nível esperado da rubrica ordinal, a distribuição sobre todos os níveis e a confiança calibrada.
O que o release v0.3.5 mudou no tipo choice do Laya?
Corrigiu um crash em perguntas choice com uma única opção e mudou o tratamento de descrição das opções: agora só None e string vazia contam como ‘sem descrição’, antes qualquer valor falsy caía pra chave nua. O mesmo release também ajustou o reconhecimento de idioma armênio e corrigiu um NameError no README.
Precisa treinar o Laya pra usar os três tipos de pergunta (choice, score e noul)?
Sem fine-tuning, a acurácia zero-shot no benchmark typed-decisions fica em 0.362, perto do acaso (esse recorte é diferente da acurácia por primitiva, que é reportada à parte e não serve de comparação direta). O número de 0.766 só aparece com fine-tuning no split de treino do próprio benchmark, e com o checkpoint ajustado os resultados variam por fluxo: 0.804 em notas fiscais, 0.766 em incidentes de segurança, 0.764 em atendimento ao cliente e 0.730 em observabilidade de traces de agente.
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Blog | Mais populares

Como fazer triagem de tickets de suporte com o laya: urgência, frustração e departamento
Triagem de tickets com laya: urgência, frustração e departamento em um forward pass, sem JSON. Veja instalação, código e a ressalva do zero-shot.

Confidence gating no laya: como desenhar o fallback quando a decisão não é confiável?
Confidence gating laya: como calibrar o corte de probabilidade e desenhar o fallback quando a decisão do modelo não é confiável.

Como usar o laya para detectar phishing, spam e risco de churn em texto, email e ticket?
Veja como o laya detecta phishing, spam e risco de churn em texto, email e ticket com scores calibrados, rodando local via pip install.
