microsoft/qlib · error · GymSpaceValidationError

Key {k} not found in sample.

Error message

Key {k} not found in sample.

What it means

Raised by qlib's strengthened gym space validator `_gym_space_contains` (qlib/rl/interpreter.py:112). When a sample is validated against a `gym.spaces.Dict` space, every key present in `space.spaces` must also exist in the sample dict. If a key `k` defined by the space is missing from `x`, this GymSpaceValidationError is thrown with full diagnostics (space and sample are attached to the exception).

Source

Thrown at qlib/rl/interpreter.py:112

        Returns
        -------
        The action needed by simulator,
        """
        raise NotImplementedError("interpret is not implemented!")


def _gym_space_contains(space: gym.Space, x: Any) -> None:
    """Strengthened version of gym.Space.contains.
    Giving more diagnostic information on why validation fails.

    Throw exception rather than returning true or false.
    """
    if isinstance(space, spaces.Dict):
        if not isinstance(x, dict) or len(x) != len(space):
            raise GymSpaceValidationError("Sample must be a dict with same length as space.", space, x)
        for k, subspace in space.spaces.items():
            if k not in x:
                raise GymSpaceValidationError(f"Key {k} not found in sample.", space, x)
            try:
                _gym_space_contains(subspace, x[k])
            except GymSpaceValidationError as e:
                raise GymSpaceValidationError(f"Subspace of key {k} validation error.", space, x) from e

    elif isinstance(space, spaces.Tuple):
        if isinstance(x, (list, np.ndarray)):
            x = tuple(x)  # Promote list and ndarray to tuple for contains check
        if not isinstance(x, tuple) or len(x) != len(space):
            raise GymSpaceValidationError("Sample must be a tuple with same length as space.", space, x)
        for i, (subspace, part) in enumerate(zip(space, x)):
            try:
                _gym_space_contains(subspace, part)
            except GymSpaceValidationError as e:
                raise GymSpaceValidationError(f"Subspace of index {i} validation error.", space, x) from e

    else:
        if not space.contains(x):

View on GitHub (pinned to 79633dd950)

Solutions

  1. Print the exception: `str(e)` shows both the expected space and the offending sample; add the missing key to the sample dict.
  2. Align your interpreter's `observation_space(...)` return value with the keys actually produced in `state_interpreter.simulator_state` -> observation conversion.
  3. If the key is genuinely optional, model it with a Tuple/Dict structure that matches, or always emit the key (possibly NaN-filled).
  4. Write a unit test that calls `_gym_space_contains(interpreter.observation_space, sample)` on real simulator output.

Example fix

// before
obs_space = spaces.Dict({"position": spaces.Box(...), "history": spaces.Box(...)})
sample = {"position": pos}  # missing "history"
// after
obs_space = spaces.Dict({"position": spaces.Box(...), "history": spaces.Box(...)})
sample = {"position": pos, "history": hist}  # all keys present
Defensive patterns

Strategy: validation

Validate before calling

from qlib.rl.interpreter import _gym_space_contains

def validate_sample(space, sample):
    assert isinstance(sample, dict), "sample must be dict for Dict space"
    missing = set(space.spaces.keys()) - set(sample.keys())
    extra = set(sample.keys()) - set(space.spaces.keys())
    assert not missing, f"sample missing keys: {missing}"
    assert not extra, f"sample has extra keys: {extra}"
    _gym_space_contains(space, sample)  # deep check before running the env

Type guard

def matches_dict_space(space: gym.Space, x: Any) -> bool:
    return (
        isinstance(space, gym.spaces.Dict)
        and isinstance(x, dict)
        and len(x) == len(space.spaces)
        and set(x.keys()) == set(space.spaces.keys())
    )

Try / catch

try:
    _gym_space_contains(obs_space, sample)
except GymSpaceValidationError as e:
    raise RuntimeError(f"Bad observation for space: {e}") from e

Prevention

When it happens

Trigger: Calling an interpreter whose observation/action space is a `spaces.Dict` with a sample dict that lacks one of the declared keys (e.g. building an observation manually, or a StateInterpreter whose `observation_space` declares keys the simulator state does not emit). Also triggered when len(x) matches but keys are renamed or mis-typed (e.g. 'position' vs 'Position').

Common situations: Custom RL interpreters where `observation_space` was copy-pasted from another interpreter; gym version changes that alter Dict space representation; tests that hand-craft observations instead of running the simulator.

Related errors


AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15). Data as JSON: /api/errors/2e85f4da7fc2e19b. Report an issue: GitHub.