OpenAI pushed a new open-source model to Hugging Face this week called Privacy Filter. It’s a PII detector that labels text across eight categories in a single pass over a 128k context window. 1.5B parameters, 50M active, Apache 2.0 license. The usual release fanfare is on their blog if you want the benchmark numbers.
I spent a few hours building with it. Not because I needed PII detection this week, but because the architecture is interesting: one forward pass, no chunking, no stitching. That alone makes it worth poking at. Here are three apps I put together, each showing a different way to use the thing.
Document Privacy Explorer
This is the most straightforward app. Drop in a PDF or DOCX, and it highlights every PII span inline by category. Sidebar filters let you toggle categories on and off without re-running the model. A summary dashboard at the top shows counts per category.
The model handles the whole document in one pass because of the 128k context. No chunking means span offsets line up directly with the rendered text. BIOES decoding keeps boundaries clean even through long ambiguous runs like “Dr. Smith at 1234 Elm Street, Apt. 5B.”
I could have built this with Gradio’s built-in HighlightedText component and a sidebar. But the reading experience I wanted — serif body font, category toggles that just flip CSS classes client-side, a dashboard that doesn’t force a page re-render — was easier to hand-author as HTML. Gradio’s Server class let me serve the frontend as a single HTML file and expose the model behind one queued endpoint:
@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 @server.api decorator is the key. It plugs the handler into Gradio’s queue, composes correctly with ZeroGPU, and makes the same endpoint reachable from both the browser and the Python client. No duplicated code.
Image Anonymizer
This one is trickier. Upload a screenshot — a Slack thread, a receipt, a Stripe dashboard — and it returns the image with black bars over names, emails, and account numbers. You can toggle bars on and off, drag them to reposition, or draw new ones by hand for anything the model missed.
The pipeline: Tesseract runs OCR and returns per-word bounding boxes. The backend reconstructs the full text with a character-offset-to-box map, then runs Privacy Filter once over the whole text. Detected character spans get looked up against the word map and joined into pixel rectangles per line.
Gradio’s ImageEditor component supports layered annotation and would be a reasonable starting point. 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 on a custom frontend. The server just returns pixel rectangles from one 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 calls it with client.predict("/anonymize_screenshot", { image: handle_file(file) }), same pattern as the document explorer. Toggles, drags, new-bar drawing, and export all happen client-side.
SmartRedact Paste
This is the simplest app and honestly the one I’d use most. Paste sensitive text, get a public URL that serves the redacted version, plus a private reveal link that shows the original. No accounts, no databases — just a keyed storage pattern.
The model runs once at paste time. The redacted version is cached as a string. The reveal link decrypts the original from an encrypted blob. The whole thing fits in about 50 lines of backend code plus a small HTML template.
What I learned
The Privacy Filter model itself is solid. One pass over 128k tokens is genuinely useful for documents that would otherwise need sliding windows with overlap and stitching. The category coverage is reasonable: private_person, private_address, private_email, private_phone, private_url, private_date, account_number, secret. I didn’t test it on every edge case, but on the documents I threw at it — contracts, chat exports, a few random screenshots — it caught everything I expected.
The bigger takeaway is the architecture pattern. Gradio’s Server class lets you keep the model behind a queued endpoint while serving a custom frontend. That’s not new — you could do this with FastAPI and a separate Gradio worker — but having it in one framework with no extra infrastructure is nice. The @server.api decorator handles queueing, GPU allocation, and client SDK compatibility without ceremony.
Is this the best way to build a PII redaction pipeline? Depends on your scale. For a single-user tool or a small team, this is fine. For production at volume, you’d want to decouple the OCR and inference steps, add caching, and probably run the model on dedicated hardware. But for a weekend project that actually works? This is hard to beat.
Comments (0)
Login Log in to comment.
Be the first to comment!