Skip to content

fix: add max_records_per_sequence when generating#663

Open
mckornfield wants to merge 3 commits into
mainfrom
max-records-sequence/mck
Open

fix: add max_records_per_sequence when generating#663
mckornfield wants to merge 3 commits into
mainfrom
max-records-sequence/mck

Conversation

@mckornfield

@mckornfield mckornfield commented Jul 17, 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

  • Closes #

Summary by CodeRabbit

  • New Features

    • Added max_records_per_sequence to cap how many records are generated per grouped sequence.
    • Grouped structured generation now supports bounded, parseable termination for both regex and structural-tag outputs.
    • The per-group bound can be derived automatically from training statistics when not explicitly set, with clear precedence.
  • Bug Fixes

    • Group sizing statistics used for bounds now respect train vs. validation/holdout separation.
  • Tests

    • Added regression tests covering bound enforcement, precedence behavior, and accept/reject round-trip scenarios.

Signed-off-by: Matt Kornfield <mkornfield@nvidia.com>
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

Grouped record bounds

Layer / File(s) Summary
Record-bound contracts and propagation
src/nemo_safe_synthesizer/config/generate.py, src/nemo_safe_synthesizer/data_processing/assembler.py, src/nemo_safe_synthesizer/llm/metadata.py, src/nemo_safe_synthesizer/training/huggingface_backend.py, tests/data_processing/test_assembler.py, tests/llm/test_metadata.py
Adds configurable sequence limits, separates training and validation group statistics, and propagates the derived maximum into model metadata with validation tests.
Bounded regex and structural-tag builders
src/nemo_safe_synthesizer/generation/regex_manager.py
Resolves explicit limits over metadata defaults and applies bounded repetition to grouped regex and structural-tag formats.
Backend wiring and regression coverage
src/nemo_safe_synthesizer/generation/vllm_backend.py, tests/generation/test_regex_manager.py, tests/generation/test_vllm_backend.py
Passes metadata defaults into both builders and tests bounds, precedence, termination, rejection cases, and call arguments.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested labels: bug, feature, test

Suggested reviewers: binaryaaron

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 accurately captures the main change: adding max_records_per_sequence support during generation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 max-records-sequence/mck

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

@coderabbitai coderabbitai Bot added feature New feature or request test Test-only addition or change labels Jul 17, 2026
@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an optional max_records_per_sequence bound that caps how many records a grouped structured-generation sequence may contain, preventing unbounded decoding when a fine-tuned model fails to emit the group-closing delimiter. It also correctly segregates training and validation group-size statistics so that holdout groups never inflate the training-derived default bound.

  • Config & metadata: Adds StructuredGenerationParameters.max_records_per_sequence (None or ≥ 1) and ModelMetadata.max_records_per_group, both with validators; the training backend's _propagate_max_records_per_group populates the latter from records_per_group.max over the training split only.
  • Grammar construction: build_json_based_regex and build_json_structural_tag now resolve an effective bound via _resolve_max_records_per_sequence (explicit config override wins over the training-derived default) and emit a single bounded group ({1,N} / repeat) instead of an unbounded outer repetition when the bound is set.
  • Stat segregation: _group_records_generator routes records_per_group and tokens_per_group updates into stats_val for validation datasets (using the pre-existing "is_val" in dataset.info.description convention), mirroring the existing pattern already in place for tokens_per_example/groups_per_example.

Confidence Score: 5/5

Safe to merge; the core grammar-construction logic is well-tested and the train/val stat separation is correct.

The bounded-group grammar path is covered by regex round-trip tests and structural-tag acceptance/rejection tests. The training stat separation is verified by asserting records_per_group.count == num_groups_train. The pre-existing is_val detection pattern is already relied on elsewhere in the assembler for identical stats. The four if max_records: truthiness nits were already called out in prior review threads.

regex_manager.py — the four if max_records: truthiness checks (already flagged in prior threads) are the only open concern.

Important Files Changed

