Skip to content

feat: add a deny list for entities in Anonymizer - #243

Open
memadi-nv wants to merge 16 commits into
mainfrom
memadi/feature/add-entity-deny-list
Open

feat: add a deny list for entities in Anonymizer#243
memadi-nv wants to merge 16 commits into
mainfrom
memadi/feature/add-entity-deny-list

Conversation

@memadi-nv

@memadi-nv memadi-nv commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds entity_label_denylist to the Detect configuration, allowing users to explicitly exclude specific entity label types from detection. Denied labels are removed from detector scope, filtered from final explicit and latent entity outputs, and excluded from entity-coverage scoring.

Changes

Config

  • Added entity_label_denylist: list[str] | None field to Detect with the same normalisation as entity_labels (strip, lowercase, deduplicate, reject empty)
  • Added model validator that warns at config construction time when entity_labels and entity_label_denylist share labels that would never be detected.

Detection Engine

  • Subtracted denied labels from the effective label set before GLiNER and LLM prompts run (_resolve_detection_labels)
  • Added safety-net filter in _materialize_final_entities to drop denied labels from final entity output regardless of LLM output
  • Threaded entity_label_denylist through all detection pipeline methods: run, detect_and_validate_entities, _build_detection_spec, identify_latent_entities
  • Threaded through export paths: build_detection_config and build_detection_builder_for_seed
  • Added warning when the denylist empties the effective label set (GLiNER returns no detections for an empty list, not defaults)
  • Rewrite latent detection receives the denylist in its prompt and deterministically filters denied latent labels from its output.
  • Final allowlist and denylist comparisons are case- and whitespace-insensitive.

Evaluation

  • Added entity_label_denylist field to AnonymizerResult and PreviewResult so evaluate() has access to the full detection scope
  • Propagated entity_label_denylist through evaluate() to EntityCoverageWorkflow and ReplacementWorkflow.evaluate()
  • Explicit allowlists have denied labels removed. When entity_labels=None, coverage remains permissive so novel augmenter-defined labels are evaluated, while denied labels are independently excluded in the prompt and postprocessing.

Telemetry

  • Added entity_label_denylist to _detect_config_metadata so it is captured in measurement records alongside entity_labels

Tests

  • Config validation: field normalisation, empty list rejection, overlap warning
  • Detection engine: label resolution, GLiNER injection, final entity filtering, combined allowlist + denylist, export paths
  • Evaluation: _effective_entity_labels helper, coverage judge postprocess filter, coverage prompt scoping
  • Telemetry: entity_label_denylist captured correctly when set and absent
  • Case-insensitive denylist enforcement.
  • Latent-entity filtering.
  • Preservation of novel non-denied labels during coverage evaluation.

Docs

  • Updated Detect fields reference table and added "Excluding labels with a deny list" section in detection.md
  • Added entity_label_denylist to the judge scoping list in evaluation.md
  • Added entity_label_denylist section and corrected the data_summary soft-exclusion claim in choosing-a-strategy.md
  • Added entity_label_denylist as the recommended fix for systematically noisy label types in troubleshooting.md
  • Updated skills/anonymizer/SKILL.md with denylist behavior and precedence rule

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • CI, release, or contributor workflow update

Contributor Checklist

  • PR title follows Conventional Commits, for example fix: handle empty entity list
  • Related issue is linked, or a maintainer-owned no-issue reason is documented above
  • For non-trivial changes, a plan document is linked above, or the no-plan reason is documented above
  • Public API impact checked; skills/anonymizer/SKILL.md updated if needed
  • No real PII added to tests, docs, notebooks, fixtures, or artifacts
  • No API keys, service tokens, private keys, credentials, or real endpoint secrets added

Validation

  • Commands run:
  • Skipped checks or known failures:

Documentation and Artifacts

  • Docs updated, or not needed
  • If docs changed: make docs-build passes locally
  • If tutorial sources changed: notebooks regenerated with make convert-notebooks
  • If e2e, benchmark, or model-provider behavior changed: relevant validation is listed above

closes issue #164

@memadi-nv
memadi-nv requested review from a team as code owners August 11, 2026 01:02
@greptile-apps

greptile-apps Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds a normalized entity-label denylist and propagates it through detection, rewrite latent-entity handling, evaluation, result metadata, measurement records, documentation, and tests.

  • Removes denied labels from detector and prompt scope and filters explicit and latent outputs defensively.
  • Preserves permissive coverage evaluation for novel non-denied labels while excluding denied candidates.
  • Carries denylist configuration through run, preview, evaluation, export, and telemetry paths.
  • Updates the packaged skill documentation, publication metadata, benchmark evidence, and signature artifact.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the current code fixes the previously reported permissive-coverage, explicit-label normalization, and latent-entity denylist issues.

Important Files Changed

Filename Overview
src/anonymizer/config/anonymizer_config.py Adds normalized denylist configuration and overlap diagnostics without leaving the previously reported bypasses reachable.
src/anonymizer/engine/detection/detection_workflow.py Propagates denylist scope through detection and latent workflows and now strips and case-folds labels in both final filtering paths.
src/anonymizer/engine/evaluation/entity_coverage_judge.py Preserves permissive evaluation when no allowlist is configured while independently excluding denied labels from prompts and postprocessing.
src/anonymizer/interface/anonymizer.py Carries denylist context consistently through run, preview, and both replace and rewrite evaluation paths.
src/anonymizer/interface/results.py Retains denylist scope on result objects so later evaluation reproduces the original detection configuration.
src/anonymizer/engine/replace/replace_runner.py Passes denylist context into replace-mode coverage evaluation.
src/anonymizer/measurement/records/run.py Adds normalized denylist configuration to sanitized run metadata.
tests/engine/test_detection_workflow.py Covers denylist resolution, explicit and latent filtering, serialization paths, and case-insensitive enforcement.
tests/engine/test_entity_coverage_judge.py Verifies permissive novel-label coverage and independent denied-label filtering.
skills/anonymizer/SKILL.md Documents denylist behavior and precedence for skill-assisted configuration.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  C[Detect configuration] --> R[Resolve effective labels]
  R --> D[GLiNER and explicit detection]
  R --> L[Latent detection]
  D --> F[Normalize and filter final entities]
  L --> LF[Normalize and filter latent entities]
  F --> O[Anonymization result]
  LF --> W[Rewrite sensitivity analysis]
  O --> E[Coverage evaluation]
  C --> E
  E --> P[Prompt scope and deterministic candidate filter]
Loading

Reviews (9): Last reviewed commit: "Attach NVSkills validation signatures" | Re-trigger Greptile

Comment thread src/anonymizer/engine/evaluation/entity_coverage_judge.py Outdated
Comment thread src/anonymizer/engine/detection/detection_workflow.py Outdated
Comment thread src/anonymizer/engine/detection/detection_workflow.py
Comment thread src/anonymizer/engine/detection/detection_workflow.py Outdated
@memadi-nv

Copy link
Copy Markdown
Contributor Author

/nvskills-ci

@memadi-nv
memadi-nv force-pushed the memadi/feature/add-entity-deny-list branch from 6a153c2 to e3b99da Compare August 12, 2026 20:09
@lipikaramaswamy

Copy link
Copy Markdown
Collaborator

/nvskills-ci

memadi-nv and others added 16 commits August 12, 2026 15:36
Adds entity_label_denylist: list[str] | None to the Detect model, with
the same normalisation (strip, lowercase, deduplicate) as entity_labels.
A model_validator warns at config construction time when entity_labels
and entity_label_denylist share labels that would never be detected.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Applies the denylist at two points:
- _resolve_detection_labels: subtracts denied labels before they reach
  GLiNER and the LLM augmenter/validator prompts
- _materialize_final_entities: safety-net filter that drops any entity
  whose label is in the denylist from COL_FINAL_ENTITIES

Threads entity_label_denylist through detect_and_validate_entities,
_build_detection_spec, identify_latent_entities, and run on
EntityDetectionWorkflow, and wires it from Anonymizer._run_internal
via config.detect.entity_label_denylist.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ion paths

Adds tests for build_detection_config and build_detection_builder_for_seed
confirming that denied labels are subtracted from the GLiNER label list in
the serialized workflow config, so external runtimes see the same effective
label set as the in-process path.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…abel set

When the denylist subtracts all labels from the effective detection set,
_resolve_detection_labels now emits a warning instead of silently passing
an empty list to GLiNER (which returns no detections, not the default set).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…peline

Stores entity_label_denylist on AnonymizerResult and PreviewResult so
evaluate() can scope its judges to the same label set that was active
during detection.

Changes:
- AnonymizerResult / PreviewResult: add entity_label_denylist field
- Anonymizer.evaluate(): extract entity_label_denylist from the result
  and pass it to EntityCoverageWorkflow (rewrite path) and
  ReplacementWorkflow.evaluate() (replace path)
- EntityCoverageWorkflow: accept entity_label_denylist, pass to
  _filter_out_of_scope_entities in postprocess
- _filter_out_of_scope_entities: exclude entities whose label is in the
  denylist so the judge does not penalise the output for not anonymizing
  denied labels
- ReplacementWorkflow.evaluate(): thread entity_label_denylist through
  to EntityCoverageWorkflow

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…filter

Signed-off-by: memadi <memadi@nvidia.com>
Signed-off-by: memadi <memadi@nvidia.com>
Signed-off-by: memadi <memadi@nvidia.com>
Signed-off-by: memadi <memadi@nvidia.com>
Signed-off-by: memadi <memadi@nvidia.com>
Signed-off-by: memadi <memadi@nvidia.com>
Signed-off-by: memadi <memadi@nvidia.com>
Signed-off-by: memadi <memadi@nvidia.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Signed-off-by: memadi <memadi@nvidia.com>
Signed-off-by: nvskills-svc-account <svc-nvskills-signing@nvidia.com>
@memadi-nv
memadi-nv force-pushed the memadi/feature/add-entity-deny-list branch from fd66bbd to 7f87728 Compare August 12, 2026 22:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants