diff --git a/src/agentscope/_init.py b/src/agentscope/_init.py index af8b0b597..85fc0e625 100644 --- a/src/agentscope/_init.py +++ b/src/agentscope/_init.py @@ -6,7 +6,7 @@ from typing import Optional, Union, Sequence from agentscope import agents from .agents import AgentBase -from ._runtime import Runtime +from ._runtime import _runtime from .file_manager import file_manager from .utils.logging_utils import LOG_LEVEL, setup_logger from .utils.monitor import MonitorFactory @@ -73,7 +73,7 @@ def init( _INIT_SETTINGS["model_configs"] = model_configs _INIT_SETTINGS["project"] = project _INIT_SETTINGS["name"] = name - _INIT_SETTINGS["runtime_id"] = Runtime.runtime_id + _INIT_SETTINGS["runtime_id"] = _runtime.runtime_id _INIT_SETTINGS["save_dir"] = save_dir _INIT_SETTINGS["save_api_invoke"] = save_api_invoke _INIT_SETTINGS["save_log"] = save_log @@ -119,7 +119,7 @@ def init_process( save_log: bool = False, logger_level: LOG_LEVEL = _DEFAULT_LOG_LEVEL, ) -> None: - """A entry to initialize the package in a process. + """An entry to initialize the package in a process. Args: project (`Optional[str]`, defaults to `None`): @@ -147,12 +147,12 @@ def init_process( read_model_configs(model_configs) # Init the runtime - Runtime.project = project - Runtime.name = name + _runtime.project = project + _runtime.name = name if runtime_id is not None: - Runtime.runtime_id = runtime_id + _runtime.runtime_id = runtime_id - # Init file manager + # Init file manager and save configs by default file_manager.init(save_dir, save_api_invoke) # Init monitor diff --git a/src/agentscope/_runtime.py b/src/agentscope/_runtime.py index 268323cb0..b432e8943 100644 --- a/src/agentscope/_runtime.py +++ b/src/agentscope/_runtime.py @@ -1,13 +1,18 @@ # -*- coding: utf-8 -*- """Manage the id for each runtime""" +from datetime import datetime +from typing import Any + from agentscope.utils.tools import _get_timestamp from agentscope.utils.tools import _generate_random_code _RUNTIME_ID_FORMAT = "run_%Y%m%d-%H%M%S_{}" +_RUNTIME_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S" -class Runtime: - """Used to record the runtime information.""" +class _Runtime: + """A singleton class used to record the runtime information, which will + be initialized when the package is imported.""" project: str = None """The project name, which is used to identify the project.""" @@ -15,8 +20,45 @@ class Runtime: name: str = None """The name for runtime, which is used to identify this runtime.""" - runtime_id: str = _get_timestamp(_RUNTIME_ID_FORMAT).format( - _generate_random_code(), - ) + runtime_id: str = None """The id for runtime, which is used to identify the this runtime and - name the saving directory.""" + name the saving directory.""" + + _timestamp: datetime = datetime.now() + """The timestamp of when the runtime is initialized.""" + + _instance = None + + def __new__(cls, *args: Any, **kwargs: Any) -> Any: + """Create a singleton instance.""" + if not cls._instance: + cls._instance = super(_Runtime, cls).__new__( + cls, + *args, + **kwargs, + ) + return cls._instance + + def __init__(self) -> None: + """Generate random project name, runtime name and default + runtime_id when the package is initialized. After that, user can set + them by calling `agentscope.init(project="xxx", name="xxx", + runtime_id="xxx")`.""" + + self.project = _generate_random_code() + self.name = _generate_random_code(uppercase=False) + + # Obtain time from timestamp in string format, and then turn it into + # runtime ID format + self.runtime_id = _get_timestamp( + _RUNTIME_ID_FORMAT, + self._timestamp, + ).format(self.name) + + @property + def timestamp(self) -> str: + """Get the current timestamp in specific format.""" + return self._timestamp.strftime(_RUNTIME_TIMESTAMP_FORMAT) + + +_runtime = _Runtime() diff --git a/src/agentscope/constants.py b/src/agentscope/constants.py index 2b557d75a..a006d4a9b 100644 --- a/src/agentscope/constants.py +++ b/src/agentscope/constants.py @@ -15,8 +15,11 @@ _DEFAULT_SUBDIR_CODE = "code" _DEFAULT_SUBDIR_FILE = "file" _DEFAULT_SUBDIR_INVOKE = "invoke" +_DEFAULT_CFG_NAME = ".config" _DEFAULT_IMAGE_NAME = "image_{}_{}.png" _DEFAULT_SQLITE_DB_PATH = "agentscope.db" + + # for model wrapper _DEFAULT_MAX_RETRIES = 3 _DEFAULT_MESSAGES_KEY = "inputs" diff --git a/src/agentscope/file_manager.py b/src/agentscope/file_manager.py index ad397b8ab..be287c64b 100644 --- a/src/agentscope/file_manager.py +++ b/src/agentscope/file_manager.py @@ -6,7 +6,7 @@ import numpy as np -from agentscope._runtime import Runtime +from agentscope._runtime import _runtime from agentscope.utils.tools import _download_file, _get_timestamp from agentscope.utils.tools import _generate_random_code from agentscope.constants import ( @@ -16,6 +16,7 @@ _DEFAULT_SUBDIR_INVOKE, _DEFAULT_SQLITE_DB_PATH, _DEFAULT_IMAGE_NAME, + _DEFAULT_CFG_NAME, ) @@ -44,19 +45,24 @@ def __new__(cls, *args: Any, **kwargs: Any) -> Any: def _get_and_create_subdir(self, subdir: str) -> str: """Get the path of the subdir and create the subdir if it does not exist.""" - path = os.path.join(self.dir, Runtime.runtime_id, subdir) + path = os.path.join(self.dir, _runtime.runtime_id, subdir) if not os.path.exists(path): os.makedirs(path) return path def _get_file_path(self, file_name: str) -> str: """Get the path of the file.""" - return os.path.join(self.dir, Runtime.runtime_id, file_name) + return os.path.join(self.dir, _runtime.runtime_id, file_name) + + @property + def dir_root(self) -> str: + """The root directory to save code, information and logs.""" + return os.path.join(self.dir, _runtime.runtime_id) @property def dir_log(self) -> str: """The directory for saving logs.""" - return os.path.join(self.dir, Runtime.runtime_id) + return os.path.join(self.dir, _runtime.runtime_id) @property def dir_file(self) -> str: @@ -82,12 +88,30 @@ def path_db(self) -> str: def init(self, save_dir: str, save_api_invoke: bool = False) -> None: """Set the directory for saving files.""" self.dir = save_dir - runtime_dir = os.path.join(save_dir, Runtime.runtime_id) + runtime_dir = os.path.join(save_dir, _runtime.runtime_id) if not os.path.exists(runtime_dir): os.makedirs(runtime_dir) self.save_api_invoke = save_api_invoke + # Save the project and name to the runtime directory + self._save_config() + + def _save_config(self) -> None: + """Save the configuration of the runtime in its root directory.""" + cfg = { + "project": _runtime.project, + "name": _runtime.name, + "id": _runtime.runtime_id, + "timestamp": _runtime.timestamp, + } + with open( + os.path.join(self.dir_root, _DEFAULT_CFG_NAME), + "w", + encoding="utf-8", + ) as file: + json.dump(cfg, file, indent=4) + def save_api_invocation( self, prefix: str, diff --git a/src/agentscope/utils/tools.py b/src/agentscope/utils/tools.py index e3177ce99..c513e4372 100644 --- a/src/agentscope/utils/tools.py +++ b/src/agentscope/utils/tools.py @@ -32,9 +32,15 @@ def extract_json_str(json_str: str) -> str: return json_str[start_index:] -def _get_timestamp(format_: str = "%Y-%m-%d %H:%M:%S") -> str: +def _get_timestamp( + format_: str = "%Y-%m-%d %H:%M:%S", + time: datetime.datetime = None, +) -> str: """Get current timestamp.""" - return datetime.datetime.now().strftime(format_) + if time is None: + return datetime.datetime.now().strftime(format_) + else: + return time.strftime(format_) def to_openai_dict(item: dict) -> dict: @@ -140,7 +146,18 @@ def _download_file(url: str, path_file: str, max_retries: int = 3) -> bool: return False -def _generate_random_code(length: int = 6) -> str: +def _generate_random_code( + length: int = 6, + uppercase: bool = True, + lowercase: bool = True, + digits: bool = True, +) -> str: """Get random code.""" - characters = string.ascii_letters + string.digits + characters = "" + if uppercase: + characters += string.ascii_uppercase + if lowercase: + characters += string.ascii_lowercase + if digits: + characters += string.digits return "".join(secrets.choice(characters) for i in range(length))