Skip to content

feat: add excluded labels for entities in Anonymizer - #243

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

feat: add excluded labels for entities in Anonymizer#243
memadi-nv wants to merge 31 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 excluded_entity_labels 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 excluded_entity_labels: 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 excluded_entity_labels 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 excluded_entity_labels 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 excluded_entity_labels field to AnonymizerResult and PreviewResult so evaluate() has access to the full detection scope
  • Propagated excluded_entity_labels 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 excluded_entity_labels 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: excluded_entity_labels 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 excluded_entity_labels to the judge scoping list in evaluation.md
  • Added excluded_entity_labels section and corrected the data_summary soft-exclusion claim in choosing-a-strategy.md
  • Added excluded_entity_labels 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

RetriggerConfidence Score: 5/5

The PR appears safe to merge; the prior exclusion-bypass findings are resolved and no new actionable failure remains.

Summary

  • Exclusions take precedence over explicit and default detection labels.
  • Explicit and latent outputs are filtered using normalized label comparisons.
  • Evaluation ignores denied labels while retaining permissive handling of novel labels.
  • Configuration now rejects exclusions that eliminate the entire effective detector scope.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    C[Detect configuration] --> R[Resolve effective labels]
    X[Excluded labels] --> R
    R --> D[Seed detection]
    R --> A[LLM augmentation]
    D --> V[Validation]
    A --> V
    X --> V
    V --> F[Final entity filtering]
    X --> F
    F --> P[Replace or rewrite]
    X --> L[Latent entity filtering]
    L --> P
    F --> E[Coverage evaluation]
    X --> E
Loading

Reviews (22) · Last reviewed commit: "feat(config): also guard entity_labels=N..."

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
memadi-nv force-pushed the memadi/feature/add-entity-deny-list branch from fd66bbd to 7f87728 Compare August 12, 2026 22:37
Comment thread src/anonymizer/engine/detection/detection_workflow.py Outdated
@lipikaramaswamy

Copy link
Copy Markdown
Collaborator

The param name entity_label_denylist is not precise and is not likely to be intuitive to a user. excluded_entity_labels (preferred) or ignored_entity_labels sounds better to me; open to other suggestions. We should agree on the name before we make changes to the public API.

Comment thread src/anonymizer/interface/anonymizer.py Outdated
@memadi-nv

Copy link
Copy Markdown
Contributor Author

The param name entity_label_denylist is not precise and is not likely to be intuitive to a user. excluded_entity_labels (preferred) or ignored_entity_labels sounds better to me; open to other suggestions. We should agree on the name before we make changes to the public API.

excluded_entity_labels is reasonable. Updated. Thanks!

@memadi-nv

Copy link
Copy Markdown
Contributor Author

/nvskills-ci

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

Copy link
Copy Markdown
Contributor Author

/nvskills-ci

1 similar comment

Copy link
Copy Markdown
Collaborator

/nvskills-ci

memadi-nv and others added 8 commits August 21, 2026 18:12
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>
memadi-nv and others added 11 commits August 21, 2026 18:12
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>
Signed-off-by: memadi <memadi@nvidia.com>
Signed-off-by: memadi <memadi@nvidia.com>
NVSkills rewrites these files without SPDX preambles, so exclude them until the generator preserves existing headers.

Upstream: NVIDIA/nvskills-ci#66
Signed-off-by: memadi <memadi@nvidia.com>
Signed-off-by: memadi <memadi@nvidia.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 1d9580f to ca63978 Compare August 22, 2026 01:13
@binaryaaron

Copy link
Copy Markdown
Collaborator

/nvskills-ci

Signed-off-by: memadi <memadi@nvidia.com>
Comment thread src/anonymizer/engine/detection/custom_columns.py
@memadi-nv memadi-nv changed the title feat: add a deny list for entities in Anonymizer feat: add excluded labels for entities in Anonymizer Aug 24, 2026
memadi-nv and others added 2 commits August 24, 2026 12:09
Signed-off-by: memadi <memadi@nvidia.com>
_filter_excluded_latent_entities only handled schema/dict/list shapes,
so a JSON-string payload (the shape every other schema's from_raw()
already anticipates via _parse_raw_wrapper) fell through unfiltered,
letting excluded latent labels leak into the rewrite prompt.

