Spaces:
Running
Running
| # 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. | |
| """ | |
| Generic environment client that works with raw dictionaries. | |
| This module provides a GenericEnvClient that doesn't require installing | |
| environment-specific packages. It's useful for connecting to remote servers | |
| without running any untrusted code locally. | |
| """ | |
| from typing import Any, Dict | |
| from .client_types import StepResult | |
| from .env_client import EnvClient | |
| class GenericEnvClient(EnvClient[Dict[str, Any], Dict[str, Any], Dict[str, Any]]): | |
| """ | |
| Environment client that works with raw dictionaries instead of typed classes. | |
| This client doesn't require installing environment-specific packages, making it | |
| ideal for: | |
| - Connecting to remote servers without installing their packages | |
| - Quick prototyping and testing | |
| - Environments where type safety isn't needed | |
| - Security-conscious scenarios where you don't want to run remote code | |
| The trade-off is that you lose type safety and IDE autocomplete for actions | |
| and observations. Instead of typed objects, you work with plain dictionaries. | |
| Example: | |
| >>> # Direct connection to a running server (no installation needed) | |
| >>> with GenericEnvClient(base_url="http://localhost:8000") as env: | |
| ... result = env.reset() | |
| ... result = env.step({"code": "print('hello')"}) | |
| ... print(result.observation) # Dict[str, Any] | |
| ... print(result.observation.get("output")) | |
| >>> # From local Docker image | |
| >>> env = GenericEnvClient.from_docker_image("coding-env:latest") | |
| >>> result = env.reset() | |
| >>> result = env.step({"code": "x = 1 + 2"}) | |
| >>> env.close() | |
| >>> # From HuggingFace Hub (pulls Docker image, no pip install) | |
| >>> env = GenericEnvClient.from_env("user/my-env", use_docker=True) | |
| >>> result = env.reset() | |
| >>> env.close() | |
| Note: | |
| GenericEnvClient inherits `from_docker_image()` and `from_env()` from | |
| EnvClient, so you can use it with Docker containers and HuggingFace | |
| Spaces without any package installation. | |
| """ | |
| def _step_payload(self, action: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Convert action to payload for the server. | |
| For GenericEnvClient, this handles both raw dictionaries and | |
| typed Action objects (Pydantic models). If a Pydantic model is | |
| passed, it will be converted to a dictionary using model_dump(). | |
| Args: | |
| action: Action as a dictionary or Pydantic BaseModel | |
| Returns: | |
| The action as a dictionary for the server | |
| """ | |
| # If it's already a dict, return as-is | |
| if isinstance(action, dict): | |
| return action | |
| # If it's a Pydantic model (Action subclass), convert to dict | |
| if hasattr(action, "model_dump"): | |
| return action.model_dump() | |
| # Fallback for other objects with __dict__ | |
| if hasattr(action, "__dict__"): | |
| return vars(action) | |
| # Last resort: try to convert to dict | |
| return dict(action) | |
| def _parse_result(self, payload: Dict[str, Any]) -> StepResult[Dict[str, Any]]: | |
| """ | |
| Parse server response into a StepResult. | |
| Extracts the observation, reward, and done fields from the | |
| server response. | |
| Args: | |
| payload: Response payload from the server | |
| Returns: | |
| StepResult with observation as a dictionary | |
| """ | |
| return StepResult( | |
| observation=payload.get("observation", {}), | |
| reward=payload.get("reward"), | |
| done=payload.get("done", False), | |
| ) | |
| def _parse_state(self, payload: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Parse state response from the server. | |
| For GenericEnvClient, this returns the payload as-is since | |
| we're working with dictionaries. | |
| Args: | |
| payload: State payload from the server | |
| Returns: | |
| The state as a dictionary | |
| """ | |
| return payload | |
| class GenericAction(Dict[str, Any]): | |
| """ | |
| A dictionary subclass for creating actions when using GenericEnvClient. | |
| This provides a semantic wrapper around dictionaries to make code more | |
| readable when working with GenericEnvClient. It behaves exactly like a | |
| dict but signals intent that this is an action for an environment. | |
| Example: | |
| >>> # Without GenericAction (works fine) | |
| >>> env.step({"code": "print('hello')"}) | |
| >>> # With GenericAction (more explicit) | |
| >>> action = GenericAction(code="print('hello')") | |
| >>> env.step(action) | |
| >>> # With multiple fields | |
| >>> action = GenericAction(code="x = 1", timeout=30, metadata={"tag": "test"}) | |
| >>> env.step(action) | |
| Note: | |
| GenericAction is just a dict with a constructor that accepts keyword | |
| arguments. It's provided for symmetry with typed Action classes and | |
| to make code more readable. | |
| """ | |
| def __init__(self, **kwargs: Any) -> None: | |
| """ | |
| Create a GenericAction from keyword arguments. | |
| Args: | |
| **kwargs: Action fields as keyword arguments | |
| Example: | |
| >>> action = GenericAction(code="print(1)", timeout=30) | |
| >>> action["code"] | |
| 'print(1)' | |
| """ | |
| super().__init__(kwargs) | |
| def __repr__(self) -> str: | |
| """Return a readable representation.""" | |
| items = ", ".join(f"{k}={v!r}" for k, v in self.items()) | |
| return f"GenericAction({items})" | |