PrivaParse

August 16, 2026 · View on GitHub

Local PII detection and pseudonymisation for text sent to an LLM — nothing leaves the machine.

CI PyPI Python versions Licence

A local privacy layer for text you want to send to an LLM. It detects personally identifiable information, replaces it with deterministic placeholders, keeps the mapping in a local database, and restores the original values in the model's answer.

Nothing leaves the machine. Phase 1 calls no external service at all.

Hallo,                                  Hallo,
mein Name ist Max Mustermann.           mein Name ist [[PERSON_A1]].
Sie erreichen mich unter        ──▶     Sie erreichen mich unter
max@test.de                             [[EMAIL_A2]]
oder +49 170 1234567.                   oder [[PHONE_A3]].

The LLM only ever sees the right-hand side. reverse puts the left-hand side back into whatever the model replies.

Install

pipx install "privaparse[gateway]"

If privaparse isn't found afterwards, run pipx ensurepath and open a new shell.

That gives you the CLI and the local gateway. Person detection needs the model backend, which pulls in PyTorch — roughly 2 GB:

pipx install "privaparse[gateway,model]"

GPU setup, Docker, and installing from source are in docs/install.md.

Try it

Save this as brief.md:

Mein Name ist Max Mustermann, erreichbar unter max@test.de.
privaparse --detector regex demo brief.md

demo runs the whole round trip and prints every stage. Person detection needs the [model] extra; without it (the plain [gateway] install above), --detector regex keeps detection to email and phone — drop it once [model] is installed. The real workflow is two commands, with your own trip to an LLM in between:

privaparse --detector regex pseudonymize brief.md -o brief.pseudo.md

Send brief.pseudo.md's contents to your LLM of choice and save its reply as antwort.md, then:

privaparse --detector regex reverse antwort.md -o antwort.klar.md

More commands, and the Python library, are in docs/quickstart.md.

Gateway

Point any OpenAI-compatible client at PrivaParse and it pseudonymises requests going out, restores answers coming back — no code changes on the client side.

privaparse run -- aider

Without the [model] extra, run privaparse --detector regex serve — otherwise the server starts fine but every request that reaches detection returns a 500.

Which clients this works with today, what it costs, and its known gaps are in docs/gateway.md.

Evidence

Phase 1 ships a catalogue of 25 placeholder types — 21 enabled by default, three disabled on measured false positives and one (COUNTRY) on judgement rather than evidence (see docs/benchmarks/labels.md) — for plain text and Markdown, as a CLI and a Python library. No OCR, no PDF, no cloud models.

Does GLiNER2 need fine-tuning for German? No.

Measured on the German gold set in eval/gold/124 documents, 33 of them containing no PII at all (a corpus of nothing but real PII can never produce a false positive, which is exactly where a wider catalogue turns out to struggle) — scored at the shipped catalogue's 21 enabled types. PERSON's bar was fixed before this run: fine-tuning is warranted if PERSON partial-match recall drops below 0.90 or precision below 0.85.

TypePrecision (partial)Recall (partial)F1Support
PERSON0.9690.9600.96499
EMAIL1.0001.0001.00021
PHONE0.8181.0000.90018
IBAN1.0001.0001.0006

PERSON clears both floors comfortably — fine-tuning not warranted — and has at every catalogue width measured, including an older, narrower one that scored it higher (see why that isn't the better number). fastino/gliner2-privacy-filter-PII-multi is built on microsoft/mdeberta-v3-base, so German is not a special case for it. EMAIL and PHONE come from rules, not the model, so they are the control group: EMAIL is clean, PHONE is not — its 0.818 precision and TAX_ID's 0.000 recall are the same defect, counted from opposite ends; see One defect, two numbers for the mechanism.

The rest of the catalogue is measured too, and it is not uniformly clean. Eight types rest on three gold entities each — thin. Seven more, measured for the first time this run, were not all clean either: LICENSE_NUMBER and ROUTING_NUMBER measured 0.000 recall — they detect nothing — and CARD_CVV measured 0.167 precision. Full tables, the per-label breakdown, and every missed or spurious detection are in docs/benchmarks/detection-quality.md.

Reproduce with privaparse eval (needs GLiNER2 — see Install). Chunk size as a recall setting is in docs/benchmarks/performance-notes.md; throughput is in docs/benchmarks/throughput.md.

Documentation

PageCovers
Installpipx and source installs, CPU/GPU, Docker
Quickstartprivaparse demo, the CLI, the Python library
GatewayThe local OpenAI-compatible gateway
APIThe direct HTTP API — detect, pseudonymise, reverse, catalogue, vault
ConfigurationEvery PRIVAPARSE_* setting
ArchitectureThe pipeline, the vault, Markdown handling
TestingRunning the suite, coverage
BenchmarksEvery measured number, with the command that reproduces it

Contributing

See CONTRIBUTING.md for issues and pull requests. The rule that matters most: no real PII in examples, tests, or gold documents, ever.

Licence

Apache 2.0. See LICENSE.