Instead of replacing individual entities, rewrite mode transforms the entire text to produce a privacy-safe version that reduces both explicit identifiers and implicit, inferable signals that could lead to re-identification. While overall meaning is preserved where possible, rewrite mode intentionally modifies or removes details when needed to eliminate latent entities and break inference pathways.
Detection runs first (same as Replace mode, plus latent entity detection for context-inferable information). This includes identifying signals that may not be explicitly tagged but can be deduced from combinations of details (e.g., location inferred from contextual cues). The text is then classified by domain, and each entity or attribute is assigned a sensitivity disposition based on contextual risk, recognizing that quasi-identifiers can emerge from any aspect of the text.
The text is then rewritten to reduce identifiability, applying targeted transformations that disrupt inference (e.g., weakening or removing linking details) rather than simply rewording content. The rewritten output is evaluated for both quality and privacy leakage using adversarial testing. If thresholds are exceeded, the system automatically refines the rewrite. Any records that failed to meet standards are tagged for human review.
!!! info "Long records" A record too large to rewrite in a single LLM call is rewritten in sequential windows, with a continuity summary carried across each so the narrative and pseudonyms stay consistent. See Long-context handling.
Sensitivity measures the intrinsic re-identification damage an entity causes if it appears in the output — independently of what else is retained. It is not the protection decision itself; rather, it feeds the downstream leakage scoring system.
| Level | Meaning | Examples | Leakage weight |
|---|---|---|---|
high |
Exposure alone can identify a person | Names, ID numbers, contact details | 1.0 |
medium |
Meaningfully narrows the identity space | Location, occupation, age | 0.6 |
low |
Minimal standalone identifying power | Generic attributes, widely shared traits | 0.3 |
Protection method describes how a sensitive entity is transformed. The choice reflects a holistic view of the document — what other entities are being protected and how, then shapes what each individual entity needs.
| Method | What it does | Typical use |
|---|---|---|
replace |
Substitutes the entity with a plausible synthetic alternative | Direct identifiers (names, IDs, contact details) |
generalize |
Replaces the entity with a broader form | Quasi-identifiers (exact date → quarter, city → region) |
suppress_inference |
Rewrites the surrounding text to remove cues that enable the inference | Latent entities that are implied rather than stated |
remove |
Deletes the entity entirely | Cases where neither replacement nor generalization can preserve meaning without retaining the identifying detail |
leave_as_is |
Leaves the entity unchanged | Entities judged not to require protection in context |
from anonymizer import Anonymizer, AnonymizerConfig, AnonymizerInput, Rewrite
anonymizer = Anonymizer()
config = AnonymizerConfig(rewrite=Rewrite())
data = AnonymizerInput(source="data.csv", text_column="text")
preview = anonymizer.preview(config=config, data=data, num_records=3)
preview.display_record()| Field | Default | Description |
|---|---|---|
privacy_goal |
Auto-populated | What to protect and what to preserve. |
instructions |
None |
Additional instructions for the rewrite LLM. |
risk_tolerance |
low |
Preset controlling repair and review thresholds: minimal, low, moderate, high. |
max_repair_iterations |
3 |
Maximum repair rounds. Set to 0 to disable repair. |
strict_entity_protection |
False |
If True, forces every detected entity to be protected regardless of risk. No entity can be left unchanged. |
PrivacyGoal tells the rewriter what matters. Defaults work for general-purpose data. Override for domain-specific needs:
from anonymizer.config.rewrite import PrivacyGoal
config = AnonymizerConfig(
rewrite=Rewrite(
privacy_goal=PrivacyGoal(
protect="All patient identifiers and clinical facility names",
preserve="Clinical findings, treatment plans, and medical terminology",
)
)
)!!! tip "Be specific"
The more precise the `protect` and `preserve` fields, the better the rewriter targets sensitive content while retaining what matters.
Controls the automated repair loop and human review flagging. Each preset bundles a coherent set of behaviors:
| Preset | Repair threshold | Repair on any high leak | Flag utility below | Flag leakage above |
|---|---|---|---|---|
minimal |
0.6 | Yes | 0.6 | 1.0 |
low |
1.0 | Yes | 0.5 | 2.0 |
moderate |
1.5 | Yes | 0.4 | 2.5 |
high |
2.0 | No | 0.3 | 3.0 |
The repair threshold is the leakage mass above which a record is sent for repair.
Leakage mass is a confidence-weighted sum of leaked entities, where each entity's weight reflects its sensitivity (high=1.0, medium=0.6, low=0.3). A leakage mass of 1.0 roughly equals one high-sensitivity entity leaked at full confidence.
config = AnonymizerConfig(
rewrite=Rewrite(
risk_tolerance="minimal",
max_repair_iterations=3,
)
)By default, some entities — particularly quasi-identifiers judged to be low-risk in context — may be left unchanged. Setting strict_entity_protection=True overrides this: every detected entity is forced into an active protection method, and no entity can be left as-is.
config = AnonymizerConfig(
rewrite=Rewrite(
strict_entity_protection=True,
)
)!!! tip "When to use this"
Use `strict_entity_protection` when you need a blanket policy that protects all entities regardless of contextual risk — for example, in medical, legal, or financial data where even seemingly benign attributes (gender, marital status, occupation) must be modified. For finer control over which entity types are always protected, combine this with a specific [`privacy_goal`](#privacy-goal).
| Column | When available | Description |
|---|---|---|
{text_col}_rewritten |
Always | The privacy-safe rewritten text. |
utility_score |
Always | Quality preservation (0.0--1.0). Higher is better. |
leakage_mass |
Always | Weighted privacy leakage. Lower is better. |
weighted_leakage_rate |
Always | Normalized leakage (0.0--1.0) relative to the maximum possible leakage mass. |
any_high_leaked |
Always | Whether any high-sensitivity entity leaked through. |
needs_human_review |
Always | Flag for records that may need manual review. |
detection_valid |
After evaluate() |
Fraction of detected entities that passed the detection judge (0.0--1.0); None if judge unavailable. |
detection_invalid_entities |
After evaluate() |
Flagged detections with value, label, and one-sentence reasoning. |
judge_evaluation |
After evaluate() |
Dict with privacy, quality, and style rubric scores and reasoning. |
Use preview.trace_dataframe for the full pipeline trace (domain, disposition, QA pairs, repair iterations).
!!! note "No entities? No rewrite."
Records with no detected entities pass through unchanged with `utility_score=1.0` and `leakage_mass=0.0`.
Records with needs_human_review=True exceeded automated thresholds for leakage or utility. To investigate and resolve:
Diagnose: Use trace_dataframe to inspect the flagged record's intermediate columns — disposition, leakage breakdown, repair iterations, and judge evaluation.
flagged = result.trace_dataframe[result.trace_dataframe["needs_human_review"] == True]
flagged[["utility_score", "leakage_mass", "any_high_leaked"]].head()Tune and re-run: Adjust settings and re-run on flagged records:
- Increase
max_repair_iterationsto give the rewriter more attempts. - Refine
privacy_goalwith more specificprotect/preserveinstructions for the domain. - Lower
risk_tolerance(e.g.minimal) to trigger more aggressive repair. - Enable
strict_entity_protection=Trueto ensure no detected entity is left unchanged in the output regardless of risk.
Last resort: Manually edit or exclude records that resist automated repair — some text is inherently difficult to rewrite without losing utility or leaking identifiers, and requires your judgement as the expert.
Rewrite uses multiple LLM roles. All default to models in the default config:
| Role | Default | Purpose |
|---|---|---|
domain_classifier |
gpt-oss-120b |
Classifies text domain. |
disposition_analyzer |
gpt-oss-120b |
Assigns sensitivity levels. |
meaning_extractor |
gpt-oss-120b |
Extracts meaning units. |
qa_generator |
gpt-oss-120b |
Generates QA pairs for evaluation. |
rewriter |
gpt-oss-120b |
Generates the rewritten text. |
evaluator |
nemotron-30b-thinking |
Evaluates quality and leakage. |
repairer |
gpt-oss-120b |
Repairs high-leakage rewrites. |
After running rewrite, you can score detection quality and the holistic rewrite quality using LLM-as-judge evaluation. See Evaluation for details on the detection judge and the three rewrite quality rubrics (privacy, quality, style), and how to call Anonymizer.evaluate().