#2869 - Changes to AbstractAgent to address some pydantic issues

This commit is contained in:
Charlie Crane
2024-12-13 16:37:39 +00:00
parent 4c20cd4ac6
commit c3a70be8d1
4 changed files with 22 additions and 29 deletions

View File

@@ -42,7 +42,6 @@ AbstractAgent
Configurable items within a new agent within PrimAITE should contain a ``ConfigSchema`` which holds all configurable variables of the agent. This should not include parameters related to its *state*. Configurable items within a new agent within PrimAITE should contain a ``ConfigSchema`` which holds all configurable variables of the agent. This should not include parameters related to its *state*.
.. code-block:: python .. code-block:: python
class ExampleAgent(AbstractAgent, identifier = "example_agent"): class ExampleAgent(AbstractAgent, identifier = "example_agent"):
@@ -63,7 +62,7 @@ AbstractAgent
#. **identifier**: #. **identifier**:
All agent classes should have a unique ``identifier`` attribute, for when they are added to the base ``AbstractAgent`` registry. PrimAITE notation is for these to be written in snake_case All agent classes should have a ``identifier`` attribute, a unique snake_case string, for when they are added to the base ``AbstractAgent`` registry.
Changes to YAML file Changes to YAML file
==================== ====================

View File

@@ -4,7 +4,6 @@ from pathlib import Path
from typing import Optional from typing import Optional
from prettytable import MARKDOWN, PrettyTable from prettytable import MARKDOWN, PrettyTable
from pydantic import BaseModel
from primaite.simulator import LogLevel, SIM_OUTPUT from primaite.simulator import LogLevel, SIM_OUTPUT
@@ -20,28 +19,23 @@ class _NotJSONFilter(logging.Filter):
return not record.getMessage().startswith("{") and not record.getMessage().endswith("}") return not record.getMessage().startswith("{") and not record.getMessage().endswith("}")
class AgentLog(BaseModel): class AgentLog:
""" """
A Agent Log class is a simple logger dedicated to managing and writing logging updates and information for an agent. A Agent Log class is a simple logger dedicated to managing and writing logging updates and information for an agent.
Each log message is written to a file located at: <simulation output directory>/agent_name/agent_name.log Each log message is written to a file located at: <simulation output directory>/agent_name/agent_name.log
""" """
agent_name: str = "unnamed_agent"
current_episode: int = 1
current_timestep: int = 0
logger: logging
def __init__(self, agent_name: Optional[str]): def __init__(self, agent_name: Optional[str]):
""" """
Constructs a Agent Log instance for a given hostname. Constructs a Agent Log instance for a given hostname.
:param hostname: The hostname associated with the system logs being recorded. :param agent_name: The agent_name associated with the system logs being recorded.
""" """
super().__init__() super().__init__()
self.agent_name = agent_name or "unnamed_agent" self.agent_name = agent_name if agent_name else "unnamed_agent"
# self.current_episode: int = 1 self.current_timestep: int = 0
# self.current_timestep: int = 0 self.current_episode: int = 0
self.setup_logger() self.setup_logger()
@property @property

View File

@@ -2,7 +2,7 @@
"""Interface for agents.""" """Interface for agents."""
from __future__ import annotations from __future__ import annotations
from abc import ABC, abstractmethod from abc import abstractmethod
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type, TYPE_CHECKING, Union from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type, TYPE_CHECKING, Union
from gymnasium.core import ActType, ObsType from gymnasium.core import ActType, ObsType
@@ -92,14 +92,12 @@ class AgentSettings(BaseModel):
return cls(**config) return cls(**config)
class AbstractAgent(BaseModel, ABC, identifier="Abstract_Agent"): class AbstractAgent(BaseModel, identifier="Abstract_Agent"):
"""Base class for scripted and RL agents.""" """Base class for scripted and RL agents."""
_registry: ClassVar[Dict[str, Type[AbstractAgent]]] = {} _registry: ClassVar[Dict[str, Type[AbstractAgent]]] = {}
config: "AbstractAgent.ConfigSchema" config: "AbstractAgent.ConfigSchema"
agent_name: str = "Abstact_Agent"
logger: AgentLog = AgentLog(agent_name)
class ConfigSchema(BaseModel): class ConfigSchema(BaseModel):
""" """
@@ -117,11 +115,13 @@ class AbstractAgent(BaseModel, ABC, identifier="Abstract_Agent"):
:type agent_settings: Optional[AgentSettings] :type agent_settings: Optional[AgentSettings]
""" """
agent_name: str = "Abstract_Agent"
history: List[AgentHistoryItem] = [] history: List[AgentHistoryItem] = []
action_manager: Optional[ActionManager] = None _logger: AgentLog = AgentLog(agent_name=agent_name)
observation_manager: Optional[ObservationManager] = None _action_manager: Optional[ActionManager] = None
reward_function: Optional[RewardFunction] = None _observation_manager: Optional[ObservationManager] = None
agent_settings: Optional[AgentSettings] = None _reward_function: Optional[RewardFunction] = None
_agent_settings: Optional[AgentSettings] = None
def __init_subclass__(cls, identifier: str, **kwargs: Any) -> None: def __init_subclass__(cls, identifier: str, **kwargs: Any) -> None:
super().__init_subclass__(**kwargs) super().__init_subclass__(**kwargs)
@@ -132,22 +132,22 @@ class AbstractAgent(BaseModel, ABC, identifier="Abstract_Agent"):
@property @property
def logger(self) -> AgentLog: def logger(self) -> AgentLog:
"""Return the AgentLog.""" """Return the AgentLog."""
return self.config.logger return self.config._logger
@property @property
def observation_manager(self) -> ObservationManager: def observation_manager(self) -> ObservationManager:
"""Returns the agents observation manager.""" """Returns the agents observation manager."""
return self.config.observation_manager return self.config._observation_manager
@property @property
def action_manager(self) -> ActionManager: def action_manager(self) -> ActionManager:
"""Returns the agents action manager.""" """Returns the agents action manager."""
return self.config.action_manager return self.config._action_manager
@property @property
def reward_function(self) -> RewardFunction: def reward_function(self) -> RewardFunction:
"""Returns the agents reward function.""" """Returns the agents reward function."""
return self.config.reward_function return self.config._reward_function
@classmethod @classmethod
def from_config(cls, config: Dict) -> "AbstractAgent": def from_config(cls, config: Dict) -> "AbstractAgent":

View File

@@ -42,11 +42,11 @@ class DataManipulationAgent(AbstractTAPAgent, identifier="Data_Manipulation_Agen
:rtype: Tuple[str, Dict] :rtype: Tuple[str, Dict]
""" """
if timestep < self.next_execution_timestep: if timestep < self.next_execution_timestep:
self.config.logger.debug(msg="Performing do nothing action") self.logger.debug(msg="Performing do nothing action")
return "do_nothing", {} return "do_nothing", {}
self._set_next_execution_timestep(timestep + self.config.agent_settings.start_settings.frequency) self._set_next_execution_timestep(timestep + self.config._agent_settings.start_settings.frequency)
self.config.logger.info(msg="Performing a data manipulation attack!") self.logger.info(msg="Performing a data manipulation attack!")
return "node_application_execute", { return "node_application_execute", {
"node_name": self.config.starting_node_name, "node_name": self.config.starting_node_name,
"application_name": self.config.starting_application_name, "application_name": self.config.starting_application_name,
@@ -55,4 +55,4 @@ class DataManipulationAgent(AbstractTAPAgent, identifier="Data_Manipulation_Agen
def setup_agent(self) -> None: def setup_agent(self) -> None:
"""Set the next execution timestep when the episode resets.""" """Set the next execution timestep when the episode resets."""
self._select_start_node() self._select_start_node()
self._set_next_execution_timestep(self.config.agent_settings.start_settings.start_step) self._set_next_execution_timestep(self.config._agent_settings.start_settings.start_step)