Skip to content

feat(audio-datasets): agent-ready initial-manifest stages (FLEURS, ReadSpeech)#2180

Open
shubhamNvidia wants to merge 2 commits into
NVIDIA-NeMo:mainfrom
shubhamNvidia:agent_pr/datasets
Open

feat(audio-datasets): agent-ready initial-manifest stages (FLEURS, ReadSpeech)#2180
shubhamNvidia wants to merge 2 commits into
NVIDIA-NeMo:mainfrom
shubhamNvidia:agent_pr/datasets

Conversation

@shubhamNvidia

Copy link
Copy Markdown
Contributor

Summary

Makes the dataset initial-manifest stages agent-ready. Additive only.

  • datasets/fleurs/create_initial_manifest.py, datasets/readspeech/create_initial_manifest.py — subclass AgentReady and add describe() / StageContract. These are source stages: they read a dataset layout and emit audio_filepath + metadata records, which the contract now declares. Manifest-creation logic unchanged.

Notes for reviewers

  • Additive, backward compatible.

Test plan

  • pytest tests/stages/audio/datasets -q

…idency resolver

Shared base imported by the audio stage modules to make them agent-ready:
- _agent_ready.py: AgentReady mixin, StageContract/IOSpec/Gates/Role
- _residency.py: input residency resolver (file/waveform/auto)
- common.py: agent-ready helpers (ensure_mono/ensure_waveform_2d)

Excludes the agent orchestration layer (registry/catalog/conformance/planning/agent).
@shubhamNvidia shubhamNvidia requested a review from a team as a code owner July 7, 2026 15:54
@shubhamNvidia shubhamNvidia requested review from praateekmahajan and removed request for a team July 7, 2026 15:54
@copy-pr-bot

copy-pr-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes the FLEURS and ReadSpeech initial-manifest stages agent-ready by introducing two new modules (_agent_ready.py, _residency.py) and retrofitting several existing stages in common.py with AgentReady mixin inheritance and describe() / StageContract implementations. All changes are additive and the manifest-creation logic is untouched.

  • _agent_ready.py defines the full agent-discovery vocabulary: AgentReady mixin, StageContract, IOSpec, Gates, ParamSpec, StaticHints, and JSON-schema helpers. The describe_static() class method references _agent_registry.static_contract via a lazy import, but _agent_registry.py does not exist in the repository, making that entire discovery path non-functional.
  • _residency.py adds helpers for resolving audio from file paths or in-memory waveforms, including a temp-WAV materializer used by downstream stages.
  • common.py, fleurs/create_initial_manifest.py, and readspeech/create_initial_manifest.py each gain AgentReady as a base and a describe() implementation that correctly declares cardinality, I/O keys, and gates.

Confidence Score: 4/5

Safe to merge if the test suite confirms describe_static() is not called anywhere; the processing logic is entirely unchanged and the additive contracts are correct for the instance path.

The manifest-creation logic for FLEURS and ReadSpeech is untouched, and all five instance-level describe() implementations look correct. The main concern is that describe_static() — a publicly advertised method on the new AgentReady mixin — will raise ImportError on every call because _agent_registry.py is absent from the repository. If agents or tooling rely on the static-discovery path, this will fail silently until exercised.

_agent_ready.py deserves another look around describe_static() and the _agent_registry dependency; _residency.py's _as_soundfile_array channel heuristic is worth verifying against short test fixtures.

Important Files Changed

Filename Overview
nemo_curator/stages/audio/_agent_ready.py New file introducing the AgentReady mixin, StageContract, IOSpec, Gates, and JSON-schema helpers. describe_static() references a missing _agent_registry module, making the static-discovery path broken at import time.
nemo_curator/stages/audio/_residency.py New file with audio-residency helpers (resolve_audio, resolve_audio_path, cleanup_temp_files, produce_audio_filepath). The channel-detection heuristic in _as_soundfile_array can misinterpret waveforms with more channels than samples.
nemo_curator/stages/audio/common.py Existing stages (GetAudioDurationStage, PreserveByValueStage, ManifestReaderStage, ManifestReader, ManifestWriterStage) now subclass AgentReady and implement describe(). ManifestReader's contract omits the audio_filepath write key. Logic unchanged.
nemo_curator/stages/audio/datasets/fleurs/create_initial_manifest.py CreateInitialManifestFleursStage now subclasses AgentReady with a correct describe() contract. Existing processing logic is unchanged.
nemo_curator/stages/audio/datasets/readspeech/create_initial_manifest.py CreateInitialManifestReadSpeechStage now subclasses AgentReady with a correct describe() contract. Existing processing logic is unchanged.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Agent
    participant Stage as AgentReady Stage
    participant Registry as _agent_registry (missing)

    Note over Agent,Stage: Instance-level discovery (works)
    Agent->>Stage: stage.describe()
    Stage-->>Agent: StageContract(writes, gates, cardinality)

    Note over Agent,Stage: Class-level discovery (broken)
    Agent->>Stage: Stage.describe_static()
    Stage->>Registry: import static_contract
    Registry-->>Stage: ImportError

    Note over Agent,Stage: Normal pipeline execution (unaffected)
    Agent->>Stage: stage.process(task)
    Stage-->>Agent: AudioTask(audio_filepath, text, ...)
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 Agent
    participant Stage as AgentReady Stage
    participant Registry as _agent_registry (missing)

    Note over Agent,Stage: Instance-level discovery (works)
    Agent->>Stage: stage.describe()
    Stage-->>Agent: StageContract(writes, gates, cardinality)

    Note over Agent,Stage: Class-level discovery (broken)
    Agent->>Stage: Stage.describe_static()
    Stage->>Registry: import static_contract
    Registry-->>Stage: ImportError

    Note over Agent,Stage: Normal pipeline execution (unaffected)
    Agent->>Stage: stage.process(task)
    Stage-->>Agent: AudioTask(audio_filepath, text, ...)
Loading

Reviews (1): Last reviewed commit: "feat(audio-datasets): make datasets stag..." | Re-trigger Greptile

Comment on lines +301 to +313
@classmethod
def describe_static(cls) -> StageContract:
"""Instance-free contract for discovery/planning.

Uses class defaults + ``AGENT_STATIC`` and never runs ``__init__`` side
effects, so it is safe for stages with required constructor args. Prefer
the instance-level :meth:`describe` (or
``_agent_registry.build_contract``) when resolved key *values* are
needed.
"""
from nemo_curator.stages.audio._agent_registry import static_contract

return static_contract(cls)

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 describe_static() always raises ImportError

describe_static() lazily imports static_contract from nemo_curator.stages.audio._agent_registry, but _agent_registry.py does not exist in the repository — it is the only file referenced from _agent_ready.py that is absent. Any call to AgentReady.describe_static() (e.g., CreateInitialManifestFleursStage.describe_static()) will immediately raise ImportError: cannot import name 'static_contract' from 'nemo_curator.stages.audio._agent_registry', making the entire static-discovery path broken. The instance-level describe() is unaffected, but this makes the describe_static / AGENT_STATIC class-variable machinery dead code right now.

Comment on lines +76 to +81
if getattr(waveform, "ndim", 0) == 2: # noqa: PLR2004 - 2 == a (channels, samples) 2-D array
channels, samples = waveform.shape
if channels == 1:
return waveform[0]
if channels < samples:
return waveform.T

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 Channel-heuristic fails for very short audio clips

The heuristic channels < samples used to detect whether the waveform is stored in (channels, samples) vs (samples, channels) layout breaks for any clip shorter than the channel count. For example, a 2-channel, 1-sample waveform has channels=2, samples=1; 2 < 1 is False, so the transpose is skipped and the array is passed to sf.write in the wrong orientation, producing corrupted output. In practice audio clips are long, but this could silently corrupt temp WAVs generated from synthetic or test fixtures with very few samples.

Comment on lines +211 to +214
if value is None or isinstance(value, (str, int, float, bool)):
return value
if isinstance(value, (list, tuple, set)):
return [_jsonable_default(v) for v in value]

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 set iteration in _jsonable_default is non-deterministic

When a ParamSpec.default or choices contains a set, _jsonable_default iterates it and returns a list, but sets are unordered in CPython 3.7+, so repeated calls may produce different orderings. Downstream consumers that compare serialized contracts (e.g., caching or hashing) may see spurious differences. Sorting the elements before building the list (or converting to a sorted list at the point where set defaults are declared) would make the output stable.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +252 to +253
def describe(self) -> StageContract:
return StageContract(cardinality="1:N fan-out", wrappable=False)

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 ManifestReader.describe() omits the audio_filepath write from its contract

ManifestReader decomposes into FilePartitioningStage + ManifestReaderStage, and the inner ManifestReaderStage.describe() correctly advertises writes=IOSpec(data_keys=["audio_filepath"], ...). But ManifestReader.describe() returns only StageContract(cardinality="1:N fan-out", wrappable=False) with no writes declared. An agent consuming the composite stage's contract won't know that audio_filepath is produced, so it cannot chain a downstream stage that reads that key using the static-discovery path. Since wrappable=False prevents wrapping, the agent must fall back to inspecting the decomposed stages manually.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant