Bayes Impact is a technology nonprofit organization building AI recommendation systems for the public interest. As part of this work, we curate public and community resource datasets and make them usable by AI agents.
Privacy Service is a Python library for PII detection and anonymization built on top of Microsoft Presidio and ai4privacy, with French-first defaults and support for custom regex-based patterns.
It exposes a simple PrivacyService class to detect and anonymize PII in strings, plus configuration helpers to control which recognizers and anonymization strategies are enabled.
- Python version: >= 3.10
Install from source using uv:
uv sync --extra cpuIf you want to use GPUs:
uv sync --extra cu128Then run:
uv pip install -e .from privacy_service import PrivacyService
service = PrivacyService() # Uses built-in defaults
text = "L'email de John Smith est john@example.com et son téléphone est +33 6 12 34 56 78."
detections = service.detect(text)
for det in detections:
print(det.entity_type, det.text, det.start, det.end, f"score={det.score:.2f}")from privacy_service import PrivacyService
service = PrivacyService()
text = "L'email de John Smith est john@example.com et son téléphone est +33 6 12 34 56 78."
result = service.anonymize(text)
print("Original:", result.original_text)
print("Anonymized:", result.text)
for item in result.items:
print(
f"{item.entity_type} [{item.start}:{item.end}] "
f"'{item.text}' -> '{item.anonymized_text}' via {item.operator}"
)By default, the service:
- Uses ai4privacy (
use_ai4privacy=True) - Keeps Presidio’s default recognizers (
use_presidio_defaults=True) - Uses spaCy NLP with French and English large models
- Uses
replaceas the default anonymization strategy - Assumes French (
language="fr") as the default language
Configuration is managed via the PrivacyConfig dataclass and helper functions in privacy_service.core.config.
You can configure the service in three ways:
- Implicit defaults (no config argument)
- Dictionary passed directly
- YAML file (recommended, using
config.example.yamlas a template)
Copy config.example.yaml to config.yaml and adjust it to your needs:
cp config.example.yaml config.yamlThen use:
from privacy_service import PrivacyService
service = PrivacyService(config="config.yaml")The example config controls:
-
Recognizers (
recognizerssection)-
use_ai4privacy: enable/disable ai4privacy recognizer -
use_presidio_defaults: enable/disable Presidio’s built-in regex/statistical recognizers -
use_spacy_nlp: enable/disable spaCy NLP engine -
spacy_nlp_model: list of spaCy models to load, e.g.:recognizers: use_spacy_nlp: true spacy_nlp_model: - lang_code: fr model_name: fr_core_news_lg - lang_code: en model_name: en_core_web_lg
-
ai4privacy:confidence_threshold: float between 0.0 and 1.0classify_pii: whether to map to specific entity types (EMAIL_ADDRESS,PERSON, …) or use a generic label
-
-
Anonymization (
anonymizationsection)-
default_strategy: default Presidio operator name, e.g.replace,mask,redact,hash,encrypt, … -
strategies: per-entity overrides, e.g.:anonymization: default_strategy: replace strategies: EMAIL_ADDRESS: hash PHONE_NUMBER: mask PERSON: replace ORGANIZATION: replace LOCATION: replace CREDIT_CARD: redact US_SSN: redact IP_ADDRESS: hash
-
-
Custom regex patterns (
custom_patternssection)Example from
config.example.yaml:custom_patterns: - name: numero_benevole entity_type: NUMERO_BENEVOLE patterns: - "BEN-\\d{6}" - "BENEVOLE\\s+\\d{4}" score: 0.9 - name: numero_dossier entity_type: NUMERO_DOSSIER patterns: - "DOS-\\d{4}-\\d{4}" - "DOSSIER[\\s-]\\d{6}" score: 0.9
These are automatically registered as Presidio pattern recognizers by
PrivacyService._init_engines. -
Language
language: fr
This sets the default language for detection (
service.detect()andservice.anonymize()), and is also passed to the ai4privacy recognizer.
You can supply a Python dict instead of a YAML file:
from privacy_service import PrivacyService
config = {
"recognizers": {
"use_ai4privacy": True,
"use_presidio_defaults": True,
"use_spacy_nlp": True,
"ai4privacy": {
"confidence_threshold": 0.01,
"classify_pii": True,
},
},
"anonymization": {
"default_strategy": "replace",
"strategies": {
"EMAIL_ADDRESS": "hash",
"PHONE_NUMBER": "mask",
},
},
"custom_patterns": [],
"language": "fr",
}
service = PrivacyService(config=config)Behind the scenes this goes through privacy_service.core.config.load_config and PrivacyConfig.
If you pass config=None and do not specify a file, PrivacyService will load built-in defaults. The lower-level get_default_config_path() helper (used in CLI tooling) searches for a config.yaml in:
./config.yaml~/.privacy-service/config.yaml/etc/privacy-service/config.yaml
You can use save_config / load_config yourself:
from privacy_service.core.config import load_config, save_config
from privacy_service.core.models import PrivacyConfig
cfg = load_config("config.yaml")
save_config(cfg, "config-out.yaml")from privacy_service import PrivacyService
service = PrivacyService()
print("Entities:", service.get_supported_entities())
print("Recognizers:", service.get_recognizers())from privacy_service import PrivacyService
service = PrivacyService()
service.add_custom_pattern(
name="employee_id",
patterns=[r"EMP-\\d{5}"],
entity_type="EMPLOYEE_ID",
score=0.9,
)For some integrations, you may want to work with the result models defined in privacy_service.core.models:
DetectionResultAnonymizationResultAnonymizationItemFileDetectionResultFileAnonymizationResultPrivacyConfig
All of these are standard @dataclass classes and work well with JSON serialization.
The provided Dockerfile builds a self-contained image with the FastAPI service.
All heavy assets (spaCy models, HuggingFace models) are downloaded at build time and baked into the image.
At runtime the container has no internet access to HuggingFace (HF_HUB_OFFLINE=1).
BuildKit is required for the --secret flag used to pass the HuggingFace token without leaking it into the image layers.
# BuildKit is the default backend since Docker 23.
# If you are on an older version, enable it explicitly:
export DOCKER_BUILDKIT=1The AP-HP/eds-pseudo-public model is gated and requires authentication.
- Create a free account at https://huggingface.co
- Go to https://huggingface.co/settings/tokens and create a token with read access
- Accept the model's terms of use at https://huggingface.co/AP-HP/eds-pseudo-public
Export the token in your shell:
export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxThe Dockerfile copies config.yaml into the image (see the COPY config.yaml ./ step).
If you do not have one yet, create it from the example template:
cp config.example.yaml config.yamldocker build \
--secret id=hf_token,env=HF_TOKEN \
-t privacy-service:latest \
.The build will:
- Install all Python dependencies (including
appgroup) - Download spaCy models
fr_core_news_lganden_core_web_lg - Log in to HuggingFace Hub and download:
ai4privacy/llama-ai4privacy-multilingual-categorical-anonymiser-openpiiAP-HP/eds-pseudo-public
- Copy application code and
config.yaml
The token is passed via a BuildKit secret and is never written into any image layer.
docker run --rm -p 8000:8000 privacy-service:latestThe API is then available at http://localhost:8000.
To mount a custom config at runtime instead of the one baked into the image:
docker run --rm -p 8000:8000 \
-v "$(pwd)/config.yaml:/app/config.yaml:ro" \
privacy-service:latestClone the repository and set up the development environment (using uv groups defined in pyproject.toml):
make devOnce dependencies are installed you can run:
-
Tests:
make test -
Lint (ruff):
make lint
-
Format (black):
make format
-
Type-check (mypy):
make type-check
To enable pre-commit hooks (see .pre-commit-config.yaml):
make pre-commit-installTo run all pre-commit checks on the whole codebase:
make pre-commitWith hooks installed, these checks will run automatically on each commit.
This project is licensed under the MIT License. See LICENSE for details.