Spaces:
Running
Running
Download app.py from abacusai/Toxicity-Lens: direct link, hf CLI and curl.
- Browser
- Download file 4.98 kB
-
https://huggingface.co/spaces/abacusai/Toxicity-Lens/resolve/main/app.py
- Command line
-
hf download hf://spaces/abacusai/Toxicity-Lens/app.py
-
curl -L -o app.py https://huggingface.co/spaces/abacusai/Toxicity-Lens/resolve/main/app.py
4.98 kB
| import json | |
| import os | |
| import gradio as gr | |
| import uvicorn | |
| from server import ATTRS, Scorer, create_app | |
| # The 8-bit ONNX export on CPU, about 20 ms per comment. The private staging copy of this Space carries the model files in model/; | |
| # the public Space loads them from the model repo. | |
| MODEL = "model" if os.path.isdir("model") else os.environ.get("MODEL_DIR", "abacusai/Toxicity-Lens") | |
| scorer = Scorer(MODEL, backend="onnx") | |
| NAMES = {"TOXICITY": "Toxicity", "SEVERE_TOXICITY": "Severe toxicity", "IDENTITY_ATTACK": "Identity attack", "INSULT": "Insult", | |
| "PROFANITY": "Profanity", "THREAT": "Threat", "SEXUALLY_EXPLICIT": "Sexually explicit"} | |
| EXAMPLES = json.load(open(os.path.join(os.path.dirname(__file__), "examples.json"))) | |
| URL = "https://abacusai-toxicity-lens.hf.space" | |
| def score(text: str) -> dict: | |
| """Score a text on the seven Perspective API attributes (TOXICITY, SEVERE_TOXICITY, IDENTITY_ATTACK, INSULT, PROFANITY, THREAT, | |
| SEXUALLY_EXPLICIT) as probabilities between 0 and 1, on the same scale as the Perspective API. | |
| Works in any language. Texts longer than 256 tokens are scored in overlapping windows and each attribute keeps its highest score. | |
| Args: | |
| text: The text to score, up to 20,480 bytes (the Perspective API's limit). | |
| """ | |
| text = (text or "").strip() | |
| if not text: | |
| raise gr.Error("Type or paste some text first.") | |
| if len(text.encode()) > 20480: | |
| raise gr.Error("Please keep the text under 20,480 bytes, the Perspective API's limit.") | |
| return {a: round(p, 4) for a, p in scorer([text])[0][0].items()} | |
| def show(text: str, threshold: float): | |
| s = score(text) | |
| hits = [NAMES[a] for a in ATTRS if s[a] >= threshold] | |
| verdict = f"**Flagged at {threshold:.2f}:** {', '.join(hits)}" if hits else f"Nothing reaches {threshold:.2f}." | |
| return {NAMES[a]: s[a] for a in ATTRS}, verdict | |
| with gr.Blocks(title="Toxicity-Lens") as demo: | |
| gr.Markdown( | |
| "# Toxicity-Lens\n" | |
| "Scores text in any language on the seven attributes of Google's Perspective API (toxicity, severe toxicity, identity attack, " | |
| "insult, profanity, threat, sexually explicit) with [Toxicity-Lens](https://huggingface.co/abacusai/Toxicity-Lens), " | |
| "an open Apache-2.0 model from Abacus.AI that runs on a CPU. The Perspective API shuts down on December 31, 2026; this Space also " | |
| "answers Perspective API requests (below), so you can try the switch by changing one URL." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| inp = gr.Textbox(label="Text, any language", lines=8, max_lines=20, placeholder="Paste a comment, a post or a web page") | |
| thr = gr.Slider(0.5, 0.95, value=0.7, step=0.05, label="Flag threshold") | |
| btn = gr.Button("Score", variant="primary") | |
| with gr.Column(): | |
| out = gr.Label(label="Probabilities (the Perspective API's 0 to 1 scale)", num_top_classes=7) | |
| verdict = gr.Markdown() | |
| gr.Examples(examples=[[e] for e in EXAMPLES], inputs=inp, label="Examples") | |
| btn.click(show, inputs=[inp, thr], outputs=[out, verdict], api_visibility="private") | |
| inp.submit(show, inputs=[inp, thr], outputs=[out, verdict], api_visibility="private") | |
| gr.api(score, api_name="score") | |
| gr.Markdown( | |
| "### Compatible with the Perspective API\n" | |
| "This Space serves the Perspective API's `comments:analyze` request and response JSON at the same path:\n" | |
| f"```bash\ncurl -s {URL}/v1alpha1/comments:analyze -H 'Content-Type: application/json' \\\n" | |
| " -d '{\"comment\": {\"text\": \"You are an idiot\"}, \"requestedAttributes\": {\"TOXICITY\": {}, \"INSULT\": {}}}'\n```\n" | |
| "Google's client library needs only a new discovery URL:\n" | |
| "```python\nfrom googleapiclient import discovery\n\n" | |
| "client = discovery.build(\"commentanalyzer\", \"v1alpha1\", developerKey=\"unused\", static_discovery=False,\n" | |
| f" discoveryServiceUrl=\"{URL}/$discovery/rest?version=v1alpha1\")\n" | |
| "body = {\"comment\": {\"text\": \"You are an idiot\"}, \"requestedAttributes\": {\"TOXICITY\": {}}}\n" | |
| "print(client.comments().analyze(body=body).execute()[\"attributeScores\"][\"TOXICITY\"][\"summaryScore\"][\"value\"])\n```\n" | |
| "This shared demo has no rate guarantees. For production, run the same server on your own CPU or GPU in one command; see the " | |
| "[model card](https://huggingface.co/abacusai/Toxicity-Lens). The `score` endpoint is also available to agents as an MCP tool." | |
| ) | |
| # the Perspective-compatible routes (server.py) first, the Gradio app and its MCP server mounted behind them. ssr_mode=False: on | |
| # Spaces, server-side rendering makes mount_gradio_app start a Node server that takes port 7860 before uvicorn binds it. | |
| app = gr.mount_gradio_app(create_app(scorer), demo, path="/", mcp_server=True, ssr_mode=False) | |
| if __name__ == "__main__": | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |