Source code for nemo_rl.environments.nemo_gym_request
# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Helpers for reading and merging NeMo Gym request metadata."""
import copy
import json
from typing import Any
[docs]
def _json_mapping(value: Any, *, field_name: str) -> dict[str, Any]:
"""Return a copied dict from a mapping or JSON object string.
Example:
``_json_mapping('{"enabled": true}', field_name="options")`` returns
``{"enabled": True}``.
Args:
value: Dict or JSON object string.
field_name: Field name used in errors.
Returns:
A new dictionary.
Raises:
TypeError: If the value is not a dict or JSON object string.
ValueError: If the string is empty or invalid JSON.
"""
if isinstance(value, dict):
return copy.deepcopy(value)
if not isinstance(value, str):
raise TypeError(f"{field_name} must be a JSON object string or a dict")
if not value.strip():
raise ValueError(f"{field_name} must not be empty")
try:
decoded = json.loads(value)
except json.JSONDecodeError as exc:
raise ValueError(f"{field_name} must contain valid JSON") from exc
if not isinstance(decoded, dict):
raise TypeError(f"{field_name} JSON must decode to an object")
return decoded
[docs]
def _metadata_extra_body(nemo_gym_example: dict[str, Any]) -> dict[str, Any]:
"""Read ``metadata.extra_body`` as a dict.
Example:
An example with ``metadata.extra_body='{"seed": 1}'`` returns
``{"seed": 1}``.
Args:
nemo_gym_example: Example containing Responses API parameters.
Returns:
Parsed ``extra_body``, or an empty dict when absent.
Raises:
TypeError: If request parameters or metadata are not dictionaries.
ValueError: If ``extra_body`` contains invalid JSON.
"""
params = nemo_gym_example.get("responses_create_params", {})
if not isinstance(params, dict):
raise TypeError("responses_create_params must be a dict")
metadata = params.get("metadata", {})
if not isinstance(metadata, dict):
raise TypeError("responses_create_params.metadata must be a dict")
if "extra_body" not in metadata:
return {}
return _json_mapping(
metadata["extra_body"],
field_name="responses_create_params.metadata.extra_body",
)
[docs]
def _chat_template_kwargs_for_processor(
nemo_gym_example: dict[str, Any],
) -> dict[str, Any]:
"""Build processor kwargs from NeMo Gym chat-template metadata.
Example:
``{"chat_template_kwargs": {"enable_thinking": False}}`` becomes the
processor kwarg with the same name and value.
Args:
nemo_gym_example: Example containing Responses API parameters.
Returns:
Keyword arguments for the processor's chat template.
Raises:
TypeError: If request metadata has an unsupported type.
ValueError: If a JSON metadata value is empty or invalid.
"""
params = nemo_gym_example.get("responses_create_params", {})
if not isinstance(params, dict):
raise TypeError("responses_create_params must be a dict")
metadata = params.get("metadata", {})
if not isinstance(metadata, dict):
raise TypeError("responses_create_params.metadata must be a dict")
extra_body = _metadata_extra_body(nemo_gym_example)
processor_kwargs: dict[str, Any] = {}
raw_chat_template_kwargs = metadata.get(
"chat_template_kwargs", extra_body.get("chat_template_kwargs")
)
chat_template_kwargs = (
_json_mapping(
raw_chat_template_kwargs,
field_name="responses_create_params.metadata.chat_template_kwargs",
)
if raw_chat_template_kwargs is not None
else {}
)
if chat_template_kwargs:
processor_kwargs["chat_template_kwargs"] = chat_template_kwargs
enable_thinking = chat_template_kwargs.get(
"enable_thinking", extra_body.get("enable_thinking")
)
if enable_thinking is not None:
processor_kwargs["enable_thinking"] = enable_thinking
return processor_kwargs
[docs]
def _deep_merge_dict(base: dict[str, Any], update: dict[str, Any]) -> dict[str, Any]:
"""Recursively merge two dictionaries without modifying either input.
Example:
Merging ``{"a": {"b": 1}}`` with ``{"a": {"c": 2}}`` returns
``{"a": {"b": 1, "c": 2}}``.
Args:
base: Initial mapping.
update: Values to merge into ``base``.
Returns:
A recursively merged deep copy.
"""
merged = copy.deepcopy(base)
for key, value in update.items():
if isinstance(value, dict) and isinstance(merged.get(key), dict):
merged[key] = _deep_merge_dict(merged[key], value)
else:
merged[key] = copy.deepcopy(value)
return merged