AsyncCoder-10 commited on
Commit
d635ce4
·
verified ·
1 Parent(s): b56fc39

Update app/main.py

Browse files
Files changed (1) hide show
  1. app/main.py +73 -49
app/main.py CHANGED
@@ -1,106 +1,130 @@
1
  import os
2
- os.environ.setdefault("NUMBA_DISABLE_CUDA", "1") # Prevents numba.cuda from initializing CUDA early
3
 
4
- import spaces # Must come before any torch imports
5
 
 
 
6
  from fastapi import FastAPI
7
  from pydantic import BaseModel
8
  from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer
9
  from glin_profanity import Filter
10
- import re
11
- import torch
12
 
13
- # Toxicity classifier — can stay at module level (CPU-based)
14
- toxicity_pipe = pipeline("text-classification", model="textdetox/twitter-xlmr-toxicity-classifier")
 
 
 
 
15
  profanity_filter = Filter({"languages": ["english", "spanish", "french"], "replace_with": "***"})
16
 
 
 
 
 
 
 
 
 
 
 
17
  app = FastAPI(title="Multilingual Moderation API")
18
 
 
19
  class TextRequest(BaseModel):
20
  text: str
21
 
 
22
  class ModerationResponse(BaseModel):
23
  original_text: str
24
  detected_categories: dict
25
  is_flagged: bool
26
  rewritten_text: list[str] | None
27
 
 
28
  THREAT_KEYWORDS = [
29
- r"\bguns?\b", r"\bweapons?\b", r"\bbombs?\b", r"\bexplosives?\b",
30
- r"\bkill\b", r"\bmurder\b", r"\bshoot\b", r"\bstab\b",
31
- r"\bdie\b", r"\bdeath\b", r"\bhurt\b"
32
  ]
33
  threat_pattern = re.compile("|".join(THREAT_KEYWORDS), re.IGNORECASE)
34
 
 
35
  @spaces.GPU
36
  def generate_suggestions(text: str) -> list[str]:
37
- """Load Phi-3 and generate rewritten sentences on GPU."""
38
- model_id = "microsoft/Phi-3-mini-4k-instruct"
39
- tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
40
- model = AutoModelForCausalLM.from_pretrained(
41
- model_id,
42
- trust_remote_code=True,
43
- attn_implementation="eager", # Solves the window_size error
44
- ).to("cuda") # Use .to("cuda"), NOT device_map="auto"
45
 
46
  messages = [
47
- {"role": "system", "content": "You are a helpful, polite assistant. Rewrite the user's toxic message into exactly 5 different safe, polite, and non-threatening sentences. Number them 1 to 5. Do not include any other text in your response."},
48
- {"role": "user", "content": text}
 
 
 
 
 
 
 
49
  ]
50
-
51
- prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
52
- outputs = model.generate(
53
- **tokenizer(prompt, return_tensors="pt").to("cuda"),
54
- max_new_tokens=200,
55
- do_sample=True,
56
- temperature=0.7,
57
  )
58
- generated_text = tokenizer.decode(outputs[0][len(tokenizer(prompt)["input_ids"]):], skip_special_tokens=True).strip()
59
-
60
- lines = generated_text.split('\n')
 
 
 
 
 
 
 
 
 
 
61
  rewritten = []
62
- for line in lines:
63
  line = line.strip()
64
  if line and line[0].isdigit():
65
- clean_line = re.sub(r'^\d+[\.\-\)]\s*', '', line).strip()
66
- clean_line = clean_line.strip('"\'')
67
- if clean_line:
68
- rewritten.append(clean_line)
69
-
70
  rewritten = list(dict.fromkeys(rewritten))[:5]
71
  return rewritten if rewritten else ["[AI could not generate a rewrite, please revise manually]"]
72
 
 
73
  @app.post("/moderate", response_model=ModerationResponse)
74
  async def moderate_text(request: TextRequest):
75
  text = request.text
76
-
77
  profanity_result = profanity_filter.check_profanity(text)
78
  tox_result = toxicity_pipe(text)[0]
79
-
80
- is_toxic_by_model = (tox_result['label'].lower() in ['toxic', 'label_1']) and (tox_result['score'] > 0.5)
 
 
81
  is_threat_by_keyword = bool(threat_pattern.search(text))
82
-
83
- is_flagged = (
84
- profanity_result["contains_profanity"] or
85
- is_toxic_by_model or
86
- is_threat_by_keyword
87
  )
88
-
89
- rewritten = None
90
- if is_flagged:
91
- rewritten = generate_suggestions(text)
92
 
93
  return ModerationResponse(
94
  original_text=text,
95
  detected_categories={
96
  "profanity": profanity_result["contains_profanity"],
97
  "ai_toxicity": is_toxic_by_model,
98
- "threat_keyword": is_threat_by_keyword
99
  },
100
  is_flagged=is_flagged,
101
- rewritten_text=rewritten
102
  )
103
 
 
104
  @app.get("/health")
105
  async def health_check():
106
  return {"status": "ok"}
 
1
  import os
2
+ os.environ.setdefault("NUMBA_DISABLE_CUDA", "1")
3
 