Filename Overview
src/nemo_safe_synthesizer/config/generate.py Adds optional max_records_per_sequence field (None or >= 1) with a ValueValidator; field definition and validation are consistent.
src/nemo_safe_synthesizer/data_processing/assembler.py Segregates records_per_group and tokens_per_group stats into train vs. val buckets using the pre-existing is_val description check; training records_per_group is now correctly exported to TrainingExamples.stats so only training groups feed the generation bound.
src/nemo_safe_synthesizer/generation/regex_manager.py Adds _resolve_max_records_per_sequence helper and bounded-repetition branches for both build_json_based_regex and build_json_structural_tag; the four truthiness checks (if max_records:) were already flagged in prior review threads.
src/nemo_safe_synthesizer/generation/vllm_backend.py Threads default_max_records_per_group=self.model_metadata.max_records_per_group into both build_json_based_regex and build_json_structural_tag call sites; straightforward plumbing change.
src/nemo_safe_synthesizer/llm/metadata.py Adds max_records_per_group field with a field_validator enforcing None
src/nemo_safe_synthesizer/training/huggingface_backend.py Adds _propagate_max_records_per_group following the exact same pattern as _propagate_max_tokens_per_example; reads training-only records_per_group.max and persists it onto ModelMetadata.
tests/generation/test_regex_manager.py Adds five new test functions covering explicit config override, training-derived default, precedence, and XGrammar round-trip acceptance/rejection for bounded grouped generation.
tests/data_processing/test_assembler.py Updates tokens_per_group.mean assertion (now train-only; changed from 219.64 to 219.925) and adds assertions verifying that records_per_group.count equals only the training group count.
tests/generation/test_vllm_backend.py Adds max_records_per_group=None to the mock metadata and wires the new parameter into four existing call-site mocks; no logic changes.
tests/llm/test_metadata.py Adds acceptance tests for None/positive bounds and a parametrised rejection test for 0 and -1, matching the validator contract.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Training Run] -->|GroupedDataExampleAssembler| B{dataset is val?}
    B -->|yes| C[stats_val: records_per_group]
    B -->|no| D[stats train: records_per_group]
    D --> E[TrainingExamples.stats]
    E --> F[_propagate_max_records_per_group]
    F --> G[ModelMetadata.max_records_per_group]

    H[Config: max_records_per_sequence] --> I[_resolve_max_records_per_sequence]
    G --> I
    I -->|explicit override wins| J{max_records resolved?}
    J -->|truthy int| K["build bounded grammar: bos + record-1-to-N + eos"]
    J -->|None| L["build unbounded grammar: bos + record-plus + eos with outer repetition"]
    K --> M[vLLM: single terminable group]
    L --> N[vLLM: multi-group unbounded]
Loading

Reviews (3): Last reviewed commit: "fix: keep group-size stats training-only..." | Re-trigger Greptile

Comment on lines +380 to +386
record_repeat = rf"({record_regex}\n){{1,{max_records}}}" if max_records else rf"({record_regex}\n)+"
sequence_regex = rf"{re.escape(bos_token)}{record_repeat}{re.escape(eos_token)}"
else:
# Without grouping, the "sequence" is a single record.
sequence_regex = record_regex

if config.generation.structured_generation.use_single_sequence and config.data.max_sequences_per_example == 1:
if config.data.group_training_examples_by is not None and max_records:

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 The four if max_records: truthiness checks conflate None (no bound) with 0 (also treated as no bound). The returned type from _resolve_max_records_per_sequence is int | None, so the intended sentinel is None. Using is not None makes the intent explicit and avoids any future confusion if a caller somehow passes 0 — e.g., _repeat_format(record_line_format, 1, 0) would produce an invalid min=1, max=0 grammar node, whereas if max_records is not None: would treat it as a (malformed) bound and surface the error clearly.

Suggested change
record_repeat = rf"({record_regex}\n){{1,{max_records}}}" if max_records else rf"({record_regex}\n)+"
sequence_regex = rf"{re.escape(bos_token)}{record_repeat}{re.escape(eos_token)}"
else:
# Without grouping, the "sequence" is a single record.
sequence_regex = record_regex
if config.generation.structured_generation.use_single_sequence and config.data.max_sequences_per_example == 1:
if config.data.group_training_examples_by is not None and max_records:
record_repeat = rf"({record_regex}\n){{1,{max_records}}}" if max_records is not None else rf"({record_regex}\n)+"
sequence_regex = rf"{re.escape(bos_token)}{record_repeat}{re.escape(eos_token)}"
else:
# Without grouping, the "sequence" is a single record.
sequence_regex = record_regex
if config.data.group_training_examples_by is not None and max_records is not None:

