Um classificador de 80 ms na frente do meu assistente de voz
Testei o Laya, um classificador local, em português e liguei ele no Hermes Agent via MCP. Os números, o que deu errado e onde ele vale a pena.
Publicado em
Neste artigo
- 1Introdução
- 2O que é o Laya
- 3Pré-requisitos
- 4Mão na massa
- 4.1Instalação
- 4.2As perguntas do roteador
- 5Testando: os números
- 5.1Primeira rodada
- 5.2Tentativas que não funcionaram
- 5.3A virada: usar a confiança como portão
- 6Ligando no Hermes via MCP
- 6.1O agente errava o formato
- 6.2A solução: uma skill de uma página
- 7Armadilhas e trade-offs
- 8Resumo
- 9Conclusão
TL;DR
- O Laya responde perguntas fechadas sobre um texto (escolha, nota, sim/não) em ~80 ms na CPU, sem gerar texto e sem custo de API.
- Em português, sem fine-tuning, ele acertou ~65% da intenção numa tarefa de 6 classes. Isso não basta como classificador final, mas funciona como portão por confiança: no meu teste, o atalho acertou 7 de 7.
- Plugado no Hermes via MCP, o agente errou o formato 4 vezes seguidas. Com uma skill de uma página explicando o schema, passou a acertar de primeira.
Introdução#
Estou montando o Meia-Sete, um assistente pessoal de voz que roda sobre o Hermes Agent. Todo assistente de voz tem o mesmo problema: cada fala vai para um LLM, mesmo quando é só “apaga a luz da sala”. Isso custa segundos de latência e tokens, e em conversa por voz cada segundo de silêncio pesa.
A ideia clássica para resolver isso é um roteador de intenção: um modelo pequeno e rápido olha a fala primeiro e decide se ela pode pegar um atalho (executar um comando direto) ou se precisa do LLM. Quando o Laya apareceu no meu radar, prometendo decisões tipadas em ~33 ms e em mais de 100 idiomas, resolvi medir em vez de acreditar.
Este post é o diário desse teste: instalação, números reais em português, o que não funcionou e como liguei tudo ao Hermes.
O que é o Laya#
O Laya não é um LLM. Ele é um encoder (ModernBERT/mmBERT, de 322M a 421M de parâmetros) treinado para responder perguntas com formato fixo sobre um texto, tudo em uma única passada pelo modelo:
| Tipo | Responde | Exemplo |
|---|---|---|
choice | uma entre N categorias | casa, musica, lembrete… |
score | uma posição numa escala ordenada | calmo → furioso |
noul | probabilidade de “sim” | “a fala transmite urgência?” |
São três checkpoints: english, multilingual e typed-decisions. Um Router detecta o idioma
e escolhe o checkpoint sozinho. O pacote traz SDK Python, CLI, servidor HTTP e servidor MCP,
que é a parte que interessa para usar com agentes.
Pré-requisitos#
- Linux com Python 3.10 a 3.12 (no 3.14 o PyTorch ainda pode não ter wheel)
- ~2 GB de disco para PyTorch CPU + checkpoint
- Hermes Agent instalado, se quiser a parte de MCP
Minha máquina: CPU de 16 threads, 31 GB de RAM, sem GPU NVIDIA. Todos os números deste post são de CPU.
Mão na massa#
Instalação#
Instalar o PyTorch CPU antes evita baixar ~3 GB de bibliotecas CUDA que eu não ia usar:
python3.11 -m venv ~/.venvs/laya~/.venvs/laya/bin/pip install torch --index-url https://download.pytorch.org/whl/cpu~/.venvs/laya/bin/pip install "laya[mcp,serve]"Versões que usei: laya 0.4.0, torch 2.14.1+cpu, transformers 5.19.0.
O primeiro teste foi pela CLI, com o preset de triagem de suporte:
LAYA_DEVICE=cpu laya "Fui cobrado duas vezes no cartão, quero o estorno" --preset triage --jsonA resposta veio com "language": "pt" e roteada para o checkpoint multilingual, e acertou a
intenção (refund). Até aqui, tudo como prometido.
As perguntas do roteador#
Para o Meia-Sete defini seis intenções. O campo do estado que a pergunta lê vai entre crases
(`fala`), uma convenção do Laya:
INTENT_QUESTIONS = { "intent": { "type": "choice", "instructions": "O que o usuário quer com `fala`?", "criteria": { "casa": "controlar a casa: luz, tomada, ar-condicionado, TV, roteador ou bloquear site", "musica": "tocar, pausar, pular ou mudar o volume de música ou mídia", "lembrete": "criar alarme, timer, lembrete ou compromisso na agenda", "consulta": "uma pergunta factual rápida: hora, clima, cotação, conta", "tarefa": "um trabalho de várias etapas: pesquisar, escrever, programar, resumir", "conversa": "papo, cumprimento, opinião ou desabafo, sem pedido concreto", }, },}E escrevi 32 frases do jeito que eu falaria com o assistente (“liga o ar no 22”, “põe um timer de 10 minutos pro miojo”, “tô meio triste hoje”). Separei em 16 de treino, usadas para ajustar os critérios, e 16 de validação, que eu só olhava para conferir, para não me enganar com overfitting.
from laya import Router
router = Router(preload=True, default="multilingual")r = router.predict({"fala": "apaga a luz da sala"}, INTENT_QUESTIONS)r["answers"]["intent"] # {'choice': 'casa', 'probabilities': {...}, 'answer_confidence': 0.97, ...}Testando: os números#
Primeira rodada#
| Configuração | Treino | Validação | Latência/frase |
|---|---|---|---|
multilingual, critérios em português | 11/16 | 10/16 | 78 ms |
multilingual, critérios em inglês | 6/16 | — | — |
english, critérios em português | 8/16 | — | 281 ms |
typed-decisions, critérios em português | 9/16 | — | 281 ms |
Duas lições logo de cara:
- Escreva os critérios no idioma do texto. Eu esperava que critérios em inglês ajudassem, porque o treino do modelo foi majoritariamente em inglês. Aconteceu o contrário: a acurácia caiu quase pela metade.
- O checkpoint
multilingualé o melhor e também o mais rápido na CPU. O roteador automático já escolhe ele para português, então não há nada a configurar.
Tentativas que não funcionaram#
Ficar em ~65% me incomodou, então tentei melhorar. Nada funcionou:
- Média sobre permutações da ordem das opções. A documentação avisa que o modelo tem viés de posição. Fiz a média com 3 rotações das opções: mesma acurácia, 2,3x mais lento.
- Reescrever os critérios com verbos e com exemplos (“ex.: me lembra de, me acorda, põe um
timer”): piorou para 6/16 e 7/16. O rótulo
consultapassou a atrair quase tudo. - Trocar a escolha por seis perguntas sim/não, cada uma perguntando “é sobre X?”, e ficar com a de maior probabilidade: 9/16 e 10/16, e 2x mais lento.
A virada: usar a confiança como portão#
Se o Laya não serve como classificador final, talvez sirva como filtro: só confiar nele quando estiver muito seguro. Medi a precisão para cada limiar de confiança nas 32 frases:
answer_confidence ≥ | Cobertura | Precisão |
|---|---|---|
| 0,50 | 30/32 | 63% |
| 0,80 | 23/32 | 74% |
| 0,90 | 20/32 | 80% |
| 0,95 | 16/32 | 88% |
Melhora, mas ainda houve erro com confiança 1,00. A documentação avisa isso: os checkpoints
distribuídos são superconfiantes. Olhando os erros com confiança alta, todos caíam em consulta,
tarefa ou conversa, exatamente as intenções que precisam do LLM de qualquer jeito.
Então a regra final ficou: só pega o atalho se a intenção for de comando E a confiança for ≥ 0,95. Qualquer outra coisa vai para o Hermes.
from dataclasses import dataclassfrom laya import Routerfrom intents import INTENT_QUESTIONS
ATALHOS = {"casa", "musica", "lembrete"}LIMIAR = 0.95
router = Router(preload=True, default="multilingual", max_loaded=1)
@dataclassclass Rota: destino: str # "atalho" ou "hermes" intent: str confianca: float
def rotear(fala: str) -> Rota: q = {"intent": INTENT_QUESTIONS["intent"]} a = router.predict({"fala": fala}, q)["answers"]["intent"] intent, conf = a["choice"], a["answer_confidence"] destino = "atalho" if intent in ATALHOS and conf >= LIMIAR else "hermes" return Rota(destino, intent, conf)Resultado nas 32 frases:
| Limiar | Falas no atalho | Atalhos corretos | Comandos que foram para o Hermes |
|---|---|---|---|
| 0,90 | 9 | 8/9 | 7/15 |
| 0,95 | 7 | 7/7 | 8/15 |
Com 0,90, “bom dia, Meia-Sete!” virou lembrete com 0,92. Com 0,95 não houve nenhum erro no
atalho, e a latência mediana foi 76 ms. O preço é que metade dos comandos ainda vai para o
Hermes. Mas isso é seguro: o pior caso é ficar mais lento, nunca fazer a coisa errada.
Ligando no Hermes via MCP#
O segundo uso é deixar o próprio agente usar o Laya como ferramenta, para tarefas como “classifica o sentimento dessas 500 avaliações”. Fazer isso com o LLM custa tokens; com o Laya, é local e de graça.
mcp_servers: laya: command: /home/voce/.venvs/laya/bin/laya-mcp-server # caminho absoluto env: LAYA_DEVICE: cpu LAYA_THREADS: '8' # deixa CPU livre pro resto da máquina LAYA_MODELS: multilingual # carrega só o checkpoint que importa pra PT LAYA_DEFAULT_MODEL: multilingual connect_timeout: 60.0O mesmo pode ser feito com hermes mcp add laya --command ... --env .... O servidor expõe 8
ferramentas: laya_predict, laya_predict_batch, laya_preset, laya_decide e outras.
O agente errava o formato#
No primeiro teste pedi para classificar 4 avaliações usando laya_predict_batch. O resultado final
veio certo, mas olhando o log da sessão vi isto:
invalid_questions: questions must be a non-empty JSON object keyed by question nameinvalid_questions: questions[sentimento].instructions must be a non-empty stringinvalid_questions: questions[sentimento].criteria must be a non-empty object of label -> descriptionMCP server 'laya' rejected the last 3 calls ... Paused for ~50s.Foram quatro chamadas erradas e uma pausa forçada antes de acertar. A descrição da ferramenta não basta para o modelo acertar o schema aninhado de primeira.
A solução: uma skill de uma página#
O Hermes carrega skills de ~/.hermes/skills/. Escrevi uma com o formato exato, um exemplo
completo e as lições deste post:
---name: layadescription: Classificar muitos textos localmente (sentimento, categoria, sim/não, nota) com o servidor MCP Laya, sem gastar tokens do LLM.---
## Formato exato (siga à risca, o servidor rejeita qualquer desvio)
- `choice`: `criteria` é um **objeto** rótulo -> descrição.- `score`: `criteria` é uma **lista** ordenada do menor para o maior.- `noul` (sim/não): sem `criteria`.- `instructions` cita o campo do estado entre crases, ex.: `texto`.
(... exemplo JSON completo ...)
## Regras aprendidas em testes com português
- Escreva `instructions` e `criteria` em português quando o texto for em português.- A confiança vem otimista demais: trate abaixo de 0.9 como "incerto".Rodei de novo, com 5 avaliações:
hermes chat -s laya -q 'Classifique o sentimento destas avaliações com o Laya...'Desta vez foi uma chamada e zero erros. E o agente seguiu a regra de confiança por conta própria: marcou “É ok, nada demais” (positivo, 0,64) como incerto e disse que classificaria como neutro.
Armadilhas e trade-offs#
Resumo#
| Uso | Vale a pena? | Por quê |
|---|---|---|
| Classificador de intenção final (PT) | Não, sem treino | ~65% de acerto em 6 classes |
| Portão de atalho com confiança ≥ 0,95 | Sim | 76 ms, sem erro no atalho, o resto segue para o LLM |
| Ferramenta MCP para rotular em lote | Sim, com skill | Local, de graça; a skill elimina os erros de formato |
| Substituir o LLM em tarefas abertas | Não | Ele não gera texto nem raciocina |
Conclusão#
O Laya não é mágico, mas é uma ferramenta honesta: rápida, local e com uma confiança que, filtrada com cuidado, diz quando dá para confiar nele. Para o Meia-Sete, o papel dele é ser o porteiro que deixa passar direto só o que tem certeza.
Os próximos passos são gravar falas reais para ter um conjunto de teste de verdade e experimentar
o laya-train, que vem no pacote, para fazer fine-tuning com as minhas intenções. Se a acurácia
subir, o limiar pode descer e mais comandos pegam o atalho. Isso fica para um próximo post.
Referências
- Laya no GitHub github.com
- Hermes Agent hermes-agent.nousresearch.com
- Model Context Protocol modelcontextprotocol.io
Encontrou um erro? Me avise por e-mail