# Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. # # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. """ Shared serialization and deserialization utilities for OpenEnv HTTP servers. This module provides common utilities for converting between JSON dictionaries and Pydantic models (Action/Observation) to eliminate code duplication across HTTP server and web interface implementations. """ from typing import Any, Dict, Type from .types import Action, Observation def deserialize_action(action_data: Dict[str, Any], action_cls: Type[Action]) -> Action: """ Convert JSON dict to Action instance using Pydantic validation. This is a basic deserialization that works for most environments. For special cases (e.g., tensor fields, custom type conversions), use deserialize_action_with_preprocessing(). Args: action_data: Dictionary containing action data action_cls: The Action subclass to instantiate Returns: Action instance Raises: ValidationError: If action_data is invalid for the action class Note: This uses Pydantic's model_validate() for automatic validation. """ return action_cls.model_validate(action_data) def deserialize_action_with_preprocessing( action_data: Dict[str, Any], action_cls: Type[Action] ) -> Action: """ Convert JSON dict to Action instance with preprocessing for special types. This version handles common type conversions needed for web interfaces: - Converting lists/strings to tensors for 'tokens' field - Converting string action_id to int - Other custom preprocessing as needed Args: action_data: Dictionary containing action data action_cls: The Action subclass to instantiate Returns: Action instance Raises: ValidationError: If action_data is invalid for the action class """ processed_data = {} for key, value in action_data.items(): if key == "tokens" and isinstance(value, (list, str)): # Convert list or string to tensor if isinstance(value, str): # If it's a string, try to parse it as a list of numbers try: import json value = json.loads(value) except Exception: # If parsing fails, treat as empty list value = [] if isinstance(value, list): try: import torch # type: ignore processed_data[key] = torch.tensor(value, dtype=torch.long) except ImportError: # If torch not available, keep as list processed_data[key] = value else: processed_data[key] = value elif key == "action_id" and isinstance(value, str): # Convert action_id from string to int try: processed_data[key] = int(value) except ValueError: # If conversion fails, keep original value processed_data[key] = value else: processed_data[key] = value return action_cls.model_validate(processed_data) def serialize_observation(observation: Observation) -> Dict[str, Any]: """ Convert Observation instance to JSON-compatible dict using Pydantic. Args: observation: Observation instance Returns: Dictionary compatible with EnvClient._parse_result() The format matches what EnvClient expects: { "observation": {...}, # Observation fields "reward": float | None, "done": bool, } """ # Use Pydantic's model_dump() for serialization obs_dict = observation.model_dump( exclude={ "reward", "done", "metadata", } # Exclude these from observation dict ) # Extract reward and done directly from the observation reward = observation.reward done = observation.done # Return in EnvClient expected format return { "observation": obs_dict, "reward": reward, "done": done, }