misukisu commited on
Commit
1048d56
·
verified ·
1 Parent(s): 6000d10

Moderation API

Browse files
Files changed (3) hide show
  1. README.md +8 -7
  2. app.py +96 -0
  3. requirements.txt +3 -0
README.md CHANGED
@@ -1,13 +1,14 @@
1
  ---
2
- title: Moderation Api
3
- emoji: 🐨
4
- colorFrom: red
5
- colorTo: red
6
  sdk: gradio
7
  sdk_version: 6.29.1
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
 
 
 
 
11
  ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Moderation API
3
+ emoji: 🛡️
4
+ colorFrom: gray
5
+ colorTo: gray
6
  sdk: gradio
7
  sdk_version: 6.29.1
 
8
  app_file: app.py
9
  pinned: false
10
+ license: apache-2.0
11
+ short_description: Free English content moderation API (13 labels)
12
+ models:
13
+ - misukisu/moderation-modernbert-en
14
  ---
 
 
app.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import numpy as np, onnxruntime as ort, gradio as gr
3
+ from fastapi import FastAPI
4
+ from pydantic import BaseModel, Field
5
+ from huggingface_hub import snapshot_download
6
+ from tokenizers import Tokenizer
7
+
8
+ REPO, ONNX = "misukisu/moderation-modernbert-en", "onnx/model.onnx"
9
+ d = snapshot_download(REPO, allow_patterns=[ONNX, "tokenizer.json", "config.json", "thresholds.json"])
10
+ tok = Tokenizer.from_file(f"{d}/tokenizer.json")
11
+ tok.enable_truncation(256)
12
+ tok.enable_padding()
13
+ so = ort.SessionOptions()
14
+ so.intra_op_num_threads = 2
15
+ sess = ort.InferenceSession(f"{d}/{ONNX}", so, providers=["CPUExecutionProvider"])
16
+ id2label = json.load(open(f"{d}/config.json"))["id2label"]
17
+ LABELS = [id2label[str(i)] for i in range(len(id2label))]
18
+ TH = json.load(open(f"{d}/thresholds.json"))
19
+ MAX_BATCH = 32
20
+
21
+
22
+ def _run(texts):
23
+ texts = [str(t)[:4000] for t in texts][:MAX_BATCH]
24
+ if not texts:
25
+ return []
26
+ enc = tok.encode_batch(texts)
27
+ ids = np.array([e.ids for e in enc], dtype=np.int64)
28
+ mask = np.array([e.attention_mask for e in enc], dtype=np.int64)
29
+ probs = sess.run(None, {"input_ids": ids, "attention_mask": mask})[0]
30
+ return [{"flagged": bool(any(p[i] >= TH[l] for i, l in enumerate(LABELS))),
31
+ "categories": [l for i, l in enumerate(LABELS) if p[i] >= TH[l]],
32
+ "scores": {l: round(float(p[i]), 4) for i, l in enumerate(LABELS)}} for p in probs]
33
+
34
+
35
+ def moderate(text: str) -> dict:
36
+ """Moderate one English text. Returns flagged, categories and per-label scores."""
37
+ return _run([text or ""])[0]
38
+
39
+
40
+ def moderate_batch(texts: list) -> list:
41
+ """Moderate up to 32 texts at once."""
42
+ return _run(texts or [])
43
+
44
+
45
+ def ui(text):
46
+ r = moderate(text)
47
+ head = "### Flagged: " + ", ".join(r["categories"]) if r["flagged"] else "### Clean"
48
+ return head, {l: s for l, s in r["scores"].items()}, r
49
+
50
+
51
+ with gr.Blocks(title="Moderation API") as demo:
52
+ gr.Markdown("# Moderation API\nEnglish content moderation with ModernBERT, 13 labels. "
53
+ "Free to use from code, see the **Use via API** link at the bottom. "
54
+ f"Model: [{REPO}](https://huggingface.co/{REPO})")
55
+ with gr.Row():
56
+ with gr.Column():
57
+ inp = gr.Textbox(label="Text", lines=5, placeholder="Paste a message...")
58
+ btn = gr.Button("Moderate", variant="primary")
59
+ gr.Examples(["have a nice day!", "you are a worthless idiot", "i will find you and hurt you",
60
+ "WIN a FREE iPhone, click http://bit.ly/x now"], inp)
61
+ with gr.Column():
62
+ verdict = gr.Markdown()
63
+ scores = gr.Label(label="Scores", num_top_classes=13)
64
+ raw = gr.JSON(label="Response")
65
+ btn.click(ui, inp, [verdict, scores, raw], api_name=False)
66
+ inp.submit(ui, inp, [verdict, scores, raw], api_name=False)
67
+ gr.api(moderate, api_name="moderate")
68
+ gr.api(moderate_batch, api_name="moderate_batch")
69
+
70
+ demo.queue(default_concurrency_limit=4)
71
+
72
+ app = FastAPI(title="Moderation API")
73
+ from fastapi.middleware.cors import CORSMiddleware
74
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
75
+
76
+
77
+ class Req(BaseModel):
78
+ input: str | list[str] = Field(..., description="A text or a list of up to 32 texts")
79
+
80
+
81
+ @app.post("/v1/moderate")
82
+ def rest(req: Req):
83
+ texts = [req.input] if isinstance(req.input, str) else req.input
84
+ return {"model": REPO, "results": _run(texts)}
85
+
86
+
87
+ @app.get("/v1/labels")
88
+ def labels():
89
+ return {"labels": LABELS, "thresholds": TH}
90
+
91
+
92
+ app = gr.mount_gradio_app(app, demo, path="/")
93
+
94
+ if __name__ == "__main__":
95
+ import uvicorn
96
+ uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ onnxruntime
2
+ tokenizers
3
+ numpy