Intermediário7 min de leitura

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

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:

TipoRespondeExemplo
choiceuma entre N categoriascasa, musica, lembrete…
scoreuma posição numa escala ordenadacalmo → furioso
noulprobabilidade 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:

Terminal window
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:

Terminal window
LAYA_DEVICE=cpu laya "Fui cobrado duas vezes no cartão, quero o estorno" --preset triage --json

A 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:

intents.py
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çãoTreinoValidaçãoLatência/frase
multilingual, critérios em português11/1610/1678 ms
multilingual, critérios em inglês6/16——
english, critérios em português8/16—281 ms
typed-decisions, critérios em português9/16—281 ms

Duas lições logo de cara:

  1. 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.
  2. 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 consulta passou 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 ≥CoberturaPrecisão
0,5030/3263%
0,8023/3274%
0,9020/3280%
0,9516/3288%

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.

roteador.py
from dataclasses import dataclass
from laya import Router
from intents import INTENT_QUESTIONS
ATALHOS = {"casa", "musica", "lembrete"}
LIMIAR = 0.95
router = Router(preload=True, default="multilingual", max_loaded=1)
@dataclass
class 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:

LimiarFalas no atalhoAtalhos corretosComandos que foram para o Hermes
0,9098/97/15
0,9577/78/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.

~/.hermes/config.yaml
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.0

O 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 name
invalid_questions: questions[sentimento].instructions must be a non-empty string
invalid_questions: questions[sentimento].criteria must be a non-empty object of label -> description
MCP 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:

~/.hermes/skills/productivity/laya/SKILL.md
---
name: laya
description: 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:

Terminal window
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#

UsoVale 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,95Sim76 ms, sem erro no atalho, o resto segue para o LLM
Ferramenta MCP para rotular em loteSim, com skillLocal, de graça; a skill elimina os erros de formato
Substituir o LLM em tarefas abertasNãoEle 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

  1. Laya no GitHub github.com
  2. Hermes Agent hermes-agent.nousresearch.com
  3. Model Context Protocol modelcontextprotocol.io

Encontrou um erro? Me avise por e-mail