Skip to content

feat: add option to use local classification#615

Closed
mckornfield wants to merge 3 commits into
mainfrom
local-classification/mck
Closed

feat: add option to use local classification#615
mckornfield wants to merge 3 commits into
mainfrom
local-classification/mck

Conversation

@mckornfield

@mckornfield mckornfield commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Pre-Review Checklist

Ensure that the following pass:

  • mise run format && mise run check or via prek validation.
  • mise run test passes locally
  • mise run test:e2e passes locally
  • mise run test:ci-container passes locally (recommended)
  • GPU CI status check passes -- comment /sync on this PR to trigger a run (auto-triggers on ready-for-review)

Pre-Merge Checklist

  • New or updated tests for any fix or new behavior
  • Updated documentation for new features and behaviors, including docstrings for API docs.

Other Notes

Summary by CodeRabbit

  • New Features
    • Added an optional local, in-process PII column classifier powered by a Hugging Face causal LM, configurable via replace_pii settings, including a new default local model.
  • Documentation
    • Updated configuration/environment/running/troubleshooting guidance to reflect the new backend options, required secrets only for the API backend, and improved offline/cache instructions.
  • Bug Fixes
    • Improved pre-flight validation for local model/cache issues and strengthened cleanup/failure handling during classification.
  • Tests
    • Added new golden fixtures and extensive unit/benchmark coverage for column classification and configuration validation.

Signed-off-by: mkornfield <mkornfield@nvidia.com>
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a local_hf backend for PII column classification that runs an in-process Hugging Face causal LM (HuggingFaceTB/SmolLM3-3B by default), selectable via replace_pii.globals.classify.backend. It also ships a golden-fixture benchmark harness, extended preflight checks for the new backend's model cache, proper close() lifecycle on both classifier backends, and comprehensive documentation updates.

  • New ColumnClassifierHF implements the ModelHost contract with lazy initialize(), chat-template–aware prompt encoding, and teardown() freeing GPU memory; get_column_classifier dispatches on backend.
  • ColumnClassifier refactored to a template-method pattern so prompt construction, entity filtering, and validation-error callbacks are shared; ColumnClassifierLLM and ColumnClassifierNoop adapted accordingly.
  • Preflight extracts a shared _check_hf_model_reference helper (with _HFModelCheckSpec config objects) reused by both the training-model check and the new classifier model check.

Confidence Score: 4/5

Safe to merge after addressing the two open issues from prior review rounds (wrong kwarg dtype vs torch_dtype in AutoModelForCausalLM and the unreachable entity labels in the gold fixture); the new code paths are well-tested and the resource lifecycle is correct.

The architecture is solid — the template-method refactor is clean, the close() lifecycle is correctly wired into the finally block, and the preflight extension follows the existing pattern faithfully. The two previously-identified issues (wrong torch_dtype kwarg causing bfloat16 to be silently ignored on CUDA, and credit_debit_card/state fixture labels that are not in DEFAULT_ENTITIES) remain unresolved and will cause real problems: GPU memory waste for larger models and a failing schema assertion when torch is available.

src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py (ColumnClassifierHF.initialize — dtype kwarg) and tests/test_data/pii/column_classification_gold.json (credit_debit_card / state labels vs DEFAULT_ENTITIES).

Important Files Changed

Filename Overview
src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py Refactored ColumnClassifier to template-method pattern, added ColumnClassifierHF for local Hugging Face inference; classify_columns public API retains Optional[OpenAI] typing with None guard
src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py get_column_classifier now dispatches on backend, passes config for remediation messaging; classifier.close() properly called in finally block; exc_info extended to local_hf failures
src/nemo_safe_synthesizer/config/replace_pii.py Added backend (api/local_hf) and model fields to ClassifyConfig; model_validator warns when model is set for the api backend
src/nemo_safe_synthesizer/preflight/checks/environment.py Extracted shared _check_hf_model_reference helper with spec objects; InferenceModelCheck now validates local_hf model path/cache; HFModelAvailabilityCheck simplified to reuse shared helper
src/nemo_safe_synthesizer/llm/model_host.py New abstract ModelHost[ModelT, TokenizerT] lifecycle contract for components owning a local model
tests/pii_replacer/test_column_classification_eval.py New benchmark/golden-fixture test module; fcntl is correctly lazy-imported with ImportError guard inside _classification_benchmark_lock(); gold fixture schema assertion will fail for credit_card_transactions case due to unreachable entity labels
tests/test_data/pii/column_classification_gold.json New gold fixture; credit_card_transactions case uses credit_debit_card and state labels absent from DEFAULT_ENTITIES, causing schema test assertion failure and permanently skewed benchmark accuracy
tests/pii_replacer/test_detect.py Added comprehensive unit tests for ColumnClassifierHF including chat-template path, plain-prompt fallback, and _load_target logic
tests/pii_replacer/test_nemo_pii.py Added TestGetColumnClassifier tests for both backends and close() behavior; added assertion that close() is called after classify_df
tests/preflight/test_preflight.py Added preflight tests for local_hf backend: API-key warnings are suppressed, default model resolved, missing path errors correctly emitted, and incomplete cache generates warning with hf_token_missing

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant NemoPII
    participant get_column_classifier
    participant ColumnClassifierLLM
    participant ColumnClassifierHF
    participant OpenAI
    participant Transformers

    User->>NemoPII: classify_df(df)
    NemoPII->>get_column_classifier: (config)
    alt "backend == local_hf"
        get_column_classifier->>ColumnClassifierHF: __init__(model, num_samples)
        get_column_classifier-->>NemoPII: ColumnClassifierHF
        NemoPII->>ColumnClassifierHF: detect_types(df, entities)
        ColumnClassifierHF->>ColumnClassifierHF: initialize() [lazy load]
        ColumnClassifierHF->>Transformers: AutoTokenizer.from_pretrained(model)
        ColumnClassifierHF->>Transformers: AutoModelForCausalLM.from_pretrained(model)
        ColumnClassifierHF->>ColumnClassifierHF: _encode_prompt()
        ColumnClassifierHF->>ColumnClassifierHF: model.generate()
        ColumnClassifierHF-->>NemoPII: "{col: entity}"
        NemoPII->>ColumnClassifierHF: close() / teardown()
    else "backend == api (default)"
        get_column_classifier->>ColumnClassifierLLM: __init__()
        get_column_classifier->>OpenAI: OpenAI(base_url, api_key)
        get_column_classifier-->>NemoPII: ColumnClassifierLLM
        NemoPII->>ColumnClassifierLLM: detect_types(df, entities)
        ColumnClassifierLLM->>OpenAI: chat.completions.create()
        OpenAI-->>ColumnClassifierLLM: response
        ColumnClassifierLLM-->>NemoPII: "{col: entity}"
        NemoPII->>ColumnClassifierLLM: close()
        ColumnClassifierLLM->>OpenAI: close()
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant NemoPII
    participant get_column_classifier
    participant ColumnClassifierLLM
    participant ColumnClassifierHF
    participant OpenAI
    participant Transformers

    User->>NemoPII: classify_df(df)
    NemoPII->>get_column_classifier: (config)
    alt "backend == local_hf"
        get_column_classifier->>ColumnClassifierHF: __init__(model, num_samples)
        get_column_classifier-->>NemoPII: ColumnClassifierHF
        NemoPII->>ColumnClassifierHF: detect_types(df, entities)
        ColumnClassifierHF->>ColumnClassifierHF: initialize() [lazy load]
        ColumnClassifierHF->>Transformers: AutoTokenizer.from_pretrained(model)
        ColumnClassifierHF->>Transformers: AutoModelForCausalLM.from_pretrained(model)
        ColumnClassifierHF->>ColumnClassifierHF: _encode_prompt()
        ColumnClassifierHF->>ColumnClassifierHF: model.generate()
        ColumnClassifierHF-->>NemoPII: "{col: entity}"
        NemoPII->>ColumnClassifierHF: close() / teardown()
    else "backend == api (default)"
        get_column_classifier->>ColumnClassifierLLM: __init__()
        get_column_classifier->>OpenAI: OpenAI(base_url, api_key)
        get_column_classifier-->>NemoPII: ColumnClassifierLLM
        NemoPII->>ColumnClassifierLLM: detect_types(df, entities)
        ColumnClassifierLLM->>OpenAI: chat.completions.create()
        OpenAI-->>ColumnClassifierLLM: response
        ColumnClassifierLLM-->>NemoPII: "{col: entity}"
        NemoPII->>ColumnClassifierLLM: close()
        ColumnClassifierLLM->>OpenAI: close()
    end
Loading

Reviews (4): Last reviewed commit: "chore: typecheck fixes" | Re-trigger Greptile

