OpenAI released Privacy Filter on the Hub this week. It’s an open-source PII detector that labels text across eight categories in a single pass over a 128k context. Model card is here. I grabbed it, spent a few hours building, and ended up with three apps that each show a different angle of what this thing can do.
- Document Privacy Explorer: drop in a PDF or DOCX, read it back with every PII span highlighted in place.
- Image Anonymizer: upload a screenshot, get it back with black bars over names, emails, account numbers. Editable canvas, too.
- SmartRedact Paste: paste sensitive text, get a public URL that shows the redacted version, keep a private reveal link for yourself.
All three run on gradio.Server. That’s the piece that lets you pair custom HTML/JS frontends with Gradio’s queue, ZeroGPU allocation, and gradio_client SDK. The backend pattern is identical across all three, and that consistency is exactly what makes it powerful.
The model itself
Privacy Filter is a 1.5B-parameter model with 50M active parameters, Apache 2.0 licensed. The categories it detects: private_person, private_address, private_email, private_phone, private_url, private_date, account_number, secret. Context window is 128,000 tokens. It claims state-of-the-art on the PII-Masking-300k benchmark. I didn’t run my own benchmarks, but in practice it handled long documents without chunking, which is the real win here.
1. Document Privacy Explorer
Try it at ysharma/OPF-Document-PII-Explorer.
The problem is straightforward: you have a contract, a resume, an exported chat log, and you want to read it with every detected PII category highlighted, a filter in the sidebar, and a summary dashboard up top. The reading experience should feel like a normal document, not a form.
Privacy Filter handles the whole file in one 128k-context forward pass. No chunking, no stitching, span offsets line up directly with the rendered text. BIOES decoding keeps span boundaries clean even through long ambiguous runs.
You could wire this up with gr.HighlightedText and a sidebar in Blocks, and it would work. But the reading experience I wanted (serif body, category filters that toggle CSS classes client-side instead of re-running the model, a summary dashboard that doesn’t force a full page re-render) was easier to hand-author. gr.Server lets me serve the reader view as a single HTML file and expose the model behind one queued endpoint:
import gradio as gr
from fastapi.responses import HTMLResponse
from gradio.data_classes import FileData
server = gr.Server()
@server.get("/", response_class=HTMLResponse)
async def homepage():
return FRONTEND_HTML
@server.api(name="analyze_document")
def analyze_document(file: FileData) -> dict:
text = extract_text(file["path"])
source_text, spans = run_privacy_filter(text)
return {
"text": source_text,
"spans": spans,
"stats": compute_stats(source_text, spans),
}
The key is the @server.api(name="analyze_document") decorator. That’s what plugs the handler into Gradio’s queue, so concurrent uploads are serialized, @spaces.GPU composes correctly on ZeroGPU, and the same endpoint is reachable from both the browser and gradio_client with no duplicated code. The browser calls it with the Gradio JS client.
2. Image Anonymizer
Try it at ysharma/OPF-Image-Anonymizer.
This one is for sharing screenshots: a Slack thread, a receipt, a Stripe dashboard. You want black bars over the PII, toggle them on and off, drag to reposition, draw one by hand for anything the model missed, then export.
Privacy Filter runs after Tesseract OCR extracts text with per-word bounding boxes. The backend reconstructs the full text with a char-offset-to-box map, runs Privacy Filter once over the whole text, then looks up detected character spans against the word map and joins them into pixel rectangles per line.
gr.ImageEditor supports layered annotation and is a reasonable starting point for image redaction. But the workflow I wanted (per-bar category metadata, toggle all bars in a category at once, client-side PNG export at natural resolution with no server round-trip) was cleaner to build on a custom frontend. gr.Server hands back pixel rectangles from one queued endpoint and lets the canvas own everything else:
@server.api(name="anonymize_screenshot")
def anonymize_screenshot(image: FileData) -> dict:
img = Image.open(image["path"]).convert("RGB")
full_text, char_to_box = ocr_image(img)
spans = run_privacy_filter(full_text)
boxes = spans_to_pixel_boxes(spans, char_to_box)
return {
"image_data_url": pil_to_base64(img),
"width": img.width,
"height": img.height,
"boxes": boxes,
}
The frontend invokes it with client.predict("/anonymize_screenshot", { image: handle_file(file) }), same pattern as above. Toggles, drags, new-bar drawing, and PNG export all happen client-side. The server never sees the final image.
3. SmartRedact Paste
Try it at ysharma/OPF-SmartRedact-Paste.
This is the simplest and maybe the most useful. Paste sensitive text, get a public URL that shows the redacted version, and a private reveal link that shows the original. Think of it as a pastebin that automatically redacts PII.
The backend stores the original text with the detected spans. The public endpoint renders the text with spans replaced by category labels (e.g., [PERSON], [EMAIL]). The private endpoint returns the raw text. Both are served from the same gr.Server instance, with the public one just being another route that applies the redaction logic.
@server.get("/paste/{paste_id}")
async def get_public_paste(paste_id: str):
paste = db.get(paste_id)
redacted = apply_redaction(paste["text"], paste["spans"])
return HTMLResponse(render_redacted_page(redacted))
@server.get("/paste/{paste_id}/reveal")
async def get_private_paste(paste_id: str, token: str):
paste = db.get(paste_id)
if token != paste["reveal_token"]:
raise HTTPException(status_code=403)
return HTMLResponse(render_private_page(paste["text"]))
No model inference on read, just text manipulation. The model only runs once when the paste is created.
What I actually think about all this
The model is good. Not perfect — it flagged a URL that was clearly a test string as a secret, and it missed a phone number in a noisy screenshot. But the 128k context is the real differentiator. No chunking means no boundary errors, no double-counting tokens, no complex stitching logic. That alone makes it worth trying for any document processing pipeline.
gradio.Server is the unsung hero here. It’s not flashy, but it solves a real problem: you want a custom frontend without rebuilding auth, queueing, GPU allocation, and client SDKs from scratch. The decorator pattern means your API endpoints are also Gradio endpoints, which means they work with Spaces, ZeroGPU, and the JS client out of the box. I’ve seen too many projects where the backend and frontend are two separate repos with two separate deployment pipelines. This collapses that into one.
The Image Anonymizer is the one I’d actually use. The canvas-based editing is smoother than I expected, and the client-side export means no server load for final rendering. If you deal with screenshots of internal tools or customer data, this is worth a bookmark.
SmartRedact Paste is clever but niche. I can see it being useful for support teams sharing logs or for legal reviews. The two-link pattern (public redacted, private original) is a good UX choice.
If you want to build something similar, the code is on the Hub. The model is Apache 2.0, so no licensing headaches. The apps are all in Spaces. Fork them, break them, make them better.
Comments (0)
Login Log in to comment.
Be the first to comment!