Comment on lines +459 to +472
record_repetition = (
_repeat_format(record_line_format, 1, max_records) if max_records else _plus_format(record_line_format)
)
sequence_format = _sequence_format(
[
_const_string_format(bos_token),
_plus_format(record_line_format),
record_repetition,
_const_string_format(eos_token),
]
)
else:
sequence_format = record_format

if config.generation.structured_generation.use_single_sequence and config.data.max_sequences_per_example == 1:
if config.data.group_training_examples_by is not None and max_records:

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 Same if max_records: truthiness pattern in build_json_structural_tag. Both conditions should use is not None to match the int | None contract of _resolve_max_records_per_sequence.

Suggested change
record_repetition = (
_repeat_format(record_line_format, 1, max_records) if max_records else _plus_format(record_line_format)
)
sequence_format = _sequence_format(
[
_const_string_format(bos_token),
_plus_format(record_line_format),
record_repetition,
_const_string_format(eos_token),
]
)
else:
sequence_format = record_format
if config.generation.structured_generation.use_single_sequence and config.data.max_sequences_per_example == 1:
if config.data.group_training_examples_by is not None and max_records:
record_repetition = (
_repeat_format(record_line_format, 1, max_records) if max_records is not None else _plus_format(record_line_format)
)
sequence_format = _sequence_format(
[
_const_string_format(bos_token),
record_repetition,
_const_string_format(eos_token),
]
)
else:
sequence_format = record_format
if config.data.group_training_examples_by is not None and max_records is not None:

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.65217% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...o_safe_synthesizer/training/huggingface_backend.py 20.00% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

@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: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 365b1892-4127-47cf-8c95-a47f6ec2d36f

📥 Commits

Reviewing files that changed from the base of the PR and between d73679a and 7f738d9.

📒 Files selected for processing (8)
  • src/nemo_safe_synthesizer/config/generate.py
  • src/nemo_safe_synthesizer/data_processing/assembler.py
  • src/nemo_safe_synthesizer/generation/regex_manager.py
  • src/nemo_safe_synthesizer/generation/vllm_backend.py
  • src/nemo_safe_synthesizer/llm/metadata.py
  • src/nemo_safe_synthesizer/training/huggingface_backend.py
  • tests/generation/test_regex_manager.py
  • tests/generation/test_vllm_backend.py
📜 Review details
⏰ Context from checks skipped due to timeout. (9)
  • GitHub Check: Unit Tests (3.13)
  • GitHub Check: Unit Tests (3.11)
  • GitHub Check: Unit Tests (3.12)
  • GitHub Check: Smoke Tests
  • GitHub Check: End-user Wheel Install
  • GitHub Check: Greptile Review
  • GitHub Check: Analyze (Python)
  • GitHub Check: Typecheck
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (16)
**/*.{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/data_processing/assembler.py
  • src/nemo_safe_synthesizer/llm/metadata.py
  • src/nemo_safe_synthesizer/config/generate.py
  • src/nemo_safe_synthesizer/training/huggingface_backend.py
  • src/nemo_safe_synthesizer/generation/vllm_backend.py
  • tests/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.py
  • src/nemo_safe_synthesizer/generation/regex_manager.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: Use American English spelling in Python code, documentation, and messages.
Use Field(description=...) for every Pydantic model field.
Use assignment-style Field() by default; use Annotated only for additional metadata such as validators, constrained aliases, or discriminated unions.
Use @dataclass(frozen=True) for immutable value objects and validators; use mutable dataclasses only for builders, accumulators, and pipeline state.
Use field(default_factory=list) instead of mutable list defaults.
Use StrEnum for string-valued configuration or serialization enums and plain Enum for internal constants.
Obtain loggers with observability.get_logger(__name__); do not call logging.getLogger() or structlog.get_logger() directly.
Use .runtime, .user, and .system category loggers appropriately.
Do not use print() for operational library output; use the approved logger, click.echo(), or sys.stdout.write() where appropriate.
Use extra={} for machine-queryable logging data and f-strings only for human-readable context.
Raise errors from the custom Safe Synthesizer error hierarchy, using the documented dual inheritance for user and internal errors.
Keep shared package code compatible with Python 3.11; do not use Python 3.12-only syntax such as PEP 695 type statements or bracketed generic parameters.
Prefer X | Y, built-in collection generics, and Self over Optional, Union, and legacy typing collections.
Use collection ABCs for function arguments and concrete collection types for return values.
Use Protocol for structural subtyping and avoid Any when object, generics, or protocols are suitable.
Use TYPE_CHECKING guards for heavy imports such as pandas, torch, and transformers.
...

Files:

  • src/nemo_safe_synthesizer/data_processing/assembler.py
  • src/nemo_safe_synthesizer/llm/metadata.py
  • src/nemo_safe_synthesizer/config/generate.py
  • src/nemo_safe_synthesizer/training/huggingface_backend.py
  • src/nemo_safe_synthesizer/generation/vllm_backend.py
  • tests/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.py
  • src/nemo_safe_synthesizer/generation/regex_manager.py
src/**/*.py

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