Comment on lines +191 to +194
model: str | None = Field(
default=None,
description="Model name or local path for column classification. For the local_hf backend, defaults to HuggingFaceTB/SmolLM3-3B.",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 model field is silently ignored for the api backend

When backend="api", get_column_classifier never reads classify_config.model; the API model comes from the NSS_INFERENCE_MODEL environment variable. A user who sets model="meta-llama/Llama-3-70b-instruct" while keeping the default backend="api" will see their value silently discarded. A model_validator that emits a warning (or raises a ParameterError) when model is non-None and backend != "local_hf" would prevent silent misconfiguration.

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.76068% with 95 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ts/pii_replacer/test_column_classification_eval.py 74.01% 53 Missing ⚠️
...afe_synthesizer/pii_replacer/data_editor/detect.py 68.93% 41 Missing ⚠️
src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py 92.30% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@kendrickb-nvidia kendrickb-nvidia 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.

I'm not opposed to adding this now to existing PII replacement, but we're also going to be redoing PII replacement in July, so could be easier to just support local mode then? But this is already written.

CC @nina-xu on the column_classification_gold.json (not sure where @mckornfield sourced this from) and using this or similar in evaluation of new pii replacement.

@mckornfield

Copy link
Copy Markdown
Collaborator Author

will DM about it, mostly was just trying to pick up old, interesting issues. This one seemed like a decent one, but I know the PII replace is going to go elsewhere than this current state haha

@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-assisted: Review of the local Hugging Face classification path.

return
collector.warning(
"classify_hf_model_cache_incomplete",
f"{message} Model loading will contact Hugging Face unless the full model snapshot is pre-downloaded.",

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-assisted: This says the loader can fetch the missing files, but _load() may pass the incomplete cache directory instead of the Hugging Face model ID. Transformers treats that directory as local, so it cannot fill in missing config, tokenizer, or shard files. Could we use the model ID when online, or make an incomplete cache an error?

tokenizer = self._tokenizer
model = self._model

if hasattr(tokenizer, "apply_chat_template"):

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-assisted: hasattr() is true for standard Transformers tokenizers even when they do not have a chat template. apply_chat_template() then raises, so the fallback below never runs. Could we check for an actual template or catch that specific error and use the plain prompt?


num_samples: int | None = Field(description="Number of column values to sample for classification.", default=3)

backend: Literal["api", "local_hf"] = Field(

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-assisted: Could we document these new settings? The current guides only cover API classification and still say an inference key is required. It would help to include a local_hf example and mention the default 3B model download, offline/cache behavior, and local model paths.

def _check_column_classifier_hf_model(model_name: str, collector: IssueCollector) -> None:
"""Validate the Hugging Face model used by local PII column classification."""
if not model_name:
collector.error("classify_model_ref_empty", "`replace_pii.globals.classify.model` must not be empty.")

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-assisted: These new classify_* codes are missing from the troubleshooting table. This path also emits shared HF codes under env.inference, while the docs list them under env.hf_model_availability. Could we update the table so the documented check names match what users actually receive?

def _column_accuracy(predicted: dict[str, str | None], expected: dict[str, str]) -> float:
if not expected:
return 1.0
correct = sum(_normalize_label(predicted.get(column)) == label for column, label in expected.items())

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-assisted: A missing prediction is counted as none here. With the current gold data, an empty result gets 0.714 accuracy even though it finds none of the PII columns. Could we reject missing columns and add a positive-recall check to the benchmark?

)


def _check_column_classifier_hf_model(model_name: str, collector: IssueCollector) -> None:

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-assisted: This repeats most of the model availability logic from HFModelAvailabilityCheck below and calls some of its private helpers. Could we pull the shared cache, offline, token, and remote-code checks into one helper and use it from both places?

@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): Requesting changes to the local classification backend

Thanks for putting this together. The local option is useful, but I think the backend boundary needs another pass before this merges. We are working through a similar local/remote split in #575: the base owns the shared flow, while each backend owns its typed setup, model call, and idempotent cleanup.

Using the same shape here would address the resource ownership and typing concerns below without making this PR depend on #575. The existing inline comments also identify correctness gaps in cache loading, chat-template fallback, and benchmark scoring that should be resolved before merge.

or self.pii_replacer_config.globals.classify.backend == "local_hf",
)
finally:
column_classifier.close()

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): Close the API client during cleanup

One cleanup issue here: get_column_classifier() creates an OpenAI client, but ColumnClassifierLLM inherits the no-op close() method. This finally block therefore releases the local HF model but leaves the API client's HTTP transport open. Can we have the API backend close its client too? This is the same lifecycle we are using for RemoteBackend in #575.

)


class ColumnClassifierHF(ColumnClassifier):

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): Follow the backend split from #575

This looks similar to the local and remote backend split we are working through in #575. There, the base class owns the shared generation flow, while each backend handles its own model call, setup, and cleanup.

I think the same split would work well here. ColumnClassifier.detect_types() could handle sampling, prompt construction, parsing, and filtering. The API and Hugging Face implementations would only need to provide the model call and their resource lifecycle. That would also give _model and _tokenizer concrete types instead of Any.

This does not need to depend on #575. Keeping the two designs close would just make them easier to bring together later.

@binaryaaron

Copy link
Copy Markdown
Collaborator

Following up on the ownership discussion here: I rebased #575 onto main and added a small typed ModelHost[ModelT, TokenizerT] boundary for locally owned model resources. VllmBackend now implements it, while the remote backend stays separate because it owns an HTTP client rather than a local model.

I think that gives this PR a cleaner shape for ColumnClassifierHF: it can implement ModelHost[PreTrainedModel, PreTrainedTokenizerBase], keep detect_types() focused on classification, and make initialization/teardown ownership explicit without the current Any model and tokenizer fields. The updated pattern is in #575.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 1222f401-6f06-4fb2-a69b-d7d49bf91390

📥 Commits

Reviewing files that changed from the base of the PR and between 6dcc5cf and 89efddc.

📒 Files selected for processing (2)
  • tests/pii_replacer/test_column_classification_eval.py
  • tests/pii_replacer/test_detect.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/pii_replacer/test_column_classification_eval.py
  • tests/pii_replacer/test_detect.py
📜 Recent review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: Unit Tests (3.13)
  • GitHub Check: Unit Tests (3.11)
  • GitHub Check: Unit Tests (3.12)
  • GitHub Check: Greptile Review

Walkthrough

Adds a local_hf backend for PII column classification, including configuration, local model inference, preflight validation, classifier selection, documentation, and tests. It also adds a gold dataset and optional benchmark runner.

Changes

Local HF classification backend

