PII Router Policy - OpenMed Privacy Filter Multilingual v2 (ONNX)
A Lemonade collection.router policy JSON: routes any prompt containing PII to a local model, and everything else to a cloud model, using lemonade-sdk/openmed-privacy-filter-multilingual-v2-onnx as the PII classifier.
This repo holds only the policy config - not model weights. It's meant to be pulled directly into a running Lemonade server.
What this policy does
- Runs every prompt through the PII classifier first (216 non-"O" BIOES labels across 54 entity types: names, emails, phone numbers, financial/government IDs, addresses, demographic attributes, credentials, and more - see the classifier's model card for the full label list).
- If any label crosses
min_score: 0.5, the prompt routes to the local candidate (Qwen3.5-0.8B-GGUFby default). - Otherwise it routes to the cloud candidate (
fireworks.kimi-k2p6by default).
On the Nemotron-PII benchmark (20,000 PII-bearing prompts, 20,001 total), this policy scored a 0% leak rate - zero PII-bearing prompts routed to the cloud candidate.
Usage
1. Register the classifier. Lemonade's onnxruntime backend (ort-server) needs a manifest.json next to model.onnx to know this is a token-classification model - one score per token, not one per prompt - rather than the default it assumes otherwise (single-label text-classification, one score for the whole input, like a sentiment model). Without the right manifest, ort-server either errors on the shape mismatch or silently misreads the output. Unlike some other Lemonade PII classifiers, lemonade-sdk/openmed-privacy-filter-multilingual-v2-onnx already ships its manifest.json alongside model.onnx at the repo root - nothing extra to place, this one pull is all it needs:
curl -X POST http://localhost:13305/v1/pull -H "Content-Type: application/json" -d '{
"model_name": "user.privacy-filter-ml-v2-onnx",
"checkpoint": "lemonade-sdk/openmed-privacy-filter-multilingual-v2-onnx",
"recipe": "onnxruntime"
}'
2. Register this policy:
hf download lemonade-sdk/pii_policy_openmed-privacy-filter-multilingual-v2-onnx --local-dir .
curl -X POST http://localhost:13305/v1/pull -H "Content-Type: application/json" \
--data-binary @pii_policy_openmed-privacy-filter-multilingual-v2-onnx.json
3. Use it - send chat completions to "model": "user.PII-ONNX-PrivacyFilter-Router" and the server will route each request per the policy above.
Why the manifest keeps all 217 raw labels distinct
The manifest deliberately keeps every BIOES-prefixed label separate (B-EMAIL, I-EMAIL, E-EMAIL, S-EMAIL, ...) rather than collapsing each entity type into one bare name. Per-label scores land in a map keyed by label string in ort-server - collapsing several label indices onto the same key would let the last-processed one silently overwrite the others' scores instead of combining them. That's why this policy's match rule checks "does the B-, I-, E-, or S- variant cross threshold" per entity type (216 leaf conditions) rather than 54 collapsed ones.
Adapting this policy
routing.candidates (local/cloud model names) and min_score are starting points, not fixed requirements - point them at whatever models you have registered, or tighten/loosen the threshold for your own precision/recall tradeoff. The classifiers[].labels list must stay in sync with whatever classifier model you point at (this one matches the 217-label BIOES scheme of openmed-privacy-filter-multilingual-v2-onnx exactly).
For building a routing policy from scratch, or a more sophisticated one (multiple classifiers, LLM-based routing, custom match logic), see the lemonade-router-builder skill - it turns a natural-language description of routing intent into a valid collection.router policy JSON.
License
Apache 2.0.