Also fixes a missing blank line that broke the MkDocs warning
admonition in docs/concepts/detection.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread src/anonymizer/engine/detection/detection_workflow.py
memadi-nv and others added 4 commits September 9, 2026 11:37
The strip+casefold idiom for comparing/filtering entity labels was
independently reimplemented in ~8 places across detection_workflow.py,
postprocess.py, and entity_coverage_judge.py, with at least one site
(entity_coverage_judge._effective_entity_labels) silently missing the
.strip() step. Consolidate into normalize_label()/normalize_labels()
in postprocess.py so the normalization rule only needs to change once.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…_labels

Previously, entity_labels being entirely canceled out by
excluded_entity_labels only logged a warning and silently produced a
config that detects nothing at runtime. Fail early instead: raise a
ValueError at config-construction time when excluded_entity_labels
entirely overlaps an explicit entity_labels, leaving an empty
effective detection set. Partial overlaps still only warn, and
entity_labels=None (the default set) is unaffected by this check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extend the ValueError-on-full-overlap documentation added in 52975e8
to the two other places that described this field: the field's own
description= (feeds the mkdocstrings API reference) and the
duplicate excluded_entity_labels section in choosing-a-strategy.md,
which never had the overlap warning at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Matches the phrasing already used in detection.md and
choosing-a-strategy.md for the same behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@binaryaaron

Copy link
Copy Markdown
Collaborator

/nvskills-ci

Signed-off-by: nvskills-svc-account <svc-nvskills-signing@nvidia.com>

@binaryaaron binaryaaron left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agent (review-pr): Blocking empty effective-label path remains

[P1] Reject exclusions that remove every default label

The explicit-overlap validator does not run when entity_labels=None. On the current head (f3cffb5), this remains valid:

Detect(
    entity_labels=None,
    excluded_entity_labels=list(DEFAULT_ENTITY_LABELS),
)

Both the standard and latent detection paths call _resolve_detection_labels(). It returns [], logs a warning, and the workflow injects that empty list into the detector configuration.

A local regression test confirms the missing guard:

Failed: DID NOT RAISE <class 'ValueError'>

With the bundled GLiNER server, this silently disables detection. Other detector endpoints may interpret an empty label list differently. Either outcome violates the intended fail-early behavior for an empty effective detection set.

Please treat this as blocking and ensure no workflow can send labels=[] to the detector.

Prompt for an implementation agent

Fix the remaining empty-effective-label defect in PR #243.

Requirements:

  • Reproduce the defect with entity_labels=None and excluded_entity_labels containing every value in DEFAULT_ENTITY_LABELS.
  • Reject that configuration with a clear ValueError before a detector workflow executes.
  • Ensure both callers of _resolve_detection_labels()—the standard detection specification and latent-entity detection—cannot inject labels=[].
  • Add a regression test for the default-label case. Update the existing warning test if the new invariant makes it obsolete.
  • Preserve partial default exclusions, label normalization, deduplication, and explicit-label overlap behavior.
  • Keep the invariant in the narrowest owning layer; do not duplicate the effective-label calculation across callers.
  • Use astnav refs _resolve_detection_labels to verify the complete caller set before editing.
  • Run the focused config and detection-workflow tests, Ruff format/lint, and git diff --check.

Report the changed files, the ownership decision, and the exact verification results.

…usion

The earlier full-overlap guard only checked an explicit entity_labels
against excluded_entity_labels. Reviewer feedback: entity_labels=None
falls back to DEFAULT_ENTITY_LABELS, and excluded_entity_labels could
still cancel that out entirely, leaving _resolve_detection_labels to
silently return [] at runtime with only a warning. Detect now raises
in that case too, and docs/SKILL.md are reworded to state the rule
once against "the effective allowlist" instead of covering the two
cases with separate sentences.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@lipikaramaswamy

Copy link
Copy Markdown
Collaborator

/nvskills-ci

@lipikaramaswamy lipikaramaswamy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit, but looks great overall!

```

!!! warning
`excluded_entity_labels` is always checked against the effective allowlist — `entity_labels` if set, otherwise `DEFAULT_ENTITY_LABELS`. A partial overlap just drops the shared labels and logs a warning. If the overlap is total, leaving an empty effective detection set, `Detect` raises a `ValueError` at config time instead of silently detecting nothing.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small mismatch: this says a partial overlap always logs a warning, but the validator only warns when entity_labels is explicitly set. Partial exclusions from DEFAULT_ENTITY_LABELS are accepted without a warning, which is also the common use case. Could we clarify that distinction here and in the matching warning in docs/concepts/choosing-a-strategy.md?

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.

4 participants