Layer / File(s) Summary
Classification configuration and model contract
src/nemo_safe_synthesizer/config/replace_pii.py, src/nemo_safe_synthesizer/defaults.py, src/nemo_safe_synthesizer/llm/model_host.py, tests/config/test_nss_config.py
Adds backend/model configuration, the default local model, lifecycle abstractions, and validator tests.
Shared classifier flow and local HF implementation
src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py, tests/pii_replacer/test_detect.py
Refactors shared detection and entity filtering, and adds lazy Hugging Face inference with chat-template fallback, generation, offline handling, and teardown.
Classifier selection and cleanup
src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py, tests/pii_replacer/test_nemo_pii.py
Selects API or local Hugging Face classifiers from configuration, propagates sampling settings, and closes classifiers after classification.
Hugging Face preflight validation
src/nemo_safe_synthesizer/preflight/checks/environment.py, tests/preflight/test_preflight.py
Validates local model references, paths, caches, offline behavior, tokens, and remote-code requirements for inference and training models.
Golden classification evaluation
tests/test_data/pii/column_classification_gold.json, tests/pii_replacer/test_column_classification_eval.py
Adds gold cases, classification metrics, configurable benchmark execution, serialized reports, summary upserts, and a gated live benchmark.
Backend and validation documentation
README.md, docs/user-guide/*.md, docs/tutorials/safe-synthesizer-101.ipynb
Documents backend configuration, API variables, local model sources and cache behavior, fallback behavior, and preflight validation codes.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested labels: feature, docs, test

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The notebook kernel/version metadata update is unrelated to local classification support and appears outside the issue scope. Remove the notebook metadata-only change or split it into a separate housekeeping PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a local classification option.
Linked Issues check ✅ Passed The PR implements a local SmolLM-based column classification backend, matching issue #121's request for local classification support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch local-classification/mck

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the feature New feature or request label Jul 8, 2026
Comment on lines +591 to +593
if torch.cuda.is_available():
load_kwargs["device_map"] = "auto"
load_kwargs["dtype"] = torch.bfloat16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Wrong kwarg name causes float32 loading on GPU

AutoModelForCausalLM.from_pretrained accepts torch_dtype, not dtype. Passing dtype=torch.bfloat16 lands in **kwargs, is forwarded as an unrecognized argument, and is silently ignored, so the model loads in float32 on CUDA hardware. This doubles GPU memory consumption and will cause OOM for larger models.

Suggested change
if torch.cuda.is_available():
load_kwargs["device_map"] = "auto"
load_kwargs["dtype"] = torch.bfloat16
if torch.cuda.is_available():
load_kwargs["device_map"] = "auto"
load_kwargs["torch_dtype"] = torch.bfloat16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py (1)

445-459: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Duplicate init-guard raises two different exception types for the same condition.

detect_types (line 447-448, pre-existing) raises bare Exception when self._llm is None; the new _classify_prompt (line 453-454) raises RuntimeError for the identical precondition. Since detect_types calls super().detect_types() which calls self._classify_prompt(), the check is now duplicated with inconsistent exception types, and neither uses the project's error hierarchy (InternalError/UserError) required for user-facing failures. Callers catching a specific exception type will behave differently depending on which code path raises first.

🐛 Proposed fix
     def detect_types(self, df: pd.DataFrame, entities: set[str]) -> dict[str, Optional[str]]:
         """Sample column data and call the inference API to classify columns into entity types."""
-        if self._llm is None:
-            raise Exception("InferenceAPI classifier not initialized. Use get_classifier() method.")
-
         return super().detect_types(df, entities)

     def _classify_prompt(self, formatted_prompt: str) -> str:
         if self._llm is None:
-            raise RuntimeError("InferenceAPI classifier not initialized. Use get_classifier() method.")
+            raise InternalError("InferenceAPI classifier not initialized. Use get_classifier() method.")
         return _classify_prompt_with_openai(

As per path instructions, "user-facing failures should raise project errors from nemo_safe_synthesizer.errors with precise messages."

Source: Path instructions

🧹 Nitpick comments (4)
src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py (3)

479-647: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing docstrings on nontrivial ColumnClassifierHF methods.

initialize() (cache/offline/device handling), _encode_prompt() (chat-template vs. plain-prompt fallback), and teardown() have non-obvious logic but no docstrings, and the class docstring omits Args: for the constructor parameters (model_name_or_path, num_samples). Per STYLE_GUIDE.md, a docstring is mandatory for functions with non-obvious logic, and constructor Args: belong in the class docstring so IDEs surface them on hover.

Source: Path instructions


608-611: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

_load alias adds an unnecessary second entry point.

This is net-new code in this PR (no external ColumnClassifierHF consumers predate it), so the "backward-compatible alias for older tests and callers" is really just carrying an earlier draft's method name forward. Consider updating the referencing tests to call initialize() directly and dropping this alias to avoid two names for the same operation.


591-593: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Hard-coded torch.bfloat16 may not suit all GPUs the local backend targets.

device_map="auto" + dtype=torch.bfloat16 is applied whenever any CUDA device is available, regardless of GPU architecture. Pre-Ampere GPUs (compute capability < 8.0) don't have native bf16 support and can hit degraded performance or backend errors depending on the op. Since this backend is meant to lower the adoption barrier (per issue #121) it will likely run on a wider variety of hardware than the training path. Consider gating on torch.cuda.is_bf16_supported() and falling back to torch.float16/"auto" otherwise.

tests/pii_replacer/test_detect.py (1)

467-492: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

LGTM on core coverage; consider adding a "complete cache" branch test.

Both _load_target tests exercise the incomplete-cache branch (online → repo id, offline → snapshot path). The complementary branch — a complete cache returning model_ref.target() — isn't covered here, though it may be covered elsewhere.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ae0c23be-f2f2-4a89-83de-2dc73cb3f5b7

📥 Commits

Reviewing files that changed from the base of the PR and between a9848f7 and e22f7d1.

📒 Files selected for processing (19)
  • README.md
  • docs/tutorials/safe-synthesizer-101.ipynb
  • docs/user-guide/configuration.md
  • docs/user-guide/docker.md
  • docs/user-guide/environment.md
  • docs/user-guide/evaluating-data.md
  • docs/user-guide/running.md
  • docs/user-guide/troubleshooting.md
  • src/nemo_safe_synthesizer/config/replace_pii.py
  • src/nemo_safe_synthesizer/defaults.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
  • src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
  • tests/config/test_nss_config.py
  • tests/pii_replacer/test_column_classification_eval.py
  • tests/pii_replacer/test_detect.py
  • tests/pii_replacer/test_nemo_pii.py
  • tests/preflight/test_preflight.py
  • tests/test_data/pii/column_classification_gold.json
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: Unit Tests (3.13)
  • GitHub Check: Unit Tests (3.11)
  • GitHub Check: Unit Tests (3.12)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (18)
**/*.{md,markdown,py}

📄 CodeRabbit inference engine (.cursor/rules/agent-markdown-style.mdc)

**/*.{md,markdown,py}: Avoid decorative bold (**text**) in list items, body text, and docstrings; use structural cues (headers, list markers, colons, backticks) for emphasis instead
Use backticks for code identifiers, paths, and CLI commands in markdown and docstrings

Files:

  • src/nemo_safe_synthesizer/defaults.py
  • tests/config/test_nss_config.py
  • tests/preflight/test_preflight.py
  • docs/user-guide/troubleshooting.md
  • docs/user-guide/environment.md
  • tests/pii_replacer/test_nemo_pii.py
  • README.md
  • docs/user-guide/configuration.md
  • docs/user-guide/evaluating-data.md
  • docs/user-guide/docker.md
  • src/nemo_safe_synthesizer/config/replace_pii.py
  • tests/pii_replacer/test_detect.py
  • docs/user-guide/running.md
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
  • tests/pii_replacer/test_column_classification_eval.py
  • src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Place durable implementation guidance in function and class docstrings for public contracts and source comments for local invariants
Target Python 3.11–3.13 with modern syntax (X | Y, list[str], Self). Python 3.14+ is not supported

**/*.py: Source code must remain Python 3.11 syntax-compatible; do not use Python 3.12-only syntax such as PEP 695 type statements or bracketed generic class/function parameters in shared package code
Use ruff for formatting and linting via mise run format and mise run check tasks; run formatting before committing
Use ty for type checking via mise run check task; ensure type hints are present and valid
New features must include tests; bug fixes must include regression tests

