#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
use crate::network::TransportMode;
use crate::network::client::agent::{ClientError, RelayRLAgent};
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
use crate::utilities::configuration::NetworkParams;
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
use active_uuid_registry::interface::get_context_entries;
use relayrl_algorithms::prelude::ppo::algorithm::{IPPOParams, MAPPOParams, PPOParams};
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
use relayrl_types::data::action::CodecConfig;
use relayrl_types::data::tensor::BackendMatcher;
use relayrl_types::model::ModelModule;
use burn_tensor::backend::Backend;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq)]
pub struct DefaultHyperparameterArgs {
pub ppo: Option<PPOParams>,
pub ippo: Option<IPPOParams>,
pub mappo: Option<MAPPOParams>,
pub config_default_init: bool,
}
impl Default for DefaultHyperparameterArgs {
fn default() -> Self {
Self {
ppo: None,
ippo: None,
mappo: None,
config_default_init: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum AlgorithmInitArgs {
PPO(Option<PPOParams>),
IPPO(Option<IPPOParams>),
MAPPO(Option<MAPPOParams>),
}
impl Default for AlgorithmInitArgs {
fn default() -> Self {
Self::PPO(None)
}
}
impl std::fmt::Display for DefaultHyperparameterArgs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "DefaultHyperparameterArgs {{")?;
if let Some(ppo) = &self.ppo {
write!(f, "ppo: {:?}", ppo)?;
}
if let Some(ippo) = &self.ippo {
write!(f, "ippo: {:?}", ippo)?;
}
if let Some(mappo) = &self.mappo {
write!(f, "mappo: {:?}", mappo)?;
}
if self.config_default_init {
write!(f, "config_default_init: true")?;
} else {
write!(f, "config_default_init: false")?;
}
write!(f, "}}")?;
Ok(())
}
}
impl AlgorithmInitArgs {
pub fn as_str(&self) -> &str {
match self {
AlgorithmInitArgs::PPO(_) => "PPO",
AlgorithmInitArgs::IPPO(_) => "IPPO",
AlgorithmInitArgs::MAPPO(_) => "MAPPO",
}
}
}
#[cfg(feature = "zmq-transport")]
#[derive(Debug, Clone, PartialEq)]
pub struct ZmqInferenceAddressesArgs {
pub inference_server_address: Option<NetworkParams>,
pub inference_scaling_server_address: Option<NetworkParams>,
}
#[cfg(feature = "zmq-transport")]
#[derive(Debug, Clone, PartialEq)]
pub struct ZmqTrainingAddressesArgs {
pub agent_listener_address: Option<NetworkParams>,
pub model_server_address: Option<NetworkParams>,
pub trajectory_server_address: Option<NetworkParams>,
pub training_scaling_server_address: Option<NetworkParams>,
}
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
#[derive(Debug, Clone, PartialEq)]
pub enum InferenceAddressesArgs {
#[cfg(feature = "zmq-transport")]
ZMQ(ZmqInferenceAddressesArgs),
#[cfg(feature = "nats-transport")]
NATS(Option<NetworkParams>),
}
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
#[derive(Debug, Clone, PartialEq)]
pub enum TrainingAddressesArgs {
#[cfg(feature = "zmq-transport")]
ZMQ(ZmqTrainingAddressesArgs),
#[cfg(feature = "nats-transport")]
NATS(Option<NetworkParams>),
}
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
#[derive(Default, Debug, Clone, PartialEq)]
pub struct InferenceParams {
pub model_mode: ModelMode,
pub codec: Option<CodecConfig>,
pub inference_addresses: Option<InferenceAddressesArgs>,
}
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
#[derive(Default, Debug, Clone, PartialEq)]
pub struct TrainingParams {
pub model_mode: ModelMode,
pub default_hyperparameters: Option<DefaultHyperparameterArgs>,
pub codec: Option<CodecConfig>,
pub training_addresses: Option<TrainingAddressesArgs>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum LocalTrajectoryFileType {
Csv,
Arrow,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LocalTrajectoryFileParams {
pub directory: PathBuf,
pub file_type: LocalTrajectoryFileType,
}
impl LocalTrajectoryFileParams {
pub fn new(
directory: PathBuf,
file_type: LocalTrajectoryFileType,
) -> Result<Self, ClientError> {
if directory.as_os_str().is_empty() {
return Err(ClientError::InvalidTrajectoryFileDirectory(format!(
"Path '{}' is empty",
directory.display()
)));
}
{
const TOTAL_ATTEMPTS: i32 = 2;
let mut attempts: i32 = 1;
while !directory.exists() {
match std::fs::create_dir_all(&directory) {
Ok(_) => break,
Err(_) if attempts < TOTAL_ATTEMPTS => {
attempts += 1;
continue;
}
Err(e) => {
return Err(ClientError::InvalidTrajectoryFileDirectory(e.to_string()));
}
}
}
}
if !directory.is_dir() {
return Err(ClientError::InvalidTrajectoryFileDirectory(format!(
"Path is not a directory, {}",
directory.display()
)));
}
Ok(Self {
directory,
file_type,
})
}
}
impl Default for LocalTrajectoryFileParams {
fn default() -> Self {
Self::new(PathBuf::from("."), LocalTrajectoryFileType::Csv).unwrap_or_else(|_| {
log::error!(
"Failed to validate the default local trajectory directory, falling back to the current directory"
);
Self {
directory: PathBuf::from("."),
file_type: LocalTrajectoryFileType::Csv,
}
})
}
}
#[non_exhaustive]
#[derive(Default, Debug, Clone, PartialEq)]
pub enum ModelMode {
#[default]
Independent,
Shared,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum ActorInferenceMode {
Client(ModelMode),
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "nats-transport", feature = "zmq-transport")))
)]
Server(InferenceParams),
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "nats-transport", feature = "zmq-transport")))
)]
ClientFallback(ModelMode, InferenceParams),
}
impl Default for ActorInferenceMode {
fn default() -> Self {
Self::Client(ModelMode::default())
}
}
pub type TrajectoryCacheSize = usize;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum ActorDataMode {
OfflineWithFiles(Option<LocalTrajectoryFileParams>),
OfflineWithCache(TrajectoryCacheSize),
OfflineWithFilesAndCache(Option<LocalTrajectoryFileParams>, TrajectoryCacheSize),
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "nats-transport", feature = "zmq-transport")))
)]
Online(TrainingParams),
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "nats-transport", feature = "zmq-transport")))
)]
OnlineWithFiles(TrainingParams, Option<LocalTrajectoryFileParams>),
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "nats-transport", feature = "zmq-transport")))
)]
OnlineWithCache(TrainingParams, TrajectoryCacheSize),
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "nats-transport", feature = "zmq-transport")))
)]
OnlineWithFilesAndCache(
TrainingParams,
Option<LocalTrajectoryFileParams>,
TrajectoryCacheSize,
),
Disabled,
}
impl Default for ActorDataMode {
fn default() -> Self {
Self::OfflineWithCache(1000)
}
}
pub(crate) fn uses_local_file_writing(training_data_mode: &ActorDataMode) -> bool {
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
return matches!(
training_data_mode,
ActorDataMode::OfflineWithFiles(_)
| ActorDataMode::OfflineWithFilesAndCache(..)
| ActorDataMode::OnlineWithFiles(..)
| ActorDataMode::OnlineWithFilesAndCache(..)
);
#[cfg(not(any(feature = "nats-transport", feature = "zmq-transport")))]
return matches!(
training_data_mode,
ActorDataMode::OfflineWithFiles(_) | ActorDataMode::OfflineWithFilesAndCache(..)
);
}
pub(crate) fn uses_trajectory_cache(training_data_mode: &ActorDataMode) -> bool {
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
return matches!(
training_data_mode,
ActorDataMode::OfflineWithCache(_)
| ActorDataMode::OfflineWithFilesAndCache(..)
| ActorDataMode::OnlineWithCache(..)
| ActorDataMode::OnlineWithFilesAndCache(..)
);
#[cfg(not(any(feature = "nats-transport", feature = "zmq-transport")))]
return matches!(
training_data_mode,
ActorDataMode::OfflineWithCache(_) | ActorDataMode::OfflineWithFilesAndCache(..)
);
}
#[derive(Default, Debug, Clone, PartialEq)]
pub struct ClientModes {
pub actor_inference_mode: ActorInferenceMode,
pub actor_data_mode: ActorDataMode,
}
pub type ReplayBufferSize = usize;
pub type SaveModelPath = PathBuf;
#[derive(Clone)]
pub struct AgentStartParameters<B: Backend + BackendMatcher<Backend = B>> {
pub data_routers: u32,
pub data_buffer_size: usize,
pub default_model: Option<ModelModule<B>>,
pub config_polling_seconds: Option<u64>,
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
pub default_hyperparameters: DefaultHyperparameterArgs,
pub config_path: Option<PathBuf>,
}
impl<B: Backend + BackendMatcher<Backend = B>> std::fmt::Debug for AgentStartParameters<B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "AgentStartParameters")
}
}
#[derive(Clone)]
pub struct AgentBuildInvariants<B: Backend + BackendMatcher<Backend = B>> {
pub builder: AgentBuilder<B>,
}
impl<B: Backend + BackendMatcher<Backend = B>> AgentBuildInvariants<B> {
fn with(builder: AgentBuilder<B>) -> Self {
Self {
builder: builder.to_owned(),
}
}
pub fn params(self) -> AgentBuildParameters<B> {
AgentBuildParameters::<B>::with(self.builder.to_owned())
}
pub fn actor_inference_mode(mut self, actor_inference_mode: ActorInferenceMode) -> Self {
self.builder.settings.client_modes.actor_inference_mode = actor_inference_mode;
self
}
pub fn actor_data_mode(mut self, actor_data_mode: ActorDataMode) -> Self {
self.builder.settings.client_modes.actor_data_mode = actor_data_mode;
self
}
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
pub fn transport_mode(mut self, transport_mode: TransportMode) -> Self {
self.builder.settings.transport_mode = Some(transport_mode);
self
}
pub async fn build(self) -> Result<(RelayRLAgent<B>, AgentStartParameters<B>), ClientError> {
self.builder.build().await
}
}
#[derive(Clone)]
pub struct AgentBuildParameters<B: Backend + BackendMatcher<Backend = B>> {
pub builder: AgentBuilder<B>,
}
impl<B: Backend + BackendMatcher<Backend = B>> AgentBuildParameters<B> {
fn with(builder: AgentBuilder<B>) -> Self {
Self { builder }
}
pub fn modes(self) -> AgentBuildInvariants<B> {
AgentBuildInvariants::<B>::with(self.builder.to_owned())
}
pub fn data_routers(mut self, count: u32) -> Self {
self.builder.settings.data_routers = Some(count);
self
}
pub fn data_buffer_size(mut self, size: usize) -> Self {
self.builder.settings.data_buffer_size = Some(size);
self
}
pub fn default_model(mut self, model: ModelModule<B>) -> Self {
self.builder.settings.default_model = Some(model);
self
}
pub fn config_polling_seconds(mut self, seconds: u64) -> Self {
self.builder.settings.config_polling_seconds = Some(seconds);
self
}
pub fn config_path(mut self, path: PathBuf) -> Self {
self.builder.settings.config_path = Some(path);
self
}
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
pub fn default_ppo_params(mut self, ppo_params: PPOParams) -> Self {
self.builder.settings.default_hyperparameters.ppo = Some(ppo_params);
self
}
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
pub fn default_ippo_params(mut self, ippo_params: IPPOParams) -> Self {
self.builder.settings.default_hyperparameters.ippo = Some(ippo_params);
self
}
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
pub fn default_mappo_params(mut self, mappo_params: MAPPOParams) -> Self {
self.builder.settings.default_hyperparameters.mappo = Some(mappo_params);
self
}
pub async fn build(self) -> Result<(RelayRLAgent<B>, AgentStartParameters<B>), ClientError> {
self.builder.build().await
}
}
#[derive(Clone)]
pub struct BuilderSettings<B: Backend + BackendMatcher<Backend = B>> {
pub client_modes: ClientModes,
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
pub transport_mode: Option<TransportMode>,
pub data_routers: Option<u32>,
pub data_buffer_size: Option<usize>,
pub default_model: Option<ModelModule<B>>,
pub config_polling_seconds: Option<u64>,
pub default_hyperparameters: DefaultHyperparameterArgs,
pub config_path: Option<PathBuf>,
}
impl<B: Backend + BackendMatcher<Backend = B>> Default for BuilderSettings<B> {
fn default() -> Self {
Self {
client_modes: ClientModes::default(),
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
transport_mode: None,
data_routers: None,
data_buffer_size: None,
default_model: None,
config_polling_seconds: None,
default_hyperparameters: DefaultHyperparameterArgs::default(),
config_path: None,
}
}
}
#[must_use = "Provides ergonomic interface for configuring runtime invariants and start parameters for RelayRLAgent"]
#[derive(Clone)]
pub struct AgentBuilder<B: Backend + BackendMatcher<Backend = B>> {
pub settings: BuilderSettings<B>,
}
impl<B: Backend + BackendMatcher<Backend = B>> AgentBuilder<B> {
pub fn builder() -> Self {
Self {
settings: BuilderSettings::<B>::default(),
}
}
pub fn modes(self) -> AgentBuildInvariants<B> {
AgentBuildInvariants::<B>::with(self)
}
pub fn params(self) -> AgentBuildParameters<B> {
AgentBuildParameters::<B>::with(self)
}
pub async fn build(self) -> Result<(RelayRLAgent<B>, AgentStartParameters<B>), ClientError> {
let agent: RelayRLAgent<B> = RelayRLAgent::<B>::init(
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
self.settings.transport_mode.unwrap_or_default(),
self.settings.client_modes,
);
let startup_params: AgentStartParameters<B> = AgentStartParameters::<B> {
data_routers: self.settings.data_routers.unwrap_or(1),
data_buffer_size: self.settings.data_buffer_size.unwrap_or(1024),
default_model: self.settings.default_model,
#[cfg(any(feature = "nats-transport", feature = "zmq-transport"))]
default_hyperparameters: self.settings.default_hyperparameters,
config_polling_seconds: self.settings.config_polling_seconds,
config_path: self.settings.config_path,
};
Ok((agent, startup_params))
}
}