d022 semantic classifier — swapping the model
d022 semantic classifier — swapping the model
d022_semantic_classifier loads a HuggingFace text-classification pipeline
whose weights are what actually decide INJECTION vs SAFE. The model is
swappable via per-detector config; the shipped default is
protectai/deberta-v3-base-prompt-injection-v2 and stays that way in v0.8.
Why the default is what it is
Deepset DeBERTa v3 is the most-recall model on this bench (100 / 100 / 100
on Liu / Garak / InjecAgent — see docs/papers/evaluation/competitor_rerun.md
§3.1), and there is a real
temptation to ship it as the default. The same 2026-08-19 rerun already
called this out in §4(b):
Deepset DeBERTa v3’s 100 / 100 / 100 is trained-to-flag behaviour, not universal superiority.
The 2026-09-22 NotInject FPR probe (§3.4 of the same file) turned that into a hard number: Deepset flags 242 of 339 NotInject benign prompts (71.4% FPR), three-quarters of which are inputs a customer would send in production (“Please enclose the details of the project…”, benign Chinese-language questions about password hygiene). Making Deepset the default would regress the paper’s headline whole-engine NotInject FPR from 0.9–3.8% to something roughly two orders of magnitude worse — published against yourself.
ProtectAI v2 alone has 43% FPR on NotInject; the engine’s overall 0.9–3.8% NotInject FPR reported in the paper comes from thresholding + composition with other detectors, not from the classifier alone. The composition is what carries the FPR guarantee, so the classifier line is free to over-defend a little — but not by as much as Deepset does.
Validated shortlist (2026-09-22)
Numbers are d022-shaped: standalone classifier, threshold P(injection) ≥
0.5, no thresholding, no composition. Higher recall is better; lower FPR
is better. Recall from competitor_rerun.md §3.1, NotInject FPR from §3.4.
| Model (HF id) | License | Liu 200 | Garak 500 | InjecAgent 500 | NotInject FPR | Verdict |
|---|---|---|---|---|---|---|
protectai/deberta-v3-base-prompt-injection-v2 (default) |
Apache-2.0 | 63.5% | 44.5% | 83.4% | 43.4% | Ship default — worst-recall of the reasonable set but relied on by the engine’s composition math |
leolee99/PIGuard |
MIT (requires trust_remote_code=True) |
85.5% | 77.3% | 80.6% | 11.5% | Best independent classifier here. Higher recall + ~4× lower FPR than the default. Swap-in candidate for deployments that want a stronger d022 without falling back to the whole engine. |
deepset/deberta-v3-base-injection |
MIT | 100% | 100% | 100% | 71.4% | Not recommended. The 100/100/100 is trained-to-flag; 71% NotInject FPR is disqualifying on mixed traffic. Use only if you know your traffic is nearly all adversarial. |
meta-llama/Llama-Prompt-Guard-2-86M |
Meta gated licence | 44.0% | 56.3% | 56.0% | 4.4% | Lowest recall, lowest FPR. Behind the Meta acceptance gate — see competitor_rerun.md §5. |
How to swap
Per-detector config (YAML shown; JSON works the same):
detectors:
d022_semantic_classifier:
enabled: true
config:
model_name: leolee99/PIGuard
# PIGuard requires trust_remote_code; this is set inside d022 for
# this model automatically when the transformers pipeline is loaded.
device: cpu
# Chunking (optional): defaults are chunk_size=512, chunk_stride=384,
# max_chunks=8. These are correct for the DeBERTa/PromptGuard family;
# override only if you understand the max-pool aggregation in d022.
The pipeline is loaded lazily on the first detect() call. If the model
isn’t in the local HF cache, the first request will attempt a network
fetch — for air-gapped deployments, pre-populate the cache with
huggingface-cli download <model_name> at install time.
The transformers dependency ships behind the [ml] extra:
pip install "prompt-shield-ai[ml]"
Without it, d022 returns detected=False on every call — regex-only
protection still runs, but classifier-shaped attacks bypass this
detector.
Caveats before you swap
- The engine’s composed FPR guarantee (paper §5.5) is measured with the
ProtectAI default. Swapping to PIGuard is very likely a net win on
independent traffic (higher recall + lower FPR), but the composed
guarantee has not been re-measured with any other classifier. If
you swap, run
python docs/papers/evaluation/run_public_datasets.pyagainst your traffic profile before publishing an FPR figure. - All four models were measured on CPU. PIGuard’s per-scan latency is
~4/sec (vs. ProtectAI’s ~10/sec) per
competitor_rerun.md§3.2 — a ~2× throughput cost for the recall/FPR improvement. d022usespipeline("text-classification")withtruncation=Trueandmax_length=512; longer inputs are handled by the sliding-window chunking ind022_semantic_classifier.py::_chunk, which max-pools the per-chunkINJECTIONscore. This is independent of which model you swap in — every classifier in the shortlist above uses the same 512-token cap.
Reproducing the FPR numbers
# Two-model probe (Deepset vs ProtectAI):
python docs/papers/evaluation/notinject_fpr/run_deepset_protectai.py
# Two-model probe (PIGuard + Meta PromptGuard 2):
python docs/papers/evaluation/notinject_fpr/run_piguard_pg2.py
# Full head-to-head rerun (recall table):
python docs/papers/evaluation/competitor_rerun.py
All three run offline from the local HF cache; the models must have been
downloaded once (see competitor_rerun.md
§6, and notinject_fpr/README.md
for the FPR-probe context and cross-validation).