4
+ import spaces # must come before torch/transformers
5
 
6
+ import re
7
+ import torch
8
  from fastapi import FastAPI
9
  from pydantic import BaseModel
10
  from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer
11
  from glin_profanity import Filter
 
 
12
 
13
+ # ---- CPU-only toxicity classifier (runs outside @spaces.GPU) ----
14
+ toxicity_pipe = pipeline(
15
+ "text-classification",
16
+ model="textdetox/twitter-xlmr-toxicity-classifier",
17
+ device=-1, # force CPU
18
+ )
19
  profanity_filter = Filter({"languages": ["english", "spanish", "french"], "replace_with": "***"})
20
 
21
+ # ---- Phi-3 loaded once on CPU; moved to GPU only inside the GPU function ----
22
+ PHI_ID = "microsoft/Phi-3-mini-4k-instruct"
23
+ phi_tokenizer = AutoTokenizer.from_pretrained(PHI_ID, trust_remote_code=True)
24
+ phi_model = AutoModelForCausalLM.from_pretrained(
25
+ PHI_ID,
26
+ trust_remote_code=True,
27
+ attn_implementation="eager",
28
+ torch_dtype=torch.float16,
29
+ )
30
+
31
  app = FastAPI(title="Multilingual Moderation API")
32
 
33
+
34
  class TextRequest(BaseModel):
35
  text: str
36
 
37
+
38
  class ModerationResponse(BaseModel):
39
  original_text: str
40
  detected_categories: dict
41
  is_flagged: bool
42
  rewritten_text: list[str] | None
43
 
44
+
45
  THREAT_KEYWORDS = [
46
+ r"\bguns?\b", r"\bweapons?\b", r"\bbombs?\b", r"\bexplosives?\b",
47
+ r"\bkill\b", r"\bmurder\b", r"\bshoot\b", r"\bstab\b",
48
+ r"\bdie\b", r"\bdeath\b", r"\bhurt\b",
49
  ]
50
  threat_pattern = re.compile("|".join(THREAT_KEYWORDS), re.IGNORECASE)
51
 
52
+
53
  @spaces.GPU
54
  def generate_suggestions(text: str) -> list[str]:
55
+ """Generate 5 polite rewrites on GPU."""
56
+ phi_model.to("cuda")
 
 
 
 
 
 
57
 
58
  messages = [
59
+ {
60
+ "role": "system",
61
+ "content": (
62
+ "You are a helpful, polite assistant. Rewrite the user's toxic message "
63
+ "into exactly 5 different safe, polite, and non-threatening sentences. "
64
+ "Number them 1 to 5. Do not include any other text in your response."
65
+ ),
66
+ },
67
+ {"role": "user", "content": text},
68
  ]
69
+
70
+ prompt = phi_tokenizer.apply_chat_template(
71
+ messages, tokenize=False, add_generation_prompt=True
 
 
 
 
72
  )
73
+ inputs = phi_tokenizer(prompt, return_tensors="pt").to("cuda")
74
+
75
+ with torch.no_grad():
76
+ outputs = phi_model.generate(
77
+ **inputs,
78
+ max_new_tokens=200,
79
+ do_sample=True,
80
+ temperature=0.7,
81
+ )
82
+
83
+ new_tokens = outputs[0][inputs["input_ids"].shape[1]:]
84
+ generated_text = phi_tokenizer.decode(new_tokens, skip_special_tokens=True).strip()
85
+
86
  rewritten = []
87
+ for line in generated_text.split("\n"):
88
  line = line.strip()
89
  if line and line[0].isdigit():
90
+ clean = re.sub(r"^\d+[\.\-\)]\s*", "", line).strip().strip("\"'")
91
+ if clean:
92
+ rewritten.append(clean)
93
+
 
94
  rewritten = list(dict.fromkeys(rewritten))[:5]
95
  return rewritten if rewritten else ["[AI could not generate a rewrite, please revise manually]"]
96
 
97
+
98
  @app.post("/moderate", response_model=ModerationResponse)
99
  async def moderate_text(request: TextRequest):
100
  text = request.text
101
+
102
  profanity_result = profanity_filter.check_profanity(text)
103
  tox_result = toxicity_pipe(text)[0]
104
+
105
+ is_toxic_by_model = (
106
+ tox_result["label"].lower() in ["toxic", "label_1"] and tox_result["score"] > 0.5
107
+ )
108
  is_threat_by_keyword = bool(threat_pattern.search(text))
109
+
110
+ is_flagged = bool(
111
+ profanity_result["contains_profanity"] or is_toxic_by_model or is_threat_by_keyword
 
 
112
  )
113
+
114
+ rewritten = generate_suggestions(text) if is_flagged else None
 
 
115
 
116
  return ModerationResponse(
117
  original_text=text,
118
  detected_categories={
119
  "profanity": profanity_result["contains_profanity"],
120
  "ai_toxicity": is_toxic_by_model,
121
+ "threat_keyword": is_threat_by_keyword,
122
  },
123
  is_flagged=is_flagged,
124
+ rewritten_text=rewritten,
125
  )
126
 
127
+
128
  @app.get("/health")
129
  async def health_check():
130
  return {"status": "ok"}