{"record":{"id":"a783298055737178","repo":"microsoft/qlib","slug":"sample-must-be-a-dict-with-same-length-as-space","errorCode":null,"errorMessage":"Sample must be a dict with same length as space.","messagePattern":"Sample must be a dict with same length as space\\.","errorType":"validation","errorClass":"GymSpaceValidationError","httpStatus":null,"severity":"error","filePath":"qlib/rl/interpreter.py","lineNumber":109,"sourceCode":"        action\n            Raw action given by policy.\n\n        Returns\n        -------\n        The action needed by simulator,\n        \"\"\"\n        raise NotImplementedError(\"interpret is not implemented!\")\n\n\ndef _gym_space_contains(space: gym.Space, x: Any) -> None:\n    \"\"\"Strengthened version of gym.Space.contains.\n    Giving more diagnostic information on why validation fails.\n\n    Throw exception rather than returning true or false.\n    \"\"\"\n    if isinstance(space, spaces.Dict):\n        if not isinstance(x, dict) or len(x) != len(space):\n            raise GymSpaceValidationError(\"Sample must be a dict with same length as space.\", space, x)\n        for k, subspace in space.spaces.items():\n            if k not in x:\n                raise GymSpaceValidationError(f\"Key {k} not found in sample.\", space, x)\n            try:\n                _gym_space_contains(subspace, x[k])\n            except GymSpaceValidationError as e:\n                raise GymSpaceValidationError(f\"Subspace of key {k} validation error.\", space, x) from e\n\n    elif isinstance(space, spaces.Tuple):\n        if isinstance(x, (list, np.ndarray)):\n            x = tuple(x)  # Promote list and ndarray to tuple for contains check\n        if not isinstance(x, tuple) or len(x) != len(space):\n            raise GymSpaceValidationError(\"Sample must be a tuple with same length as space.\", space, x)\n        for i, (subspace, part) in enumerate(zip(space, x)):\n            try:\n                _gym_space_contains(subspace, part)\n            except GymSpaceValidationError as e:\n                raise GymSpaceValidationError(f\"Subspace of index {i} validation error.\", space, x) from e","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/rl/interpreter.py#L91-L127","documentation":"_gym_space_contains (qlib/rl/interpreter.py:109) is a hardened replacement for gym.Space.contains used to validate action/observation samples against the declared gym space. For gym.spaces.Dict spaces, the sample must be a dict with exactly the same number of keys as the space; otherwise it raises GymSpaceValidationError('Sample must be a dict with same length as space.') instead of returning a boolean, so the exact mismatch is visible.","triggerScenarios":"An RL interpreter (action interpreter of the QlibRLSimulator, e.g. Categorical-Interpreter outputs) produces a sample that is not a dict, or a dict with extra/missing keys relative to the nested gym Dict space; custom interpreters returning plain arrays for Dict-typed action spaces.","commonSituations":"Writing a custom qlib.rl simulator/interpreter whose to_action_* return type drifts from the declared action space; changing the gym space definition (adding/removing keys like 'amount') without updating the interpreter; gym version differences changing space.spaces contents.","solutions":["Make the interpreter's returned sample a dict whose keys exactly match the space's keys (same count, same names)","Update the gym.spaces.Dict definition and the interpreter together when the action schema changes","Validate with _gym_space_contains(space, sample) in interpreter unit tests before running the simulator"],"exampleFix":"# before\n# space = gym.spaces.Dict({'amount': gym.spaces.Box(...)})\nsample = np.array([0.5])  # not a dict -> GymSpaceValidationError\naction = interpreter.to_action(sample)\n\n# after\nsample = {'amount': np.array([0.5], dtype=np.float32)}\naction = interpreter.to_action(sample)","handlingStrategy":"validation","validationCode":"from gym import spaces\nassert isinstance(sample, dict) and len(sample) == len(space.spaces), 'sample must match Dict space keys'","typeGuard":"def matches_dict_space(sample, space) -> bool:\n    return isinstance(sample, dict) and set(sample.keys()) == set(space.spaces.keys())","tryCatchPattern":"try:\n    _gym_space_contains(space, sample)\nexcept GymSpaceValidationError as e:\n    logger.error('interpreter sample rejected: %s', e)\n    raise","preventionTips":["Keep interpreter output schemas and gym space definitions in one module and change them together","Add a unit test per interpreter asserting sample/space compatibility via _gym_space_contains"],"tags":["qlib","rl","gym","action-space","interpreter","validation"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}