Source code for unilab.utils.reward

"""Utility functions for reward config handling."""

from typing import Any, cast

from omegaconf import DictConfig, OmegaConf

RewardDict = dict[str, Any]


def _to_reward_dict(value: object, *, error_message: str) -> RewardDict:
    """Convert an OmegaConf container into a plain reward dictionary."""
    resolved = OmegaConf.to_container(value, resolve=True)
    if not isinstance(resolved, dict):
        raise ValueError(error_message)
    # Some reward configs are mounted as a full `reward:` section.
    # Env config injection expects the inner reward mapping.
    if set(resolved) == {"reward"} and isinstance(resolved["reward"], dict):
        return cast(RewardDict, resolved["reward"])
    return cast(RewardDict, resolved)


[docs] def resolve_reward_dict(cfg: DictConfig, *, target_field: str = "rewards") -> dict[str, RewardDict]: """Extract and validate the reward config from a composed Hydra config. Args: cfg: Hydra DictConfig containing the root ``reward`` section. target_field: Env config override key the mapping is mounted under. Returns: Dictionary with ``target_field`` as the env config override key. Raises: ValueError: If the reward config is missing or empty, or the ``target_field`` is not the supported ``"rewards"`` override key. """ if target_field != "rewards": raise ValueError(f"Reward config target_field must be 'rewards', got {target_field!r}") reward_cfg = OmegaConf.select(cfg, "reward") if not reward_cfg: raise ValueError( "Missing 'reward' config in Hydra. Reward config must be explicitly provided." ) reward_dict = _to_reward_dict( reward_cfg, error_message="Reward config must resolve to a mapping.", ) if not reward_dict: raise ValueError( "Reward config resolved to empty. Please select a non-default reward override." ) return {target_field: reward_dict}