Metadata-Version: 2.4
Name: serigy
Version: 0.3.0
Summary: Inference-only PT-BR typed predictors: Serigy Sapiens (predict / system_one with confidence + uncertainty)
Author-email: Wendell Barreto <wendellbarreto@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/wendellbigato/serigy-jev
Project-URL: Hugging Face, https://huggingface.co/wendellperbar/serigy-fake-news-br
Requires-Python: >=3.14
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.26.0
Requires-Dist: pydantic>=2.5.0
Requires-Dist: torch
Requires-Dist: transformers>=4.40.0
Requires-Dist: sentence-transformers>=3.0.0
Requires-Dist: safetensors>=0.4.0
Requires-Dist: huggingface_hub>=0.24.0
Requires-Dist: python-dotenv>=1.0.0
Provides-Extra: mlx
Requires-Dist: mlx>=0.31.2; extra == "mlx"
Requires-Dist: mlx-lm>=0.31.0; extra == "mlx"
Dynamic: license-file

<p align="center">
  <img src="assets/brand/simbolo_transparente.png" alt="Serigy" width="140"/>
</p>

# Serigy

Perguntas tipadas sobre textos em português, respondidas localmente pelo **Serigy Sapiens**:
**Escolha** (uma opção de um conjunto), **Indice** (posição numa escala de 2 a 10 níveis) e
**Verdadeiro** (P(sim)). Cada resposta traz `confidence`, para decidir, e `uncertainty`,
para saber se o texto é terreno conhecido.

Marca: [`assets/brand/`](assets/brand/) · [spec](.cursor/specs/marca/spec.md).

```bash
pip install serigy
```

### Qual versão para qual modelo

| Modelo no Hub | Versão do `serigy` |
|---|---|
| [`wendellperbar/serigy-fake-news-br`](https://huggingface.co/wendellperbar/serigy-fake-news-br) (padrão) | **a partir da 0.3.0** |
| `wendellperbar/serigy-jev`, `serigy-golpes-br`, `serigy-trex` (formato antigo) | **até a 0.2.0**: `pip install "serigy<0.3"` |

A 0.3.0 não lê os modelos antigos: carregá-los dá um erro que aponta a versão certa.
Mudanças em [`CHANGELOG.md`](CHANGELOG.md).

### System One (`system_one`)

```python
from serigy import SerigyModel, Escolha, Indice, Verdadeiro

model = SerigyModel("wendellperbar/serigy-fake-news-br")
perguntas = {
    "tema": Escolha(
        instructions="Qual o tema da notícia?",
        criteria={"politica": None, "tv_celebridades": None, "sociedade_cotidiano": None},
    ),
    "gravidade": Indice(instructions="Quão grave?", criteria=["Baixa", "Média", "Alta"]),
    "falsa": Verdadeiro(instructions="A notícia é falsa?"),
}
result = model.system_one(
    "BOMBA!!! Apresentador famoso da Globo é preso em 2026 e a emissora paga milhões para "
    "esconder o caso. Veja o vídeo antes que tirem do ar!",
    perguntas,
    leitura=True,
)
tema = result.answers["tema"]
print(tema.escolha, tema.confidence, tema.uncertainty, tema.leitura)
# tv_celebridades 0.82 0.08 pode_apostar
print(result.answers["falsa"].verdadeiro)
# 0.997
print(result.as_public_dict())  # JSON público
```

As mesmas três perguntas sobre os seis textos do notebook de quick start:

| Texto | "A notícia é falsa?" (P(sim) · leitura) | Tema (confidence · leitura) | `uncertainty` (falsa · tema) |
|---|---|---|---|
| Boato de celebridade (2026) | sim, 1,00 · `pode_apostar` | `tv_celebridades` 0,82 · `pode_apostar` | 0,13 · 0,08 |
| Boato de saúde (2026) | sim, 0,96 · `pode_apostar` | `tv_celebridades` 0,48 · `nao_sei` | 0,67 · 0,96 |
| Nota de política (2026) | não, 0,00 · `pode_apostar` | `politica` 0,70 · `nao_sei` | 0,14 · 0,21 |
| Texto em inglês | não, 0,17 · `nao_me_comprometo` | `tv_celebridades` 0,42 · `nao_sei` | 1,00 · 1,00 |
| Código Python | sim, 0,63 · `nao_sei` | `tv_celebridades` 0,46 · `nao_sei` | 1,00 · 1,00 |
| Receita de bolo | não, 0,12 · `pode_apostar` | `tv_celebridades` 0,51 · `nao_sei` | 0,89 · 0,92 |

Nas notícias, a `uncertainty` fica baixa ou média; nos textos fora do domínio, vai a 0,89–1,00. Ela
sobe, mas nem sempre o bastante para barrar a resposta: na receita, o "é falsa?" ainda sai como
`pode_apostar`.

O primeiro argumento de `SerigyModel` é o repositório dos pesos no Hub
(`"wendellperbar/<nome-do-modelo>"`, ver a tabela acima) ou uma pasta local com `comite.json`.
Sem argumento, ele usa `wendellperbar/serigy-fake-news-br`.

| Primitiva | O que responde | Resposta |
|---|---|---|
| `Escolha` | Qual opção? (2–6 opções, chaves livres, descrição opcional) | `escolha`, `probabilities`, `confidence`, `uncertainty` |
| `Indice` | Em que ponto da escala? (2–10 níveis ordenados) | `indice` (Σ k·P(k)), `legend`, `probabilities`, `confidence`, `uncertainty` |
| `Verdadeiro` | A proposição é verdadeira? | `verdadeiro` (P(sim)), `uncertainty` |

As perguntas e as opções são texto livre: paráfrases e perguntas novas funcionam sem
re-treino. Quando a lista de opções pode não cobrir o texto, inclua uma opção de escape
como `nenhuma_das_anteriores`. O texto é lido uma vez; cada pergunta a mais sobre ele sai
quase de graça.

### Como ler `confidence` e `uncertainty`

- **`confidence`** (a maior probabilidade, calibrada) serve para **decidir**: aja quando
  passar de um limiar que vem do custo do erro no seu caso. No `Verdadeiro`, ela é
  `max(verdadeiro, 1 − verdadeiro)`.
- **`uncertainty`** (0–1) diz se o texto é **terreno conhecido**: um valor alto significa
  um texto diferente daqueles em que o modelo aprendeu. Em textos como os do treino, metade
  das respostas fica abaixo de ~0,4 e 90% abaixo de ~0,95.

Com `leitura=True`, cada resposta ganha o campo `leitura`:

| `leitura` | Quando | Em palavras |
|---|---|---|
| `pode_apostar` | `confidence ≥ tau` e `uncertainty` ≤ o quantil `q` de referência | "a resposta é X e pode apostar" |
| `nao_me_comprometo` | `confidence ≥ tau`, mas `uncertainty` acima do quantil | "a resposta é X, mas não quero me comprometer" |
| `nao_sei` | `confidence < tau` | "não sei" |

O padrão é `tau=0.8` e `q=0.9`. Os quantis disponíveis são 0.5, 0.8, 0.9 e 0.95:
`model.system_one(texto, perguntas, leitura=True, tau=0.9, q=0.8)`.

### Atalho de fake news (`predict`)

```python
out = model.predict(
    "A Câmara dos Deputados aprovou nesta terça-feira, em votação simbólica, o projeto que altera "
    "regras do orçamento para 2026. O texto segue agora para análise do Senado."
)
# falsa=False, p_falsa=0.005, uncertainty=0.14, leitura="pode_apostar"; tema "politica"
# {"falsa": bool, "p_falsa": float, "confidence": float, "uncertainty": float,
#  "leitura": "pode_apostar" | "nao_me_comprometo" | "nao_sei",
#  "tema": {"escolha": str, "probabilities": {...}, "confidence": float, "uncertainty": float, "leitura": ...},
#  "usage": {"input_tokens": int}}
```

São as duas perguntas do treino ("A notícia é falsa?" e o tema: `politica`,
`tv_celebridades`, `sociedade_cotidiano`) numa leitura só do texto.

### CLI

```bash
serigy --model wendellperbar/serigy-fake-news-br "texto da notícia..."                           # predict
serigy --model wendellperbar/serigy-fake-news-br --json '{"state":"…","questions":{…}}' --leitura # system_one
```

### Requisitos e limites

- O modelo lê o texto com dois LLMs de ~1 B parâmetros (baixados do Hub na primeira vez).
  `SerigyModel(...)` já os carrega no construtor (`warm=False` adia para a primeira pergunta),
  e depois do primeiro download eles saem do cache local, sem consultar o Hub.
- Dois backends para ler o texto, com os mesmos pesos: `backend="torch"` (padrão; o dispositivo
  é escolhido sozinho, CUDA → MPS → CPU, e `device="cpu"` fixa um) e `backend="mlx"` (GPU do
  Mac com Apple Silicon; `pip install "serigy[mlx]"`). Na CLI: `--backend mlx`.
  O `dtype` padrão é `"auto"`: bf16 na GPU (CUDA, MPS ou MLX; ~4,5 GB) e fp32 na CPU (~9 GB).
  `dtype="float32"` ou `"bfloat16"` fixa um.
- Treinado em notícias em português (FakeBR). Fora desse domínio a `uncertainty` sobe, mas
  não o bastante para barrar tudo: cerca de metade das respostas ainda sai como
  `pode_apostar`. **Não é um verificador de fatos para produção.**
- `Prioridade` (3 patamares) não existe mais: use `Verdadeiro` ou `Indice`.
- Se algum modelo for gated, configure `HF_TOKEN`. Por segurança, só modelos de uma
  allowlist são baixados (`allow_custom_models=True` para sair dela). Em serviços HTTP, fixe
  o modelo no servidor.

Spec: [`.cursor/specs/sdk-sapiens/spec.md`](.cursor/specs/sdk-sapiens/spec.md).

### Console de treino (API + UI)

Stack Docker na raiz do repo:

```bash
make up    # postgres, rabbitmq, api, web (:3000), worker-gpu
```

UI: [http://127.0.0.1:3000](http://127.0.0.1:3000) · login via `CONSOLE_USERNAME` / `CONSOLE_PASSWORD` em `deploy/compose/.env` · detalhes em [`deploy/compose/README.md`](deploy/compose/README.md).

> Escopo: **localhost / trusted network**. Próximos passos se publicar fora do loopback: ver nota de segurança em [`deploy/compose/README.md`](deploy/compose/README.md).

### Extra: benchmark T-Rex (monorepo)

Comparativo Serigy × Jev × Laya no Chrome T-Rex Runner (`serigy_trex`):  
[src/serigy_trex/README.md](src/serigy_trex/README.md) · [docs/RELATORIO_TREX.md](docs/RELATORIO_TREX.md) · [spec](.cursor/specs/trex-benchmark/spec.md).

MIT — Copyright © 2026 Wendell Barreto.