src/**/*.py: Use relative imports for package code under src/.
Do not use assert for validation in library code; raise an appropriate exception instead.

Write Google-style docstrings for public Python APIs because API reference pages are generated from source docstrings.

Files:

  • src/nemo_safe_synthesizer/data_processing/assembler.py
  • src/nemo_safe_synthesizer/llm/metadata.py
  • src/nemo_safe_synthesizer/config/generate.py
  • src/nemo_safe_synthesizer/training/huggingface_backend.py
  • src/nemo_safe_synthesizer/generation/vllm_backend.py
  • src/nemo_safe_synthesizer/generation/regex_manager.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/data_processing/assembler.py
  • src/nemo_safe_synthesizer/llm/metadata.py
  • src/nemo_safe_synthesizer/config/generate.py
  • src/nemo_safe_synthesizer/training/huggingface_backend.py
  • src/nemo_safe_synthesizer/generation/vllm_backend.py
  • src/nemo_safe_synthesizer/generation/regex_manager.py
**/*

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

**/*: Every source file requires the SPDX copyright and license header appropriate to its file format.
End files with a newline, remove trailing whitespace, use one space between sentences, and keep code, comments, and docstrings within 120 characters.

**/*: All contributions must use verified Git commits and DCO sign-off; unsigned or unsigned-off commits cannot be merged.
Branches other than main must follow <author>/<description>, optionally including an issue ID or type; branch names must use lowercase alphanumeric characters and hyphens.
Commits merged to main must follow Conventional Commits, using a valid lowercase type and a description of at most 100 characters.

Files:

  • src/nemo_safe_synthesizer/data_processing/assembler.py
  • src/nemo_safe_synthesizer/llm/metadata.py
  • src/nemo_safe_synthesizer/config/generate.py
  • src/nemo_safe_synthesizer/training/huggingface_backend.py
  • src/nemo_safe_synthesizer/generation/vllm_backend.py
  • tests/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.py
  • src/nemo_safe_synthesizer/generation/regex_manager.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/data_processing/assembler.py
  • src/nemo_safe_synthesizer/llm/metadata.py
  • src/nemo_safe_synthesizer/config/generate.py
  • src/nemo_safe_synthesizer/training/huggingface_backend.py
  • src/nemo_safe_synthesizer/generation/vllm_backend.py
  • tests/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.py
  • src/nemo_safe_synthesizer/generation/regex_manager.py
**/*.{py,sh,yaml,yml,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

All Python, shell, YAML, YML, and Markdown source files require SPDX copyright headers.

Files:

  • src/nemo_safe_synthesizer/data_processing/assembler.py
  • src/nemo_safe_synthesizer/llm/metadata.py
  • src/nemo_safe_synthesizer/config/generate.py
  • src/nemo_safe_synthesizer/training/huggingface_backend.py
  • src/nemo_safe_synthesizer/generation/vllm_backend.py
  • tests/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.py
  • src/nemo_safe_synthesizer/generation/regex_manager.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Shared Python package code must remain compatible with Python 3.11 syntax; do not use Python 3.12-only syntax such as PEP 695 type statements or bracketed generic class/function parameters.

Files:

  • src/nemo_safe_synthesizer/data_processing/assembler.py
  • src/nemo_safe_synthesizer/llm/metadata.py
  • src/nemo_safe_synthesizer/config/generate.py
  • src/nemo_safe_synthesizer/training/huggingface_backend.py
  • src/nemo_safe_synthesizer/generation/vllm_backend.py
  • tests/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.py
  • src/nemo_safe_synthesizer/generation/regex_manager.py
**/*.{py,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use the repository's documented Python and Markdown style conventions and validate changes with the pinned mise formatting and checking tasks.

Files:

  • src/nemo_safe_synthesizer/data_processing/assembler.py
  • src/nemo_safe_synthesizer/llm/metadata.py
  • src/nemo_safe_synthesizer/config/generate.py
  • src/nemo_safe_synthesizer/training/huggingface_backend.py
  • src/nemo_safe_synthesizer/generation/vllm_backend.py
  • tests/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.py
  • src/nemo_safe_synthesizer/generation/regex_manager.py
**/*.{py,sh}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use the repository's pinned mise tasks for formatting, linting, type checking, and testing rather than invoking ruff or ty directly for project-wide checks.

Files:

  • src/nemo_safe_synthesizer/data_processing/assembler.py
  • src/nemo_safe_synthesizer/llm/metadata.py
  • src/nemo_safe_synthesizer/config/generate.py
  • src/nemo_safe_synthesizer/training/huggingface_backend.py
  • src/nemo_safe_synthesizer/generation/vllm_backend.py
  • tests/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.py
  • src/nemo_safe_synthesizer/generation/regex_manager.py
src/nemo_safe_synthesizer/data_processing/**/*.py

⚙️ CodeRabbit configuration file

Review for data-contract regressions. Check input/training/test/synthetic naming, group boundaries, token-budget math, record ordering, schema and column validation, nullable dtypes, and deterministic behavior.

Files:

  • src/nemo_safe_synthesizer/data_processing/assembler.py
src/**/config/**/*.py

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

Use NSSBaseModel for user-facing configuration and parameter models.

Files:

  • src/nemo_safe_synthesizer/config/generate.py
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/generate.py
src/nemo_safe_synthesizer/training/**/*.py

⚙️ CodeRabbit configuration file

Review training changes for dataset preprocessing, model path handling, artifact writes, LoRA/DP behavior, GPU memory usage, reproducibility, and cleanup on failure.

Files:

  • src/nemo_safe_synthesizer/training/huggingface_backend.py
src/nemo_safe_synthesizer/generation/**/*.py

⚙️ CodeRabbit configuration file

Review generation changes for retry loops, stopping conditions, invalid record handling, regex/structured output contracts, backend teardown, memory cleanup, and vLLM assumptions.

Files:

  • src/nemo_safe_synthesizer/generation/vllm_backend.py
  • src/nemo_safe_synthesizer/generation/regex_manager.py
**/test_*.py

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • tests/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.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/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.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/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.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/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.py
🧠 Learnings (2)
📚 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/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.py
📚 Learning: 2026-06-04T16:14:09.868Z
Learnt from: binaryaaron
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 526
File: tests/generation/test_vllm_backend.py:399-509
Timestamp: 2026-06-04T16:14:09.868Z
Learning: In this repo, only apply `pytest.mark.vllm` to smoke tests under `tests/smoke/` that actually run real vLLM GPU generation and therefore require per-file process isolation (e.g., `test-smoke-gpu-*` Makefile targets). Do not apply `pytest.mark.vllm` to unit-style tests under `tests/generation/` that merely import `vllm_backend` but never instantiate a real vLLM engine and never call `.generate()` (GPU not required). Note that `tests/conftest.py` auto-marks these as `unit` via `pytest_collection_modifyitems`, and `vllm` is not among the auto-mark categories—so if a test in `tests/generation/` has `vllm`, it should be treated as a review issue unless it meets the real GPU generation criteria above.

Applied to files:

  • tests/generation/test_vllm_backend.py
  • tests/generation/test_regex_manager.py
🪛 ast-grep (0.44.1)
tests/generation/test_regex_manager.py

[warning] 463-463: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.fullmatch(regex, one_record)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)


