test / app.py
Ramosh99's picture
revert: Gradio 5.x + ZeroGPU @spaces.GPU + demo.launch (was working, 4.x had Starlette bug)
1cca871
Raw History Blame Contribute Delete
4.62 kB
import spaces
import os
import gradio as gr
from transformers import pipeline
from fastapi import HTTPException
from pydantic import BaseModel, ConfigDict
from typing import List
# ── Model ──────────────────────────────────────────────────────────────────────
ner = pipeline(
"ner",
model="Ramosh99/mixcode_pii_detector",
aggregation_strategy="average",
token=os.getenv("HF_TOKEN"),
)
# ── Core inference (ZeroGPU β€” GPU used only during inference call) ──────────────
@spaces.GPU
def detect_pii(text: str):
"""Run NER and return structured PII results (used by REST API)."""
if not text.strip():
return {"text": text, "entities": []}
raw = ner(text)
entities = []
for item in raw:
label = item.get("entity_group", "")
if label.startswith(("B-", "I-")):
label = label[2:]
entities.append({
"entity": label,
"start": item["start"],
"end": item["end"],
"score": round(float(item["score"]), 4),
})
return {"text": text, "entities": entities}
def detect_pii_ui(text: str):
"""Return list-of-tuples for Gradio HighlightedText (used by UI)."""
res = detect_pii(text)
entities = sorted(res["entities"], key=lambda x: x["start"])
if not entities:
return [(text, None)]
result, last = [], 0
for ent in entities:
if ent["start"] > last:
result.append((text[last:ent["start"]], None))
result.append((text[ent["start"]:ent["end"]], ent["entity"]))
last = ent["end"]
if last < len(text):
result.append((text[last:], None))
return result
# ── Gradio Interface ───────────────────────────────────────────────────────────
demo = gr.Interface(
fn=detect_pii_ui,
inputs=gr.Textbox(
label="Input Text",
placeholder="Enter Sinhala-English codemixed text...",
lines=4,
),
outputs=gr.HighlightedText(label="Detected PII Entities"),
title="Sinhala-English Codemix PII Detector",
description=(
"Detects PII entities (PERSON, ORG, PHONE, EMAIL, NIC, etc.) in "
"Sinhala-English code-mixed text.\n\n"
"**REST API:** `POST /api/detect` Β· `GET /api/health` Β· Docs: `/docs`"
),
examples=[
["Mage name Kamal, man wada karanne MAS Holdings eke, phone 071-1234567"],
["ࢸ࢜ේ email address kamal.perera@gmail.com, NIC number 199912345678"],
["I live in Colombo and my phone number is 0771234567"],
],
flagging_mode="never",
)
# ── Launch Gradio β€” properly registers @spaces.GPU with ZeroGPU ───────────────
gr_app, _local_url, _share_url = demo.launch(
server_name="0.0.0.0",
server_port=7860,
prevent_thread_lock=True,
show_api=False,
)
# ── REST API routes (added to Gradio's internal FastAPI app) ───────────────────
class DetectRequest(BaseModel):
model_config = ConfigDict(
json_schema_extra={"example": {"text": "Mage name Kamal, phone 071-1234567"}}
)
text: str
class Entity(BaseModel):
entity: str
start: int
end: int
score: float
class DetectResponse(BaseModel):
text: str
entities: List[Entity]
redacted_text: str
@gr_app.get("/api/health")
def health_check():
return {"status": "ok", "model": "Ramosh99/mixcode_pii_detector"}
@gr_app.post("/api/detect", response_model=DetectResponse)
async def api_detect_pii(request: DetectRequest):
if not request.text.strip():
raise HTTPException(status_code=422, detail="Input text cannot be empty.")
result = detect_pii(request.text)
text = result["text"]
entities = sorted(result["entities"], key=lambda e: e["start"], reverse=True)
redacted = text
for ent in entities:
redacted = redacted[: ent["start"]] + f"[{ent['entity']}]" + redacted[ent["end"]:]
return {
"text": result["text"],
"entities": result["entities"],
"redacted_text": redacted,
}
# ── Keep process alive ─────────────────────────────────────────────────────────
demo.block_thread()