Ramosh99 commited on
Commit
3a03e86
Β·
1 Parent(s): 9a16674

fix: cpu-basic hardware + gr.mount_gradio_app + uvicorn (removes ZeroGPU dependency)

Browse files
Files changed (3) hide show
  1. README.md +1 -0
  2. app.py +53 -56
  3. 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, # force CPU β€” consistent latency, no cold starts
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": item["start"],
33
- "end": item["end"],
34
- "score": round(float(item["score"]), 4),
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
- """Format for Gradio HighlightedText (list of tuples)."""
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"] > last_idx:
56
- result.append((text[last_idx:ent["start"]], None))
57
  result.append((text[ent["start"]:ent["end"]], ent["entity"]))
58
- last_idx = ent["end"]
59
-
60
- if last_idx < len(text):
61
- result.append((text[last_idx:], None))
62
-
63
  return result
64
 
65
 
66
- # ── Gradio UI ──────────────────────────────────────────────────────────────────
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 using the Ramosh99/mixcode_pii_detector model.\n\n"
79
- "**REST API:** POST `/api/detect` with `{\"text\": \"...\"}` Β· Docs: `/api/docs`"
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
- # ── Launch Gradio (non-blocking) ───────────────────────────────────────────────
90
- gr_app, _local_url, _share_url = demo.launch(
91
- server_name="0.0.0.0",
92
- server_port=7860,
93
- prevent_thread_lock=True,
94
- show_api=False,
 
 
 
 
 
 
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: 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
- @gr_app.get("/api/health")
120
  def health_check():
121
- """Health check endpoint."""
122
  return {"status": "ok", "model": "Ramosh99/mixcode_pii_detector", "device": "cpu"}
123
 
124
 
125
- @gr_app.post("/api/detect", response_model=DetectResponse)
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 = detect_pii(request.text)
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": result["text"],
146
- "entities": result["entities"],
147
  "redacted_text": redacted,
148
  }
149
 
150
 
151
- # ── Keep the process alive ─────────────────────────────────────────────────────
152
- demo.block_thread()
 
 
 
 
 
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