Skip to content

(fix) Move mlflow/wandb out of core deps and into extras - #3547

Merged
hwolff99 merged 9 commits into
mainfrom
hwolff/optional-exporter-deps
Oct 4, 2026
Merged

hwolff99 merged 9 commits into
mainfrom
hwolff/optional-exporter-deps

Conversation

@hwolff99

@hwolff99 hwolff99 commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Moves optional exporter deps into pyproject.toml extras so they are not automatically installed.
Closes #3207

Checklist

  • I have read the contributing guidelines.
  • The change is focused; unrelated "drive-by" edits are tracked as separate issues/PRs.
  • Tests added or updated and pass locally, or N/A for docs-only / non-code changes (so CI unit/server checks pass when applicable).
  • Documentation added or updated, or N/A with justification.
  • Pre-commit checks pass locally (pre-commit run --all-files) (so CI lint/format checks pass).
  • New source files include the required Apache-2.0 SPDX header.
  • All commits have DCO sign-off (git commit -s) (so the DCO check passes).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: haydeniw <hwolff@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Sep 18, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

hwolff99 and others added 2 commits September 18, 2026 15:42
…ency change

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: haydeniw <hwolff@nvidia.com>
gitlab_utils.py's GitLab model-registry integration imported mlflow eagerly at module
top-level, breaking any import of nemo_gym.cli.dataset / nemo_gym.dataset_orchestrator
without mlflow installed. create_mlflow_client() now imports mlflow lazily and raises an
actionable ConfigError (pip install nemo-gym[mlflow]) instead of an opaque ImportError.

reward_profile.py was the bigger find: `from wandb import Histogram` at module top-level
made wandb a de-facto core dependency of nearly all of Gym (via base_resources_server.py /
base_responses_api_agent.py), even though the histogram stat is always stripped before it
reaches any output or exporter. Made that import lazy too, falling back to None when wandb
isn't installed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: haydeniw <hwolff@nvidia.com>
@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

The generic except Exception fallback already surfaces a missing SDK's ImportError
message without crashing setup_exporters, so this extra ImportError-specific branch
wasn't necessary. Reverts the registry 3-tuple and its test in test_exporters.py too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: haydeniw <hwolff@nvidia.com>
@hwolff99
hwolff99 marked this pull request as ready for review September 21, 2026 05:24
@hwolff99
hwolff99 requested a review from a team as a code owner September 21, 2026 05:24
@nemo-automation-bot
nemo-automation-bot Bot requested review from a team September 21, 2026 05:24
Comment thread nemo_gym/reward_profile.py Resolved
Comment thread nemo_gym/gitlab_utils.py Resolved
Comment thread tests/unit_tests/test_exporters.py Resolved
@yaoyu-33 yaoyu-33 added area:build Packaging, dependencies, containers, and releases bug Something isn't working complexity:medium Single-domain change with interacting parts or a moderate review surface waiting-on-customer Waiting on the original author to respond labels Sep 21, 2026
@github-actions github-actions Bot added the sla:author-overdue Author response is over the one-business-day SLA label Sep 22, 2026
setup_exporters now catches ImportError specifically and names the extra to install
(pip install nemo-gym[mlflow] / [wandb]) instead of relying on the generic except
Exception fallback's raw exception text.

This restores the change dropped in 580e893. That revert was a mistake: I judged the
change unnecessary since setup_exporters already failed gracefully, but PR review
(#3547) asked for exactly this behavior, plus extending it to rollout
collection.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: haydeniw <hwolff@nvidia.com>
Signed-off-by: haydeniw <hwolff@nvidia.com>
Signed-off-by: haydeniw <hwolff@nvidia.com>
@hwolff99
hwolff99 added this pull request to the merge queue Oct 4, 2026
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/37224471699

Merged via the queue into main with commit 128eab4 Oct 4, 2026
43 checks passed
@hwolff99
hwolff99 deleted the hwolff/optional-exporter-deps branch October 4, 2026 18:41
khazic pushed a commit to khazic/Gym-lao that referenced this pull request Oct 5, 2026
…the migration script (NVIDIA-NeMo#3929)

## What does this PR do?

Since NVIDIA-NeMo#3724, config validation rejects any agent that no environment
server names, with `AgentWithoutEnvironmentServerError`. A config
written before environment servers therefore stops loading when Gym is
upgraded. This also affects configs in this repository:
`resources_servers/wordle/configs/wordle.yaml` and
`environments/wordle/config.yaml` fail to parse on `main`.

This PR turns that error into a deprecation, so these configs keep
running while users migrate. It also fixes the migration script so it
can update configs outside this repository.

### What happens to an agent without an environment server

During config parsing, Gym finds each agent that no environment server
names. For each one, it adds a `legacy_agent` environment server in
front of the agent. A `legacy_agent` server relays `/run` to its agent
unchanged, so rollouts and results are the same as before. Gym then logs
one deprecation warning:

```
DEPRECATED: agents without an environment server: `workplace_assistant_simple_agent`.
Rollout collection reaches an agent only through an environment server.
This run generated a legacy_agent environment server for each agent above.
Each one relays /run to its agent unchanged, so results are not affected.
A future release will reject these configs with AgentWithoutEnvironmentServerError.

To migrate, declare the environment servers in your config. Either:

1. Run the migration script from a NeMo Gym checkout on your config files or directories:

     python scripts/add_legacy_agent_environment_servers.py path/to/config.yaml [more paths] [--check]

   --check reports what would change without writing.

2. Or add these blocks yourself, at the same level as the agents:

     workplace_assistant_environment_server:
       environment_servers:
         legacy_agent:
           entrypoint: app.py
           agent_server:
             type: responses_api_agents
             name: workplace_assistant_simple_agent

To make this an error now, set error_on_agent_without_environment_server: true in your config.
You can also pass +error_on_agent_without_environment_server=true on the command line.
```

- The generated server is named the way the migration script names it:
the agent type is stripped from the agent name. For example,
`workplace_assistant_simple_agent` gets
`workplace_assistant_environment_server`. The block in the warning is
therefore the block the script writes. If that name is already taken,
the server is named `<agent>_environment_server` instead.
- Gym only adds a server for an agent that has none. An agent with an
existing environment server is left alone, so the new server can never
make agent routing ambiguous.
- Setting `error_on_agent_without_environment_server: true` restores the
error. It follows the existing `error_on_almost_servers` switch. The
error message now also points at the migration script.
- Renaming an agent with `_inherit_from` moves it to the new name. An
environment server that named the old agent is then left pointing at an
agent that no longer exists, and parsing fails with
`ServerRefNotFoundError`. That error now says the environment server
must be renamed with the agent, and points at the script.

The test this PR replaces explained why the error existed: without it, a
config written before environment servers "would otherwise run, silently
dispatching straight to the agent." The generated server keeps that
guarantee. Rollout collection still reaches the agent only through an
environment server, and the warning names every server Gym added.

### Fixes to `scripts/add_legacy_agent_environment_servers.py`

- The script now accepts config files and directories as arguments.
Before, it could only update this repository's configs, so users had no
way to migrate their own. Run without arguments, it still updates this
repository's configs.
- The script now skips an agent that any environment server already
names. Before, it only checked whether a server with the generated name
existed. In `benchmarks/swebench/pro/hermes.yaml`, a
`single_agent_turn_legacy` server already fronts the agent under a
different name, so the script added a second server. Two servers in
front of one agent make agent routing ambiguous, and rollout collection
then fails.
- The script now handles an agent that only renames another one, such as
`renamed_agent: {_inherit_from: some_agent}`. Before, such configs were
skipped because the new instance has no agent body of its own. The
script now writes a server for the new name that inherits the original
agent's server. Because `_inherit_from` moves the original server, the
renamed agent ends up with exactly one server.
- A config that cannot be read or parsed is now reported, and the script
exits with 2. Before, such files were skipped without any message.

`test_langchain_deepagents_configs_have_environment_servers` now parses
with `error_on_agent_without_environment_server: true`. The test checks
that in-repo configs declare their environment server. A generated
server would have the same name, so without strict mode the test would
pass even when the declaration is missing.

The script's `--check` mode reports six configs on `main` that declare
no environment server: two `wordle` configs, two `swemer_oml` configs,
and two `swe_internal_v1` configs. With this PR they load with the
deprecation warning. Migrating them is left to their owners.

No issue is linked. This is a compatibility follow-up to NVIDIA-NeMo#3724.

## Validation

- `pytest tests/unit_tests/test_global_config.py`, covering the
generated server, its name, the warning text, strict mode, an agent that
already has a server, a name collision, a full `parse()` of a config
without environment servers, and the rename hint on
`ServerRefNotFoundError`.
- `pytest tests/unit_tests/test_add_legacy_agent_environment_servers.py`
(new), covering configs outside the repository, `--check` not writing,
an agent that already has a server, a rename through `_inherit_from`, an
unreadable config, and that the script and Gym name servers the same
way. The behavior tests fail against the script on `main`.
- Six configs outside this repository that failed validation were
migrated with the script, covering both added agents and `_inherit_from`
renames. Each migrated config was then parsed with
`GlobalConfigDictParser` and `error_on_agent_without_environment_server:
true`. An unmigrated copy, parsed the same way, raised
`AgentWithoutEnvironmentServerError`.
- `pytest tests/unit_tests/test_rollout_collection.py
tests/unit_tests/test_environment_scaffold.py`.
- `pytest tests/unit_tests`. The tests that fail in this environment
fail identically on `main`. They need the `sandbox` extra, or `mlflow`
and `wandb`, which NVIDIA-NeMo#3547 moved into extras.
- `pre-commit run --all-files`.

## Rollouts

`example_single_tool_call` was run with its environment server block
removed from the config, against `Qwen/Qwen3-0.6B` served by a local
vLLM with the hermes tool parser:

- `gym env start` logged the deprecation warning and started the
generated environment server alongside the agent, the resources server,
and the model server.
- `gym eval run --no-serve` completed every requested rollout without
failures. Each record has the generated server in
`_ng_environment_server` and `legacy_agent` in `_ng_result_type`.
- When the model called `get_weather`, the verifier scored the rollout
1.0. When it answered in prose without calling the tool, the verifier
scored it 0.0.

Dispatch through the generated server has not changed since that run.
Only the server's name and the warning text have.

## Compatibility and benchmark impact

- Configs that failed with `AgentWithoutEnvironmentServerError` now load
and log a deprecation warning. Results do not change, because
`legacy_agent` relays `/run` to the agent unchanged.
- Users who want the error back can set
`error_on_agent_without_environment_server: true`. The warning tells
users that a future release will make this the default.
- No benchmark impact.

Proposed labels: `feature`, `area:config`.

## Checklist

- [x] I have read the [contributing
guidelines](https://github.com/NVIDIA-NeMo/Gym/blob/main/CONTRIBUTING.md).
- [x] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [x] Tests added or updated and pass locally, or N/A for docs-only /
non-code changes (so CI unit/server checks pass when applicable).
- [x] Documentation added or updated, or N/A with justification.
- [x] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format checks pass).
- [x] New source files include the required Apache-2.0 SPDX header.
- [x] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
ananthsub added a commit to ananthsub/Gym that referenced this pull request Oct 5, 2026
…the migration script (NVIDIA-NeMo#3929)

Since NVIDIA-NeMo#3724, config validation rejects any agent that no environment
server names, with `AgentWithoutEnvironmentServerError`. A config
written before environment servers therefore stops loading when Gym is
upgraded. This also affects configs in this repository:
`resources_servers/wordle/configs/wordle.yaml` and
`environments/wordle/config.yaml` fail to parse on `main`.

This PR turns that error into a deprecation, so these configs keep
running while users migrate. It also fixes the migration script so it
can update configs outside this repository.

During config parsing, Gym finds each agent that no environment server
names. For each one, it adds a `legacy_agent` environment server in
front of the agent. A `legacy_agent` server relays `/run` to its agent
unchanged, so rollouts and results are the same as before. Gym then logs
one deprecation warning:

```
DEPRECATED: agents without an environment server: `workplace_assistant_simple_agent`.
Rollout collection reaches an agent only through an environment server.
This run generated a legacy_agent environment server for each agent above.
Each one relays /run to its agent unchanged, so results are not affected.
A future release will reject these configs with AgentWithoutEnvironmentServerError.

To migrate, declare the environment servers in your config. Either:

1. Run the migration script from a NeMo Gym checkout on your config files or directories:

     python scripts/add_legacy_agent_environment_servers.py path/to/config.yaml [more paths] [--check]

   --check reports what would change without writing.

2. Or add these blocks yourself, at the same level as the agents:

     workplace_assistant_environment_server:
       environment_servers:
         legacy_agent:
           entrypoint: app.py
           agent_server:
             type: responses_api_agents
             name: workplace_assistant_simple_agent

To make this an error now, set error_on_agent_without_environment_server: true in your config.
You can also pass +error_on_agent_without_environment_server=true on the command line.
```

- The generated server is named the way the migration script names it:
the agent type is stripped from the agent name. For example,
`workplace_assistant_simple_agent` gets
`workplace_assistant_environment_server`. The block in the warning is
therefore the block the script writes. If that name is already taken,
the server is named `<agent>_environment_server` instead.
- Gym only adds a server for an agent that has none. An agent with an
existing environment server is left alone, so the new server can never
make agent routing ambiguous.
- Setting `error_on_agent_without_environment_server: true` restores the
error. It follows the existing `error_on_almost_servers` switch. The
error message now also points at the migration script.
- Renaming an agent with `_inherit_from` moves it to the new name. An
environment server that named the old agent is then left pointing at an
agent that no longer exists, and parsing fails with
`ServerRefNotFoundError`. That error now says the environment server
must be renamed with the agent, and points at the script.

The test this PR replaces explained why the error existed: without it, a
config written before environment servers "would otherwise run, silently
dispatching straight to the agent." The generated server keeps that
guarantee. Rollout collection still reaches the agent only through an
environment server, and the warning names every server Gym added.

- The script now accepts config files and directories as arguments.
Before, it could only update this repository's configs, so users had no
way to migrate their own. Run without arguments, it still updates this
repository's configs.
- The script now skips an agent that any environment server already
names. Before, it only checked whether a server with the generated name
existed. In `benchmarks/swebench/pro/hermes.yaml`, a
`single_agent_turn_legacy` server already fronts the agent under a
different name, so the script added a second server. Two servers in
front of one agent make agent routing ambiguous, and rollout collection
then fails.
- The script now handles an agent that only renames another one, such as
`renamed_agent: {_inherit_from: some_agent}`. Before, such configs were
skipped because the new instance has no agent body of its own. The
script now writes a server for the new name that inherits the original
agent's server. Because `_inherit_from` moves the original server, the
renamed agent ends up with exactly one server.
- A config that cannot be read or parsed is now reported, and the script
exits with 2. Before, such files were skipped without any message.

`test_langchain_deepagents_configs_have_environment_servers` now parses
with `error_on_agent_without_environment_server: true`. The test checks
that in-repo configs declare their environment server. A generated
server would have the same name, so without strict mode the test would
pass even when the declaration is missing.

The script's `--check` mode reports six configs on `main` that declare
no environment server: two `wordle` configs, two `swemer_oml` configs,
and two `swe_internal_v1` configs. With this PR they load with the
deprecation warning. Migrating them is left to their owners.

No issue is linked. This is a compatibility follow-up to NVIDIA-NeMo#3724.

- `pytest tests/unit_tests/test_global_config.py`, covering the
generated server, its name, the warning text, strict mode, an agent that
already has a server, a name collision, a full `parse()` of a config
without environment servers, and the rename hint on
`ServerRefNotFoundError`.
- `pytest tests/unit_tests/test_add_legacy_agent_environment_servers.py`
(new), covering configs outside the repository, `--check` not writing,
an agent that already has a server, a rename through `_inherit_from`, an
unreadable config, and that the script and Gym name servers the same
way. The behavior tests fail against the script on `main`.
- Six configs outside this repository that failed validation were
migrated with the script, covering both added agents and `_inherit_from`
renames. Each migrated config was then parsed with
`GlobalConfigDictParser` and `error_on_agent_without_environment_server:
true`. An unmigrated copy, parsed the same way, raised
`AgentWithoutEnvironmentServerError`.
- `pytest tests/unit_tests/test_rollout_collection.py
tests/unit_tests/test_environment_scaffold.py`.
- `pytest tests/unit_tests`. The tests that fail in this environment
fail identically on `main`. They need the `sandbox` extra, or `mlflow`
and `wandb`, which NVIDIA-NeMo#3547 moved into extras.
- `pre-commit run --all-files`.

`example_single_tool_call` was run with its environment server block
removed from the config, against `Qwen/Qwen3-0.6B` served by a local
vLLM with the hermes tool parser:

- `gym env start` logged the deprecation warning and started the
generated environment server alongside the agent, the resources server,
and the model server.
- `gym eval run --no-serve` completed every requested rollout without
failures. Each record has the generated server in
`_ng_environment_server` and `legacy_agent` in `_ng_result_type`.
- When the model called `get_weather`, the verifier scored the rollout
1.0. When it answered in prose without calling the tool, the verifier
scored it 0.0.

Dispatch through the generated server has not changed since that run.
Only the server's name and the warning text have.

- Configs that failed with `AgentWithoutEnvironmentServerError` now load
and log a deprecation warning. Results do not change, because
`legacy_agent` relays `/run` to the agent unchanged.
- Users who want the error back can set
`error_on_agent_without_environment_server: true`. The warning tells
users that a future release will make this the default.
- No benchmark impact.

Proposed labels: `feature`, `area:config`.

- [x] I have read the [contributing
guidelines](https://github.com/NVIDIA-NeMo/Gym/blob/main/CONTRIBUTING.md).
- [x] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [x] Tests added or updated and pass locally, or N/A for docs-only /
non-code changes (so CI unit/server checks pass when applicable).
- [x] Documentation added or updated, or N/A with justification.
- [x] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format checks pass).
- [x] New source files include the required Apache-2.0 SPDX header.
- [x] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).


---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
(cherry picked from commit 1f2446c)
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub added a commit to ananthsub/Gym that referenced this pull request Oct 6, 2026
…the migration script (NVIDIA-NeMo#3929)

Since NVIDIA-NeMo#3724, config validation rejects any agent that no environment
server names, with `AgentWithoutEnvironmentServerError`. A config
written before environment servers therefore stops loading when Gym is
upgraded. This also affects configs in this repository:
`resources_servers/wordle/configs/wordle.yaml` and
`environments/wordle/config.yaml` fail to parse on `main`.

This PR turns that error into a deprecation, so these configs keep
running while users migrate. It also fixes the migration script so it
can update configs outside this repository.

During config parsing, Gym finds each agent that no environment server
names. For each one, it adds a `legacy_agent` environment server in
front of the agent. A `legacy_agent` server relays `/run` to its agent
unchanged, so rollouts and results are the same as before. Gym then logs
one deprecation warning:

```
DEPRECATED: agents without an environment server: `workplace_assistant_simple_agent`.
Rollout collection reaches an agent only through an environment server.
This run generated a legacy_agent environment server for each agent above.
Each one relays /run to its agent unchanged, so results are not affected.
A future release will reject these configs with AgentWithoutEnvironmentServerError.

To migrate, declare the environment servers in your config. Either:

1. Run the migration script from a NeMo Gym checkout on your config files or directories:

     python scripts/add_legacy_agent_environment_servers.py path/to/config.yaml [more paths] [--check]

   --check reports what would change without writing.

2. Or add these blocks yourself, at the same level as the agents:

     workplace_assistant_environment_server:
       environment_servers:
         legacy_agent:
           entrypoint: app.py
           agent_server:
             type: responses_api_agents
             name: workplace_assistant_simple_agent

To make this an error now, set error_on_agent_without_environment_server: true in your config.
You can also pass +error_on_agent_without_environment_server=true on the command line.
```

- The generated server is named the way the migration script names it:
the agent type is stripped from the agent name. For example,
`workplace_assistant_simple_agent` gets
`workplace_assistant_environment_server`. The block in the warning is
therefore the block the script writes. If that name is already taken,
the server is named `<agent>_environment_server` instead.
- Gym only adds a server for an agent that has none. An agent with an
existing environment server is left alone, so the new server can never
make agent routing ambiguous.
- Setting `error_on_agent_without_environment_server: true` restores the
error. It follows the existing `error_on_almost_servers` switch. The
error message now also points at the migration script.
- Renaming an agent with `_inherit_from` moves it to the new name. An
environment server that named the old agent is then left pointing at an
agent that no longer exists, and parsing fails with
`ServerRefNotFoundError`. That error now says the environment server
must be renamed with the agent, and points at the script.

The test this PR replaces explained why the error existed: without it, a
config written before environment servers "would otherwise run, silently
dispatching straight to the agent." The generated server keeps that
guarantee. Rollout collection still reaches the agent only through an
environment server, and the warning names every server Gym added.

- The script now accepts config files and directories as arguments.
Before, it could only update this repository's configs, so users had no
way to migrate their own. Run without arguments, it still updates this
repository's configs.
- The script now skips an agent that any environment server already
names. Before, it only checked whether a server with the generated name
existed. In `benchmarks/swebench/pro/hermes.yaml`, a
`single_agent_turn_legacy` server already fronts the agent under a
different name, so the script added a second server. Two servers in
front of one agent make agent routing ambiguous, and rollout collection
then fails.
- The script now handles an agent that only renames another one, such as
`renamed_agent: {_inherit_from: some_agent}`. Before, such configs were
skipped because the new instance has no agent body of its own. The
script now writes a server for the new name that inherits the original
agent's server. Because `_inherit_from` moves the original server, the
renamed agent ends up with exactly one server.
- A config that cannot be read or parsed is now reported, and the script
exits with 2. Before, such files were skipped without any message.

`test_langchain_deepagents_configs_have_environment_servers` now parses
with `error_on_agent_without_environment_server: true`. The test checks
that in-repo configs declare their environment server. A generated
server would have the same name, so without strict mode the test would
pass even when the declaration is missing.

The script's `--check` mode reports six configs on `main` that declare
no environment server: two `wordle` configs, two `swemer_oml` configs,
and two `swe_internal_v1` configs. With this PR they load with the
deprecation warning. Migrating them is left to their owners.

No issue is linked. This is a compatibility follow-up to NVIDIA-NeMo#3724.

- `pytest tests/unit_tests/test_global_config.py`, covering the
generated server, its name, the warning text, strict mode, an agent that
already has a server, a name collision, a full `parse()` of a config
without environment servers, and the rename hint on
`ServerRefNotFoundError`.
- `pytest tests/unit_tests/test_add_legacy_agent_environment_servers.py`
(new), covering configs outside the repository, `--check` not writing,
an agent that already has a server, a rename through `_inherit_from`, an
unreadable config, and that the script and Gym name servers the same
way. The behavior tests fail against the script on `main`.
- Six configs outside this repository that failed validation were
migrated with the script, covering both added agents and `_inherit_from`
renames. Each migrated config was then parsed with
`GlobalConfigDictParser` and `error_on_agent_without_environment_server:
true`. An unmigrated copy, parsed the same way, raised
`AgentWithoutEnvironmentServerError`.
- `pytest tests/unit_tests/test_rollout_collection.py
tests/unit_tests/test_environment_scaffold.py`.
- `pytest tests/unit_tests`. The tests that fail in this environment
fail identically on `main`. They need the `sandbox` extra, or `mlflow`
and `wandb`, which NVIDIA-NeMo#3547 moved into extras.
- `pre-commit run --all-files`.

`example_single_tool_call` was run with its environment server block
removed from the config, against `Qwen/Qwen3-0.6B` served by a local
vLLM with the hermes tool parser:

- `gym env start` logged the deprecation warning and started the
generated environment server alongside the agent, the resources server,
and the model server.
- `gym eval run --no-serve` completed every requested rollout without
failures. Each record has the generated server in
`_ng_environment_server` and `legacy_agent` in `_ng_result_type`.
- When the model called `get_weather`, the verifier scored the rollout
1.0. When it answered in prose without calling the tool, the verifier
scored it 0.0.

Dispatch through the generated server has not changed since that run.
Only the server's name and the warning text have.

- Configs that failed with `AgentWithoutEnvironmentServerError` now load
and log a deprecation warning. Results do not change, because
`legacy_agent` relays `/run` to the agent unchanged.
- Users who want the error back can set
`error_on_agent_without_environment_server: true`. The warning tells
users that a future release will make this the default.
- No benchmark impact.

Proposed labels: `feature`, `area:config`.

- [x] I have read the [contributing
guidelines](https://github.com/NVIDIA-NeMo/Gym/blob/main/CONTRIBUTING.md).
- [x] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [x] Tests added or updated and pass locally, or N/A for docs-only /
non-code changes (so CI unit/server checks pass when applicable).
- [x] Documentation added or updated, or N/A with justification.
- [x] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format checks pass).
- [x] New source files include the required Apache-2.0 SPDX header.
- [x] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).

---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
(cherry picked from commit 1f2446c)
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
khazic pushed a commit to khazic/Gym-lao that referenced this pull request Oct 15, 2026
…EADME (NVIDIA-NeMo#4574)

## What does this PR do?

The math_with_judge README's "Prepare for trajectory collection" command
works again. It loads `configs/dapo17k_trajectory_collection.yaml`,
which no longer exists, so `gym dataset collate` exited with
`ConfigPathNotFoundError` before downloading anything. This PR restores
that config in the current schema, documents the step's prerequisites,
and adds a regression test that replays the README command.

Running the README's other GitLab download, OpenMathReasoning, from
outside the Gym root exposed a second bug: the GitLab downloader did not
create the output directory. The second commit fixes it.

Fixes NVBug [6847517](https://nvbugspro.nvidia.com/bug/6847517).
Closes NVIDIA-NeMo#4498.

### Cause

NVIDIA-NeMo#859 ("Gym 0.2.0 huggingface dataset pointers") deleted three dapo17k
variants under `resources_servers/math_with_judge/configs/`, including
`dapo17k_trajectory_collection.yaml`, and removed their root-README
rows. The math_with_judge README, which has cited that file since NVIDIA-NeMo#291,
was not updated. Config resolution (`nemo_gym/global_config.py`
`load_extra_config_paths`) then raises `ConfigPathNotFoundError`.

`dapo17k.yaml` is not a drop-in replacement. The deleted config put the
DAPO17k train problems in the **validation** split, repeated 16 times,
because NeMo RL with `is_trajectory_collection: true` collects from the
validation dataloader only. `dapo17k.yaml` has DAPO17k as `train` ×1 and
AIME24 ×32 as `validation`, so trajectory collection would run on
AIME24.

Restoring the old file verbatim would not work either. It set
`judge_model_server.name: ???`, and since NVIDIA-NeMo#1575 the parser rejects any
`???` left after merging (`ConfigMissingValuesError`).

The second bug is in `download_jsonl_dataset`
(`nemo_gym/gitlab_utils.py`). It opened `output_fpath` for writing
without creating its directory. A Gym checkout tracks `data/`, so the
README's `--output data/open_math_reasoning_problems.jsonl` works from
the Gym root but fails with `FileNotFoundError` from any other
directory. `gym dataset collate --download` fails the same way for
GitLab-backed datasets. The Hugging Face download already creates the
directory (`hf_utils.py`).

### Fix

- Restore `configs/dapo17k_trajectory_collection.yaml`. The validation
split is the `dapo17k` 0.0.1 `dapo17k_train.jsonl` artifact (the same
GitLab source as `dapo17k.yaml`), `num_repeats: 16`. Grading uses
math-verify only (`should_use_judge: false`), so
`judge_model_server.name` is `policy_model` and the README command needs
no override. The file has `description`/`value` and the
environment-server block like `dapo17k.yaml`. The `update-readme-table`
hook re-adds its root-README row.
- It keeps its own local file,
`data/dapo17k_trajectory_collection_train.jsonl`, as before NVIDIA-NeMo#859.
Collate checks each dataset against `<jsonl stem>_metrics.json`. Sharing
`dapo17k.yaml`'s `dapo17k_train.jsonl` would compare the ×16 validation
stats with that file's train sidecar and fail (`train != validation`,
`17398 != 278368`).
- Add `data/dapo17k_trajectory_collection_train_metrics.json`, written
by a real collate run (278368 rows = 17398 × 16). Its word and turn
statistics match `dapo17k_train_metrics.json` exactly, so the rows are
the same.
- README: the command is unchanged. One note says the GitLab downloads
in this README (the OpenMathReasoning download and this `--download`)
need the `mlflow` extra (optional since NVIDIA-NeMo#3547) and `mlflow_tracking_uri`
/ `mlflow_tracking_token` in `env.yaml`. The section says what it writes
and why the split is validation.

Test:
`tests/unit_tests/test_math_with_judge_readme_trajectory_config.py`
takes the README's `gym dataset collate` block and runs it through the
real CLI with `dispatch` intercepted. It resolves the config offline (no
`env.yaml`), checks the single dataset is `validation` ×16 with
`dapo17k.yaml`'s GitLab identifier, and checks the committed sidecar
describes that split. It then runs `TrainDataProcessor` on a three-row
fixture in a temporary directory, without network access, and checks
that only `validation.jsonl` and its metrics come out, with each
question repeated 16 times in order.

- `download_jsonl_dataset` now creates the output's parent directory, as
the Hugging Face download does. A new test in
`tests/unit_tests/test_gitlab_utils.py` fakes the registry lookup and
the HTTP fetch, then downloads into a missing nested directory. It fails
before this change with the same `FileNotFoundError` and passes after.

These plausible wrong fixes all fail the trajectory test:
- restoring the file verbatim (`???`);
- pointing the README at `dapo17k.yaml` with a judge override;
- `num_repeats: 1`;
- a `train` split;
- sharing `dapo17k_train.jsonl`.

### Before the fix

Gym `977583d3d` (`main`), the README command from the Gym root:

```bash
gym dataset collate \
    --config resources_servers/math_with_judge/configs/dapo17k_trajectory_collection.yaml \
    --config responses_api_models/openai_model/configs/openai_model.yaml \
    --output-dir data/dapo17k_trajectory_collection \
    --mode train_preparation \
    --download
```

```text
nemo_gym.config_types.ConfigPathNotFoundError: config_paths entry 'resources_servers/math_with_judge/configs/dapo17k_trajectory_collection.yaml' was not found. Looked in:
  - <gym>/resources_servers/math_with_judge/configs/dapo17k_trajectory_collection.yaml
Check the path is spelled correctly and is relative to your working directory, an extra root
(NEMO_GYM_EXTRA_ROOTS / --search-dir), or the Gym install root.
EXIT=1
```

The README's OpenMathReasoning GitLab download, from a directory without
`data/`, same base:

```bash
gym dataset download --storage gitlab --name math_open_math_reasoning --revision 0.0.1 \
    --artifact open_math_reasoning_problems.jsonl --output data/open_math_reasoning_problems.jsonl
```

```text
  File ".../nemo_gym/gitlab_utils.py", line 111, in download_jsonl_dataset
    with open(config.output_fpath, "w") as f:
FileNotFoundError: [Errno 2] No such file or directory: '<workspace>/data/open_math_reasoning_problems.jsonl'
```

### After the fix

This branch, same command, with the `mlflow` extra and GitLab registry
credentials in `env.yaml`. No local data beforehand:

```text
Downloading dataset `validation` for `math_with_judge_simple_agent` from gitlab using dataset_name='dapo17k' version='0.0.1' artifact_fpath='dapo17k_train.jsonl' output_fpath='resources_servers/math_with_judge/data/dapo17k_trajectory_collection_train.jsonl'
Dataset validation repeating 16x: each line repeated 16 times (e.g. pattern: abc -> aaaabbbbcccc)
Aggregate metrics for resources_servers/math_with_judge/data/dapo17k_trajectory_collection_train_metrics.json
Aggregate metrics for data/dapo17k_trajectory_collection/validation_metrics.json
EXIT=0
$ wc -l data/dapo17k_trajectory_collection/validation.jsonl
278368 data/dapo17k_trajectory_collection/validation.jsonl
```

There are 17398 runs of identical questions, each 16 rows long, and no
`train.jsonl`. A second run skips the download, reports the sidecar
`already up to date`, and exits 0.

The OpenMathReasoning download, same command and directory: exit 0, with
the file written (the QA case below checks its rows).

### Validation

- QA testcase `math_with_judge`, which runs both README GitLab steps
from a scratch directory with registry credentials:
- base `977583d3d`: 0/2. Trajectory collation fails because the config
is missing; the download fails with `FileNotFoundError`.
- this branch: 2/2. The download passes in 4.4 s; the trajectory
collation passes in 20.3 s with 17398 × 16 rows and the committed
sidecar unchanged.
  - The QA-suite change ships separately.
- New tests: trajectory test before 1 failed
(`ConfigPathNotFoundError`), after 1 passed, and the five wrong fixes
above fail it. Downloader test before 1 failed (`FileNotFoundError`),
after 1 passed, also with the real `mlflow` 3.16.1 installed.
- `test_train_data_utils.py`, `test_cli_main.py`,
`test_global_config.py`, `test_dataset_source.py`,
`test_task_data.py::TestSchemaPresence`, `test_ray_opt_out.py`,
`test_cli_resources_servers.py`, `test_model_registry.py`,
`test_resources_server_registry.py`, `test_environment_scaffold.py`,
`test_gitlab_utils.py`, `test_hf_utils.py`, and the new tests: 592
passed.
- `pre-commit run --all-files`: passed.
- Rollout evidence: N/A. This is a config and docs change, and `gym
dataset collate` runs no model. The full download-and-collate run above
uses the real registry data.

### Compatibility

A new config file. Nothing that loads other configs changes. GitLab
downloads now create a missing output directory instead of failing. The
restored config uses the same GitLab artifact as `dapo17k.yaml`, but a
separate local file, so each keeps its own metrics sidecar.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: qiaochuz <qiaochuz@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
public — 735e0b5b Deployed Oct 2, 2026 by copy-pr-bot[bot] via release / finalize / notify #4224
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:build Packaging, dependencies, containers, and releases bug Something isn't working complexity:medium Single-domain change with interacting parts or a moderate review surface waiting-on-customer Waiting on the original author to respond

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make MLflow and Weights & Biases dependencies optional

5 participants