Skip to content
Merged
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ repos:
files: ^resources_servers/.*/configs/.*\.yaml$
pass_filenames: true
- id: update-readme-table
name: Update resources server list in README
name: Update environment list in README
language: python
entry: python scripts/update_resource_servers.py
entry: python scripts/update_env_list.py
additional_dependencies: [pyyaml]
files: ^README\.md$|^resources_servers/.*/configs/.*\.yaml$
files: ^README\.md$|^resources_servers/.*/configs/.*\.yaml$|^responses_api_agents/.*/configs/.*\.yaml$
10 changes: 9 additions & 1 deletion README.md

Large diffs are not rendered by default.

21 changes: 15 additions & 6 deletions nemo_gym/server_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@


@dataclass
class ResourcesServerMetadata:
"""Metadata extracted from resources server YAML config."""
class ServerMetadata:
"""Metadata extracted from a resources-server or agent-server YAML config."""

domain: Optional[str] = None
description: Optional[str] = None
Expand All @@ -37,9 +37,18 @@ def to_dict(self) -> dict[str, str | bool | None]: # pragma: no cover
}


def visit_resources_server(data: dict, level: int = 1) -> ResourcesServerMetadata: # pragma: no cover
def visit_resources_server(data: dict, level: int = 1) -> ServerMetadata: # pragma: no cover
"""Extract resources server metadata from YAML data."""
resource = ResourcesServerMetadata()
return _visit_server(data, "resources_servers", level)


def visit_agent_server(data: dict, level: int = 1) -> ServerMetadata: # pragma: no cover
"""Extract agent server metadata from YAML data."""
return _visit_server(data, "responses_api_agents", level)


def _visit_server(data: dict, server_type_key: str, level: int = 1) -> ServerMetadata: # pragma: no cover
resource = ServerMetadata()
if level == 4:
resource.domain = data.get("domain")
resource.description = data.get("description")
Expand All @@ -49,7 +58,7 @@ def visit_resources_server(data: dict, level: int = 1) -> ResourcesServerMetadat
return resource
elif isinstance(data, dict):
for k, v in data.items():
if level == 2 and k != "resources_servers":
if level == 2 and k != server_type_key:
continue
return visit_resources_server(v, level + 1)
return _visit_server(v, server_type_key, level + 1)
return resource
3 changes: 3 additions & 0 deletions responses_api_agents/harbor_agent/configs/harbor_agent.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ harbor_agent:
harbor_agent:
# Python module entrypoint loaded by NeMo Gym.
entrypoint: app.py
domain: agent
description: Harbor integration for ageng harnesses and environments.
value: Improve models in popular agentic environments supported by Harbor such as Terminus2.
# Max concurrent requests handled by this agent server process.
concurrency: 50

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ mini_swe_simple_agent:
responses_api_agents:
mini_swe_agent:
entrypoint: app.py
domain: coding
description: Software engineering tasks driven by mini-swe agent harness.
value: Improve agentic software engineering capabilities.
model_server:
type: responses_api_models
name: policy_model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ swe_agents:
responses_api_agents:
swe_agents: &swe_agents_config
entrypoint: app.py

domain: coding
description: Software engineering tasks with OpenHands agent harness.
value: Improve agentic software engineering capabilities.

# Agent framework configuration
agent_framework: openhands
agent_config: responses_api_agents/swe_agents/configs/oh_config.toml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ swe_agents:
responses_api_agents:
swe_agents:
entrypoint: app.py

domain: coding
description: SWE-bench driven by the OpenHands agent framework.
value: Eval software engineering capabilities on SWE-bench.

# Agent framework configuration
agent_framework: openhands
agent_config: responses_api_agents/swe_agents/configs/oh_config.toml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ swe_agents_train:
responses_api_agents:
swe_agents:
entrypoint: app.py
domain: coding
description: Software engineering tasks with OpenHands agent harness.
value: Improve agentic software engineering capabilities.
# Agent framework configuration
agent_framework: openhands
agent_config: responses_api_agents/swe_agents/configs/oh_config.toml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ swe_agents:
responses_api_agents:
swe_agents:
entrypoint: app.py

domain: coding
description: Software engineering tasks with OpenHands agent harness.
value: Improve agentic software engineering capabilities.

# Agent framework configuration
agent_framework: swe_agent
agent_config: configs/swe_agent_config.yaml
Expand Down
3 changes: 3 additions & 0 deletions responses_api_agents/tau2/configs/tau2_agent.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ tau2_agent:
responses_api_agents:
tau2:
entrypoint: app.py
domain: agent
description: Tau2 benchmark integration
value: Evaluate multi-turn agentic capability with user simulation.
model_server:
type: responses_api_models
name: policy_model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ verifiers_agent:
responses_api_agents:
verifiers_agent:
entrypoint: app.py
domain: math
description: Prime intellect verifiers and environments hub integration, ace-reason math environment example.
value: Improve math reasoning capabilities.
model_server:
type: responses_api_models
name: policy_model
Expand Down
152 changes: 92 additions & 60 deletions scripts/update_resource_servers.py → scripts/update_env_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@

import yaml

from nemo_gym.server_metadata import ResourcesServerMetadata, visit_resources_server
from nemo_gym.server_metadata import ServerMetadata, visit_agent_server, visit_resources_server


README_PATH = Path("README.md")

TARGET_FOLDER = Path("resources_servers")
RESOURCES_SERVERS_FOLDER = Path("resources_servers")
RESPONSES_API_AGENTS_FOLDER = Path("responses_api_agents")


@dataclass
Expand Down Expand Up @@ -65,7 +66,7 @@ class ConfigMetadata:

