📣 Release Notes - 0.7.0
0.7.0 is a huge release with quite some breaking changes as we are working towards a 1.0.0
release.
⏰ For some statistics! This release contains 344 changed files with 16,423 additions and 9,679 deletions.
💥 Breaking Change
Breaking changes happen, we aim to help create a migration guide that helps you transition from the old version to the new one. Please read through this from top to bottom and apply the required changes accordingly.
Dropping support for Python 3.8 and 3.9
We are dropping support for Python 3.8 and 3.9 as they are nearing end of life. This means that to use Composabl, you will have to utilize Python 3.10 or 3.11 going foward. There are several reasons that made us make this decision:
- Python 3.8 and 3.9 are nearing end of life
- Pickling of certain aspects to the SDK is not possible in 3.8 and 3.9
- Ray is dropping support for 3.8
- Async/await support is not completely possible in 3.8 or 3.9
Standardizing Namings
To conform to the Machine Teaching standard and to make it clearer what a Skill is, we renamed the following and moved it from namespaces:
ClassName | Old Namespace | New Namespace |
---|---|---|
SkillController | agent.controller | agent.skill.skill_controller |
SkillTeacher | agent.teaching | agent.skill.skill_teacher |
ServerComposabl | composabl_core.grpc.server | composabl_core.networking.server_composabl |
Async Implementation / Standardizing API Specification
We implemented async/await within the codebase for optimized I/O operations and to improve the overall performance of the SDK. This change is a breaking change as it requires a substantial overhaul of the existing codebase. This means that the following will change on the implementation side:
[Simulator] ServerComposabl
We have renamed the methods to follow snake-case and made them async/await compatible:
class ServerComposabl(abc.ABC):
@abc.abstractmethod
async def make(self, env_id: str, env_init: Dict[str, Any]) -> EnvSpec:
raise NotImplementedError
@abc.abstractmethod
async def observation_space_info(self) -> Space:
raise NotImplementedError
@abc.abstractmethod
async def action_space_info(self) -> Space:
raise NotImplementedError
@abc.abstractmethod
async def action_space_sample(self) -> Union[Any, List[Any]]:
raise NotImplementedError
@abc.abstractmethod
async def reset(self) -> Tuple[Any, Dict[str, Any]]:
raise NotImplementedError
@abc.abstractmethod
async def step(self, action) -> Tuple[Any, SupportsFloat, bool, bool, Dict[str, Any]]:
raise NotImplementedError
@abc.abstractmethod
async def close(self):
raise NotImplementedError
@abc.abstractmethod
async def set_scenario(self, scenario):
raise NotImplementedError
@abc.abstractmethod
async def get_scenario(self):
raise NotImplementedError
@abc.abstractmethod
async def set_render_mode(self, render_mode):
raise NotImplementedError
@abc.abstractmethod
async def get_render_mode(self, render_mode):
raise NotImplementedError
@abc.abstractmethod
async def get_render(self, render_mode):
raise NotImplementedError
[Simulator] ServerComposabl
We have renamed the methods to follow snake-case and made them async/await compatible:
class ServerComposabl(abc.ABC):
@abc.abstractmethod
async def make(self, env_id: str, env_init: Dict[str, Any]) -> EnvSpec:
raise NotImplementedError
@abc.abstractmethod
async def observation_space_info(self) -> Space:
raise NotImplementedError
@abc.abstractmethod
async def action_space_info(self) -> Space:
raise NotImplementedError
@abc.abstractmethod
async def action_space_sample(self) -> Union[Any, List[Any]]:
raise NotImplementedError
@abc.abstractmethod
async def reset(self) -> Tuple[Any, Dict[str, Any]]:
raise NotImplementedError
@abc.abstractmethod
async def step(self, action) -> Tuple[Any, SupportsFloat, bool, bool, Dict[str, Any]]:
raise NotImplementedError
@abc.abstractmethod
async def close(self):
raise NotImplementedError
@abc.abstractmethod
async def set_scenario(self, scenario):
raise NotImplementedError
@abc.abstractmethod
async def get_scenario(self):
raise NotImplementedError
@abc.abstractmethod
async def set_render_mode(self, render_mode):
raise NotImplementedError
@abc.abstractmethod
async def get_render_mode(self, render_mode):
raise NotImplementedError
@abc.abstractmethod
async def get_render(self, render_mode):
raise NotImplementedError
[Agent Creation] SkillController
We have renamed the methods to follow snake-case and made them async/await compatible:
class SkillController(ABC):
def __init__(self):
self.action_space = None
self.observation_space = None
pass
@abstractmethod
async def compute_action(self, transformed_obs, action):
pass
@abstractmethod
async def transform_obs(self, obs):
pass
@abstractmethod
async def filtered_observation_space(self):
pass
@abstractmethod
async def compute_success_criteria(self, transformed_obs, action):
pass
@abstractmethod
async def compute_termination(self, transformed_obs, action):
pass
[Agent Creation] SkillTeacher
We have renamed the methods to follow snake-case and made them async/await compatible:
class SkillTeacher(ABC):
def __init__(self):
pass
@abstractmethod
async def compute_reward(self, transformed_obs, action, sim_reward):
pass
@abstractmethod
async def compute_action_mask(self, transformed_obs, action):
pass
@abstractmethod
async def compute_success_criteria(self, transformed_obs, action) -> bool:
pass
@abstractmethod
async def compute_termination(self, transformed_obs, action) -> bool:
pass
@abstractmethod
async def transform_obs(self, obs, action):
pass
@abstractmethod
async def transform_action(self, transformed_obs, action):
pass
@abstractmethod
async def filtered_observation_space(self) -> List[str]:
pass
[Agent Creation] SkillCoach
We have renamed the methods to follow snake-case and made them async/await compatible:
class SkillCoach(ABC):
def __init__(self):
pass
@abstractmethod
async def compute_reward(self, transformed_obs, action, sim_reward):
"""
Computes the reward for the given transformed observation and action
:param transformed_obs: The transformed observation
:param action: The actions dict
:param sim_reward: The reward from the simulation
:return: The reward, as a dictionary, with each key the sub-skill name and the value the reward
"""
pass
@abstractmethod
async def compute_action_mask(self, transformed_obs, action):
pass
@abstractmethod
async def compute_success_criteria(self, transformed_obs, action) -> bool:
pass
@abstractmethod
async def compute_termination(self, transformed_obs, action) -> bool:
pass
@abstractmethod
async def transform_action(self, transformed_obs, action):
pass
[Agent Creation] Perceptors
Perceptors have more changes as we support portable perceptors as well. This brings the following breaking changes:
- We have renamed the methods to follow snake-case
- We have made the methods async/await compatible where required
- We have created a new
PerceptorImpl
class that specifies the API implementation for the perceptor which you should inherit from compute
now receives the Observation Specification as the first argument (i.e.,compute(self, observation)
becomescompute(self, observation_spec, observation)
)
class PerceptorImpl(ABC):
def __init__(self):
pass
@abc.abstractmethod
async def compute(self, obs_spec, obs):
"""
Compute the return value for this perceptor based on the observation space
"""
raise NotImplementedError
@abc.abstractmethod
def filtered_observation_space(self, obs):
"""
Return the observation space part that is needed
"""
raise NotImplementedError
🚀 Features
Besides breaking changes, we also have added more exciting new features to the SDK.
CLI - Skills, Perceptors and Simulators Templates
You can now create new skills, perceptors and simulators through the CLI! For this you can simply use the below, which will prompt you with the required information.
# Create a new skill
composabl skill create
# Create a new perceptor
composabl perceptor create
# Create a new simulator
composabl sim create
Sensor Mapping
Sensors were simple, requiring you to follow the observation spec order in which they appeared and map them out through keys and descriptions. We have now added a feature that allows you to create a Sensor from the entire observation space and narrow it down as much as required.
This would allow easier support for more complex sensors such as LIDARs, Cameras, ... requiring huge observation space mappings otherwise, which you can now simply define through one sensor, or a sensor per color channel for a camera.
# LIDAR Example
sensor_lidar = Sensor("lidar", "LIDAR", mapping=lambda obs: obs["lidar"])
# Camera Example
sensor_camera_r = Sensor("r", "red channel", mapping=lambda obs: obs["img"][0])
sensor_camera_g = Sensor("g", "green channel", mapping=lambda obs: obs["img"][1])
sensor_camera_b = Sensor("b", "blue channel", mapping=lambda obs: obs["img"][2])
Portable Skills
As we prepare for the 1.0.0 release and marketplace support coming by this summer, we are introducing portable skills. This means that you can now create a skill, package it up and tell the SDK to use it from a pre-defined URL. This allows you to:
- Create and Share Skills through the Marketplace for anyone to use
- Create more complex skills with pre-packed logic (e.g., a Machine Learning) model that could not be serialized before
To use this, you can create an Agent and add a skill from a URL:
# Create a Portable Skill from a URL
# this is great for sharing with others
s = Skill("my-skill", "https://example.com/composabl-skill-my-skill-0.0.1.tar.gz")
# Create a Portable Skill from a Directory
# this is great for testing locally
s = Skill("my-skill", "/tmp/composabl-skill-my-skill-0.0.1.tar.gz")
# Create a Portable Skill - Alternative
# note: we use SkillController as the base class API Spec to use for the portable skill
s = Skill("my-skill", SkillController, SkillOptions.model_validate({
"remote_address": "https://example.com/composabl-skill-my-skill-0.0.1.tar.gz"
}))
Implementing a Skill is just as easy as creating a Python package and creating a tar.gz
archive from it:
⚠️ You should prefix a skill with
composabl-skill-
to avoid name clashes with other packages
composabl-skill-cstr-drl-py
├── README.md
├── composabl_skill_cstr_drl_py
│ ├── __init__.py
│ └── teacher.py
└── pyproject.toml
In the pyproject.toml
we then define an entrypoint and type under the [composabl]
section:
[project]
name = "composabl-skill-cstr-drl-py"
version = "0.1.0"
description = "A Composabl Demo Portable Teacher Skill"
authors = [{ name = "Xavier Geerinck", email = "xavier@composabl.io" }]
dependencies = [
"composabl-core"
]
[composabl]
type = "teacher"
entrypoint = "composabl_skill_cstr_drl_py.teacher:Teacher"
Portable Perceptors
As we prepare for the 1.0.0 release and marketplace support coming by this summer, we are introducing portable perceptors. This means that you can now create a perceptor, package it up and tell the SDK to use it from a pre-defined URL. This allows you to:
- Create and Share Perceptors through the Marketplace for anyone to use
- Create more complex perceptors with pre-packed logic (e.g., a Machine Learning) model that could not be serialized before
To use this, you can create an Agent and add a perceptor from a URL:
# Create a Portable Perceptor from a URL
# this is great for sharing with others
p = Perceptor("my-perceptor", "https://example.com/composabl-perceptor-my-perceptor-0.0.1.tar.gz")
# Create a Portable Perceptor from a Directory
# this is great for testing locally
p = Perceptor("my-perceptor", "/tmp/composabl-perceptor-my-perceptor-0.0.1.tar.gz")
# Create a Portable Perceptor - Alternative
# note: we use PerceptorImpl as the base class API Spec to use for the portable perceptor
p = Perceptor("my-perceptor", PerceptorImpl, PerceptorOptions.model_validate({
"remote_address": "https://example.com/composabl-perceptor-my-perceptor-0.0.1.tar.gz"
}))
Implementing a Perceptor is just as easy as creating a Python package and creating a tar.gz
archive from it:
⚠️ You should prefix a perceptor with
composabl-perceptor-
to avoid name clashes with other packages
composabl-perceptor-my-perceptor
├── README.md
├── composabl_perceptor_my_perceptor
│ ├── __init__.py
│ └── perceptor.py
└── pyproject.toml
In the pyproject.toml
we then define an entrypoint and type under the [composabl]
section:
[project]
name = "composabl-perceptor-my-perceptor"
version = "0.1.0"
description = "A Composabl Demo Portable Teacher Perceptor"
authors = [{ name = "Xavier Geerinck", email = "xavier@composabl.io" }]
dependencies = [
"composabl-core"
]
[composabl]
type = "teacher"
entrypoint = "composabl_perceptor_my_perceptor.perceptor:MyPerceptor"
HTTP Support
We only supported Simulators to be implemented through the gRPC protocol before. We are changing this and will be allowing the HTTP protocol as well (through Protobuf). This means that you can now create a simulator that is accessible through HTTP and use it within the Composabl SDK.
As simulators are supposed to run quickly, the performance is important as well. Below you can find a comparison between gRPC and HTTP:
gRPC
HTTP
Thus representing a small slow-down over HTTP, which is still acceptable at large scale. If performance is an important factor, we recommend to keep using gRPC.
As for supporting HTTP, this would still mean using our ServerComposabl
specification, but we change a small runtime flag. Internally, we will then create the server over HTTP instead of gRPC for you!
r = Runtime({
"target": {
"local": {
"address": "localhost:1337",
"protocol": "http"
}
}
})
Environment Input Validation
Upon creating a Simulator integration (Environment), we now validate to ensure that the correct implementation was used:
- Are all methods async?
- Are all methods implemented?
- Are the required parameter used in the correct order?
Agent Input Validation
Upon creating an Agent, we now validate to ensure that the correct implementation was used:
- Are all methods async?
- Are all methods implemented?
- Are the required parameter used in the correct order?
🔧 Maintenance
- Ray has been upgraded to 2.9.3
- Torch has been upgraded to 2.2.1 and above