[warning] 466-466: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.fullmatch(regex, two_records)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)


[warning] 470-470: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.fullmatch(regex, three_records)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)


[warning] 474-474: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.fullmatch(regex, two_groups)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

🪛 Ruff (0.15.21)
src/nemo_safe_synthesizer/training/huggingface_backend.py

[warning] 792-792: Value being cast to int is already an integer

Remove unnecessary int call

(RUF046)

🔇 Additional comments (3)
src/nemo_safe_synthesizer/config/generate.py (1)

168-188: LGTM!

src/nemo_safe_synthesizer/generation/vllm_backend.py (1)

396-402: LGTM!

Also applies to: 412-418

tests/generation/test_vllm_backend.py (1)

50-50: LGTM!

Also applies to: 204-210, 254-260, 287-293, 320-326

Comment thread src/nemo_safe_synthesizer/data_processing/assembler.py
Comment on lines +386 to +390
if config.data.group_training_examples_by is not None and max_records:
# Grouped generation produces one group per completion; a bounded single sequence
# (no outer repetition) guarantees a terminable, parseable group.
regex = sequence_regex
elif config.generation.structured_generation.use_single_sequence and config.data.max_sequences_per_example == 1:

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 | 🟠 Major | ⚡ Quick win

Keep record-count and sequence-count constraints independent.

