Instructions to use vinimlo/gama with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use vinimlo/gama with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("token-classification", model="vinimlo/gama")# Load model directly from transformers import AutoTokenizer, AutoModelForTokenClassification tokenizer = AutoTokenizer.from_pretrained("vinimlo/gama") model = AutoModelForTokenClassification.from_pretrained("vinimlo/gama", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Gama
Extrator de citações jurídicas em português do Brasil. Lê um documento judicial e marca onde está cada citação de jurisprudência e de lei, com o tipo de cada uma. É o extrator da nossa solução para o Desafio Caça-Alucinações (BRACIS 2026 × Jusbrasil), que verifica se as citações de um texto existem de verdade num acervo de decisões.
O nome homenageia Luiz Gama (1830–1882), advogado abolicionista que libertou centenas de pessoas nos tribunais citando a lei com precisão.
Esta é a versão v1.3: as 12 primeiras das 22 camadas do v1.2, treinadas para imitar as
probabilidades do v1.2 (destilação). Dentro da solução, a saída no estilo dos documentos do
desafio é a mesma do v1.2; em texto real fica no mesmo nível, e roda 1,5 vez mais rápido.
O v1.2 continua disponível na tag v1.2.
Código, pipeline completo e experimentos: github.com/vinimlo/gama.
O que o modelo faz
Classificação de tokens no esquema BIO, com três tipos:
| Rótulo | O que marca | Exemplo |
|---|---|---|
JURIS |
precedente identificado por número, súmula ou tema | REsp nº 1.234.567/SP, Súmula 7 do STJ |
LEI |
dispositivo de lei, com o nome do diploma | art. 927 do Código de Processo Civil |
VAGA |
julgado citado sem número: tribunal, ano e relator | julgado do STJ proferido em 2021 pela relatoria de Nancy Andrighi |
As bordas seguem as convenções do gabarito do desafio: artigo e preposição antes da citação ficam fora, pontuação depois fica fora, a UF no fim do número entra.
O modelo só diz onde a citação está. Se ela existe ou foi inventada, quem decide é uma consulta exata ao acervo, feita pelo resto do pipeline. Um modelo que decidisse isso sozinho estaria chutando com fluência, que é justamente a alucinação que se quer pegar.
Como usar
O repositório traz o bio.py, o mesmo módulo que rotulou o treino e decodifica a saída:
import importlib.util
import torch
from huggingface_hub import hf_hub_download
from transformers import AutoModelForTokenClassification, AutoTokenizer
REPO, REV = "vinimlo/gama", "5f924ca2fa77c2aae6afe6d770c78ca4438f52f3"
tok = AutoTokenizer.from_pretrained(REPO, revision=REV)
modelo = AutoModelForTokenClassification.from_pretrained(REPO, revision=REV).eval()
spec = importlib.util.spec_from_file_location("bio", hf_hub_download(REPO, "bio.py", revision=REV))
bio = importlib.util.module_from_spec(spec)
spec.loader.exec_module(bio)
texto = ("Nesse sentido, invoca-se o REsp nº 1.234.567/SP e o art. 927 do Código de Processo Civil, "
"bem como o julgado do STJ proferido em 2021 pela relatoria de Nancy Andrighi.")
enc = tok(texto, return_offsets_mapping=True, return_tensors="pt")
with torch.no_grad():
rotulos = modelo(input_ids=enc["input_ids"],
attention_mask=enc["attention_mask"]).logits[0].argmax(-1).tolist()
offsets = [tuple(o) for o in enc["offset_mapping"][0].tolist()]
for ini, fim, tipo in bio.decodificar(offsets, rotulos, texto):
print(tipo, texto[ini:fim])
# JURIS REsp nº 1.234.567/SP
# LEI art. 927 do Código de Processo Civil
# VAGA julgado do STJ proferido em 2021 pela relatoria de Nancy Andrighi
O contexto é de 8.192 tokens, o bastante para a maioria dos documentos numa passada só.
Para documentos maiores, para a confiança por citação e para a versão mais recente do
bio.py, use o extrator do repositório no GitHub (src/gama/extratores/neural.py).
Rodar a solução completa
Os pesos sozinhos só acham as citações. A solução inteira, com a guarda, a consulta ao acervo e a confiança calibrada, roda em Docker a partir do repositório no GitHub:
git clone https://github.com/vinimlo/gama && cd gama
bash run.sh --preparar # com rede, uma vez
bash run.sh <caminho_db> <pasta_txt> <arquivo_saida.csv> # sem rede
O primeiro comando constrói a imagem e baixa estes pesos na revisão fixa. O segundo lê a
base SQLite do acervo e uma pasta de .txt, roda sem rede e grava o CSV no formato da
submissão, com um JSON por documento ao lado. Usa a GPU se o Docker tiver o runtime NVIDIA;
sem ela roda em CPU, a cerca de 1 s por documento.
A guarda da solução
Na solução, o modelo não trabalha sozinho. Entre ele e o resto do pipeline há uma guarda, e os números "com a guarda" desta página são dela. Quem usar só os pesos obtém o "Gama sozinho" das tabelas.
A guarda existe porque o modelo foi treinado no estilo dos documentos do desafio e, fora dele, erra de um jeito previsível. Em ementa real ele pode tomar o "Rel. Min. Fulano, julgado em ..." que vem logo depois de um precedente numerado por uma referência vaga, ou marcar pedaços soltos como "Rel", "2011" e "DJe". Mas ele sabe quando hesita. O que separa esses erros dos acertos é a confiança do span: a média, sobre os tokens rotulados como citação, da probabilidade que o modelo deu ao rótulo escolhido. No estilo do desafio, o menor dos 4.447 acertos do v1.3 no estresse difícil tem 0,9993 (com o v1.2, nenhum de 34.171 acertos ficou abaixo de 0,98). Os falsos positivos do v1.3 em ementa têm mediana de 0,83.
São duas regras, nesta ordem:
VAGAa até 2 caracteres de outro span sai. No molde do desafio, a referência vaga é uma frase própria, e a mais próxima de outra citação fica a 4 caracteres.- Span com confiança abaixo de 0,95 sai. No lugar entra o span de uma régua de expressões regulares que cruza aquele trecho, se houver, desde que não cruze um span confiante do modelo.
No estilo do desafio nenhuma das duas dispara. Em 4.626 documentos, a saída é a mesma com e
sem guarda, e a nota oficial não se mexe. Fora dele, sim. Nas 305 ementas do benchmark, o
F1 de extração vai de 0,620 a 0,818. Num teste de 172 ementas sorteadas depois, fora de tudo
o que foi usado para escolher as regras, o ganho foi +0,205 (IC95 +0,154 a +0,246). A
implementação está em src/gama/extratores/guarda.py no GitHub; o extrator da solução,
--extrator neural, já a inclui.
Resultados
v1.3 contra v1.2, dentro da solução
Solução completa (extrator, guarda, resolver, confiança calibrada) com cada versão do modelo.
| v1.2 | v1.3 | |
|---|---|---|
| Dev, estresse difícil e 4.000 sintéticos (4.626 documentos), métrica oficial | 1,10000 / 1,09999 / 1,09999 | iguais, com o JSON final idêntico documento a documento |
| Texto real, F1 de extração, 172 ementas separadas antes da escolha da guarda | 0,820 | 0,839 (+0,019; IC95 +0,003 a +0,033) |
| Tempo por documento, NVIDIA L4 / CPU, medidos lado a lado | 0,071 s / 1,62 s | 0,046 s / 0,93 s |
| Parâmetros / pesos em FP32 | 307,5M / 1,23 GB | 257,4M / 1,03 GB |
As 172 ementas foram separadas antes da escolha da guarda e mediram a guarda uma vez só. Depois elas também serviram para comparar os alunos e escolher o v1.3, então para a diferença entre v1.2 e v1.3 são dado de desenvolvimento, não um teste independente. E retreinado com outras sementes o v1.2 varia mais do que essa diferença (0,785 a 0,842 nas 172, com a guarda).
v1.3 contra extratores sem treino
Métrica oficial do desafio, macroF1 · (1 − 0,5·τ) · (1 + 0,10·(1 − Brier)), com o mesmo
resolver para todos os extratores. O teto prático é 1,10.
| Conjunto | Gama v1.3 | Gama v1.3 com a guarda da solução | Régua (regex) | Qwen3-8B zero-shot | GLiNER 2.5 zero-shot |
|---|---|---|---|---|---|
| Dev da organização, 26 documentos | 1,10000 | 1,10000 | 1,09848 | não medido¹ | não medido¹ |
| Estresse difícil, 600 documentos, métrica oficial | 1,09999 | 1,09999 | 0,84880 | 0,81065 | 0,44445 |
| Texto real, 305 ementas, F1 de extração | 0,620 | 0,818 | 0,663 | 0,707 | 0,608 |
| Tempo por documento, NVIDIA L4 | 0,035 s | 0,037 s | 0,001 s | 11,7 s | 0,110 s |
¹ Os documentos do dev são da organização e não saem da nossa máquina.
O estresse difícil segue o estilo dos documentos do desafio, com frases escritas por um
LLM que nenhum modelo viu no treino e ruído de OCR forte. Nele o fine-tune é a diferença:
o melhor extrator sem treino fica abaixo até da régua. O texto real são ementas do STF,
STJ e TJRJ (celsowm/jurisprudencias_br,
CC-BY-4.0), com citações anotadas por dois LLMs e adjudicadas por critério escrito. Ali a
ordem se inverte: um LLM genérico de 8B extrai melhor que o modelo sozinho, a um custo
cerca de 330 vezes maior por documento. A guarda da solução troca os trechos em que o
modelo hesita (confiança < 0,95) pelos da régua e põe o Gama de volta na frente.
Na fase de treino do Kaggle, a solução completa com o v1.2 marcou 1,09999.
v1.3 contra modelos treinados nos mesmos dados
O gráfico acima compara o Gama com extratores sem treino. Para separar o que vem do fine-tune do que vem da escolha do modelo, treinamos concorrentes nos mesmos documentos sintéticos do v1.2 e medimos todos pelo mesmo harness, sozinhos e com a guarda da solução. F1 de extração em texto real; as 305 ementas do benchmark escolheram os limiares, e as 172 separadas depois só confirmam.
| Texto real sozinho, 305 / 172 | Texto real com a guarda, 305 / 172 | Estresse difícil sozinho, métrica oficial | |
|---|---|---|---|
| Gama v1.3 | 0,620 / 0,635 | 0,818 / 0,839 | 1,09999 |
| BERTimbau, mesma receita do v1.2 | 0,697 / 0,705 | 0,813 / 0,826 | 1,09999 |
| GLiNER 2.5 multi treinado | 0,808 / 0,824 | 0,816 / 0,837 | 1,09615 |
| mmBERT-base com o encoder congelado, só a saída treinada | 0,243 / 0,247 | 0,350 / 0,353 | 0,90182 |
Sozinhos, BERTimbau e GLiNER treinados extraem melhor que o Gama em texto real. Com a guarda, nenhum passa o v1.3. O GLiNER 2.5 treinado chega ao mesmo nível sem guarda nenhuma (−0,010 nas 305 e −0,015 nas 172, IC95 cruzando zero), porque já generaliza fora do molde; a guarda quase não o ajuda, já que a maior parte dos erros dele sai com score acima de 0,99. O Gama especializa no estilo do desafio e compensa sabendo quando hesita, e no estilo do desafio segue na frente (1,09999 contra 1,09615). Uma semente por modelo: não é equivalência. Até 29/09 estas tabelas usavam o GLiNER multi v2.1, que era pior nos dois papéis.
O encoder congelado vai longe no estilo do desafio e desaba em texto real. O fine-tune rende pela adaptação do encoder. O mmBERT-base original, com uma cabeça de rótulos sem treino, dá F1 de 0,0005: ruído, como se esperava.
Retreinamos também o v1.2 e o v1.3 com as sementes 7 e 21. Com a guarda, o v1.2 vai de 0,760 a 0,830 nas 305 ementas; o v1.3, destilado do mesmo professor, fica entre 0,812 e 0,818. No estilo do desafio as seis sementes dão o mesmo JSON final no dev e 1,09999 no estresse com a guarda.
Todo número de texto real aqui usa um gabarito de LLM adjudicado, e cada concorrente teve uma semente. Um teste com gabarito humano, num conjunto que não participou de nenhuma escolha, ainda não foi feito. Desenho e intervalos: controles.
Treino
v1.3, destilado do v1.2:
- Aluno: as camadas 0 a 11 do v1.2, com embeddings e cabeça herdados. A poda das camadas de cima segue Sajjad et al., 2004.03844.
- Perda:
0,5·CE(ouro) + T²·KL(professor/T ‖ aluno/T) + 0,5·KL(professor ‖ aluno), T = 2, com o v1.2 rodando no próprio treino. O aluno copia as probabilidades, não só o rótulo, porque a solução usa a confiança do modelo. - Dados: os mesmos 6.000 documentos sintéticos e 1.818 ementas reais sem rótulo, onde só
vale a imitação do v1.2. As ementas vêm do
celsowm/jurisprudencias_br, que o Celso F. publica sob CC-BY-4.0 com decisões do STF, do STJ e do TJRJ coletadas pelo Juriscraper; nenhuma delas está nos testes. Foi nesse texto que o limite do v1.2 fora dos moldes apareceu, e é nele que o v1.3 aprende a reproduzir a confiança do professor fora do estilo do desafio, que é de onde a guarda lê. - Hiperparâmetros: 3 épocas,
max_len2048, lote 8, taxa de aprendizado 5e-5, semente 13. - Hardware: 1× A100 80 GB, via HF Jobs, 18 minutos. Script:
treino/destilar.pyno GitHub.
v1.2, o professor:
- Base:
jhu-clsp/mmBERT-base(MIT), revisãoc5955035435e2bf121cde7f3c8863ef52ff35d82. - Dados: 6.000 documentos sintéticos, nenhum real. Os documentos do desafio saem de um gerador por moldes; desmontamos o dev set em bancos de peças e remontamos documentos novos citando fichas reais do acervo, com rótulo exato por construção. LLMs de pesos abertos (DeepSeek-V4-Pro, Kimi-K3) só ampliaram os bancos de frases, sem escrever citação nem rótulo. Um injetor de ruído de OCR completa o conjunto.
- Hiperparâmetros: 3 épocas,
max_len1024, lote 8, taxa de aprendizado 5e-5, semente 13. - Hardware: 1× A100 80 GB, via HF Jobs. Script:
treino/treinar.pyno GitHub.
O comando exato de cada passo, dos bancos de frases aos pesos, está no guia de reprodução.
Limitações
- O modelo especializou no estilo dos documentos do desafio. Em ementas reais, com citação em lista depois de "DJe", entre parênteses ou em caixa-alta, ele marca fragmentos soltos ("Rel", "2011", "DJe"); sozinho, fica atrás da régua de regex (0,620 contra 0,663). Na solução, a guarda troca os trechos em que o modelo hesita pelos da régua, e o F1 em texto real sobe para 0,818 com o v1.3.
- Sozinho, o modelo não é o melhor extrator em texto real: um BERTimbau e um GLiNER 2.5 treinados nos mesmos dados passam dele. A vantagem da solução vem da guarda, que depende da confiança do modelo.
- Só português do Brasil e formatos de citação dos tribunais brasileiros.
- Sozinho, não verifica nada: achar a citação é metade do trabalho, a outra metade é a consulta ao acervo.
Reprodutibilidade
A revisão acima é fixa e é a que a solução usa. A inferência é determinística (eval(),
sem amostragem, algoritmos determinísticos do torch) e dá a mesma saída em GPU e em CPU.
Os dados de treino estão em vinimlo/gama-goldenset,
nas revisões 31474b1 (v1.2) e ec430c0 (v1.3).
Referências
- Marone et al., mmBERT, 2025, e Warner et al., ModernBERT, 2024: o encoder de partida.
- Hinton et al., Distilling the Knowledge in a Neural Network, 2015, e Sajjad et al., On the Effect of Dropping Layers of Pre-trained Transformer Models, 2020: a destilação e a poda das camadas de cima.
- Geifman e El-Yaniv, Selective Classification for Deep Neural Networks, 2017: a ideia por trás da guarda.
- Dahl et al., Large Legal Fictions, 2024, e Magesh et al., Hallucination-Free?, 2024: o problema da citação jurídica inventada.
A lista comentada está em literatura.
Como citar
@misc{gama2026,
title = {Gama: extrator de citações jurídicas do Desafio Caça-Alucinações (BRACIS 2026 × Jusbrasil)},
author = {Melo, Vinícius and Siron, Gabriel},
year = {2026},
howpublished = {\url{https://huggingface.co/vinimlo/gama}},
note = {v1.3, revisão 5f924ca}
}
Equipe
Vinícius Melo e Gabriel Siron, para o Desafio Caça-Alucinações (BRACIS 2026 × Jusbrasil).
In English
Gama is a token-classification model (BIO: JURIS case-law citations, LEI statute
provisions, VAGA unnumbered references by court, year and rapporteur) for Brazilian
Portuguese legal documents. Version v1.3 keeps the first 12 of the 22 layers of v1.2
(fine-tuned from mmBERT-base on 6,000 synthetic documents) and is distilled from it: inside
the full pipeline it gives the same outputs on challenge-style documents, the same level on
real court summaries (within seed variance), and runs 1.5 times faster. It only locates
citations; whether a citation exists is decided by an exact lookup in a frozen collection of
decisions. On challenge-style documents it clearly beats zero-shot open models (Qwen3-8B,
GLiNER 2.5); on real court summaries outside that style, a zero-shot Qwen3-8B extracts
better than the model alone at about 330 times the cost, and the pipeline's guard (falling
back to rules where the model is unsure) puts Gama back ahead. Trained on the same synthetic
data, BERTimbau and a fine-tuned GLiNER 2.5 extract better than the model alone on real court
summaries, but neither beats v1.3 once the guard is on (GLiNER 2.5 reaches the same level
with no guard at all, within one seed); with a frozen encoder and only the output layer
trained, mmBERT-base collapses on real text (F1 0.243), so the gain comes from adapting the
encoder. Real-text gold labels are LLM-adjudicated, not human. The full pipeline runs in
Docker from the GitHub repository with bash run.sh --preparar (network, once) and
bash run.sh <db_path> <txt_folder> <output.csv> (offline). Built for the BRACIS 2026 ×
Jusbrasil hallucination detection challenge.
- Downloads last month
- 101
Model tree for vinimlo/gama
Base model
jhu-clsp/mmBERT-base