fix: cpu-basic hardware + gr.mount_gradio_app + uvicorn (removes ZeroGPU dependency)
Browse files- README.md +1 -0
- app.py +53 -56
- requirements.txt +1 -0
README.md
CHANGED
|
@@ -8,6 +8,7 @@ sdk_version: "4.44.0"
|
|
| 8 |
python_version: "3.10"
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
# π Sinhala-English Codemix PII Detector
|
|
|
|
| 8 |
python_version: "3.10"
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
+
hardware: cpu-basic
|
| 12 |
---
|
| 13 |
|
| 14 |
# π Sinhala-English Codemix PII Detector
|
app.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
| 1 |
import os
|
| 2 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
| 3 |
from transformers import pipeline
|
| 4 |
-
from fastapi import HTTPException
|
| 5 |
from pydantic import BaseModel, ConfigDict
|
| 6 |
from typing import List
|
| 7 |
|
|
@@ -10,14 +12,15 @@ ner = pipeline(
|
|
| 10 |
"ner",
|
| 11 |
model="Ramosh99/mixcode_pii_detector",
|
| 12 |
aggregation_strategy="average",
|
| 13 |
-
device=-1, #
|
| 14 |
token=os.getenv("HF_TOKEN"),
|
| 15 |
)
|
| 16 |
|
|
|
|
| 17 |
|
| 18 |
# ββ Core inference βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 19 |
def detect_pii(text: str):
|
| 20 |
-
"""Run NER and return structured PII results."""
|
| 21 |
if not text.strip():
|
| 22 |
return {"text": text, "entities": []}
|
| 23 |
|
|
@@ -29,41 +32,33 @@ def detect_pii(text: str):
|
|
| 29 |
label = label[2:]
|
| 30 |
entities.append({
|
| 31 |
"entity": label,
|
| 32 |
-
"start":
|
| 33 |
-
"end":
|
| 34 |
-
"score":
|
| 35 |
})
|
| 36 |
-
|
| 37 |
-
highlighted = [
|
| 38 |
-
{"entity": e["entity"], "start": e["start"], "end": e["end"], "score": e["score"]}
|
| 39 |
-
for e in entities
|
| 40 |
-
]
|
| 41 |
-
return {"text": text, "entities": highlighted}
|
| 42 |
|
| 43 |
|
| 44 |
def detect_pii_ui(text: str):
|
| 45 |
-
"""
|
| 46 |
res = detect_pii(text)
|
| 47 |
entities = sorted(res["entities"], key=lambda x: x["start"])
|
| 48 |
-
|
| 49 |
if not entities:
|
| 50 |
return [(text, None)]
|
| 51 |
-
|
| 52 |
-
result = []
|
| 53 |
-
last_idx = 0
|
| 54 |
for ent in entities:
|
| 55 |
-
if ent["start"] >
|
| 56 |
-
result.append((text[
|
| 57 |
result.append((text[ent["start"]:ent["end"]], ent["entity"]))
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
result.append((text[last_idx:], None))
|
| 62 |
-
|
| 63 |
return result
|
| 64 |
|
| 65 |
|
| 66 |
-
# ββ Gradio
|
| 67 |
demo = gr.Interface(
|
| 68 |
fn=detect_pii_ui,
|
| 69 |
inputs=gr.Textbox(
|
|
@@ -75,8 +70,8 @@ demo = gr.Interface(
|
|
| 75 |
title="Sinhala-English Codemix PII Detector",
|
| 76 |
description=(
|
| 77 |
"Detects PII entities (PERSON, ORG, PHONE, EMAIL, NIC, etc.) in "
|
| 78 |
-
"Sinhala-English code-mixed text
|
| 79 |
-
"**REST API:** POST
|
| 80 |
),
|
| 81 |
examples=[
|
| 82 |
["Mage name Kamal, man wada karanne MAS Holdings eke, phone 071-1234567"],
|
|
@@ -86,15 +81,20 @@ demo = gr.Interface(
|
|
| 86 |
allow_flagging="never",
|
| 87 |
)
|
| 88 |
|
| 89 |
-
# ββ
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
)
|
| 96 |
|
| 97 |
-
# ββ REST API routes ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 98 |
|
| 99 |
class DetectRequest(BaseModel):
|
| 100 |
model_config = ConfigDict(
|
|
@@ -105,48 +105,45 @@ class DetectRequest(BaseModel):
|
|
| 105 |
|
| 106 |
class Entity(BaseModel):
|
| 107 |
entity: str
|
| 108 |
-
start:
|
| 109 |
-
end:
|
| 110 |
-
score:
|
| 111 |
|
| 112 |
|
| 113 |
class DetectResponse(BaseModel):
|
| 114 |
-
text:
|
| 115 |
-
entities:
|
| 116 |
redacted_text: str
|
| 117 |
|
| 118 |
|
| 119 |
-
@
|
| 120 |
def health_check():
|
| 121 |
-
"""Health check endpoint."""
|
| 122 |
return {"status": "ok", "model": "Ramosh99/mixcode_pii_detector", "device": "cpu"}
|
| 123 |
|
| 124 |
|
| 125 |
-
@
|
| 126 |
async def api_detect_pii(request: DetectRequest):
|
| 127 |
-
"""
|
| 128 |
-
Detect PII entities in Sinhala-English code-mixed text.
|
| 129 |
-
|
| 130 |
-
Returns detected entities with positions, confidence scores,
|
| 131 |
-
and a redacted version of the original text.
|
| 132 |
-
"""
|
| 133 |
if not request.text.strip():
|
| 134 |
raise HTTPException(status_code=422, detail="Input text cannot be empty.")
|
| 135 |
|
| 136 |
-
result
|
| 137 |
-
|
| 138 |
-
text = result["text"]
|
| 139 |
entities = sorted(result["entities"], key=lambda e: e["start"], reverse=True)
|
|
|
|
| 140 |
redacted = text
|
| 141 |
for ent in entities:
|
| 142 |
-
redacted = redacted[: ent["start"]] + f"[{ent['entity']}]" + redacted[ent["end"]
|
| 143 |
|
| 144 |
return {
|
| 145 |
-
"text":
|
| 146 |
-
"entities":
|
| 147 |
"redacted_text": redacted,
|
| 148 |
}
|
| 149 |
|
| 150 |
|
| 151 |
-
# ββ
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import gradio as gr
|
| 3 |
+
import uvicorn
|
| 4 |
+
from fastapi import FastAPI, HTTPException
|
| 5 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 6 |
from transformers import pipeline
|
|
|
|
| 7 |
from pydantic import BaseModel, ConfigDict
|
| 8 |
from typing import List
|
| 9 |
|
|
|
|
| 12 |
"ner",
|
| 13 |
model="Ramosh99/mixcode_pii_detector",
|
| 14 |
aggregation_strategy="average",
|
| 15 |
+
device=-1, # CPU β consistent latency, no cold starts
|
| 16 |
token=os.getenv("HF_TOKEN"),
|
| 17 |
)
|
| 18 |
|
| 19 |
+
print("Model loaded on CPU.")
|
| 20 |
|
| 21 |
# ββ Core inference βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 22 |
def detect_pii(text: str):
|
| 23 |
+
"""Run NER and return structured PII results (used by REST API)."""
|
| 24 |
if not text.strip():
|
| 25 |
return {"text": text, "entities": []}
|
| 26 |
|
|
|
|
| 32 |
label = label[2:]
|
| 33 |
entities.append({
|
| 34 |
"entity": label,
|
| 35 |
+
"start": item["start"],
|
| 36 |
+
"end": item["end"],
|
| 37 |
+
"score": round(float(item["score"]), 4),
|
| 38 |
})
|
| 39 |
+
return {"text": text, "entities": entities}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
|
| 42 |
def detect_pii_ui(text: str):
|
| 43 |
+
"""Return list-of-tuples for Gradio HighlightedText (used by UI)."""
|
| 44 |
res = detect_pii(text)
|
| 45 |
entities = sorted(res["entities"], key=lambda x: x["start"])
|
| 46 |
+
|
| 47 |
if not entities:
|
| 48 |
return [(text, None)]
|
| 49 |
+
|
| 50 |
+
result, last = [], 0
|
|
|
|
| 51 |
for ent in entities:
|
| 52 |
+
if ent["start"] > last:
|
| 53 |
+
result.append((text[last:ent["start"]], None))
|
| 54 |
result.append((text[ent["start"]:ent["end"]], ent["entity"]))
|
| 55 |
+
last = ent["end"]
|
| 56 |
+
if last < len(text):
|
| 57 |
+
result.append((text[last:], None))
|
|
|
|
|
|
|
| 58 |
return result
|
| 59 |
|
| 60 |
|
| 61 |
+
# ββ Gradio Interface βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 62 |
demo = gr.Interface(
|
| 63 |
fn=detect_pii_ui,
|
| 64 |
inputs=gr.Textbox(
|
|
|
|
| 70 |
title="Sinhala-English Codemix PII Detector",
|
| 71 |
description=(
|
| 72 |
"Detects PII entities (PERSON, ORG, PHONE, EMAIL, NIC, etc.) in "
|
| 73 |
+
"Sinhala-English code-mixed text.\n\n"
|
| 74 |
+
"**REST API:** `POST /api/detect` Β· `GET /api/health` Β· Docs: `/docs`"
|
| 75 |
),
|
| 76 |
examples=[
|
| 77 |
["Mage name Kamal, man wada karanne MAS Holdings eke, phone 071-1234567"],
|
|
|
|
| 81 |
allow_flagging="never",
|
| 82 |
)
|
| 83 |
|
| 84 |
+
# ββ FastAPI app ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 85 |
+
app = FastAPI(
|
| 86 |
+
title="PII Detector API",
|
| 87 |
+
description="REST API for Sinhala-English codemix PII detection.",
|
| 88 |
+
version="1.0.0",
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
app.add_middleware(
|
| 92 |
+
CORSMiddleware,
|
| 93 |
+
allow_origins=["*"],
|
| 94 |
+
allow_methods=["*"],
|
| 95 |
+
allow_headers=["*"],
|
| 96 |
)
|
| 97 |
|
|
|
|
| 98 |
|
| 99 |
class DetectRequest(BaseModel):
|
| 100 |
model_config = ConfigDict(
|
|
|
|
| 105 |
|
| 106 |
class Entity(BaseModel):
|
| 107 |
entity: str
|
| 108 |
+
start: int
|
| 109 |
+
end: int
|
| 110 |
+
score: float
|
| 111 |
|
| 112 |
|
| 113 |
class DetectResponse(BaseModel):
|
| 114 |
+
text: str
|
| 115 |
+
entities: List[Entity]
|
| 116 |
redacted_text: str
|
| 117 |
|
| 118 |
|
| 119 |
+
@app.get("/api/health")
|
| 120 |
def health_check():
|
|
|
|
| 121 |
return {"status": "ok", "model": "Ramosh99/mixcode_pii_detector", "device": "cpu"}
|
| 122 |
|
| 123 |
|
| 124 |
+
@app.post("/api/detect", response_model=DetectResponse)
|
| 125 |
async def api_detect_pii(request: DetectRequest):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
if not request.text.strip():
|
| 127 |
raise HTTPException(status_code=422, detail="Input text cannot be empty.")
|
| 128 |
|
| 129 |
+
result = detect_pii(request.text)
|
| 130 |
+
text = result["text"]
|
|
|
|
| 131 |
entities = sorted(result["entities"], key=lambda e: e["start"], reverse=True)
|
| 132 |
+
|
| 133 |
redacted = text
|
| 134 |
for ent in entities:
|
| 135 |
+
redacted = redacted[: ent["start"]] + f"[{ent['entity']}]" + redacted[ent["end"]:]
|
| 136 |
|
| 137 |
return {
|
| 138 |
+
"text": result["text"],
|
| 139 |
+
"entities": result["entities"],
|
| 140 |
"redacted_text": redacted,
|
| 141 |
}
|
| 142 |
|
| 143 |
|
| 144 |
+
# ββ Mount Gradio UI at "/" βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 145 |
+
app = gr.mount_gradio_app(app, demo, path="/")
|
| 146 |
+
|
| 147 |
+
# ββ Start server βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 148 |
+
if __name__ == "__main__":
|
| 149 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
requirements.txt
CHANGED
|
@@ -2,3 +2,4 @@ transformers>=4.40.0
|
|
| 2 |
huggingface_hub>=0.19.3,<0.26.0
|
| 3 |
fastapi>=0.110.0
|
| 4 |
pydantic>=2.0.0
|
|
|
|
|
|
| 2 |
huggingface_hub>=0.19.3,<0.26.0
|
| 3 |
fastapi>=0.110.0
|
| 4 |
pydantic>=2.0.0
|
| 5 |
+
uvicorn>=0.29.0
|