A non-null per-group record bound currently forces exactly one group for both output formats, bypassing use_single_sequence and the previous multi-group behavior.

  • src/nemo_safe_synthesizer/generation/regex_manager.py#L386-L390: retain or separately bound the outer sequence repetition unless single-sequence generation is selected.
  • src/nemo_safe_synthesizer/generation/regex_manager.py#L472-L476: apply the same independent outer repetition to the structural-tag format.
📍 Affects 1 file
  • src/nemo_safe_synthesizer/generation/regex_manager.py#L386-L390 (this comment)
  • src/nemo_safe_synthesizer/generation/regex_manager.py#L472-L476

Source: Path instructions

Comment thread src/nemo_safe_synthesizer/llm/metadata.py
Signed-off-by: Matt Kornfield <mkornfield@nvidia.com>

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8da42f22-1222-4e14-a6dc-1301b6bdc06a

📥 Commits

Reviewing files that changed from the base of the PR and between 7f738d9 and 22a73b2.

📒 Files selected for processing (1)
  • src/nemo_safe_synthesizer/config/generate.py
📜 Review details
⏰ Context from checks skipped due to timeout. (10)
  • GitHub Check: Unit Tests (3.12)
  • GitHub Check: Smoke Tests
  • GitHub Check: Unit Tests (3.13)
  • GitHub Check: Unit Tests (3.11)
  • GitHub Check: End-user Wheel Install
  • GitHub Check: Greptile Review
  • GitHub Check: Typecheck
  • GitHub Check: Analyze (Python)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{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/config/generate.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: Use American English spelling in Python code, documentation, and messages.
Use Field(description=...) for every Pydantic model field.
Use assignment-style Field() by default; use Annotated only for additional metadata such as validators, constrained aliases, or discriminated unions.
Use @dataclass(frozen=True) for immutable value objects and validators; use mutable dataclasses only for builders, accumulators, and pipeline state.
Use field(default_factory=list) instead of mutable list defaults.
Use StrEnum for string-valued configuration or serialization enums and plain Enum for internal constants.
Obtain loggers with observability.get_logger(__name__); do not call logging.getLogger() or structlog.get_logger() directly.
Use .runtime, .user, and .system category loggers appropriately.
Do not use print() for operational library output; use the approved logger, click.echo(), or sys.stdout.write() where appropriate.
Use extra={} for machine-queryable logging data and f-strings only for human-readable context.
Raise errors from the custom Safe Synthesizer error hierarchy, using the documented dual inheritance for user and internal errors.
Keep shared package code compatible with Python 3.11; do not use Python 3.12-only syntax such as PEP 695 type statements or bracketed generic parameters.
Prefer X | Y, built-in collection generics, and Self over Optional, Union, and legacy typing collections.
Use collection ABCs for function arguments and concrete collection types for return values.
Use Protocol for structural subtyping and avoid Any when object, generics, or protocols are suitable.
Use TYPE_CHECKING guards for heavy imports such as pandas, torch, and transformers.
...

Files:

  • src/nemo_safe_synthesizer/config/generate.py
src/**/config/**/*.py

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

Use NSSBaseModel for user-facing configuration and parameter models.

Files:

  • src/nemo_safe_synthesizer/config/generate.py
src/**/*.py

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

src/**/*.py: Use relative imports for package code under src/.
Do not use assert for validation in library code; raise an appropriate exception instead.

Write Google-style docstrings for public Python APIs because API reference pages are generated from source docstrings.

Files:

  • src/nemo_safe_synthesizer/config/generate.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/config/generate.py
**/*

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

**/*: Every source file requires the SPDX copyright and license header appropriate to its file format.
End files with a newline, remove trailing whitespace, use one space between sentences, and keep code, comments, and docstrings within 120 characters.

**/*: All contributions must use verified Git commits and DCO sign-off; unsigned or unsigned-off commits cannot be merged.
Branches other than main must follow <author>/<description>, optionally including an issue ID or type; branch names must use lowercase alphanumeric characters and hyphens.
Commits merged to main must follow Conventional Commits, using a valid lowercase type and a description of at most 100 characters.

Files:

  • src/nemo_safe_synthesizer/config/generate.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/config/generate.py
**/*.{py,sh,yaml,yml,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

All Python, shell, YAML, YML, and Markdown source files require SPDX copyright headers.

Files:

  • src/nemo_safe_synthesizer/config/generate.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Shared Python package code must remain compatible with Python 3.11 syntax; do not use Python 3.12-only syntax such as PEP 695 type statements or bracketed generic class/function parameters.

Files:

  • src/nemo_safe_synthesizer/config/generate.py
**/*.{py,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use the repository's documented Python and Markdown style conventions and validate changes with the pinned mise formatting and checking tasks.

Files:

  • src/nemo_safe_synthesizer/config/generate.py
**/*.{py,sh}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use the repository's pinned mise tasks for formatting, linting, type checking, and testing rather than invoking ruff or ty directly for project-wide checks.

Files:

  • src/nemo_safe_synthesizer/config/generate.py
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/generate.py

Comment on lines +174 to +175
"Max records per grouped sequence under structured generation. "
"None uses the largest training group size. Must be None or >= 1."

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

Document the None fallback accurately.

None uses the largest training group size only when a training-derived default is provided. If that default is also None, the downstream regex and structural-tag builders use unbounded repetition. Update the description so CLI/config help does not promise a bound that may not exist.

Proposed fix
-                "None uses the largest training group size. Must be None or >= 1."
+                "When unset, uses the largest training group size when available; "
+                "otherwise, generation is unbounded. Must be None or >= 1."

As per path instructions, config changes are user-facing API changes and documented parameter semantics must match runtime behavior.

📝 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
"Max records per grouped sequence under structured generation. "
"None uses the largest training group size. Must be None or >= 1."
"Max records per grouped sequence under structured generation. "
"When unset, uses the largest training group size when available; "
"otherwise, generation is unbounded. Must be None or >= 1."

Source: Path instructions

Signed-off-by: Matt Kornfield <mkornfield@nvidia.com>
@coderabbitai coderabbitai Bot added the bug Defects in shipped behavior label Jul 22, 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: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 7cbb23ab-dc46-4af4-9708-0c558cb04318

📥 Commits

Reviewing files that changed from the base of the PR and between 22a73b2 and 3868216.

📒 Files selected for processing (4)
  • src/nemo_safe_synthesizer/data_processing/assembler.py
  • src/nemo_safe_synthesizer/llm/metadata.py
  • tests/data_processing/test_assembler.py
  • tests/llm/test_metadata.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/nemo_safe_synthesizer/data_processing/assembler.py
  • src/nemo_safe_synthesizer/llm/metadata.py
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{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:

  • tests/llm/test_metadata.py
  • tests/data_processing/test_assembler.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: Use American English spelling in Python code, documentation, and messages.
Use Field(description=...) for every Pydantic model field.
Use assignment-style Field() by default; use Annotated only for additional metadata such as validators, constrained aliases, or discriminated unions.
Use @dataclass(frozen=True) for immutable value objects and validators; use mutable dataclasses only for builders, accumulators, and pipeline state.
Use field(default_factory=list) instead of mutable list defaults.
Use StrEnum for string-valued configuration or serialization enums and plain Enum for internal constants.
Obtain loggers with observability.get_logger(__name__); do not call logging.getLogger() or structlog.get_logger() directly.
Use .runtime, .user, and .system category loggers appropriately.
Do not use print() for operational library output; use the approved logger, click.echo(), or sys.stdout.write() where appropriate.
Use extra={} for machine-queryable logging data and f-strings only for human-readable context.
Raise errors from the custom Safe Synthesizer error hierarchy, using the documented dual inheritance for user and internal errors.
Keep shared package code compatible with Python 3.11; do not use Python 3.12-only syntax such as PEP 695 type statements or bracketed generic parameters.
Prefer X | Y, built-in collection generics, and Self over Optional, Union, and legacy typing collections.
Use collection ABCs for function arguments and concrete collection types for return values.
Use Protocol for structural subtyping and avoid Any when object, generics, or protocols are suitable.
Use TYPE_CHECKING guards for heavy imports such as pandas, torch, and transformers.
...

Files:

  • tests/llm/test_metadata.py
  • tests/data_processing/test_assembler.py
**/test_*.py

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • tests/llm/test_metadata.py
  • tests/data_processing/test_assembler.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/llm/test_metadata.py
  • tests/data_processing/test_assembler.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/llm/test_metadata.py
  • tests/data_processing/test_assembler.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/llm/test_metadata.py
  • tests/data_processing/test_assembler.py
**/*

📄 CodeRabbit inference engine (STYLE_GUIDE.md)

**/*: Every source file requires the SPDX copyright and license header appropriate to its file format.
End files with a newline, remove trailing whitespace, use one space between sentences, and keep code, comments, and docstrings within 120 characters.

**/*: All contributions must use verified Git commits and DCO sign-off; unsigned or unsigned-off commits cannot be merged.
Branches other than main must follow <author>/<description>, optionally including an issue ID or type; branch names must use lowercase alphanumeric characters and hyphens.
Commits merged to main must follow Conventional Commits, using a valid lowercase type and a description of at most 100 characters.

Files:

  • tests/llm/test_metadata.py
  • tests/data_processing/test_assembler.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:

  • tests/llm/test_metadata.py
  • tests/data_processing/test_assembler.py
**/*.{py,sh,yaml,yml,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

All Python, shell, YAML, YML, and Markdown source files require SPDX copyright headers.

Files:

  • tests/llm/test_metadata.py
  • tests/data_processing/test_assembler.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Shared Python package code must remain compatible with Python 3.11 syntax; do not use Python 3.12-only syntax such as PEP 695 type statements or bracketed generic class/function parameters.

Files:

  • tests/llm/test_metadata.py
  • tests/data_processing/test_assembler.py
**/*.{py,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use the repository's documented Python and Markdown style conventions and validate changes with the pinned mise formatting and checking tasks.

Files:

  • tests/llm/test_metadata.py
  • tests/data_processing/test_assembler.py
**/*.{py,sh}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use the repository's pinned mise tasks for formatting, linting, type checking, and testing rather than invoking ruff or ty directly for project-wide checks.

Files:

  • tests/llm/test_metadata.py
  • tests/data_processing/test_assembler.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/llm/test_metadata.py
  • tests/data_processing/test_assembler.py
🔇 Additional comments (2)
tests/data_processing/test_assembler.py (1)

457-460: LGTM!

tests/llm/test_metadata.py (1)

729-742: LGTM!

Comment on lines +461 to +463
# Holdout groups must not inflate the training-derived generation bound.
assert examples.stats["records_per_group"].count == assembler.num_groups_train
assert assembler.stats_val["records_per_group"].count == assembler.num_groups_validation

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

Assert the training maximum, not only the count.

These assertions do not prove the stated holdout invariant: validation data could still affect examples.stats["records_per_group"].max while the count remains equal to num_groups_train. Assert the recorded training maximum against an independently known training-only value (and, if practical, the validation maximum separately).

As per path instructions, tests should prove the affected invariant rather than only exercising the code path.

Source: Path instructions

Comment on lines +708 to +727
def test_metadata_max_records_per_group_accepts_none_or_positive(
self, sample_prompt_config, mock_autoconfig_obj, sample_workdir
):
"""``None`` and values ``>= 1`` are valid persisted bounds."""
none_meta = ModelMetadata(
model_name_or_path="test-model",
prompt_config=sample_prompt_config,
autoconfig=mock_autoconfig_obj,
workdir=sample_workdir,
max_records_per_group=None,
)
assert none_meta.max_records_per_group is None
positive_meta = ModelMetadata(
model_name_or_path="test-model",
prompt_config=sample_prompt_config,
autoconfig=mock_autoconfig_obj,
workdir=sample_workdir,
max_records_per_group=3,
)
assert positive_meta.max_records_per_group == 3

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

Cover the inclusive lower bound with 1.

The contract says values >= 1 are valid, but the only positive case uses 3. Add 1 to the accepted cases so a validator that accidentally rejects the boundary cannot pass this test.

As per path instructions, tests should cover focused boundary behavior for changed validation logic.

Source: Path instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant