Como versionar os critérios de decisão do Laya sem quebrar o histórico

Versionar critérios do Laya é uma prática de governança do seu time, não um recurso do modelo. Como o espaço de respostas é definido na requisição, o critério vive no seu código. Então: centralize cada decisão num arquivo com os criteria e o rótulo other, dê uma versão explícita e um hash do enunciado, grave versão, enunciado, checkpoint, resposta, answer_confidence e limiar junto de cada resultado, recalibre o limiar a cada versão e compare antes e depois reprocessando a mesma amostra com predict_batch, nunca comparando períodos diferentes
Fala aí, beleza? No Laya, mudar a definição de um rótulo é mudar a pergunta
E aí o seu histórico passa a misturar duas perguntas diferentes debaixo do mesmo nome, sem ninguém perceber 😛
Esse post é pra quem já roda decisões do Laya em produção e precisa ajustar critérios sem perder a capacidade de comparar resultados
Rapidinho, pra situar: o Laya é o modelo de decisão de pesos abertos da Convai Innovations, lançado em 18 de setembro de 2026 sob a licença Apache 2.0
Você entrega um estado (e-mail, ticket, documento JSON, tabuleiro de jogo) e algumas perguntas tipadas, e ele devolve respostas tipadas com uma probabilidade pra cada opção, numa única passada, sem gerar texto
Só que tem um detalhe importante: o Laya não guarda versão de critério pra você
Versionar é governança do SEU sistema, não um recurso do modelo
Bora ver como fazer isso direito?
O que você precisa antes de começar:
- Laya instalado com
pip install -U laya(isso também instala o comandolayapra teste local) - Uso via Python com
laya.load,agent.predict,agent.predict_batchouagent.decidecom schema (JSON schema ou modelo pydantic) - Saber qual checkpoint está em uso:
convaiinnovations/laya(base em inglês),convaiinnovations/laya-multilingual(pro que não é inglês) ouconvaiinnovations/laya-typed-decisions(ajustado pra decisões tipadas), ou se é o router do repositório do Laya que escolhe o checkpoint por requisição - Um lugar onde as decisões já são gravadas: banco, log, data warehouse, o que o time já usa
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 48min
Agora o porquê de tudo isso
No Laya, o espaço de respostas é definido no momento da requisição, então um schema novo não precisa de retreino
Isso é massa pra agilidade, mas tem um efeito colateral: o critério vive no seu código, não no modelo
Se ninguém versiona esse código como dado, a pergunta muda e o histórico não fica sabendo…
Se você já passou por isso com outro modelo do mesmo estilo, a lógica é bem parecida com a de versionar o schema de decisão do Jev
Passo a passo para versionar os critérios de decisão:
- Extraia as perguntas para um arquivo único por decisão
Cada decisão ganha um lugar só, com as perguntas, os criteria no formato {rótulo: definição de uma linha} e o rótulo other obrigatório nas perguntas choice
A estrutura abaixo é do seu time, não da API: depois você converte pro formato de requisição que a sua integração já monta
# criterios/triagem_ticket.py
TRIAGEM_TICKET = {
"decisao": "triagem_ticket",
"versao": 1,
"checkpoint": "convaiinnovations/laya",
"perguntas": [
{
"nome": "categoria",
"tipo": "choice",
"descricao": "Qual é a categoria principal deste ticket?",
"criteria": {
"cobranca": "Dúvida ou problema com fatura, pagamento ou reembolso",
"acesso": "Cliente não consegue entrar na conta ou recuperar a senha",
"bug": "Relato de erro ou comportamento inesperado do produto",
"other": "Qualquer caso que não se encaixe nos rótulos acima",
},
}
],
}
O erro comum deste passo: definições espalhadas pelo código, uma string num serviço, outra num notebook, outra num prompt de teste
Aí ninguém sabe qual enunciado rodou de verdade em produção
- Atribua uma versão explícita e um hash do conteúdo do enunciado
A versão é o número que humanos leem, o hash é o que pega mudança que passou batida
import hashlib
import json
def hash_enunciado(decisao: dict) -> str:
conteudo = {
"perguntas": decisao["perguntas"],
"checkpoint": decisao["checkpoint"],
}
bruto = json.dumps(conteudo, sort_keys=True, ensure_ascii=False)
return hashlib.sha256(bruto.encode("utf-8")).hexdigest()
# hashes já publicados, por versão (fica versionado no git junto dos critérios)
HASHES_PUBLICADOS = {
("triagem_ticket", 1): "cole_aqui_o_hash_da_v1",
}
def validar_versao(decisao: dict) -> None:
chave = (decisao["decisao"], decisao["versao"])
atual = hash_enunciado(decisao)
publicado = HASHES_PUBLICADOS.get(chave)
if publicado and publicado != atual:
raise ValueError(
f"{chave} mudou de conteúdo sem mudar de versão. Suba a versão."
)
Roda o validar_versao no CI e pronto: ninguém mexe numa definição e esquece de subir a versão
O erro comum deste passo: versionar só o nome da pergunta (categoria_v2) e deixar as definições dos rótulos mudarem livremente embaixo dele
- Grave o contexto completo junto de cada resultado
Com cada decisão vão: versão, hash (referência imutável ao enunciado), checkpoint, resposta completa, answer_confidence e o limiar vigente naquele momento
from datetime import datetime, timezone
import laya
from criterios.triagem_ticket import TRIAGEM_TICKET
agent = laya.load(TRIAGEM_TICKET["checkpoint"])
def decidir_e_gravar(estado, perguntas_req, limiar, gravar):
resultado = agent.predict(estado, perguntas_req)
registro = {
"decisao": TRIAGEM_TICKET["decisao"],
"versao": TRIAGEM_TICKET["versao"],
"hash_enunciado": hash_enunciado(TRIAGEM_TICKET),
"checkpoint": TRIAGEM_TICKET["checkpoint"],
"estado": estado,
# resposta inteira, incluindo o answer_confidence e as probabilidades
"resultado": resultado,
"limiar": limiar,
"gravado_em": datetime.now(timezone.utc).isoformat(),
}
gravar(registro)
return resultado

VPS Hostinger para seu sistema
10% OFF extraVPS com SSD NVMe e backups semanais. O cupom MATHEUSHORADECODAR é aplicado automaticamente pelo link.
Ver planos com 10% OFF →Se o enunciado completo for grande pra repetir em toda linha, grava o hash e mantém uma tabela à parte com hash e enunciado (que nunca é editada, só recebe linha nova)
E se a sua integração processa em lote? Aí entra o agent.predict_batch(states, questions), e a regra é a mesma: um registro por estado, todos com a versão, o hash e o checkpoint daquele lote
def decidir_lote_e_gravar(estados, perguntas_req, limiar, gravar):
resultados = agent.predict_batch(estados, perguntas_req)
for estado, resultado in zip(estados, resultados):
gravar({
"decisao": TRIAGEM_TICKET["decisao"],
"versao": TRIAGEM_TICKET["versao"],
"hash_enunciado": hash_enunciado(TRIAGEM_TICKET),
"checkpoint": TRIAGEM_TICKET["checkpoint"],
"estado": estado,
"resultado": resultado,
"limiar": limiar,
"gravado_em": datetime.now(timezone.utc).isoformat(),
})
return resultados
Esse mesmo predict_batch é o que vai fazer a comparação entre versões lá no passo 6
O erro comum deste passo: guardar só o rótulo vencedor
Sem a confiança e sem o limiar, você não consegue nem responder por que aquela decisão foi automática e não foi pra revisão humana
- Defina o que conta como mudança de versão
Sobe a versão quando:
- muda a definição de qualquer rótulo
- inclui ou remove rótulo
- muda a escala de uma pergunta score
- troca o checkpoint
- muda a descrição da pergunta
Se liga nisso: nas perguntas choice, a descrição vira a instrução da pergunta, e uma boa descrição é o que torna a decisão precisa
Ou seja, reescrever uma definição "só pra ficar mais clara" muda o comportamento do modelo
E tem mais: a documentação recomenda manter perguntas choice abaixo de cerca de 20 opções, porque elas dividem um orçamento fixo de 256 tokens da cabeça e um espaço de rótulos grande derruba a precisão
Então adicionar rótulo não mexe só na pergunta, mexe também na precisão das outras opções
O erro comum deste passo: achar que reescrever uma definição é cosmético
Não é! É uma pergunta nova
- Recalibre o limiar a cada versão
O Laya é treinado com reinforcement learning contra regras de pontuação estritamente próprias (RLCD), o que força probabilidades calibradas e não só rótulos corretos
Mas o limiar de confiança é política sua, escolhida a partir da precisão medida naquela cobertura nos seus dados, não uma propriedade do modelo
Pega uma amostra rotulada por humanos, roda na versão nova e mede:
def precisao_por_cobertura(itens, limiares):
# itens: lista de (confianca, acertou) da MESMA versão
linhas = []
total = len(itens)
for limiar in limiares:
aceitos = [acertou for conf, acertou in itens if conf >= limiar]
if not aceitos:
continue
cobertura = len(aceitos) / total
precisao = sum(aceitos) / len(aceitos)
linhas.append((limiar, cobertura, precisao))
return linhas
Aí o time escolhe o limiar que entrega a precisão que o negócio exige, e grava esse limiar junto da versão
O erro comum deste passo: herdar o limiar da versão anterior
Se a pergunta mudou, a distribuição de confiança mudou junto, e o limiar antigo não diz mais nada
- Compare antes e depois com a mesma amostra, não com períodos diferentes
Aqui o predict_batch brilha: em vez de chamar agent.predict estado por estado, tu pega uma amostra de estados históricos e roda o lote inteiro nas duas versões, pra comparar os resultados lado a lado
estados = carregar_amostra_historica() # sua função, tamanho da amostra é decisão do time
res_v1 = agent.predict_batch(estados, perguntas_v1)
res_v2 = agent.predict_batch(estados, perguntas_v2)
# agora sim: mesmo estado, duas versões, lado a lado
for estado, a, b in zip(estados, res_v1, res_v2):
comparar(estado, a, b) # onde mudou? mudou pra melhor segundo o rótulo humano?

Crie sistemas com IA para vender
Prompts, modelos e checklists para criar e publicar seu sistema com IA e preparar a oferta para o primeiro cliente.
Quero o e-book gratuito Receba o livro e os bônus no seu e-mail.(os parâmetros perguntas_v1 e perguntas_v2 saem do mesmo arquivo de critérios, em versões diferentes, no formato que a sua integração já usa)
É a mesma cabeça de quem faz backup antes de atualizar sem quebrar fluxos: você quer poder olhar pro antes e pro depois com a mesma régua
O erro comum deste passo: abrir o dashboard de agosto, abrir o de setembro e declarar vitória haha
Por que comparar métricas de antes e depois de mudar um rótulo dá conclusão errada?
Essa é a armadilha mais comum de quem já está em produção, então vale destrinchar
O sintoma:
Logo depois de ajustar os critérios, a taxa de um rótulo (ou do other) sobe ou cai
E o time conclui que o mundo mudou, ou que a versão nova é MUITO melhor
A causa:
A pergunta mudou
Quando você reescreve uma definição ou inclui um rótulo, a probabilidade se redistribui entre as opções, e casos que antes caíam em bug agora podem cair no rótulo novo ou no other
Por cima disso, a população de estados também pode ter mudado no período: campanha, sazonalidade, produto novo e por aí vai
E a confiança não salva você aqui: ela ordena decisões, não prova que estão corretas
Confiança alta na versão nova não significa acerto maior
A solução:
- compare métricas só dentro da mesma versão
- pra decidir se a versão nova é melhor, faça comparação pareada: os mesmos estados rodados nas duas versões, avaliados contra rótulo humano
Como prevenir:
- versão obrigatória em todo registro (registro sem versão nem entra no banco)
- bloqueio de relatório que misture versões: se a consulta pega mais de uma versão, o relatório quebra ou separa por versão automaticamente
Parece burocracia, mas é o tipo de coisa que evita uma reunião inteira discutindo um número que não quer dizer nada 🙂
Quando criar uma nova versão de critério (e quando não precisa)?
Resumão por tipo de pergunta:
| Situação | Tipo | Nova versão? | Por quê |
|---|---|---|---|
| Triagem de ticket ganha um rótulo novo | choice | Sim | O espaço de respostas mudou e os rótulos dividem o mesmo orçamento da cabeça |
| Definição de um rótulo foi reescrita | choice | Sim | A descrição vira a instrução da pergunta |
| Escala de 0 a 3 vira 0 a 4 | score | Sim | O valor é mínimo + argmax, então o mesmo número passa a significar outra coisa |
| Limiar de decisão sai de 0.5 pra outro valor | noul | Não muda a pergunta | Muda a política: registre o limiar novo |
Enum ou modelo pydantic do decide mudou |
schema | Sim | Enum/Literal viram perguntas choice, inteiro limitado vira score, booleano vira noul |
| Troca de checkpoint (ex: passar pro multilingual) | todos | Sim | É outro modelo respondendo a mesma pergunta |
Choice: triagem de ticket
Incluiu fraude na triagem? Nova versão
Reescreveu a definição de acesso pra incluir autenticação em dois fatores? Nova versão também, mesmo que o nome do rótulo seja o mesmo
Score: escala ordinal
No score, o valor retornado é mínimo + argmax
Se a escala era 0 a 3 e virou 0 a 4, um "3" de antes e um "3" de agora não são a mesma coisa, então comparar média de score entre as duas é misturar régua
Noul: sim ou não
O noul devolve P(verdadeiro) de 0.0 a 1.0
Mudar o ponto de corte (sair de 0.5 pra outro valor) não muda a pergunta, mas muda o que vira "sim" no seu sistema
Não precisa subir a versão do enunciado, mas o limiar tem que estar gravado em cada registro, senão ninguém entende por que a taxa de "sim" mudou
Schema no decide
Se você usa agent.decide(state, schema=...) com pydantic ou JSON schema, o schema É o critério
Mexeu num Enum, num Literal, num limite de inteiro? Isso muda as perguntas geradas, então o schema entra no mesmo esquema de versão e hash
Troca de checkpoint
Passar do convaiinnovations/laya pro convaiinnovations/laya-multilingual (ou pro laya-typed-decisions) é trocar quem responde
Mesmo com enunciado idêntico, é nova versão, e se o router escolhe o checkpoint por requisição, grave o checkpoint usado em cada linha
Loop engineering: o mesmo princípio aplicado a vibe coding
Essa ideia de mudar uma coisa de cada vez, de forma controlada, sem quebrar o que já funciona, não é exclusividade do Laya
Pra começar do zero com loop engineering, este vídeo do canal mostra como a evolução do vibe coding passa por ciclos controlados que fazem a IA não quebrar nada
Próximo passo: auditar suas decisões atuais
Recapitulando: o Laya define o espaço de respostas na requisição, então o critério é código seu, e código seu precisa de versão
A ação concreta pra hoje é essa:
- levante todas as perguntas que já rodam em produção
- atribua versão 1 ao enunciado atual de cada uma, com hash
- comece a gravar versão, hash, checkpoint, resposta,
answer_confidencee limiar junto de cada resultado, a partir de hoje - só então mexa nos critérios
Tome cuidado com a ordem: se você ajustar o critério antes de começar a gravar a versão, perde a fronteira entre o antes e o depois
E histórico sem versão não se recupera depois, não tem script mágico que adivinhe qual enunciado rodou em cada linha…
Até o próximo post! 😀
Perguntas frequentes
O Laya guarda histórico de versões dos critérios sozinho?
Não, isso não é um recurso do modelo. Versionar critérios do Laya é responsabilidade do seu sistema: você extrai as perguntas pra um arquivo, atribui a versão e grava tudo junto com o resultado. O modelo só recebe o espaço de respostas na hora da requisição.
Preciso retreinar o Laya quando mudo a definição de um critério?
Não. O espaço de respostas é definido no momento da requisição, então um schema novo não exige retreino. Isso agiliza mudanças, mas também é o motivo pelo qual o histórico pode misturar perguntas diferentes se ninguém versionar o enunciado.
Só o número de versão já resolve, ou preciso do hash também?
O número de versão é o que humanos leem, mas ele não pega mudança silenciosa: alguém pode editar uma definição de rótulo e esquecer de subir a versão. O hash do conteúdo do enunciado serve exatamente pra travar isso no CI antes que vire produção.
Trocar o checkpoint do Laya sem mexer nas perguntas conta como mudança de versão?
Sim. O checkpoint (convaiinnovations/laya, laya-multilingual ou laya-typed-decisions) faz parte do conteúdo usado pra gerar o hash do enunciado. Trocar de checkpoint muda o comportamento da decisão mesmo com as perguntas idênticas, então isso também precisa subir a versão.
Quantas opções uma pergunta choice pode ter antes de perder precisão?
O recomendado é manter abaixo de cerca de 20 opções. As opções dividem um orçamento fixo de 256 tokens da cabeça de decisão, e um espaço de rótulos grande demais derruba a precisão da resposta.
Como escolher o limiar de confiança pra decisão automática do Laya?
O limiar não é uma propriedade do modelo, é uma política sua. Você usa o answer_confidence retornado pelo Laya e mede a precisão real naquela cobertura nos seus dados; a confiança ordena as decisões, mas não prova sozinha que estão corretas.
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 30min
Blog | Mais populares

Choice, score e noul: quais são os tipos de pergunta do Laya e quando usar cada um?
Entenda os tipos de pergunta do Laya: choice, score e noul. Como cada um funciona, a acurácia de cada primitiva e quando usar em cada situação.

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.

Quando não usar o laya? Os limites de um motor de decisões que não gera texto
Quando não usar laya? Veja os limites do motor de decisões: sem texto, sem zero-shot eficaz, sem raciocínio em várias etapas. Descubra onde ele falha.
