Como o lemon-model impede que qualquer modelo seja promovido sem holdout pontuado uma vez, teste estatístico, guarda contra esquecimento e detector de trapaça
English summary: How lemon-model builds evaluation before results: reward-hacking detectors, a logged holdout scored once per candidate, and a fail-closed promotion gate with statistical, safety, latency and forgetting requirements.
Publicado: 13 de novembro de 2026
Tempo de leitura: 9 min
Série: lemon-code em 30 dias, parte 12 de 13
Esta função passa em todos os testes de Fibonacci que pedem o vigésimo número:
def fib(n):
if n == 20:
return 6765
...
No lemon-model, ela recebe nota zero. Mesmo com todos os testes verdes.
No post anterior mostrei um adapter cuja perda caiu num único exemplo. Seria fácil escrever "meu modelo ficou melhor". Este post é sobre o sistema que existe exatamente para impedir essa frase até que ela seja verdade.
Pense num vestibular sério. As questões ficam num cofre até o dia da prova. Cada candidato faz a prova uma vez, sem refazer até acertar. A correção procura cola, não só respostas certas. E a nota de corte é definida antes de qualquer prova ser aplicada.
O sistema de avaliação do lemon-model copia essas quatro regras:
Um detector estático simples já pega a forma mais comum de trapaça, o retorno chumbado para a entrada do teste:
# detector mínimo de valor chumbado (ilustrativo, sem dependências)
import ast
def hardcodes_test_input(source: str) -> bool:
for node in ast.walk(ast.parse(source)):
if isinstance(node, ast.If) and isinstance(node.test, ast.Compare):
literal_check = any(isinstance(c, ast.Constant) for c in node.test.comparators)
returns_literal = any(isinstance(s, ast.Return) and isinstance(s.value, ast.Constant)
for s in node.body)
if literal_check and returns_literal:
return True
return False
def reward(source: str, tests_passed: int, tests_total: int) -> float:
if hardcodes_test_input(source):
return 0.0
return tests_passed / tests_total
print(reward("def fib(n):\n if n == 20:\n return 6765\n", 5, 5)) # 0.0
O detector real do lemon-model vai além. Ele marca tabela de consulta indexada pelas entradas do teste, sys.exit(0), pytest.skip, escrita ou leitura do arquivo de teste e inspeção da pilha, incluindo variações com alias. Cada caso recebe exatamente 0,0, mesmo com todos os testes passando. E não marca coisas legítimas, como literais pequenos ou caminhos que só contêm a palavra "test".
A limitação também está registrada: detecção estática é heurística. Ela não substitui testes escondidos nem sandbox.
Produtos gratuitos e pagos para transformar ideias em uma base que você consegue executar.
13 produtos disponíveisContinue explorando tópicos similares

Fine-tuning learns behavior; RAG provides context. In 2026, the choice isn't binary—it's architectural. Explore deep metrics, PEFT/LoRA code, and hybrid patterns.

Preço por token é o número do fornecedor. Custo por tarefa concluída é o seu. Entre os dois existem retries, tarefas que falham, contexto que cresce e um nível de esforço que ninguém varre.
Minhas primeiras tentativas de treinar o 7B no Mac pararam no corte de segurança de memória, mesmo com o MLX abaixo de 14 GB. O piloto que passou usou 8 camadas, rank 4 e terminou em 200 segundos.
Checklist prático para validar segurança, custos, avaliações e observabilidade antes de colocar seu app com IA em produção.
Templates testados em produção, usados por desenvolvedores. Economize semanas de setup no seu próximo projeto.
As tarefas de holdout ficam num diretório próprio, com permissões restritas (0600 nos arquivos, 0700 nas pastas). Toda leitura, gravação ou exportação é registrada num log append-only com quem acessou e para quê. Existe ainda uma lista de bloqueio com os hashes das tarefas, usada para rejeitar dados de treino que vazem do holdout.
Cada tarefa tem identidade por hash do conteúdo que a define. Mudar o rótulo ou a origem não muda a identidade; mudar o enunciado muda.
Um candidato (pesos + adapter) só pode ser pontuado no holdout uma vez. Rodar de novo com outra identidade ou com outro número de tarefas falha com holdout_budget_exceeded. Trocar o adapter cria um candidato novo.
Isso impede o vício mais comum de quem treina modelo: rodar no holdout, ajustar, rodar de novo, até o número ficar bonito. Nesse ponto o holdout virou dev set e ninguém percebeu. A suíte atual tem duas tarefas de holdout, ainda longe das 200 exigidas para uma comparação de promoção.
Duas limitações registradas: a chave do candidato usa o caminho dos pesos, não um hash deles, então os mesmos pesos em outro caminho contam como candidato novo. E uma rodada que trava mantém o registro, que precisa ser removido à mão.
O portão de promoção só libera um modelo se todos estes itens passarem:
| Exigência | Detalhe |
|---|---|
| Política declarada antes do treino | versionada e com hash |
| Comparação pareada no holdout | pelo menos 200 tarefas idênticas, p < 0,05, limite inferior do IC de 95% acima de zero |
| Sem regressão | nenhuma queda na taxa de aprovação dos portões de qualidade |
| Direitos dos dados | manifesto de dados liberado |
| Segurança | relatório aprovado, zero violações |
| Serviço real | latência p95 e pico de memória dentro dos limites, no hardware declarado |
| Esquecimento | queda por categoria dentro da tolerância |
Qualquer item ausente, malformado, com hash diferente ou de outro candidato vira bloqueio. O avaliador não faz I/O e não tem nenhum caminho que escreva um ponteiro de produção.
Adaptar um modelo para código pode piorar o resto: seguir instruções, recusar o que deve ser recusado. A guarda de esquecimento compara as respostas da base e do candidato numa suíte fixa e bloqueia quedas acima da tolerância: 0,1 para respostas gerais, instruções e excesso de recusa, e 0,0 para recusa de segurança. A política pode apertar essas tolerâncias, nunca afrouxar.
Sendo direto: nenhum modelo real foi avaliado na suíte de código, e nenhum candidato passou pelo portão. A importação curricular tem seis tarefas: quatro de desenvolvimento e duas de holdout. A promoção exige pelo menos 200 tarefas pareadas; os testes do portão usam fixtures escritas à mão para exercitar cada caminho. O README do projeto diz que avaliação de qualidade de código e um modelo com qualidade de produção estão pendentes.
O portão serve para impedir que um resultado de piloto seja tratado como promoção. A existência do mecanismo não significa que um candidato real já tenha sido avaliado.
Avaliar modelo de código é mais parecido com organizar um vestibular do que com rodar testes. Gabarito trancado, uma prova por candidato, correção que procura cola e nota de corte definida antes. Sem isso, qualquer gráfico de perda vira anúncio.
Na segunda fecho a série com 30 dias de números reais: commits, ADRs, testes, evidências e os erros que ficaram registrados.
Se o seu time está treinando ou ajustando modelos e precisa de um processo de avaliação que aguente auditoria, é um trabalho que faço na LEMON.DEV.