-
Notifications
You must be signed in to change notification settings - Fork 615
Add NeMo Gym #1886
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add NeMo Gym #1886
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # nemo-gym-workplace-v1 | ||
|
|
||
| NeMo Gym's `workplace_assistant` example through the built-in `NeMoGymTaskset`. | ||
| This environment pins the taskset config only; the standard Verifiers harness owns the | ||
| rollout loop, and NeMo Gym's packaged resource server owns tool execution. | ||
|
|
||
| ## Develop | ||
|
|
||
| Install + run: | ||
|
|
||
| ```bash | ||
| uv run --with-editable . --with-editable environments/nemo_gym_workplace_v1 \ | ||
| --with nemo-gym==0.3.0 eval nemo-gym-workplace-v1 -n 1 -r 1 -c 1 | ||
| ``` | ||
|
|
||
| ## Layout | ||
|
|
||
| - `nemo_gym_workplace_v1/taskset.py` — a thin config wrapper over `NeMoGymTaskset`. | ||
|
|
||
| Tune the packaged dataset from the CLI with `--taskset.data-name`, and use ordinary | ||
| Verifiers harness flags for rollout behavior. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| from nemo_gym_workplace_v1.taskset import NemoGymWorkplaceTaskset | ||
|
|
||
| __all__ = ["NemoGymWorkplaceTaskset"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| from typing import Literal | ||
|
|
||
| import verifiers.v1 as vf | ||
| from verifiers.v1.tasksets.nemo_gym import ( | ||
| NeMoGymConfig, | ||
| NeMoGymTask, | ||
| NeMoGymTaskset, | ||
| ) | ||
|
|
||
|
|
||
| class NemoGymWorkplaceConfig(NeMoGymConfig): | ||
| nemo_env: Literal["workplace_assistant"] = "workplace_assistant" | ||
|
|
||
|
|
||
| class NemoGymWorkplaceTaskset(vf.Taskset[NeMoGymTask, NemoGymWorkplaceConfig]): | ||
| def load_tasks(self) -> list[NeMoGymTask]: | ||
| return NeMoGymTaskset(self.config).load_tasks() | ||
|
|
||
| def tools(self, task: NeMoGymTask) -> list[vf.Toolset]: | ||
| return NeMoGymTaskset(self.config).tools(task) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| [project] | ||
| name = "nemo-gym-workplace-v1" | ||
| version = "0.1.0" | ||
| description = "nemo-gym-workplace-v1 - NeMo Gym workplace_assistant through the built-in taskset." | ||
| requires-python = ">=3.11" | ||
| dependencies = ["verifiers"] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For packaged installs of this environment, this metadata only installs Useful? React with 👍 / 👎. |
||
|
|
||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["nemo_gym_workplace_v1"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| from verifiers.v1.tasksets.nemo_gym.taskset import ( | ||
| NeMoGymConfig, | ||
| NeMoGymTask, | ||
| NeMoGymTaskset, | ||
| ) | ||
|
|
||
| __all__ = [ | ||
| "NeMoGymConfig", | ||
| "NeMoGymTask", | ||
| "NeMoGymTaskset", | ||
| ] |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,157 @@ | ||||||||||||||||||||||
| """Run a packaged NeMo Gym dataset with any MCP-capable Verifiers harness.""" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import importlib | ||||||||||||||||||||||
| import json | ||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||
| from typing import Any | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import httpx | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from verifiers.v1.decorators import tool | ||||||||||||||||||||||
| from verifiers.v1.dialects.responses import ResponsesDialect | ||||||||||||||||||||||
| from verifiers.v1.mcp import Toolset, ToolsetConfig | ||||||||||||||||||||||
| from verifiers.v1.task import Task | ||||||||||||||||||||||
| from verifiers.v1.taskset import Taskset, TasksetConfig | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| DEFAULT_ENV = "example_single_tool_call" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class NeMoGymTask(Task): | ||||||||||||||||||||||
| nemo_gym_row: dict[str, Any] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class NeMoGymConfig(TasksetConfig): | ||||||||||||||||||||||
| nemo_env: str = DEFAULT_ENV | ||||||||||||||||||||||
| data_name: str = "example.jsonl" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class _NeMoGymToolsConfig(ToolsetConfig): | ||||||||||||||||||||||
| nemo_env: str | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class _NeMoGymTools(Toolset[_NeMoGymToolsConfig]): | ||||||||||||||||||||||
| """Expose a NeMo Gym resource server through one generic MCP call.""" | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could we use instead nemogym rollout collection helper. in nemo gym we define environments similar to verifiers, in that they include the harness, taskset, stateful system and verifier, the resources server is just an optional decoupling of stateful system, tools, verifier from the full environment. |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| TOOL_PREFIX = "nemo_gym" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| async def setup(self) -> None: | ||||||||||||||||||||||
| from nemo_gym import PARENT_DIR | ||||||||||||||||||||||
| from nemo_gym.base_resources_server import SimpleResourcesServer | ||||||||||||||||||||||
| from nemo_gym.config_types import BaseServerConfig | ||||||||||||||||||||||
| from nemo_gym.global_config import ( | ||||||||||||||||||||||
| GlobalConfigDictParser, | ||||||||||||||||||||||
| GlobalConfigDictParserConfig, | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| from nemo_gym.server_utils import ServerClient | ||||||||||||||||||||||
| from omegaconf import OmegaConf | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| root = Path(PARENT_DIR) | ||||||||||||||||||||||
| path = ( | ||||||||||||||||||||||
|
macroscopeapp[bot] marked this conversation as resolved.
|
||||||||||||||||||||||
| root | ||||||||||||||||||||||
| / "resources_servers" | ||||||||||||||||||||||
| / self.config.nemo_env | ||||||||||||||||||||||
| / "configs" | ||||||||||||||||||||||
| / f"{self.config.nemo_env}.yaml" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| parser = GlobalConfigDictParser() | ||||||||||||||||||||||
| config = parser.parse_no_environment( | ||||||||||||||||||||||
| OmegaConf.merge( | ||||||||||||||||||||||
| OmegaConf.load(path), | ||||||||||||||||||||||
| GlobalConfigDictParserConfig.NO_MODEL_GLOBAL_CONFIG_DICT, | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| resource = next( | ||||||||||||||||||||||
| server | ||||||||||||||||||||||
| for server in parser.filter_for_server_instance_configs(config) | ||||||||||||||||||||||
| if server.SERVER_TYPE == "resources_servers" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| entrypoint = Path(resource.get_inner_run_server_config().entrypoint).stem | ||||||||||||||||||||||
| module = importlib.import_module( | ||||||||||||||||||||||
| f"resources_servers.{self.config.nemo_env}.{entrypoint}" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| server_cls: Any = next( | ||||||||||||||||||||||
| value | ||||||||||||||||||||||
| for value in vars(module).values() | ||||||||||||||||||||||
| if isinstance(value, type) | ||||||||||||||||||||||
| and issubclass(value, SimpleResourcesServer) | ||||||||||||||||||||||
| and value.__module__ == module.__name__ | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| server_config = server_cls.model_fields["config"].annotation.model_validate( | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| "name": resource.name, | ||||||||||||||||||||||
| **resource.get_inner_run_server_config().model_dump(), | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| server = server_cls( | ||||||||||||||||||||||
| config=server_config, | ||||||||||||||||||||||
| server_client=ServerClient( | ||||||||||||||||||||||
| head_server_config=BaseServerConfig.model_validate( | ||||||||||||||||||||||
| config["head_server"] | ||||||||||||||||||||||
| ), | ||||||||||||||||||||||
| global_config_dict=config, | ||||||||||||||||||||||
| ), | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| self.client = httpx.AsyncClient( | ||||||||||||||||||||||
| transport=httpx.ASGITransport(app=server.setup_webserver()), | ||||||||||||||||||||||
| base_url="http://nemo-gym", | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| async def setup_task(self, task: NeMoGymTask) -> None: | ||||||||||||||||||||||
| self.tool_names = { | ||||||||||||||||||||||
| spec["name"] | ||||||||||||||||||||||
| for spec in task.nemo_gym_row["responses_create_params"].get("tools", []) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| response = await self.client.post("/seed_session", json=task.nemo_gym_row) | ||||||||||||||||||||||
| response.raise_for_status() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @tool | ||||||||||||||||||||||
| async def call(self, name: str, arguments: dict[str, Any]) -> Any: | ||||||||||||||||||||||
| """Call a tool declared by the current NeMo Gym task.""" | ||||||||||||||||||||||
| if name not in self.tool_names: | ||||||||||||||||||||||
| raise ValueError(f"unknown NeMo Gym tool: {name}") | ||||||||||||||||||||||
| response = await self.client.post(f"/{name}", json=arguments) | ||||||||||||||||||||||
| response.raise_for_status() | ||||||||||||||||||||||
| return response.json() | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AsyncClient crosses event loopsHigh Severity The in-process Reviewed by Cursor Bugbot for commit 59b494f. Configure here. |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class NeMoGymTaskset(Taskset[NeMoGymTask, NeMoGymConfig]): | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
As introduced, this taskset loads tasks and tools but never defines any Useful? React with 👍 / 👎. |
||||||||||||||||||||||
| def load_tasks(self) -> list[NeMoGymTask]: | ||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| from nemo_gym import PARENT_DIR | ||||||||||||||||||||||
| except ImportError as exc: | ||||||||||||||||||||||
| raise ImportError( | ||||||||||||||||||||||
| "Run this taskset with `uv run --with nemo-gym==0.3.0 eval nemo_gym`." | ||||||||||||||||||||||
| ) from exc | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| path = ( | ||||||||||||||||||||||
| Path(PARENT_DIR) | ||||||||||||||||||||||
| / "resources_servers" | ||||||||||||||||||||||
| / self.config.nemo_env | ||||||||||||||||||||||
| / "data" | ||||||||||||||||||||||
| / self.config.data_name | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| rows = [json.loads(line) for line in path.read_text().splitlines() if line] | ||||||||||||||||||||||
| dialect = ResponsesDialect() | ||||||||||||||||||||||
| return [ | ||||||||||||||||||||||
| NeMoGymTask( | ||||||||||||||||||||||
| idx=idx, | ||||||||||||||||||||||
| name=f"{self.config.nemo_env}:{idx}", | ||||||||||||||||||||||
| prompt=dialect.parse_request(row["responses_create_params"])[0], | ||||||||||||||||||||||
| system_prompt=( | ||||||||||||||||||||||
| "Call `nemo_gym_call` with the matching tool name and arguments. " | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a NeMo row has no Useful? React with 👍 / 👎. |
||||||||||||||||||||||
| f"Available NeMo Gym tools: " | ||||||||||||||||||||||
| f"{json.dumps(row['responses_create_params'].get('tools', []))}" | ||||||||||||||||||||||
| ), | ||||||||||||||||||||||
|
Comment on lines
+140
to
+144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium
Suggested change
🚀 Reply "fix it for me" or copy this AI Prompt for your agent: |
||||||||||||||||||||||
| nemo_gym_row=row, | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| for idx, row in enumerate(rows) | ||||||||||||||||||||||
| ] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def tools(self, task: NeMoGymTask) -> list[Toolset]: | ||||||||||||||||||||||
| if not task.nemo_gym_row["responses_create_params"].get("tools"): | ||||||||||||||||||||||
| return [] | ||||||||||||||||||||||
| return [_NeMoGymTools(_NeMoGymToolsConfig(nemo_env=self.config.nemo_env))] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||
| _NeMoGymTools.run() | ||||||||||||||||||||||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The root
AGENTS.mdsays, "Environment READMEs must use the generatedprime env initsection structure; freeform environment READMEs are not allowed." This new README starts as a custom overview/Develop/Layout page instead of that required structure, so it does not satisfy the repository's documented environment README contract. Please regenerate/use the standard sections and fill them in.Useful? React with 👍 / 👎.