@classmethod
def from_yaml_data(
cls, resource: ResourcesServerMetadata, agent: AgentDatasetsMetadata
cls, resource: ServerMetadata, agent: AgentDatasetsMetadata
) -> "ConfigMetadata": # pragma: no cover
"""Combine resources server and agent datasets metadata."""
return cls(
Expand All @@ -91,6 +92,7 @@ class ServerInfo:
config_filename: str
readme_path: str
yaml_file: Path
base_folder: str = "resources_servers"

@property
def huggingface_repo_id(self) -> str | None: # pragma: no cover
Expand Down Expand Up @@ -155,27 +157,48 @@ def get_readme_link(self) -> str: # pragma: no cover

def visit_agent_datasets(data: dict) -> AgentDatasetsMetadata: # pragma: no cover
agent = AgentDatasetsMetadata()
for k1, v1 in data.items():
if k1.endswith("_agent") and isinstance(v1, dict):
v2 = v1.get("responses_api_agents")
if isinstance(v2, dict):
# Look for any agent key
for agent_key, v3 in v2.items():
if isinstance(v3, dict):
datasets = v3.get("datasets")
if isinstance(datasets, list):
for entry in datasets:
if isinstance(entry, dict):
agent.types.append(entry.get("type"))
if entry.get("type") == "train":
agent.license = entry.get("license")
hf_id = entry.get("huggingface_identifier")
if hf_id and isinstance(hf_id, dict):
agent.huggingface_repo_id = hf_id.get("repo_id")
if not isinstance(data, dict):
return agent
for v1 in data.values():
if not isinstance(v1, dict):
continue
v2 = v1.get("responses_api_agents")
if not isinstance(v2, dict):
continue
for v3 in v2.values():
if not isinstance(v3, dict):
continue
datasets = v3.get("datasets")
if isinstance(datasets, list):
for entry in datasets:
if isinstance(entry, dict):
agent.types.append(entry.get("type"))
if entry.get("type") == "train":
agent.license = entry.get("license")
hf_id = entry.get("huggingface_identifier")
if hf_id and isinstance(hf_id, dict):
agent.huggingface_repo_id = hf_id.get("repo_id")
elif v3.get("harbor_datasets") or v3.get("vf_env_id"):
agent.types.append("train")
return agent


def extract_config_metadata(yaml_path: Path) -> ConfigMetadata: # pragma: no cover
def agent_has_resources_server_ref(data: dict) -> bool: # pragma: no cover
if not isinstance(data, dict):
return False
for v1 in data.values():
if not isinstance(v1, dict):
continue
v2 = v1.get("responses_api_agents")
if not isinstance(v2, dict):
continue
for v3 in v2.values():
if isinstance(v3, dict) and v3.get("resources_server"):
return True
return False


def extract_config_metadata(yaml_path: Path, from_agent: bool = False) -> ConfigMetadata: # pragma: no cover
"""
Domain:
{name}_resources_server:
Expand Down Expand Up @@ -203,7 +226,7 @@ def extract_config_metadata(yaml_path: Path) -> ConfigMetadata: # pragma: no co
with yaml_path.open() as f:
data = yaml.safe_load(f)

resource_data = visit_resources_server(data)
resource_data = visit_agent_server(data) if from_agent else visit_resources_server(data)
agent_data = visit_agent_datasets(data)

return ConfigMetadata.from_yaml_data(resource_data, agent_data)
Expand All @@ -214,47 +237,56 @@ def get_example_and_training_server_info() -> tuple[list[ServerInfo], list[Serve
example_only_servers = []
training_servers = []

for subdir in TARGET_FOLDER.iterdir():
if not subdir.is_dir():
continue

configs_folder = subdir / "configs"
if not (configs_folder.exists() and configs_folder.is_dir()):
continue

yaml_files = list(configs_folder.glob("*.yaml"))
if not yaml_files:
continue

for yaml_file in yaml_files:
yaml_data = extract_config_metadata(yaml_file)
if not yaml_data.types:
for base_folder in (RESOURCES_SERVERS_FOLDER, RESPONSES_API_AGENTS_FOLDER):
from_agent = base_folder == RESPONSES_API_AGENTS_FOLDER
for subdir in base_folder.iterdir():
if not subdir.is_dir():
continue

server_name = subdir.name
is_example_only = server_name.startswith("example_")

display_name = (
(server_name[len("example_") :] if is_example_only else server_name).replace("_", " ").title()
)

config_path = f"{TARGET_FOLDER.name}/{server_name}/configs/{yaml_file.name}"
readme_path = f"{TARGET_FOLDER.name}/{server_name}/README.md"
configs_folder = subdir / "configs"
if not (configs_folder.exists() and configs_folder.is_dir()):
continue

server_info = ServerInfo(
name=server_name,
display_name=display_name,
config_metadata=yaml_data,
config_path=config_path,
config_filename=yaml_file.name,
readme_path=readme_path,
yaml_file=yaml_file,
)
yaml_files = list(configs_folder.glob("*.yaml"))
if not yaml_files:
continue

if is_example_only:
example_only_servers.append(server_info)
else:
training_servers.append(server_info)
for yaml_file in yaml_files:
if from_agent:
with yaml_file.open() as f:
raw = yaml.safe_load(f) or {}
if agent_has_resources_server_ref(raw):
continue

yaml_data = extract_config_metadata(yaml_file, from_agent=from_agent)
if not yaml_data.types:
continue

server_name = subdir.name
is_example_only = server_name.startswith("example_")

display_name = (
(server_name[len("example_") :] if is_example_only else server_name).replace("_", " ").title()
)

config_path = f"{base_folder.name}/{server_name}/configs/{yaml_file.name}"
readme_path = f"{base_folder.name}/{server_name}/README.md"

server_info = ServerInfo(
name=server_name,
display_name=display_name,
config_metadata=yaml_data,
config_path=config_path,
config_filename=yaml_file.name,
readme_path=readme_path,
yaml_file=yaml_file,
base_folder=base_folder.name,
)

if is_example_only:
example_only_servers.append(server_info)
else:
training_servers.append(server_info)

return example_only_servers, training_servers

Expand Down Expand Up @@ -287,7 +319,7 @@ def generate_example_only_table(servers: list[ServerInfo]) -> str: # pragma: no
def generate_training_table(servers: list[ServerInfo]) -> str: # pragma: no cover
"""Generate table for training resources servers."""
col_names = [
"Resources Server",
"Environment",
"Domain",
"Description",
"Value",
Expand Down
Loading