pngwn's picture
pngwn HF Staff
rebuild: gradio.Server backend with queued /classify API
6760f45 verified
Raw History Blame Contribute Delete
2.38 kB
"""gradio.Server demo: 4-class GitHub issue classifier with a fully custom frontend.
Backend: gradio.Server (FastAPI + Gradio's API engine — queue, gradio_client
compatibility). One queued endpoint `classify` plus a custom `/` route serving
the hand-written Vercel-style index.html.
Models: pngwn/github-issue-classifier-setfit-minilm and
pngwn/github-issue-classifier-distilbert, trained on pngwn/github-issues-4class.
"""
from pathlib import Path
import numpy as np
import torch
from fastapi.responses import HTMLResponse
from gradio import Server
from setfit import SetFitModel
from transformers import AutoModelForSequenceClassification, AutoTokenizer
LABELS = ["bug", "feature", "question", "support"]
SETFIT_ID = "pngwn/github-issue-classifier-setfit-minilm"
DISTIL_ID = "pngwn/github-issue-classifier-distilbert"
INDEX = Path(__file__).parent / "index.html"
print("loading models (first start takes ~1 minute)...")
setfit_model = SetFitModel.from_pretrained(SETFIT_ID)
distil_tokenizer = AutoTokenizer.from_pretrained(DISTIL_ID)
distil_model = AutoModelForSequenceClassification.from_pretrained(DISTIL_ID).eval()
print("models loaded.")
app = Server()
def run_classify(text: str, model: str) -> dict:
text = (text or "").strip()
if not text:
return {"labels": LABELS, "probs": [0.25, 0.25, 0.25, 0.25], "error": "empty_input"}
if model == "distilbert":
inputs = distil_tokenizer(text, truncation=True, max_length=256, return_tensors="pt")
with torch.no_grad():
logits = distil_model(**inputs).logits
probs = torch.softmax(logits, dim=-1)[0].numpy()
else:
probs = np.asarray(setfit_model.predict_proba([text])).ravel()
probs = np.asarray(probs, dtype=float).ravel()
assert probs.shape == (4,)
return {"labels": LABELS, "probs": [round(float(p), 4) for p in probs]}
@app.api(name="classify")
def classify(text: str, model: str) -> dict:
"""Classify a GitHub issue as bug / feature / question / support.
Args:
text: issue title and body.
model: "setfit" (22M) or "distilbert" (66M); anything else falls back to setfit.
"""
return run_classify(text, model if model in ("setfit", "distilbert") else "setfit")
@app.get("/", response_class=HTMLResponse)
async def index() -> str:
return INDEX.read_text()
if __name__ == "__main__":
app.launch()