Text Classification
Transformers
Safetensors
Portuguese
support-automation
e-commerce
portuguese
ticket-routing
decision-model
multi-task
ensemble
Instructions to use syvorai/decisum with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use syvorai/decisum with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="syvorai/decisum")# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("syvorai/decisum", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download scripts/servir_ensemble.py from syvorai/decisum: direct link, hf CLI and curl.
- Browser
- Download file 5.31 kB
-
https://huggingface.co/syvorai/decisum/resolve/main/scripts/servir_ensemble.py
- Command line
-
hf download hf://syvorai/decisum/scripts/servir_ensemble.py
-
curl -L -o servir_ensemble.py https://huggingface.co/syvorai/decisum/resolve/main/scripts/servir_ensemble.py
5.31 kB
| #!/usr/bin/env python3 | |
| """ | |
| servir_ensemble.py — API do ensemble (média das probs calibradas de N decisors). | |
| Mesmos endpoints do servir_decisor (GET /saude, POST /decidir); carrega 2+ | |
| checkpoints, roda cada pergunta em cada modelo e devolve a probabilidade | |
| MÉDIA (calibrada por modelo antes da média — média de distribuições | |
| calibradas continua calibrada). Roteamento: mínima confiança da média. | |
| Uso (host .144, GPU 1, porta de produção ou teste): | |
| python3 scripts/servir_ensemble.py \ | |
| --checkpoints models/decisor-v10 models/decisor-v11_cons \ | |
| --device cuda --porta 8101 | |
| """ | |
| import argparse | |
| import json | |
| import sys | |
| from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parent)) | |
| from chamados_para_decisor import CATEGORIAS, DECISOES, URGENCIA # noqa: E402 | |
| from inferencia_decisor import DecisorCPU # noqa: E402 | |
| PERGUNTAS = { | |
| "categoria": {"tipo": "choice", | |
| "instrucao": "Qual a categoria principal do chamado?", | |
| "opcoes": CATEGORIAS}, | |
| "decisao": {"tipo": "choice", | |
| "instrucao": "Qual decisão tomar para este caso?", | |
| "opcoes": DECISOES}, | |
| "urgencia": {"tipo": "score", | |
| "instrucao": "Qual a urgência? (0=baixa, 3=crítica)", | |
| "opcoes": URGENCIA}, | |
| } | |
| DECISORES = [] | |
| def decidir(estado: str, limiar: float) -> dict: | |
| respostas, confs = {}, [] | |
| for nome, pergunta in PERGUNTAS.items(): | |
| ops = pergunta["opcoes"] | |
| media = None | |
| votos = [] | |
| for d in DECISORES: | |
| r = d["modelo"].prever(estado, pergunta) | |
| probs = [r["distribuicao"].get(op, 0.0) for op in ops] | |
| votos.append(probs) | |
| votos.append(None) # placeholder p/ manter par modelo/(peso) | |
| votos.pop() | |
| media = [sum(v[k] for v in votos) / len(votos) for k in range(len(ops))] | |
| i = max(range(len(ops)), key=lambda k: media[k]) | |
| respostas[nome] = { | |
| "resposta": ops[i], | |
| "confianca": round(media[i], 4), | |
| "distribuicao": {op: round(media[k], 4) | |
| for op, k in zip(ops, range(len(ops)))}, | |
| } | |
| confs.append(media[i]) | |
| conf = min(confs) | |
| return {"respostas": respostas, | |
| "confianca_roteamento": round(conf, 4), | |
| "limiar": limiar, | |
| "destino": "automatico" if conf >= limiar else "system2"} | |
| class Handler(BaseHTTPRequestHandler): | |
| def _json(self, obj, status=200): | |
| corpo = json.dumps(obj, ensure_ascii=False).encode() | |
| self.send_response(status) | |
| self.send_header("Content-Type", "application/json; charset=utf-8") | |
| self.send_header("Content-Length", str(len(corpo))) | |
| self.end_headers() | |
| self.wfile.write(corpo) | |
| def log_message(self, *a): | |
| pass | |
| def do_GET(self): | |
| if self.path == "/saude": | |
| self._json({ | |
| "status": "ok", | |
| "ensemble": [d["nome"] for d in DECISORES], | |
| "n_modelos": len(DECISORES), | |
| "calibrado": all(bool(d["modelo"].temps) for d in DECISORES), | |
| "categorias": len(CATEGORIAS), | |
| }) | |
| return | |
| self._json({"erro": "use GET /saude ou POST /decidir"}, 404) | |
| def do_POST(self): | |
| if self.path != "/decidir": | |
| self._json({"erro": "rota desconhecida"}, 404) | |
| return | |
| try: | |
| corpo = json.loads(self.rfile.read(int(self.headers["Content-Length"]))) | |
| estado = str(corpo["estado"]).strip() | |
| if not estado: | |
| raise ValueError("estado vazio") | |
| limiar = float(corpo.get("limiar", 0.8)) | |
| if not 0.0 <= limiar <= 1.0: | |
| raise ValueError("limiar fora de [0,1]") | |
| except (KeyError, ValueError, TypeError) as e: | |
| self._json({"erro": f"payload inválido: {e}", | |
| "formato": {"estado": "texto", "limiar": 0.8}}, 400) | |
| return | |
| self._json(decidir(estado, limiar)) | |
| def _device_auto() -> str: | |
| """Escolhe o melhor device disponível: cuda → mps → cpu.""" | |
| import torch | |
| if torch.cuda.is_available(): | |
| return "cuda" | |
| if getattr(torch.backends, "mps", None) and torch.backends.mps.is_available(): | |
| return "mps" | |
| return "cpu" | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--checkpoints", nargs="+", required=True) | |
| ap.add_argument("--device", default="auto", | |
| choices=["auto", "cuda", "mps", "xpu", "cpu"], | |
| help="default: auto (cuda → mps → cpu)") | |
| ap.add_argument("--porta", type=int, default=8101) | |
| args = ap.parse_args() | |
| device = _device_auto() if args.device == "auto" else args.device | |
| print(f"device: {device}") | |
| for ck in args.checkpoints: | |
| DECISORES.append({"nome": ck.rstrip("/").split("/")[-1], | |
| "modelo": DecisorCPU(ck, device=device)}) | |
| print(f"✓ carregado: {ck}") | |
| print(f"ensemble de {len(DECISORES)} modelos na porta {args.porta}") | |
| ThreadingHTTPServer(("0.0.0.0", args.porta), Handler).serve_forever() | |
| if __name__ == "__main__": | |
| main() | |