**/*.py: Use BaseSettings for env/CLI settings, prefer AliasChoices for per-field dual naming, and use env_prefix only for simple settings classes with a shared prefix.
Use Field(description=...) as the canonical field docstring for Pydantic models, and always include it.
Use assignment-style Field(default=..., description="...") as the default for model fields; prefer it over Annotated unless extra metadata is needed.
Use Annotated only when the field carries additional metadata beyond Field() (for example validators, reusable constrained aliases, nested-type constraints, or discriminated unions).
When Annotated is used, place defaults as bare assignments (= value), except default_factory, which should still use assignment-style Field(default_factory=...).
For immutable value objects and validators, prefer @dataclass(frozen=True); use mutable dataclasses only for builders, accumulators, and pipeline state.
Use field(default_factory=list) for mutable defaults; never use = [].
Use StrEnum for string-valued enums used in configs or serialization; use plain Enum for internal-only named constants.
Use observability.get_logger(__name__) for logging; do not call logging.getLogger() or structlog.get_logger() direc...

Files:

  • src/nemo_safe_synthesizer/defaults.py
  • tests/config/test_nss_config.py
  • tests/preflight/test_preflight.py
  • tests/pii_replacer/test_nemo_pii.py
  • src/nemo_safe_synthesizer/config/replace_pii.py
  • tests/pii_replacer/test_detect.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
  • tests/pii_replacer/test_column_classification_eval.py
  • src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
**/*.{py,sh,yaml,yml,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

All source files (.py, .sh, .yaml, .yml, .md) require SPDX copyright headers; mise run format adds them automatically

Files:

  • src/nemo_safe_synthesizer/defaults.py
  • tests/config/test_nss_config.py
  • tests/preflight/test_preflight.py
  • docs/user-guide/troubleshooting.md
  • docs/user-guide/environment.md
  • tests/pii_replacer/test_nemo_pii.py
  • README.md
  • docs/user-guide/configuration.md
  • docs/user-guide/evaluating-data.md
  • docs/user-guide/docker.md
  • src/nemo_safe_synthesizer/config/replace_pii.py
  • tests/pii_replacer/test_detect.py
  • docs/user-guide/running.md
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
  • tests/pii_replacer/test_column_classification_eval.py
  • src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
src/nemo_safe_synthesizer/**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

API reference pages are auto-generated from Python docstrings using Google-style format; write docstrings in src/nemo_safe_synthesizer/ and they will appear in the reference/

Files:

  • src/nemo_safe_synthesizer/defaults.py
  • src/nemo_safe_synthesizer/config/replace_pii.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
  • src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
src/**/*.py

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

src/**/*.py: Use relative imports inside src/, for example from ..observability import get_logger.
Every directory under src/ that contains Python files must include an __init__.py file.

Files:

  • src/nemo_safe_synthesizer/defaults.py
  • src/nemo_safe_synthesizer/config/replace_pii.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
  • src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py

⚙️ CodeRabbit configuration file

Review library code against STYLE_GUIDE.md. Focus on behavior, API contracts, error handling, resource cleanup, typing, logging, and user-facing failures. Public APIs and nontrivial functions need Google-style docstrings.

Files:

  • src/nemo_safe_synthesizer/defaults.py
  • src/nemo_safe_synthesizer/config/replace_pii.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
  • src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
**/*

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

**/*: Every source file must include the required SPDX copyright and license header; use HTML comments for Markdown, hash comments for .py, .sh, .yaml, and .yml, and hash-comment headers inside YAML frontmatter for Markdown files with frontmatter.
Ensure files end with a newline and have no trailing whitespace; use a single space between sentences.

Files:

  • src/nemo_safe_synthesizer/defaults.py
  • tests/test_data/pii/column_classification_gold.json
  • tests/config/test_nss_config.py
  • docs/tutorials/safe-synthesizer-101.ipynb
  • tests/preflight/test_preflight.py
  • docs/user-guide/troubleshooting.md
  • docs/user-guide/environment.md
  • tests/pii_replacer/test_nemo_pii.py
  • README.md
  • docs/user-guide/configuration.md
  • docs/user-guide/evaluating-data.md
  • docs/user-guide/docker.md
  • src/nemo_safe_synthesizer/config/replace_pii.py
  • tests/pii_replacer/test_detect.py
  • docs/user-guide/running.md
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
  • tests/pii_replacer/test_column_classification_eval.py
  • src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py

⚙️ CodeRabbit configuration file

**/*: Review as a senior maintainer for NeMo Safe Synthesizer. Prioritize issues that can change behavior, break user workflows, weaken privacy guarantees, hide failures, make tests unreliable, or create maintenance risk. Avoid generic style commentary unless it points to a concrete project convention that automated tools will not catch.
Comment only when the finding is actionable and tied to changed code. For each finding, state the impact, the condition that triggers it, and the smallest practical fix. Prefer one precise comment over broad advice. Do not ask for refactors outside the PR scope unless the changed code creates the problem.
Review type guidance: - Potential issue: use for correctness bugs, data loss, privacy leaks,
security risks, broken public APIs, invalid config behavior, missing
validation, hidden failures, nondeterministic tests, or CI breakage.

  • Refactor suggestion: use for local maintainability problems introduced
    by the diff when they have clear future cost, such as duplicated setup,
    unclear boundaries, over-mocking, avoidable complexity, or opaque test
    helpers.
  • Nitpick: avoid in chill mode. Do not emit formatting, import-order,
    wording, or style-only comments unless automated tools cannot catch the
    issue and it affects maintainability.

Severity guidance: - Critical: security/privacy leaks, data loss, training/test/holdout
contamination, or broken release/package/core pipeline execution.

  • Major: incorrect generation/training/evaluation behavior, broken
    CLI/SDK public API, invalid config defaults or validators, or GPU/vLLM
    cleanup and process-isolation bugs likely to fail CI or production
    runs.
  • Minor: localized bugs, missing focused tests for changed behavior, or
    bad test patterns that weaken regression coverage.
  • Trivial: small cleanup with no behavior impact. Usually suppress in
    chill mode.
  • Info: context only. Avoid unless it helps reviewers understand risk.
    Safe-Synthesizer-specific review focus: - Data ...

Files:

  • src/nemo_safe_synthesizer/defaults.py
  • tests/test_data/pii/column_classification_gold.json
  • tests/config/test_nss_config.py
  • docs/tutorials/safe-synthesizer-101.ipynb
  • tests/preflight/test_preflight.py
  • docs/user-guide/troubleshooting.md
  • docs/user-guide/environment.md
  • tests/pii_replacer/test_nemo_pii.py
  • README.md
  • docs/user-guide/configuration.md
  • docs/user-guide/evaluating-data.md
  • docs/user-guide/docker.md
  • src/nemo_safe_synthesizer/config/replace_pii.py
  • tests/pii_replacer/test_detect.py
  • docs/user-guide/running.md
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
  • tests/pii_replacer/test_column_classification_eval.py
  • src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
**

⚙️ CodeRabbit configuration file

**:

AGENTS.md

Guide for AI agents (Cursor, Windsurf, Claude Code, etc.) working in the Safe-Synthesizer repo.

This project loads local developer preferences from @AGENTS.local.md. You MUST read this file if it exists and give its instructions top priority.

Skills

Repo-specific skills live in .agents/skills/; see .agents/README.md for the catalog. Read a skill when the task matches its scope instead of copying workflow details into this file.

Durable implementation guidance belongs with the code it describes: function and class docstrings for public contracts and source comments for local invariants. Test-suite guidance belongs in tests/TESTING.md.

Repo Conventions

See STYLE_GUIDE.md for detailed code style conventions (Python, markdown, Dockerfiles, shell scripts, testing, config files, docstrings).

Use uv for everything -- never pip or raw python. Python 3.11–3.13 with modern syntax (X | Y, list[str], Self). Python 3.14+ is not supported.

Common commands: mise run test (unit tests), mise run format (auto-fix formatting + lint + copyright), mise run check (read-only local quality checks), mise run validate (pre-PR quality, lock, and CI unit checks), mise run typecheck (ty only). Always use mise tasks or the wrapper scripts in tools/ instead of running ruff or ty directly. Use uv run for Python execution. When in doubt, inspect mise tasks and pytest --markers.

The canonical uv sync command for a full GPU/dev environment is:

uv sync --frozen --extra cu129 --extra engine --group dev

Bare uv sync --frozen (without extras) installs an incomplete environment -- ty, import checks, and GPU tests will fail.

Feature branches off main. Branch names often include an issue number prefix (e.g., <author>/123-short-name).

Do ...

Files:

  • src/nemo_safe_synthesizer/defaults.py
  • tests/test_data/pii/column_classification_gold.json
  • tests/config/test_nss_config.py
  • docs/tutorials/safe-synthesizer-101.ipynb
  • tests/preflight/test_preflight.py
  • docs/user-guide/troubleshooting.md
  • docs/user-guide/environment.md
  • tests/pii_replacer/test_nemo_pii.py
  • README.md
  • docs/user-guide/configuration.md
  • docs/user-guide/evaluating-data.md
  • docs/user-guide/docker.md
  • src/nemo_safe_synthesizer/config/replace_pii.py
  • tests/pii_replacer/test_detect.py
  • docs/user-guide/running.md
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
  • tests/pii_replacer/test_column_classification_eval.py
  • src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
tests/**

📄 CodeRabbit inference engine (.cursor/rules/repo-navigation.mdc)

tests/**: Mirror src/ directory structure in tests/ directory for test organization
Auto-mark tests by directory: tests/e2e/e2e, tests/smoke/smoke, otherwise default to unit

Mirror source code directory structure in tests directory (e.g., tests/training/, tests/generation/ parallel to source structure)

Files:

  • tests/test_data/pii/column_classification_gold.json
  • tests/config/test_nss_config.py
  • tests/preflight/test_preflight.py
  • tests/pii_replacer/test_nemo_pii.py
  • tests/pii_replacer/test_detect.py
  • tests/pii_replacer/test_column_classification_eval.py

⚙️ CodeRabbit configuration file

tests/**:

Testing Guide

Comprehensive testing reference for Safe-Synthesizer developers. Covers commands, markers, test data, fixtures, and gotchas.

Read First

  1. tests/conftest.py -- auto-marking, load_test_dataset/load_test_dataframe, fixture_mock_processor pattern
  2. pytest.ini -- markers, asyncio, timeout
  3. tests/evaluation/conftest.py -- most complex: Faker-based make_df, nullable dtype conversion
  4. tests/generation/conftest.py -- JSONL/schema fixtures, fixture_valid_iris_dataset_jsonl_and_schema

Running Tests

All mise test tasks, grouped by scope:

mise run test                              # Unit (excludes slow, e2e, and smoke)
mise run test:unit-slow                    # Unit tests including slow (excludes e2e and smoke)
mise run test:smoke                        # CPU smoke tests (~few min, no GPU required)
mise run test:smoke:gpu                    # All staged GPU smoke tests (requires CUDA)
mise run test:smoke:gpu:train-only
mise run test:smoke:gpu:generation
mise run test:smoke:gpu:resume
mise run test:smoke:gpu:structured-generation
mise run test:smoke:gpu:timeseries
mise run test:smoke:gpu:smollm2
mise run test:e2e                          # All e2e (requires CUDA) -- runs default + dp
mise run test:e2e:default                  # e2e default (no-DP) tests only
mise run test:e2e:dp                       # e2e DP tests only
mise run test:ci                           # CI unit tests with coverage (excludes slow, e2e, gpu, smoke)
mise run test:ci-slow                      # CI slow tests with coverage
mise run test:ci-container                 # CI tests in a Linux container (Docker/Podman)

Run a single test:

uv run --frozen pytest tests/path/test_file.py::test_name -vvs -n0

Test runner: uv run --frozen pytest -n auto --dist loadscope -vv...

Files:

  • tests/test_data/pii/column_classification_gold.json
  • tests/config/test_nss_config.py
  • tests/preflight/test_preflight.py
  • tests/pii_replacer/test_nemo_pii.py
  • tests/pii_replacer/test_detect.py
  • tests/pii_replacer/test_column_classification_eval.py
tests/{test_data,stub_datasets,stub_tokenizer}/**

📄 CodeRabbit inference engine (.cursor/rules/repo-navigation.mdc)

Store stub data and test data in tests/test_data/, tests/stub_datasets/, and tests/stub_tokenizer/

Files:

  • tests/test_data/pii/column_classification_gold.json
**/test_*.py

📄 CodeRabbit inference engine (AGENTS.md)

Use the unit marker instead of the deprecated unit_test marker for test identification

Files:

  • tests/config/test_nss_config.py
  • tests/preflight/test_preflight.py
  • tests/pii_replacer/test_nemo_pii.py
  • tests/pii_replacer/test_detect.py
  • tests/pii_replacer/test_column_classification_eval.py
tests/**/*.py

📄 CodeRabbit inference engine (tests/TESTING.md)

tests/**/*.py: Auto-mark tests based on file path: tests under /e2e/ get e2e marker, tests under /smoke/ get smoke marker, all others get unit marker (only if no category marker already present)
Every test should have exactly one category marker: unit, smoke, or e2e
Use pytest.mark.requires_gpu modifier on tests that need CUDA hardware
Use pytest.mark.vllm on tests using vLLM generation backend and ensure each vLLM test file runs in its own process for GPU memory isolation
Use pytest.mark.slow on long-running tests
Use pytest.mark.smollm2 for SmolLM2 Hub download tests to enable process isolation
Use pytest.mark.noautouse to skip autouse fixtures for specific tests
Use load_test_dataset(filename) helper to load test datasets from tests/stub_datasets/ as HuggingFace Dataset objects
Use load_test_dataframe(filename) helper to load test data files from tests/stub_datasets/ as pandas DataFrames
Convert pandas columns to nullable dtypes (pd.Int64Dtype(), pd.BooleanDtype()) before assigning np.nan values
Use fake.seed_instance(seed) and random.seed(seed) together for Faker-based test data reproducibility
When sharing methods across multiple test files, define them in conftest.py and import them using relative imports (e.g., from .conftest import train_with_sdk); note that importing from other test files like tests/cli/helpers.py does not work
Use fixture_mock_processor or fixture_mock_processor_without_valid_records for mocking ParsedResponse objects with valid_records, invalid_records, errors, and prompt_number fields
Use pytest.importorskip to gate tests on optional dependencies that require specific extras (e.g., sentence_transformers, vllm)
Run vLLM tests with separate pytest invocations (one per file) using -n 0 (single process) for GPU memory isolation, or use staged mise tasks for CI visibility
Print statements are allowed in tests (ruff T201 is suppressed for tests/ directory) and should...

Files:

  • tests/config/test_nss_config.py
  • tests/preflight/test_preflight.py
  • tests/pii_replacer/test_nemo_pii.py
  • tests/pii_replacer/test_detect.py
  • tests/pii_replacer/test_column_classification_eval.py

⚙️ CodeRabbit configuration file

Review tests against tests/TESTING.md. Check marker usage, fixture naming, tmp_path usage, determinism, and GPU/vLLM process-isolation requirements. Flag slop tests that only check that code runs, assert result is not None when stronger invariants exist, over-mock internal implementation details, patch around the bug instead of reproducing it, or add broad snapshot/golden churn without a clear contract. Flag change detector tests that fail on harmless refactors, formatting, record ordering, incidental wording, or private implementation details without demonstrating a behavior regression. Prefer existing fixtures or focused new fixtures for repeated setup; keep tests DRY when reasonable without making the behavior under test opaque. print() is allowed in tests.

Files:

  • tests/config/test_nss_config.py
  • tests/preflight/test_preflight.py
  • tests/pii_replacer/test_nemo_pii.py
  • tests/pii_replacer/test_detect.py
  • tests/pii_replacer/test_column_classification_eval.py
docs/**

⚙️ CodeRabbit configuration file

Review documentation as MkDocs Material content. Check Diataxis fit, accurate commands, internal links, code fences, and markdown style from STYLE_GUIDE.md.

Files:

  • docs/tutorials/safe-synthesizer-101.ipynb
  • docs/user-guide/troubleshooting.md
  • docs/user-guide/environment.md
  • docs/user-guide/configuration.md
  • docs/user-guide/evaluating-data.md
  • docs/user-guide/docker.md
  • docs/user-guide/running.md
**/*.{md,markdown}

📄 CodeRabbit inference engine (.cursor/rules/agent-markdown-style.mdc)

**/*.{md,markdown}: Bold is acceptable only in markdown tables where it's the conventional way to mark header-like cells in the body
Use ## headers to segment markdown sections instead of bold text
Use -- (em-dash) instead of - (hyphen) for asides in markdown

Files:

  • docs/user-guide/troubleshooting.md
  • docs/user-guide/environment.md
  • README.md
  • docs/user-guide/configuration.md
  • docs/user-guide/evaluating-data.md
  • docs/user-guide/docker.md
  • docs/user-guide/running.md
docs/**/*.md

📄 CodeRabbit inference engine (.cursor/rules/writing-docs.mdc)

docs/**/*.md: Use MkDocs Material admonition syntax (!!! note, !!! warning, ??? tip) for highlighting important information and collapsible sections in documentation
Use MkDocs Material tabs syntax (=== "Label") to present alternative views or language-specific examples in documentation
Use code block syntax with title and highlight line parameters (title="filename", hl_lines="2 3") for code examples in documentation
Use Mermaid diagram syntax (```mermaid flowchart, etc.) for visualizations in documentation

Classify documentation pages as tutorial, how-to, explanation, or reference, and use MkDocs Material syntax such as admonitions, tabs, and titled/highlighted code blocks.

Files:

  • docs/user-guide/troubleshooting.md
  • docs/user-guide/environment.md
  • docs/user-guide/configuration.md
  • docs/user-guide/evaluating-data.md
  • docs/user-guide/docker.md
  • docs/user-guide/running.md
**/*.md

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

**/*.md: Use single backticks for code identifiers, paths, and CLI commands in Markdown, and use -- for asides rather than a hyphen.
Do not use decorative bold in Markdown body text or list items; bold is acceptable only in table-header-like cells and MkDocs Material card-grid titles.
For Mermaid diagrams, avoid spaces in node IDs, quote labels containing special characters, and do not use explicit colors or styles.

Files:

  • docs/user-guide/troubleshooting.md
  • docs/user-guide/environment.md
  • README.md
  • docs/user-guide/configuration.md
  • docs/user-guide/evaluating-data.md
  • docs/user-guide/docker.md
  • docs/user-guide/running.md
README.md

⚙️ CodeRabbit configuration file

Treat README.md as the project overview. Check that setup, usage, and links stay consistent with CONTRIBUTING.md, Makefile, and docs/.

Files:

  • README.md
src/nemo_safe_synthesizer/config/**/*.py

⚙️ CodeRabbit configuration file

Treat config changes as user-facing API changes. Check Pydantic field descriptions, defaults, validators, aliases, override behavior, CLI help text impact, YAML compatibility, and documented parameter semantics.

Files:

  • src/nemo_safe_synthesizer/config/replace_pii.py
src/nemo_safe_synthesizer/pii_replacer/**/*.py

⚙️ CodeRabbit configuration file

Treat PII replacement changes as high-risk. Check entity coverage, replacement determinism, leakage of original values, handling of empty or multilingual text, and compatibility with optional dependencies.

Files:

  • src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
🧠 Learnings (1)
📚 Learning: 2026-05-27T22:20:37.354Z
Learnt from: kendrickb-nvidia
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 520
File: tests/generation/test_vllm_backend.py:556-587
Timestamp: 2026-05-27T22:20:37.354Z
Learning: In NVIDIA-NeMo/Safe-Synthesizer, `tests/conftest.py`’s `pytest_collection_modifyitems` hook applies pytest category markers automatically based on each test file’s path: tests under `/e2e/` get `pytest.mark.e2e`, tests under `/smoke/` get `pytest.mark.smoke`, and all other tests get `pytest.mark.unit`. Therefore, when reviewing pytest tests outside `tests/e2e/` and `tests/smoke/`, do not flag missing explicit `pytest.mark.unit` decorators on test classes/functions as an issue (the hook will add them during collection). If a new test directory/category is introduced, ensure the hook is updated so it’s categorized correctly.

Applied to files:

  • tests/config/test_nss_config.py
  • tests/preflight/test_preflight.py
  • tests/pii_replacer/test_nemo_pii.py
  • tests/pii_replacer/test_detect.py
  • tests/pii_replacer/test_column_classification_eval.py
🪛 ast-grep (0.44.1)
tests/pii_replacer/test_column_classification_eval.py

[info] 226-226: use jsonify instead of json.dumps for JSON output
Context: json.dumps(report, indent=2)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 240-240: use jsonify instead of json.dumps for JSON output
Context: json.dumps(summary, indent=2)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 markdownlint-cli2 (0.22.1)
docs/user-guide/running.md

[warning] 631-631: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


[warning] 639-639: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

🪛 Ruff (0.15.20)
src/nemo_safe_synthesizer/preflight/checks/environment.py

[error] 477-477: Possible hardcoded password assigned to: "token_missing"

(S105)

src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py

[warning] 402-403: ColumnClassifier.close is an empty method in an abstract base class, but has no abstract decorator

(B027)


[warning] 466-466: Do not catch blind exception: Exception

(BLE001)


[warning] 634-634: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (29)
README.md (1)

264-277: LGTM!

docs/user-guide/configuration.md (1)

196-198: LGTM!

docs/user-guide/docker.md (1)

127-128: LGTM!

docs/user-guide/environment.md (1)

51-52: LGTM!

Also applies to: 180-182, 192-208

docs/user-guide/evaluating-data.md (1)

92-95: LGTM!

docs/user-guide/running.md (1)

285-285: LGTM!

Also applies to: 624-654

docs/user-guide/troubleshooting.md (1)

520-530: LGTM!

docs/tutorials/safe-synthesizer-101.ipynb (1)

220-220: LGTM!

Also applies to: 234-234

src/nemo_safe_synthesizer/preflight/checks/environment.py (3)

476-529: 📐 Maintainability & Code Quality | ⚡ Quick win

Classify path emits non-namespaced shared codes under env.inference.

_CLASSIFY_HF_MODEL_SPEC overrides most codes with a classify_ prefix but inherits the defaults token_missing="hf_token_missing" and remote_code_not_cached="hf_remote_code_not_cached". Since the local_hf branch runs inside InferenceModelCheck (env.inference), those two codes now surface under env.inference while HFModelAvailabilityCheck (env.hf_model_availability) emits the same code strings, making code-based filtering/troubleshooting ambiguous. Consider prefixing them (e.g. classify_hf_token_missing, classify_hf_remote_code_not_cached) or documenting the shared emission, consistent with the troubleshooting-table concern raised previously.


467-660: Consolidating the training and classify validation paths into one spec-parameterized _check_hf_model_reference (plus the local-path / cache / remote-code helpers) cleanly removes the duplication flagged in earlier review rounds. Logic across the empty / local-path / repo-id / partial-cache branches traces correctly.


691-696: LGTM!

tests/preflight/test_preflight.py (1)

482-542: LGTM!

src/nemo_safe_synthesizer/pii_replacer/nemo_pii.py (5)

25-28: LGTM!


138-175: LGTM!


287-316: LGTM!

Also applies to: 338-370


372-404: LGTM!


317-337: 🩺 Stability & Availability

column_classifier.close() is already teardown-safe. The concrete backends swallow cleanup failures and clear their internal state, so this finally does not currently turn a successful classification into a crash.

			> Likely an incorrect or invalid review comment.
tests/pii_replacer/test_column_classification_eval.py (3)

337-342: Prior review feedback about a missing prediction being scored as none (0.714 with empty output) is now resolved: _validate_prediction_columns rejects missing columns and _positive_recall adds the positive-recall check.


77-101: LGTM!


312-313: 🩺 Stability & Availability

pii_test_data_dir is defined in tests/conftest.py and points to tests/test_data/pii/; no issue here.

			> Likely an incorrect or invalid review comment.
src/nemo_safe_synthesizer/config/replace_pii.py (1)

7-8: LGTM!

src/nemo_safe_synthesizer/defaults.py (1)

76-76: LGTM!

tests/config/test_nss_config.py (1)

16-16: LGTM!

Also applies to: 128-145

src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py (3)

273-322: LGTM!

Also applies to: 373-414


461-469: LGTM! The blind except Exception at these teardown boundaries matches the STYLE_GUIDE.md-documented pattern for non-fatal cleanup (logger.debug(..., exc_info=True)), so the Ruff BLE001 hints here are expected/acceptable.

Also applies to: 629-639


62-62: 📐 Maintainability & Code Quality

DefaultLLMConfig.LOCAL_HF_CONFIG_ID is used by the local HF classifier path.

			> Likely an incorrect or invalid review comment.
tests/pii_replacer/test_detect.py (2)

1-21: LGTM!


383-465: 🎯 Functional Correctness

No change needed
ColumnClassifierHF.initialize() already returns early when _model and _tokenizer are set, so these test fakes stay in place.

			> Likely an incorrect or invalid review comment.
tests/pii_replacer/test_nemo_pii.py (1)

13-22: LGTM!

Also applies to: 39-76, 138-138

Comment on lines +187 to +214
backend: Literal["api", "local_hf"] = Field(
default="api",
description="Column classification backend. Use 'api' for an OpenAI-compatible endpoint or 'local_hf' for an in-process Hugging Face model.",
)

model: str | None = Field(
default=None,
description="Model name or local path for column classification. For the local_hf backend, defaults to HuggingFaceTB/SmolLM3-3B.",
)

classify_model_provider: str | None = Field(
default=None,
description="Name of the model provider in the Inference Gateway for column classification. "
"The job compiler will resolve this to the appropriate endpoint URL.",
)

@model_validator(mode="after")
def warn_api_model_ignored(self) -> Self:
"""Warn when a local-HF-only model setting is provided for API classification."""
if self.backend != "local_hf" and self.model is not None:
warnings.warn(
"`replace_pii.globals.classify.model` is only used when "
"`replace_pii.globals.classify.backend` is 'local_hf'. "
"For the api backend, set `NSS_INFERENCE_MODEL` or use `--inference-model-id`.",
stacklevel=2,
)
return self

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how ClassifyConfig / PiiReplacerConfig overrides are merged (model_copy vs re-validation).
rg -n -B2 -A8 'model_copy\(update' src/nemo_safe_synthesizer/config src/nemo_safe_synthesizer/configurator

Repository: NVIDIA-NeMo/Safe-Synthesizer

Length of output: 5611


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Inspect the relevant config model and its construction/override paths.
sed -n '1,280p' src/nemo_safe_synthesizer/config/replace_pii.py
printf '\n---\n'
sed -n '1,260p' src/nemo_safe_synthesizer/configurator/parameters.py
printf '\n---\n'
rg -n "replace_pii|classify_model_provider|backend.*local_hf|local_hf" src/nemo_safe_synthesizer -g'*.py'

Repository: NVIDIA-NeMo/Safe-Synthesizer

Length of output: 24158


warn_api_model_ignored needs to run after overrides are merged. Parameters.from_yaml() / from_json() apply overrides with model_copy(update=...), which skips after validators, so replace_pii.globals.classify.model can be set on an api config without this warning ever showing. Re-validate after merging or move the warning into the override path.

Comment on lines +40 to +88
{
"User": 0,
"Card": 0,
"Year": 2002,
"Month": 9,
"Day": 1,
"Time": "06:21",
"Amount": "$134.09",
"Use Chip": "Swipe Transaction",
"Merchant Name": "3527213246127876953",
"Merchant City": "La Verne",
"Merchant State": "CA",
"Zip": "91750.0",
"MCC": 5300,
"Is Fraud?": "No"
},
{
"User": 0,
"Card": 0,
"Year": 2002,
"Month": 9,
"Day": 1,
"Time": "06:42",
"Amount": "$38.48",
"Use Chip": "Swipe Transaction",
"Merchant Name": "-727612092139916043",
"Merchant City": "Monterey Park",
"Merchant State": "CA",
"Zip": "91754.0",
"MCC": 5411,
"Is Fraud?": "No"
}
],
"expected_entities": {
"User": "none",
"Card": "credit_debit_card",
"Year": "none",
"Month": "none",
"Day": "none",
"Time": "none",
"Amount": "none",
"Use Chip": "none",
"Merchant Name": "none",
"Merchant City": "city",
"Merchant State": "state",
"Zip": "postcode",
"MCC": "none",
"Is Fraud?": "none"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check the Card gold label

The credit_card_transactions values are just 0, so Card reads like an ID/index field rather than a payment-card column. If the benchmark is meant to classify actual card-number columns, keep credit_debit_card; otherwise use none.

Signed-off-by: Matt Kornfield <mkornfield@nvidia.com>
@mckornfield
mckornfield force-pushed the local-classification/mck branch from e22f7d1 to 6dcc5cf Compare July 8, 2026 23:15
@coderabbitai coderabbitai Bot added the refactor Internal restructuring with no behavior change label Jul 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py (1)

579-606: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Clean up partial HF resources when initialization fails.

If tokenizer loading succeeds but model loading or device placement fails, _tokenizer remains set and any partially allocated model/GPU memory is not cleaned until a later close(). Wrap the load sequence and call teardown() before re-raising.

Proposed fix
-        logger.info("Loading local column classification model: %s", self._model_name_or_path)
-        self._tokenizer = AutoTokenizer.from_pretrained(
-            load_target,
-            trust_remote_code=model_ref.trust_remote_code,
-            local_files_only=hf_offline_enabled(),
-        )
-        if getattr(self._tokenizer, "pad_token_id", None) is None:
-            self._tokenizer.pad_token = self._tokenizer.eos_token
-        self._model = AutoModelForCausalLM.from_pretrained(load_target, **load_kwargs)
-        if not torch.cuda.is_available():
-            self._model = self._model.to("cpu")
-        self._model.eval()
+        logger.info("Loading local column classification model: %s", self._model_name_or_path)
+        try:
+            self._tokenizer = AutoTokenizer.from_pretrained(
+                load_target,
+                trust_remote_code=model_ref.trust_remote_code,
+                local_files_only=hf_offline_enabled(),
+            )
+            if getattr(self._tokenizer, "pad_token_id", None) is None:
+                self._tokenizer.pad_token = self._tokenizer.eos_token
+            self._model = AutoModelForCausalLM.from_pretrained(load_target, **load_kwargs)
+            if not torch.cuda.is_available():
+                self._model = self._model.to("cpu")
+            self._model.eval()
+        except Exception:
+            self.teardown()
+            raise

As per path instructions, PII replacement changes are high-risk and generation/model resource paths should check cleanup behavior.

Source: Path instructions

🧹 Nitpick comments (1)
src/nemo_safe_synthesizer/llm/model_host.py (1)

33-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document initialize() idempotency contract, matching teardown().

teardown() explicitly states it "must be idempotent" (line 39), but initialize() has no equivalent note even though the concrete implementation (ColumnClassifierHF.initialize()) relies on repeated calls being cheap/safe (guards on already-loaded model/tokenizer). Documenting this on the abstract contract makes the expectation explicit for future implementers.

📝 Proposed docstring tweak
     `@abstractmethod`
     def initialize(self) -> None:
-        """Load the model and any resources required to use it."""
+        """Load the model and any resources required to use it.
+
+        Implementations should make this safe to call multiple times
+        (e.g., a no-op if the model is already loaded).
+        """

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2735c2bc-7da4-4327-bfdb-0d60c05f79bb

📥 Commits

Reviewing files that changed from the base of the PR and between e22f7d1 and 6dcc5cf.

📒 Files selected for processing (16)
  • README.md
  • docs/user-guide/configuration.md
  • docs/user-guide/docker.md
  • docs/user-guide/environment.md
  • docs/user-guide/evaluating-data.md
  • docs/user-guide/running.md
  • docs/user-guide/troubleshooting.md
  • src/nemo_safe_synthesizer/config/replace_pii.py
  • src/nemo_safe_synthesizer/llm/model_host.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
  • tests/config/test_nss_config.py
  • tests/pii_replacer/test_column_classification_eval.py
  • tests/pii_replacer/test_detect.py
  • tests/pii_replacer/test_nemo_pii.py
  • tests/preflight/test_preflight.py
✅ Files skipped from review due to trivial changes (6)
  • docs/user-guide/configuration.md
  • README.md
  • docs/user-guide/docker.md
  • docs/user-guide/troubleshooting.md
  • docs/user-guide/evaluating-data.md
  • docs/user-guide/environment.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/config/test_nss_config.py
  • tests/pii_replacer/test_nemo_pii.py
  • src/nemo_safe_synthesizer/config/replace_pii.py
  • tests/preflight/test_preflight.py
  • tests/pii_replacer/test_column_classification_eval.py
  • tests/pii_replacer/test_detect.py
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: Unit Tests (3.13)
  • GitHub Check: Unit Tests (3.11)
  • GitHub Check: Unit Tests (3.12)
  • GitHub Check: Smoke Tests
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{md,markdown,py}

📄 CodeRabbit inference engine (.cursor/rules/agent-markdown-style.mdc)

**/*.{md,markdown,py}: Avoid decorative bold (**text**) in list items, body text, and docstrings; use structural cues (headers, list markers, colons, backticks) for emphasis instead
Use backticks for code identifiers, paths, and CLI commands in markdown and docstrings

Files:

  • src/nemo_safe_synthesizer/llm/model_host.py
  • docs/user-guide/running.md
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Place durable implementation guidance in function and class docstrings for public contracts and source comments for local invariants
Target Python 3.11–3.13 with modern syntax (X | Y, list[str], Self). Python 3.14+ is not supported

**/*.py: Source code must remain Python 3.11 syntax-compatible; do not use Python 3.12-only syntax such as PEP 695 type statements or bracketed generic class/function parameters in shared package code
Use ruff for formatting and linting via mise run format and mise run check tasks; run formatting before committing
Use ty for type checking via mise run check task; ensure type hints are present and valid
New features must include tests; bug fixes must include regression tests

**/*.py: Use BaseSettings for env/CLI settings, prefer AliasChoices for per-field dual naming, and use env_prefix only for simple settings classes with a shared prefix.
Use Field(description=...) as the canonical field docstring for Pydantic models, and always include it.
Use assignment-style Field(default=..., description="...") as the default for model fields; prefer it over Annotated unless extra metadata is needed.
Use Annotated only when the field carries additional metadata beyond Field() (for example validators, reusable constrained aliases, nested-type constraints, or discriminated unions).
When Annotated is used, place defaults as bare assignments (= value), except default_factory, which should still use assignment-style Field(default_factory=...).
For immutable value objects and validators, prefer @dataclass(frozen=True); use mutable dataclasses only for builders, accumulators, and pipeline state.
Use field(default_factory=list) for mutable defaults; never use = [].
Use StrEnum for string-valued enums used in configs or serialization; use plain Enum for internal-only named constants.
Use observability.get_logger(__name__) for logging; do not call logging.getLogger() or structlog.get_logger() direc...

Files:

  • src/nemo_safe_synthesizer/llm/model_host.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
**/*.{py,sh,yaml,yml,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

All source files (.py, .sh, .yaml, .yml, .md) require SPDX copyright headers; mise run format adds them automatically

Files:

  • src/nemo_safe_synthesizer/llm/model_host.py
  • docs/user-guide/running.md
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
src/nemo_safe_synthesizer/**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

API reference pages are auto-generated from Python docstrings using Google-style format; write docstrings in src/nemo_safe_synthesizer/ and they will appear in the reference/

Files:

  • src/nemo_safe_synthesizer/llm/model_host.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
src/**/*.py

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

src/**/*.py: Use relative imports inside src/, for example from ..observability import get_logger.
Every directory under src/ that contains Python files must include an __init__.py file.

Files:

  • src/nemo_safe_synthesizer/llm/model_host.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py

⚙️ CodeRabbit configuration file

Review library code against STYLE_GUIDE.md. Focus on behavior, API contracts, error handling, resource cleanup, typing, logging, and user-facing failures. Public APIs and nontrivial functions need Google-style docstrings.

Files:

  • src/nemo_safe_synthesizer/llm/model_host.py
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
**/*

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

**/*: Every source file must include the required SPDX copyright and license header; use HTML comments for Markdown, hash comments for .py, .sh, .yaml, and .yml, and hash-comment headers inside YAML frontmatter for Markdown files with frontmatter.
Ensure files end with a newline and have no trailing whitespace; use a single space between sentences.

Files:

  • src/nemo_safe_synthesizer/llm/model_host.py
  • docs/user-guide/running.md
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py

⚙️ CodeRabbit configuration file

**/*: Review as a senior maintainer for NeMo Safe Synthesizer. Prioritize issues that can change behavior, break user workflows, weaken privacy guarantees, hide failures, make tests unreliable, or create maintenance risk. Avoid generic style commentary unless it points to a concrete project convention that automated tools will not catch.
Comment only when the finding is actionable and tied to changed code. For each finding, state the impact, the condition that triggers it, and the smallest practical fix. Prefer one precise comment over broad advice. Do not ask for refactors outside the PR scope unless the changed code creates the problem.
Review type guidance: - Potential issue: use for correctness bugs, data loss, privacy leaks,
security risks, broken public APIs, invalid config behavior, missing
validation, hidden failures, nondeterministic tests, or CI breakage.

  • Refactor suggestion: use for local maintainability problems introduced
    by the diff when they have clear future cost, such as duplicated setup,
    unclear boundaries, over-mocking, avoidable complexity, or opaque test
    helpers.
  • Nitpick: avoid in chill mode. Do not emit formatting, import-order,
    wording, or style-only comments unless automated tools cannot catch the
    issue and it affects maintainability.

Severity guidance: - Critical: security/privacy leaks, data loss, training/test/holdout
contamination, or broken release/package/core pipeline execution.

  • Major: incorrect generation/training/evaluation behavior, broken
    CLI/SDK public API, invalid config defaults or validators, or GPU/vLLM
    cleanup and process-isolation bugs likely to fail CI or production
    runs.
  • Minor: localized bugs, missing focused tests for changed behavior, or
    bad test patterns that weaken regression coverage.
  • Trivial: small cleanup with no behavior impact. Usually suppress in
    chill mode.
  • Info: context only. Avoid unless it helps reviewers understand risk.
    Safe-Synthesizer-specific review focus: - Data ...

Files:

  • src/nemo_safe_synthesizer/llm/model_host.py
  • docs/user-guide/running.md
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
**

⚙️ CodeRabbit configuration file

**:

AGENTS.md

Guide for AI agents (Cursor, Windsurf, Claude Code, etc.) working in the Safe-Synthesizer repo.

This project loads local developer preferences from @AGENTS.local.md. You MUST read this file if it exists and give its instructions top priority.

Skills

Repo-specific skills live in .agents/skills/; see .agents/README.md for the catalog. Read a skill when the task matches its scope instead of copying workflow details into this file.

Durable implementation guidance belongs with the code it describes: function and class docstrings for public contracts and source comments for local invariants. Test-suite guidance belongs in tests/TESTING.md.

Repo Conventions

See STYLE_GUIDE.md for detailed code style conventions (Python, markdown, Dockerfiles, shell scripts, testing, config files, docstrings).

Use uv for everything -- never pip or raw python. Python 3.11–3.13 with modern syntax (X | Y, list[str], Self). Python 3.14+ is not supported.

Common commands: mise run test (unit tests), mise run format (auto-fix formatting + lint + copyright), mise run check (read-only local quality checks), mise run validate (pre-PR quality, lock, and CI unit checks), mise run typecheck (ty only). Always use mise tasks or the wrapper scripts in tools/ instead of running ruff or ty directly. Use uv run for Python execution. When in doubt, inspect mise tasks and pytest --markers.

The canonical uv sync command for a full GPU/dev environment is:

uv sync --frozen --extra cu129 --extra engine --group dev

Bare uv sync --frozen (without extras) installs an incomplete environment -- ty, import checks, and GPU tests will fail.

Feature branches off main. Branch names often include an issue number prefix (e.g., <author>/123-short-name).

Do ...

Files:

  • src/nemo_safe_synthesizer/llm/model_host.py
  • docs/user-guide/running.md
  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
  • src/nemo_safe_synthesizer/preflight/checks/environment.py
**/*.{md,markdown}

📄 CodeRabbit inference engine (.cursor/rules/agent-markdown-style.mdc)

**/*.{md,markdown}: Bold is acceptable only in markdown tables where it's the conventional way to mark header-like cells in the body
Use ## headers to segment markdown sections instead of bold text
Use -- (em-dash) instead of - (hyphen) for asides in markdown

Files:

  • docs/user-guide/running.md
docs/**/*.md

📄 CodeRabbit inference engine (.cursor/rules/writing-docs.mdc)

docs/**/*.md: Use MkDocs Material admonition syntax (!!! note, !!! warning, ??? tip) for highlighting important information and collapsible sections in documentation
Use MkDocs Material tabs syntax (=== "Label") to present alternative views or language-specific examples in documentation
Use code block syntax with title and highlight line parameters (title="filename", hl_lines="2 3") for code examples in documentation
Use Mermaid diagram syntax (```mermaid flowchart, etc.) for visualizations in documentation

Classify documentation pages as tutorial, how-to, explanation, or reference, and use MkDocs Material syntax such as admonitions, tabs, and titled/highlighted code blocks.

Files:

  • docs/user-guide/running.md
**/*.md

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

**/*.md: Use single backticks for code identifiers, paths, and CLI commands in Markdown, and use -- for asides rather than a hyphen.
Do not use decorative bold in Markdown body text or list items; bold is acceptable only in table-header-like cells and MkDocs Material card-grid titles.
For Mermaid diagrams, avoid spaces in node IDs, quote labels containing special characters, and do not use explicit colors or styles.

Files:

  • docs/user-guide/running.md
docs/**

⚙️ CodeRabbit configuration file

Review documentation as MkDocs Material content. Check Diataxis fit, accurate commands, internal links, code fences, and markdown style from STYLE_GUIDE.md.

Files:

  • docs/user-guide/running.md
src/nemo_safe_synthesizer/pii_replacer/**/*.py

⚙️ CodeRabbit configuration file

Treat PII replacement changes as high-risk. Check entity coverage, replacement determinism, leakage of original values, handling of empty or multilingual text, and compatibility with optional dependencies.

Files:

  • src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py
🪛 markdownlint-cli2 (0.22.1)
docs/user-guide/running.md

[warning] 631-631: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


[warning] 639-639: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

🪛 Ruff (0.15.20)
src/nemo_safe_synthesizer/pii_replacer/data_editor/detect.py

[warning] 466-466: Do not catch blind exception: Exception

(BLE001)


[warning] 634-634: Do not catch blind exception: Exception

(BLE001)

src/nemo_safe_synthesizer/preflight/checks/environment.py

[error] 477-477: Possible hardcoded password assigned to: "token_missing"

(S105)

🔇 Additional comments (2)
src/nemo_safe_synthesizer/llm/model_host.py (1)

1-39: LGTM!

src/nemo_safe_synthesizer/preflight/checks/environment.py (1)

467-756: LGTM!

|-------|-------|-------------------|
| `gpu.cuda` | config | PyTorch is importable and a CUDA GPU is visible |
| `env.inference` | config | Inference config for PII classification: `NSS_INFERENCE_KEY` is set, `NSS_INFERENCE_MODEL` is non-empty, and `NSS_INFERENCE_ENDPOINT` is a valid http(s) URL (warnings only) |
| `env.inference` | config | Inference config for PII classification: API backend env vars are usable, or the `local_hf` classifier model reference is usable locally or fetchable from Hugging Face |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Avoid saying preflight proves the model is fetchable.

The local HF check does not contact Hugging Face; it verifies local/cache state and warns when runtime may need an online fetch. Saying the reference is “fetchable” can make users trust --validate for network/gated-model availability that it has not proven.

Suggested wording
-| `env.inference` | config | Inference config for PII classification: API backend env vars are usable, or the `local_hf` classifier model reference is usable locally or fetchable from Hugging Face |
+| `env.inference` | config | Inference config for PII classification: API backend env vars are usable, or the `local_hf` classifier model reference resolves locally; warns when runtime may need Hugging Face access |

As per path instructions, “Review documentation as MkDocs Material content. Check Diataxis fit, accurate commands, internal links, and markdown style from STYLE_GUIDE.md.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `env.inference` | config | Inference config for PII classification: API backend env vars are usable, or the `local_hf` classifier model reference is usable locally or fetchable from Hugging Face |
| `env.inference` | config | Inference config for PII classification: API backend env vars are usable, or the `local_hf` classifier model reference resolves locally; warns when runtime may need Hugging Face access |

Source: Path instructions

Signed-off-by: Matt Kornfield <mkornfield@nvidia.com>
from nemo_safe_synthesizer.pii_replacer.ner.ner import NERPrediction

if TYPE_CHECKING:
from transformers import PreTrainedTokenizerBase
@coderabbitai coderabbitai Bot added docs Documentation-only change test Test-only addition or change labels Jul 13, 2026
@coderabbitai coderabbitai Bot removed the refactor Internal restructuring with no behavior change label Jul 13, 2026
@mckornfield
mckornfield requested a review from binaryaaron July 14, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:config area:data-processing area:docs area:llm area:pii area:tests docs Documentation-only change feature New feature or request test Test-only addition or change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Allow and validate a locally hosted LLM (smollm) for column classification

3 participants