Building with OpenAI’s Privacy Filter: Three Apps That Actually Work

Building with OpenAI’s Privacy Filter: Three Apps That Actually Work

6 0 0

OpenAI released Privacy Filter on the Hub this week: an open-source personally-identifiable information (PII) detector that labels text across eight categories in a single forward pass over a 128k context. The model card is up on Hugging Face. I spent a few hours building with it and landed on three apps that each reveals a different slice of what it can do.

  • Document Privacy Explorer: drop in a PDF or DOCX, read the document back with every PII span highlighted in place.
  • Image Anonymizer: upload an image, get it back with redacted black bars over names, emails, and account numbers. The image is also editable on a canvas so you can make your own annotations before downloading.
  • SmartRedact Paste: paste sensitive text, share a public URL that serves the redacted version, keep a private reveal link for yourself.

All three are built on gradio.Server, which lets you pair custom HTML/JS frontends with Gradio’s queueing, ZeroGPU allocation, and gradio_client SDK. In all these apps, gr.Server plays the same backend role, and that consistency is exactly what makes it really powerful.

The model

Privacy Filter is a 1.5B-parameter model with 50M active parameters, permissively licensed under Apache 2.0. PII categories are private_person, private_address, private_email, private_phone, private_url, private_date, account_number, secret. Context is 128,000 tokens. Achieves state-of-the-art performance on the PII-Masking-300k benchmark. Full numbers and methodology are in the official release blog.

1. Document Privacy Explorer

Try it at ysharma/OPF-Document-PII-Explorer.

User problem. You want to read a PII-heavy document (a contract, a resume, an exported chat log) with every detected span highlighted by category, a filter in the sidebar, and a summary dashboard up top. The reading experience should feel like a normal document, not a form.

What Privacy Filter does here. The whole file goes through in a single 128k-context forward pass, so there’s no chunking, no stitching, and span offsets line up directly with the rendered text. BIOES decoding keeps span boundaries clean through long ambiguous runs.

What gr.Server does here. You could wire this up in Blocks with gr.HighlightedText and a sidebar, and it would work. The reading experience we 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 page re-render) was easier to hand-author than to compose. gr.Server lets us 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),
    }

Note the decorator: @server.api(name=”analyze_document”), not a plain @server.post. That’s the piece that 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.

User problem. You want to share an image or any screenshot (a Slack thread, a receipt, a Stripe dashboard) with black bars over the PII. You want to toggle bars on and off, drag them to reposition, or draw one by hand for anything the model missed, then export the result.

What Privacy Filter does here. Tesseract runs OCR and returns per-word bounding boxes. The backend reconstructs the full text with a char-offset to box map, then runs Privacy Filter once over the whole text. Detected character spans are looked up against the word map and joined into pixel rectangles per line.

What gr.Server does here. gr.ImageEditor supports layered annotation and is a reasonable starting point for image redaction. The workflow we 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) }), the same pattern as above. Toggles, drags, new-bar drawing, all happen in the browser without hitting the server again.

3. SmartRedact Paste

Try it at ysharma/OPF-SmartRedact-Paste.

User problem. You need to share a snippet of sensitive text (a support ticket, a code snippet with credentials, a leaked config) but want the public version redacted. You want a private URL that shows the original, and a public URL that only shows the redacted version.

What Privacy Filter does here. Same single-pass 128k context. The model runs on the raw text, returns spans with categories. The backend replaces detected spans with [REDACTED] or category-specific placeholders.

What gr.Server does here. This one is particularly clever. gr.Server lets you define multiple endpoints that share state. The paste endpoint stores the original text and its redacted version in memory. The public endpoint returns the redacted text. The private endpoint returns the original, but only if you pass a secret token. The queueing ensures that even under load, the model doesn’t get overwhelmed:

pastes = {}  # simple in-memory store

@server.api(name="create_paste")
def create_paste(text: str) -> dict:
    paste_id = uuid.uuid4().hex[:8]
    redacted_text, _ = run_privacy_filter(text)
    pastes[paste_id] = {
        "original": text,
        "redacted": redacted_text,
        "secret": secrets.token_urlsafe(16)
    }
    return {
        "paste_id": paste_id,
        "public_url": f"/paste/{paste_id}",
        "private_url": f"/paste/{paste_id}?secret={pastes[paste_id]['secret']}"
    }

@server.get("/paste/{paste_id}")
async def get_paste(paste_id: str, secret: str = None):
    paste = pastes.get(paste_id)
    if not paste:
        return HTMLResponse("Not found", status_code=404)
    if secret and secret == paste["secret"]:
        return HTMLResponse(paste["original"])
    return HTMLResponse(paste["redacted"])

This is a pattern I’ve seen before in pastebins, but the integration with Gradio’s queue and GPU management makes it production-ready out of the box.

What I think

Privacy Filter is a solid model. The 128k context means you can throw entire documents at it without chunking, and the permissive license (Apache 2.0) means you can actually deploy it without legal headaches. The 50M active parameters out of 1.5B total is a nice touch for inference speed.

But the real story here is gr.Server. I’ve been using Gradio for years, and the old approach of composing everything in Blocks was fine for demos but painful for real apps. gr.Server changes that. You get the GPU queueing and ZeroGPU allocation that Gradio is good at, but you’re not forced into their component model. You can write your own HTML, CSS, and JS, and just use Gradio for what it’s good at: serving ML models.

The three apps demonstrate this well. The Document Privacy Explorer is basically a custom document viewer with a Gradio backend. The Image Anonymizer is a canvas app with OCR. SmartRedact Paste is a pastebin with access control. None of them look like a typical Gradio demo. They look like real products.

If you’re building anything with PII detection, this is worth a look. The model is good, the tooling is better, and the combination is genuinely useful.

Comments (0)

Be the first to comment!