devserenerocks commited on
Commit
f992d56
·
verified ·
1 Parent(s): b005382

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -7
app.py CHANGED
@@ -1,25 +1,129 @@
1
- from fastapi import FastAPI
2
- from pydantic import BaseModel
3
- from huggingface_hub import InferenceClient
4
  import os
 
 
 
 
 
 
 
 
 
 
5
 
6
  app = FastAPI()
7
 
8
- client = InferenceClient(
9
- api_key=os.environ.get("HF_TOKEN")
10
- )
 
 
 
 
 
 
 
 
 
 
11
 
12
  class ChatRequest(BaseModel):
13
  message: str
14
 
 
 
 
 
 
15
  @app.get("/")
16
  def greet_json():
17
  return {"Hello": "World!"}
18
 
 
19
  @app.post("/chat")
20
  def chat(request: ChatRequest):
21
  response = client.chat.completions.create(
22
  model="Qwen/Qwen3-VL-30B-A3B-Instruct",
23
  messages=[{"role": "user", "content": request.message}],
24
  )
25
- return {"response": response.choices[0].message.content}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ from functools import lru_cache
3
+ from io import BytesIO
4
+
5
+ import requests
6
+ import torch
7
+ from fastapi import FastAPI, HTTPException
8
+ from huggingface_hub import InferenceClient
9
+ from PIL import Image
10
+ from pydantic import BaseModel, HttpUrl
11
+ from transformers import AutoProcessor, ShieldGemma2ForImageClassification, pipeline
12
 
13
  app = FastAPI()
14
 
15
+ client = InferenceClient(api_key=os.environ.get("HF_TOKEN"))
16
+
17
+ NSFW_MODEL = "Falconsai/nsfw_image_detection"
18
+ SHIELD_MODEL = "google/shieldgemma-2-4b-it"
19
+ THRESHOLD = 0.5
20
+
21
+ # ShieldGemma 2 checks one image against each policy separately.
22
+ POLICIES = {
23
+ "dangerous": "dangerous content",
24
+ "sexual": "sexually explicit content",
25
+ "violence": "violence or gore",
26
+ }
27
+
28
 
29
  class ChatRequest(BaseModel):
30
  message: str
31
 
32
+
33
+ class ImageRequest(BaseModel):
34
+ image_url: HttpUrl
35
+
36
+
37
  @app.get("/")
38
  def greet_json():
39
  return {"Hello": "World!"}
40
 
41
+
42
  @app.post("/chat")
43
  def chat(request: ChatRequest):
44
  response = client.chat.completions.create(
45
  model="Qwen/Qwen3-VL-30B-A3B-Instruct",
46
  messages=[{"role": "user", "content": request.message}],
47
  )
48
+ return {"response": response.choices[0].message.content}
49
+
50
+
51
+ # ---------- Image moderation ----------
52
+
53
+ @lru_cache(maxsize=1)
54
+ def get_nsfw_classifier():
55
+ return pipeline("image-classification", model=NSFW_MODEL)
56
+
57
+
58
+ @lru_cache(maxsize=1)
59
+ def get_shield():
60
+ token = os.environ.get("HF_TOKEN") # ShieldGemma 2 is a gated model
61
+ processor = AutoProcessor.from_pretrained(SHIELD_MODEL, token=token)
62
+ model = ShieldGemma2ForImageClassification.from_pretrained(
63
+ SHIELD_MODEL,
64
+ token=token,
65
+ torch_dtype=torch.bfloat16,
66
+ device_map="auto",
67
+ ).eval()
68
+ return processor, model
69
+
70
+
71
+ def load_image(url: str) -> Image.Image:
72
+ try:
73
+ r = requests.get(url, timeout=15)
74
+ r.raise_for_status()
75
+ return Image.open(BytesIO(r.content)).convert("RGB")
76
+ except Exception as e:
77
+ raise HTTPException(status_code=400, detail=f"Could not load image: {e}")
78
+
79
+
80
+ def run_nsfw_check(image: Image.Image) -> float:
81
+ """Returns the probability the image is NSFW (Falconsai ViT classifier)."""
82
+ results = get_nsfw_classifier()(image)
83
+ return next(r["score"] for r in results if r["label"].lower() == "nsfw")
84
+
85
+
86
+ def run_shield_check(image: Image.Image) -> dict:
87
+ """Returns the probability of a violation per ShieldGemma 2 policy."""
88
+ processor, model = get_shield()
89
+ inputs = processor(
90
+ images=[image], policies=list(POLICIES), return_tensors="pt"
91
+ ).to(model.device)
92
+ with torch.inference_mode():
93
+ out = model(**inputs)
94
+ # probabilities columns are [Yes (violates), No]
95
+ probs = out.probabilities[:, 0].float().tolist()
96
+ return dict(zip(POLICIES, probs))
97
+
98
+
99
+ @app.post("/moderate-image")
100
+ def moderate_image(request: ImageRequest):
101
+ image = load_image(str(request.image_url))
102
+
103
+ nsfw_score = run_nsfw_check(image)
104
+ shield_scores = run_shield_check(image)
105
+
106
+ reasons = []
107
+ if nsfw_score >= THRESHOLD:
108
+ reasons.append(
109
+ f"NSFW classifier flagged the image as explicit ({nsfw_score:.0%} confidence)"
110
+ )
111
+ for policy, score in shield_scores.items():
112
+ if score >= THRESHOLD:
113
+ reasons.append(
114
+ f"ShieldGemma 2 flagged {POLICIES[policy]} ({score:.0%} confidence)"
115
+ )
116
+
117
+ is_safe = not reasons
118
+ return {
119
+ "rating": "safe" if is_safe else "not safe",
120
+ "reason": (
121
+ "No policy violations detected by either model."
122
+ if is_safe
123
+ else "; ".join(reasons)
124
+ ),
125
+ "scores": {
126
+ "nsfw": round(nsfw_score, 4),
127
+ **{k: round(v, 4) for k, v in shield_scores.items()},
128
+ },
129
+ }