diff --git a/README.md b/README.md index e261ef38a..227ef5601 100644 --- a/README.md +++ b/README.md @@ -16,24 +16,28 @@ Welcome to join our community on [Discord](https://discord.gg/Fwg5hZ2S) or DingD Table of Contents ================= -* [Installation](#installation) - * [From source](#from-source) - * [Using pip](#using-pip) -* [Quick Start](#quick-start) - * [Basic Usage](#basic-usage) - * [Step 1: Prepare Model Configs](#step-1-prepare-model-configs) - * [Step2: Create Agents](#step-2-create-agents) - * [Step3: Construct Conversation](#step-3-construct-conversation) - * [Advanced Usage](#advanced-usage) - * [Pipeline and MsgHub](#pipeline-and-msghub) - * [Customize Your Own Agent](#customize-your-own-agent) - * [Built-in Resources](#built-in-resources) - * [Agent Pool](#agent-pool) - * [Services](#services) - * [Examples Applications](#example-applications) -* [License](#license) -* [Contributing](#contributing) -* [References](#references) +- [AgentScope](#agentscope) +- [Table of Contents](#table-of-contents) + - [Installation](#installation) + - [From source](#from-source) + - [Using pip](#using-pip) + - [Quick Start](#quick-start) + - [Basic Usage](#basic-usage) + - [Step 1: Prepare Model Configs](#step-1-prepare-model-configs) + - [OpenAI API Config](#openai-api-config) + - [Post Request API Config](#post-request-api-config) + - [Step 2: Create Agents](#step-2-create-agents) + - [Step 3: Construct Conversation](#step-3-construct-conversation) + - [Advanced Usage](#advanced-usage) + - [**Pipeline** and **MsgHub**](#pipeline-and-msghub) + - [Customize Your Own Agent](#customize-your-own-agent) + - [Built-in Resources](#built-in-resources) + - [Agent Pool](#agent-pool) + - [Services](#services) + - [Example Applications](#example-applications) + - [License](#license) + - [Contributing](#contributing) + - [References](#references) ## Installation @@ -209,7 +213,7 @@ To simplify the construction of agents communication, AgentScope provides two he agent2.observe(x1) # The message x1 should be broadcast to other agents agent3.observe(x1) - x2 = agent2(x1) + x2 = agent2(x1) agent1.observe(x2) agent3.observe(x2) ``` diff --git a/docs/sphinx_doc/source/agentscope.rst b/docs/sphinx_doc/source/agentscope.rst index 42018ef12..13a8123cf 100644 --- a/docs/sphinx_doc/source/agentscope.rst +++ b/docs/sphinx_doc/source/agentscope.rst @@ -1,6 +1,14 @@ Module contents =============== +constants module +-------------------------------- + +.. automodule:: agentscope.constants + :noindex: + :members: + :undoc-members: + :show-inheritance: file\_manager module -------------------------------- diff --git a/docs/sphinx_doc/source/agentscope.service.rst b/docs/sphinx_doc/source/agentscope.service.rst index 6c7666345..cb029eaf8 100644 --- a/docs/sphinx_doc/source/agentscope.service.rst +++ b/docs/sphinx_doc/source/agentscope.service.rst @@ -12,3 +12,22 @@ Service package agentscope.service.text_processing agentscope.service.web_search + +service\_status module +-------------------------------- + +.. automodule:: agentscope.service.service_status + :noindex: + :members: + :undoc-members: + :show-inheritance: + + +service\_response module +-------------------------------- + +.. automodule:: agentscope.service.service_response + :noindex: + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/sphinx_doc/source/agentscope.utils.rst b/docs/sphinx_doc/source/agentscope.utils.rst index 22f08054b..740188d8b 100644 --- a/docs/sphinx_doc/source/agentscope.utils.rst +++ b/docs/sphinx_doc/source/agentscope.utils.rst @@ -10,14 +10,6 @@ common module :undoc-members: :show-inheritance: -enums module ------------------------------- - -.. automodule:: agentscope.utils.enums - :members: - :undoc-members: - :show-inheritance: - logging\_utils module --------------------------------------- diff --git a/src/agentscope/__init__.py b/src/agentscope/__init__.py index 5a2d5c42d..5aa9b7eaf 100644 --- a/src/agentscope/__init__.py +++ b/src/agentscope/__init__.py @@ -7,7 +7,6 @@ from . import models from . import pipelines from . import service -from . import utils # TODO: not exposed to the user from . import message from . import prompt diff --git a/src/agentscope/agents/__init__.py b/src/agentscope/agents/__init__.py index 84df208c2..14723727c 100644 --- a/src/agentscope/agents/__init__.py +++ b/src/agentscope/agents/__init__.py @@ -1,18 +1,16 @@ # -*- coding: utf-8 -*- -""" Import all agent-related modules in the package. """ -from typing import Callable +""" Import all agent related modules in the package. """ from .agent import AgentBase +from .operator import Operator from .rpc_agent import RpcAgentBase from .dialog_agent import DialogAgent from .dict_dialog_agent import DictDialogAgent from .user_agent import UserAgent -# todo: convert Operator to a common base class for AgentBase and PipelineBase -_Operator = Callable[..., dict] __all__ = [ "AgentBase", - "_Operator", + "Operator", "RpcAgentBase", "DialogAgent", "DictDialogAgent", diff --git a/src/agentscope/agents/agent.py b/src/agentscope/agents/agent.py index 9527aae46..6dee7c4a1 100644 --- a/src/agentscope/agents/agent.py +++ b/src/agentscope/agents/agent.py @@ -10,12 +10,12 @@ from loguru import logger -from .operator import _Operator +from .operator import Operator from ..models import load_model_by_name from ..memory import TemporaryMemory -class AgentBase(_Operator): +class AgentBase(Operator): """Base class for all agents. All agents should inherit from this class and implement the `reply` diff --git a/src/agentscope/agents/dialog_agent.py b/src/agentscope/agents/dialog_agent.py index febcceac6..1f749c289 100644 --- a/src/agentscope/agents/dialog_agent.py +++ b/src/agentscope/agents/dialog_agent.py @@ -6,7 +6,7 @@ from ..message import Msg from .agent import AgentBase from ..prompt import PromptEngine -from ..utils.enums import PromptType +from ..prompt import PromptType class DialogAgent(AgentBase): diff --git a/src/agentscope/agents/dict_dialog_agent.py b/src/agentscope/agents/dict_dialog_agent.py index d2eca9eef..a6e66d9fe 100644 --- a/src/agentscope/agents/dict_dialog_agent.py +++ b/src/agentscope/agents/dict_dialog_agent.py @@ -8,7 +8,7 @@ from ..message import Msg from .agent import AgentBase from ..prompt import PromptEngine -from ..utils.enums import PromptType +from ..prompt import PromptType class DictDialogAgent(AgentBase): diff --git a/src/agentscope/agents/operator.py b/src/agentscope/agents/operator.py index cb788eb09..6504a31ae 100644 --- a/src/agentscope/agents/operator.py +++ b/src/agentscope/agents/operator.py @@ -5,7 +5,7 @@ from typing import Any -class _Operator(ABC): +class Operator(ABC): """ Abstract base class `Operator` defines a protocol for classes that implement callable behavior. diff --git a/src/agentscope/agents/rpc_agent.py b/src/agentscope/agents/rpc_agent.py index ae438ede8..4b3abe0fc 100644 --- a/src/agentscope/agents/rpc_agent.py +++ b/src/agentscope/agents/rpc_agent.py @@ -382,7 +382,6 @@ def setup_rcp_agent_server( f"Stopping rpc server [{servicer_class.__name__}] at port [{port}]", ) server.stop(0) - stop_event.set() logger.info( f"rpc server [{servicer_class.__name__}] at port [{port}] stopped " "successfully", @@ -541,9 +540,13 @@ def shutdown(self) -> None: if self.server is not None: if self.stop_event is not None: self.stop_event.set() - self.stop_event.clear() - self.stop_event.wait() self.stop_event = None self.server.terminate() - self.server.join() + self.server.join(timeout=5) + if self.server.is_alive(): + self.server.kill() + logger.info( + f"Rpc server [{self.agent_class.__name__}] at port" + f" [{self.port}] is killed.", + ) self.server = None diff --git a/src/agentscope/agents/rpc_dialog_agent.py b/src/agentscope/agents/rpc_dialog_agent.py index 5b59b3cd0..6f81c15ba 100644 --- a/src/agentscope/agents/rpc_dialog_agent.py +++ b/src/agentscope/agents/rpc_dialog_agent.py @@ -10,7 +10,7 @@ from agentscope.message import Msg from agentscope.agents.rpc_agent import RpcAgentBase from agentscope.prompt import PromptEngine -from agentscope.utils.enums import PromptType +from agentscope.prompt import PromptType class RpcDialogAgent(RpcAgentBase): diff --git a/src/agentscope/configs/model_config.py b/src/agentscope/configs/model_config.py index a3f981fc0..774be726a 100644 --- a/src/agentscope/configs/model_config.py +++ b/src/agentscope/configs/model_config.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- """The model config.""" from typing import Any - - -DEFAULT_MAX_RETRIES = 3 -DEFAULT_MESSAGES_KEY = "inputs" +from ..constants import _DEFAULT_MAX_RETRIES +from ..constants import _DEFAULT_MESSAGES_KEY class CfgBase(dict): @@ -108,10 +106,10 @@ class PostApiCfg(CfgBase): **generate_args)`, which depends on the specific requirements of the model API.""" - max_retries: int = DEFAULT_MAX_RETRIES + max_retries: int = _DEFAULT_MAX_RETRIES """The max retries of the request.""" - messages_key: str = DEFAULT_MESSAGES_KEY + messages_key: str = _DEFAULT_MESSAGES_KEY """The key of the prompt messages in `requests.post()`, e.g. `request.post(json={${messages_key}: messages, **json_args})`. For huggingface and modelscope inference API, the key is `inputs`""" diff --git a/src/agentscope/constants.py b/src/agentscope/constants.py index 6885cc3db..997d2998f 100644 --- a/src/agentscope/constants.py +++ b/src/agentscope/constants.py @@ -1,4 +1,54 @@ # -*- coding: utf-8 -*- """ Some constants used in the project""" +from numbers import Number +from enum import IntEnum + PACKAGE_NAME = "agentscope" MSG_TOKEN = f"<{PACKAGE_NAME}_msg>" + + +# default values + +# for file manager +_DEFAULT_DIR = "./runs" +_DEFAULT_LOG_LEVEL = "INFO" +_DEFAULT_SUBDIR_CODE = "code" +_DEFAULT_SUBDIR_FILE = "file" +_DEFAULT_SUBDIR_INVOKE = "invoke" +_DEFAULT_IMAGE_NAME = "image_{}_{}.png" +# for model wrapper +_DEFAULT_MAX_RETRIES = 3 +_DEFAULT_MESSAGES_KEY = "inputs" +_DEFAULT_RETRY_INTERVAL = 1 +# for execute python +_DEFAULT_PYPI_MIRROR = "http://mirrors.aliyun.com/pypi/simple/" +_DEFAULT_TRUSTED_HOST = "mirrors.aliyun.com" +# for summarization +_DEFAULT_SUMMARIZATION_PROMPT = """ +TEXT: {} +""" +_DEFAULT_SYSTEM_PROMPT = """ +You are a helpful agent to summarize the text. +You need to keep all the key information of the text in the summary. +""" +_DEFAULT_TOKEN_LIMIT_PROMPT = """ +Summarize the text after TEXT in less than {} tokens: +""" + +# typing +Embedding = list[Number] + + +# enums +class ResponseFormat(IntEnum): + """Enum for model response format.""" + + NONE = 0 + JSON = 1 + + +class ShrinkPolicy(IntEnum): + """Enum for shrink strategies when the prompt is too long.""" + + TRUNCATE = 0 + SUMMARIZE = 1 diff --git a/src/agentscope/file_manager.py b/src/agentscope/file_manager.py index 8c90abc1e..db73cda40 100644 --- a/src/agentscope/file_manager.py +++ b/src/agentscope/file_manager.py @@ -9,14 +9,13 @@ from agentscope._runtime import Runtime from agentscope.utils.tools import _download_file, _get_timestamp from agentscope.utils.tools import _generate_random_code - -# TODO: move default values into one file -_DEFAULT_DIR = "./runs" -_DEFAULT_SUBDIR_CODE = "code" -_DEFAULT_SUBDIR_FILE = "file" -_DEFAULT_SUBDIR_INVOKE = "invoke" - -_DEFAULT_IMAGE_NAME = "image_{}_{}.png" +from agentscope.constants import ( + _DEFAULT_DIR, + _DEFAULT_SUBDIR_CODE, + _DEFAULT_SUBDIR_FILE, + _DEFAULT_SUBDIR_INVOKE, + _DEFAULT_IMAGE_NAME, +) class _FileManager: diff --git a/src/agentscope/models/model.py b/src/agentscope/models/model.py index 8922ad69a..77bacee6b 100644 --- a/src/agentscope/models/model.py +++ b/src/agentscope/models/model.py @@ -63,9 +63,8 @@ from ..file_manager import file_manager from ..utils.tools import _get_timestamp - -# TODO: move default values into a single file -DEFAULT_MAX_RETRIES = 1 +from ..constants import _DEFAULT_MAX_RETRIES +from ..constants import _DEFAULT_RETRY_INTERVAL def _response_parse_decorator( @@ -102,7 +101,7 @@ def checking_wrapper(self: Any, *args: Any, **kwargs: Any) -> dict: # Step1: Extract parse_func and fault_handler parse_func = kwargs.pop("parse_func", None) fault_handler = kwargs.pop("fault_handler", None) - max_retries = kwargs.pop("max_retries", None) or DEFAULT_MAX_RETRIES + max_retries = kwargs.pop("max_retries", None) or _DEFAULT_MAX_RETRIES # Step2: Call the model and parse the response # Return the response directly if parse_func is not provided @@ -124,7 +123,7 @@ def checking_wrapper(self: Any, *args: Any, **kwargs: Any) -> dict: f"{response}.\n Exception: {e}, " f"\t Attempt {itr} / {max_retries}", ) - time.sleep(0.5 * itr) + time.sleep(_DEFAULT_RETRY_INTERVAL * itr) if fault_handler is not None and callable(fault_handler): return fault_handler(response) diff --git a/src/agentscope/models/post_model.py b/src/agentscope/models/post_model.py index e8b35110e..40fb176da 100644 --- a/src/agentscope/models/post_model.py +++ b/src/agentscope/models/post_model.py @@ -8,11 +8,9 @@ from loguru import logger from .model import ModelWrapperBase - -# TODO: move default values into a single file -DEFAULT_MAX_RETRIES = 3 -DEFAULT_MESSAGES_KEY = "inputs" -RETRY_TIME_INTERVAL = 1 +from ..constants import _DEFAULT_MAX_RETRIES +from ..constants import _DEFAULT_MESSAGES_KEY +from ..constants import _DEFAULT_RETRY_INTERVAL class PostApiModelWrapper(ModelWrapperBase): @@ -27,8 +25,9 @@ def __init__( timeout: int = 30, json_args: dict = None, post_args: dict = None, - max_retries: int = DEFAULT_MAX_RETRIES, - messages_key: str = DEFAULT_MESSAGES_KEY, + max_retries: int = _DEFAULT_MAX_RETRIES, + messages_key: str = _DEFAULT_MESSAGES_KEY, + retry_interval: int = _DEFAULT_RETRY_INTERVAL, ) -> None: """Initialize the model wrapper. @@ -52,6 +51,8 @@ def __init__( exception. messages_key (`str`, defaults to `inputs`): The key of the input messages in the json argument. + retry_interval (`int`, defaults to `1`): + The interval between retries when a request fails. Note: When an object of `PostApiModelWrapper` is called, the arguments @@ -79,6 +80,7 @@ def __init__( self.post_args = post_args or {} self.max_retries = max_retries self.messages_key = messages_key + self.retry_interval = retry_interval def __call__(self, input_: str, **kwargs: Any) -> dict: """Calling the model with requests.post. @@ -122,14 +124,14 @@ def __call__(self, input_: str, **kwargs: Any) -> dict: if response.status_code == requests.codes.ok: break - if i < DEFAULT_MAX_RETRIES: + if i < self.max_retries: # av logger.warning( f"Failed to call the model with " f"requests.codes == {response.status_code}, retry " f"{i + 1}/{self.max_retries} times", ) - time.sleep(i * RETRY_TIME_INTERVAL) + time.sleep(i * self.retry_interval) # step3: record model invocation # record the model api invocation, which will be skipped if diff --git a/src/agentscope/pipelines/functional.py b/src/agentscope/pipelines/functional.py index fe05f7c31..06f470326 100644 --- a/src/agentscope/pipelines/functional.py +++ b/src/agentscope/pipelines/functional.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- """ Functional counterpart for Pipeline """ - -from typing import Callable, Sequence, Optional +from typing import Callable, Sequence, Optional, Union from typing import Any from typing import Mapping -from ..agents import _Operator +from ..agents.operator import Operator + +Operators = Union[Operator, Sequence[Operator]] def placeholder(x: dict = None) -> dict: @@ -17,7 +18,7 @@ def placeholder(x: dict = None) -> dict: def sequentialpipeline( - operators: Sequence[_Operator], + operators: Sequence[Operator], x: Optional[dict] = None, ) -> dict: """Functional version of SequentialPipeline. @@ -40,115 +41,125 @@ def sequentialpipeline( return msg +def _operators(operators: Operators, x: Optional[dict] = None) -> dict: + """Syntactic sugar for executing a single operator or a sequence of + operators.""" + if isinstance(operators, Sequence): + return sequentialpipeline(operators, x) + else: + return operators(x) + + def ifelsepipeline( - x: dict, condition_func: Callable, - if_body_operator: _Operator, - else_body_operator: _Operator = placeholder, + if_body_operators: Operators, + else_body_operators: Operators = placeholder, + x: Optional[dict] = None, ) -> dict: """Functional version of IfElsePipeline. Args: - x (`dict`): - The input dictionary. condition_func (`Callable`): A function that determines whether to exeucte `if_body_operator` or `else_body_operator` based on x. - if_body_operator (`_Operator`): - An operator executed when `condition_func` returns True. - else_body_operator (`_Operator`, defaults to `placeholder`): - An operator executed when condition_func returns False, + if_body_operator (`Operators`): + Operators executed when `condition_func` returns True. + else_body_operator (`Operators`, defaults to `placeholder`): + Operators executed when condition_func returns False, does nothing and just return the input by default. + x (`Optional[dict]`, defaults to `None`): + The input dictionary. Returns: `dict`: the output dictionary. """ if condition_func(x): - return if_body_operator(x) + return _operators(if_body_operators, x) else: - return else_body_operator(x) + return _operators(else_body_operators, x) def switchpipeline( - x: dict, - condition_func: Callable[[dict], Any], - case_operators: Mapping[Any, _Operator], - default_operator: _Operator = placeholder, + condition_func: Callable[[Any], Any], + case_operators: Mapping[Any, Operators], + default_operators: Operators = placeholder, + x: Optional[dict] = None, ) -> dict: """Functional version of SwitchPipeline. Args: - x (`dict`): - The input dictionary. - condition_func (`Callable[[dict], Any]`): + condition_func (`Callable[[Any], Any]`): A function that determines which case_operator to execute based on the input x. - case_operators (`Mapping[Any, _Operator]`): + case_operators (`Mapping[Any, Operator]`): A dictionary containing multiple operators and their corresponding trigger conditions. - default_operator (`_Operator`, defaults to `placeholder`): - An operator that is executed when the actual condition do not + default_operators (`Operators`, defaults to `placeholder`): + Operators that are executed when the actual condition do not meet any of the case_operators, does nothing and just return the input by default. + x (`Optional[dict]`, defaults to `None`): + The input dictionary. Returns: dict: the output dictionary. """ target_case = condition_func(x) if target_case in case_operators: - return case_operators[target_case](x) + return _operators(case_operators[target_case], x) else: - return default_operator(x) + return _operators(default_operators, x) def forlooppipeline( - x: dict, - loop_body_operator: _Operator, + loop_body_operators: Operators, max_loop: int, break_func: Callable[[dict], bool] = lambda _: False, + x: Optional[dict] = None, ) -> dict: """Functional version of ForLoopPipeline. Args: - x (`dict`): - The input dictionary. - loop_body_operator (`_Operator`): - An operator executed as the body of the loop. + loop_body_operators (`Operators`): + Operators executed as the body of the loop. max_loop (`int`): maximum number of loop executions. break_func (`Callable[[dict], bool]`): A function used to determine whether to break out of the loop based on the output of the loop_body_operator, defaults to `lambda _: False` + x (`Optional[dict]`, defaults to `None`): + The input dictionary. Returns: `dict`: The output dictionary. """ for _ in range(max_loop): # loop body - x = loop_body_operator(x) + x = _operators(loop_body_operators, x) # check condition if break_func(x): break - return x + return x # type: ignore [return-value] def whilelooppipeline( - x: dict, - loop_body_operator: _Operator, - condition_func: Callable[[int, dict], bool] = lambda _, __: False, + loop_body_operators: Operators, + condition_func: Callable[[int, Any], bool] = lambda _, __: False, + x: Optional[dict] = None, ) -> dict: """Functional version of WhileLoopPipeline. Args: - x (`dict`): The input dictionary. - loop_body_operator (`_Operator`): An operator executed as the body of + loop_body_operators (`Operators`): Operators executed as the body of the loop. - condition_func (`Callable[[int, dict], bool]`, optional): A function + condition_func (`Callable[[int, Any], bool]`, optional): A function that determines whether to continue executing the loop body based on the current loop number and output of the loop_body_operator, defaults to `lambda _,__: False` + x (`Optional[dict]`, defaults to `None`): + The input dictionary. Returns: `dict`: the output dictionary. @@ -156,7 +167,7 @@ def whilelooppipeline( i = 0 while condition_func(i, x): # loop body - x = loop_body_operator(x) + x = _operators(loop_body_operators, x) # check condition i += 1 - return x + return x # type: ignore [return-value] diff --git a/src/agentscope/pipelines/pipeline.py b/src/agentscope/pipelines/pipeline.py index f415f55bd..138e62337 100644 --- a/src/agentscope/pipelines/pipeline.py +++ b/src/agentscope/pipelines/pipeline.py @@ -4,9 +4,11 @@ from typing import Callable, Sequence from typing import Any from typing import Mapping +from typing import Optional from abc import abstractmethod from .functional import ( + Operators, placeholder, sequentialpipeline, ifelsepipeline, @@ -14,10 +16,10 @@ forlooppipeline, whilelooppipeline, ) -from ..agents.operator import _Operator +from ..agents.operator import Operator -class PipelineBase(_Operator): +class PipelineBase(Operator): r"""Base interface of all pipelines. The pipeline is a special kind of operator that includes @@ -25,79 +27,79 @@ class PipelineBase(_Operator): """ @abstractmethod - def __call__(self, x: dict = None) -> dict: - r"""Define the actions taken by this pipeline. + def __call__(self, x: Optional[dict] = None) -> dict: + """Define the actions taken by this pipeline. Args: - x (`dict`): + x (Optional[`dict`], optional): Dialog history and some environment information Returns: - The pipeline's response to the input. + `dict`: The pipeline's response to the input. """ class IfElsePipeline(PipelineBase): r"""A template pipeline for implementing control flow like if-else. - IfElsePipeline(condition_operator, condition_func, if_body_operator, - else_body_operator) represents the following workflow:: + IfElsePipeline(condition_func, if_body_operators, else_body_operators) + represents the following workflow:: if condition_func(x): - if_body_operator(x) + if_body_operators(x) else: - else_body_operator(x) + else_body_operators(x) """ def __init__( self, condition_func: Callable[[dict], bool], - if_body_operator: _Operator, - else_body_operator: _Operator = placeholder, + if_body_operators: Operators, + else_body_operators: Operators = placeholder, ) -> None: r"""Initialize an IfElsePipeline. Args: condition_func (`Callable[[dict], bool]`): A function that determines whether to execute - if_body_operator or else_body_operator based on the input x. - if_body_operator (`_Operator`): - An operator executed when condition_func returns True. - else_body_operator (`_Optional`): - An operator executed when condition_func returns False, + if_body_operators or else_body_operators based on the input x. + if_body_operators (`Operators`): + Operators executed when condition_func returns True. + else_body_operators (`Operators`): + Operators executed when condition_func returns False, does nothing and just return the input by default. """ self.condition_func = condition_func - self.if_body_operator = if_body_operator - self.else_body_operator = else_body_operator + self.if_body_operator = if_body_operators + self.else_body_operator = else_body_operators - def __call__(self, x: dict = None) -> dict: + def __call__(self, x: Optional[dict] = None) -> dict: return ifelsepipeline( - x, condition_func=self.condition_func, - if_body_operator=self.if_body_operator, - else_body_operator=self.else_body_operator, + if_body_operators=self.if_body_operator, + else_body_operators=self.else_body_operator, + x=x, ) class SwitchPipeline(PipelineBase): r"""A template pipeline for implementing control flow like switch-case. - SwitchPipeline(condition_operator, condition_func, case_operators, - default_operator) represents the following workflow:: + SwitchPipeline(condition_func, case_operators, default_operators) + represents the following workflow:: switch condition_func(x): case k1: return case_operators[k1](x) case k2: return case_operators[k2](x) ... - default: return default_operator(x) + default: return default_operators(x) """ def __init__( self, condition_func: Callable[[dict], Any], - case_operators: Mapping[Any, _Operator], - default_operator: _Operator = placeholder, + case_operators: Mapping[Any, Operators], + default_operators: Operators = placeholder, ) -> None: """Initialize a SwitchPipeline. @@ -105,98 +107,98 @@ def __init__( condition_func (`Callable[[dict], Any]`): A function that determines which case_operator to execute based on the input x. - case_operators (`dict[Any, _Operator]`): + case_operators (`dict[Any, Operators]`): A dictionary containing multiple operators and their corresponding trigger conditions. - default_operator (`_Operator`, defaults to `placeholder`): - An operator that is executed when the actual condition do + default_operators (`Operators`, defaults to `placeholder`): + Operators that are executed when the actual condition do not meet any of the case_operators, does nothing and just return the input by default. """ self.condition_func = condition_func self.case_operators = case_operators - self.default_operator = default_operator + self.default_operators = default_operators - def __call__(self, x: dict = None) -> dict: + def __call__(self, x: Optional[dict] = None) -> dict: return switchpipeline( - x, condition_func=self.condition_func, case_operators=self.case_operators, - default_operator=self.default_operator, + default_operators=self.default_operators, + x=x, ) class ForLoopPipeline(PipelineBase): r"""A template pipeline for implementing control flow like for-loop - ForLoopPipeline(loop_body_operator, max_loop) represents the following + ForLoopPipeline(loop_body_operators, max_loop) represents the following workflow:: for i in range(max_loop): - x = loop_body_operator(x) + x = loop_body_operators(x) - ForLoopPipeline(loop_body_operator, max_loop, break_operator, break_func) + ForLoopPipeline(loop_body_operators, max_loop, break_func) represents the following workflow:: for i in range(max_loop): - x = loop_body_operator(x) + x = loop_body_operators(x) if break_func(x): break """ def __init__( self, - loop_body_operator: _Operator, + loop_body_operators: Operators, max_loop: int, break_func: Callable[[dict], bool] = lambda _: False, ): r"""Initialize a ForLoopPipeline. Args: - loop_body_operator (`_Operator`): - An operator executed as the body of the loop. + loop_body_operators (`Operators`): + Operators executed as the body of the loop. max_loop (`int`): Maximum number of loop executions. break_func (`Callable[[dict], bool]`, defaults to `lambda _: False`): A function used to determine whether to break out of the - loop based on the output of the loop_body_operator. + loop based on the output of the loop_body_operators. """ - self.loop_body_operator = loop_body_operator + self.loop_body_operators = loop_body_operators self.max_loop = max_loop self.break_func = break_func - def __call__(self, x: dict = None) -> dict: + def __call__(self, x: Optional[dict] = None) -> dict: return forlooppipeline( - x, - loop_body_operator=self.loop_body_operator, + loop_body_operators=self.loop_body_operators, max_loop=self.max_loop, break_func=self.break_func, + x=x, ) class WhileLoopPipeline(PipelineBase): r"""A template pipeline for implementing control flow like while-loop - WhileLoopPipeline(loop_body_operator, condition_operator, condition_func) + WhileLoopPipeline(loop_body_operators, condition_operator, condition_func) represents the following workflow:: i = 0 while (condition_func(i, x)) - x = loop_body_operator(x) + x = loop_body_operators(x) i += 1 """ def __init__( self, - loop_body_operator: _Operator, + loop_body_operators: Operators, condition_func: Callable[[int, dict], bool] = lambda _, __: False, ): """Initialize a WhileLoopPipeline. Args: - loop_body_operator (`_Operator`): - An operator executed as the body of the loop. + loop_body_operators (`Operators`): + Operators executed as the body of the loop. condition_func (`Callable[[int, dict], bool]`, defaults to `lambda _, __: False`): A function that determines whether to continue executing the @@ -204,13 +206,13 @@ def __init__( `loop_body_operator` """ self.condition_func = condition_func - self.loop_body_operator = loop_body_operator + self.loop_body_operators = loop_body_operators - def __call__(self, x: dict = None) -> dict: + def __call__(self, x: Optional[dict] = None) -> dict: return whilelooppipeline( - x, - loop_body_operator=self.loop_body_operator, + loop_body_operators=self.loop_body_operators, condition_func=self.condition_func, + x=x, ) @@ -225,14 +227,14 @@ class SequentialPipeline(PipelineBase): x = operators[n](x) """ - def __init__(self, operators: Sequence[_Operator]) -> None: + def __init__(self, operators: Sequence[Operator]) -> None: r"""Initialize a Sequential pipeline. Args: - operators (`Sequence[_Operator]`): + operators (`Sequence[Operator]`): A Sequence of operators to be executed sequentially. """ self.operators = operators - def __call__(self, x: dict = None) -> dict: + def __call__(self, x: Optional[dict] = None) -> dict: return sequentialpipeline(operators=self.operators, x=x) diff --git a/src/agentscope/prompt.py b/src/agentscope/prompt.py index c432f778c..736b94dc0 100644 --- a/src/agentscope/prompt.py +++ b/src/agentscope/prompt.py @@ -1,13 +1,20 @@ # -*- coding: utf-8 -*- """Prompt engineering module.""" from typing import Any, Optional, Union +from enum import IntEnum from agentscope.models import OpenAIWrapper, ModelWrapperBase - -from agentscope.utils.enums import ShrinkPolicy, PromptType +from agentscope.constants import ShrinkPolicy from agentscope.utils.tools import to_openai_dict, to_dialog_str +class PromptType(IntEnum): + """Enum for prompt types.""" + + STRING = 0 + LIST = 1 + + class PromptEngine: """Prompt engineering module for both list and string prompt""" diff --git a/src/agentscope/service/__init__.py b/src/agentscope/service/__init__.py index ad8b00a3e..f466189e3 100644 --- a/src/agentscope/service/__init__.py +++ b/src/agentscope/service/__init__.py @@ -21,6 +21,7 @@ from .retrieval.similarity import cos_sim from .text_processing.summarization import summarization from .retrieval.retrieval_from_list import retrieve_from_list +from .service_status import ServiceExecStatus def get_help() -> None: @@ -49,4 +50,5 @@ def get_help() -> None: "cos_sim", "summarization", "retrieve_from_list", + "ServiceExecStatus", ] diff --git a/src/agentscope/service/code/exec_python.py b/src/agentscope/service/code/exec_python.py index c2f65329d..8032cb4f1 100644 --- a/src/agentscope/service/code/exec_python.py +++ b/src/agentscope/service/code/exec_python.py @@ -25,8 +25,12 @@ docker = None from agentscope.utils.common import create_tempdir, timer -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus from agentscope.service.service_response import ServiceResponse +from agentscope.constants import ( + _DEFAULT_PYPI_MIRROR, + _DEFAULT_TRUSTED_HOST, +) def execute_python_code( @@ -192,10 +196,6 @@ def _execute_python_code_docker( packages and retry execution until no ImportErrors are found or until execution succeeds. """ - # TODO: delete it or make it configurable when release. - # sources pip install from - pypi_mirror = "http://pypi.douban.com/simple" - pypi_trusted_host = "pypi.douban.com" def docker_execute( exec_code: str, @@ -231,8 +231,8 @@ def docker_execute( # Check if there are missing modules to install install_command = ( f"pip install -q {' '.join(missing_modules)} -i" - f" {pypi_mirror} " - f"--trusted-host {pypi_trusted_host}" + f" {_DEFAULT_PYPI_MIRROR} " + f"--trusted-host {_DEFAULT_TRUSTED_HOST}" if missing_modules else "" ) diff --git a/src/agentscope/service/file/common.py b/src/agentscope/service/file/common.py index 32f840fe8..82ec9eed9 100644 --- a/src/agentscope/service/file/common.py +++ b/src/agentscope/service/file/common.py @@ -5,7 +5,7 @@ from agentscope.utils.common import write_file from agentscope.service.service_response import ServiceResponse -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus def create_file(file_path: str, content: str = "") -> ServiceResponse: diff --git a/src/agentscope/service/file/json.py b/src/agentscope/service/file/json.py index 3fb983446..4d0c2b2ee 100644 --- a/src/agentscope/service/file/json.py +++ b/src/agentscope/service/file/json.py @@ -5,7 +5,7 @@ from typing import Any from agentscope.service.service_response import ServiceResponse -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus def read_json_file(file_path: str) -> ServiceResponse: diff --git a/src/agentscope/service/file/text.py b/src/agentscope/service/file/text.py index e76eaf20a..725d08a56 100644 --- a/src/agentscope/service/file/text.py +++ b/src/agentscope/service/file/text.py @@ -4,7 +4,7 @@ from agentscope.utils.common import write_file from agentscope.service.service_response import ServiceResponse -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus def read_text_file(file_path: str) -> ServiceResponse: diff --git a/src/agentscope/service/retrieval/retrieval_from_list.py b/src/agentscope/service/retrieval/retrieval_from_list.py index 57b41e254..4b1af94dd 100644 --- a/src/agentscope/service/retrieval/retrieval_from_list.py +++ b/src/agentscope/service/retrieval/retrieval_from_list.py @@ -4,7 +4,7 @@ from loguru import logger from agentscope.service.service_response import ServiceResponse -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus from agentscope.models import ModelWrapperBase diff --git a/src/agentscope/service/retrieval/similarity.py b/src/agentscope/service/retrieval/similarity.py index c138f17be..7cdc5d5ca 100644 --- a/src/agentscope/service/retrieval/similarity.py +++ b/src/agentscope/service/retrieval/similarity.py @@ -7,12 +7,9 @@ except ImportError: np = None -from numbers import Number -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus from agentscope.service.service_response import ServiceResponse - -# TODO: moving type definition into a single file -Embedding = list[Number] +from agentscope.constants import Embedding def cos_sim( diff --git a/src/agentscope/service/service_response.py b/src/agentscope/service/service_response.py index 08eec4b13..66b6154a1 100644 --- a/src/agentscope/service/service_response.py +++ b/src/agentscope/service/service_response.py @@ -2,7 +2,7 @@ """ Service response module """ from typing import Any -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus class ServiceResponse(dict): diff --git a/src/agentscope/service/service_status.py b/src/agentscope/service/service_status.py new file mode 100644 index 000000000..79f4beedb --- /dev/null +++ b/src/agentscope/service/service_status.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" Enum for service execution status.""" +from enum import IntEnum + + +class ServiceExecStatus(IntEnum): + """Enum for service execution status.""" + + SUCCESS = 1 + ERROR = -1 diff --git a/src/agentscope/service/sql_query/mongodb.py b/src/agentscope/service/sql_query/mongodb.py index d38114f91..09ff4f3c4 100644 --- a/src/agentscope/service/sql_query/mongodb.py +++ b/src/agentscope/service/sql_query/mongodb.py @@ -3,7 +3,7 @@ from typing import Optional, Any from ..service_response import ServiceResponse -from ...utils.enums import ServiceExecStatus +from ...service.service_status import ServiceExecStatus try: import pymongo.errors diff --git a/src/agentscope/service/sql_query/mysql.py b/src/agentscope/service/sql_query/mysql.py index dbbfffd78..da3394fbb 100644 --- a/src/agentscope/service/sql_query/mysql.py +++ b/src/agentscope/service/sql_query/mysql.py @@ -5,7 +5,7 @@ from ..service_response import ServiceResponse from ...utils.common import if_change_database -from ...utils.enums import ServiceExecStatus +from ...service.service_status import ServiceExecStatus try: import pymysql diff --git a/src/agentscope/service/sql_query/sqlite.py b/src/agentscope/service/sql_query/sqlite.py index 3a25376da..d921f2af1 100644 --- a/src/agentscope/service/sql_query/sqlite.py +++ b/src/agentscope/service/sql_query/sqlite.py @@ -5,7 +5,7 @@ from ...service.service_response import ServiceResponse from ...utils.common import if_change_database -from ...utils.enums import ServiceExecStatus +from ...service.service_status import ServiceExecStatus try: import sqlite3 diff --git a/src/agentscope/service/text_processing/summarization.py b/src/agentscope/service/text_processing/summarization.py index 8e19a2892..e6b61830b 100644 --- a/src/agentscope/service/text_processing/summarization.py +++ b/src/agentscope/service/text_processing/summarization.py @@ -4,23 +4,12 @@ """ from agentscope.models import ModelWrapperBase, OpenAIWrapper -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus from agentscope.service.service_response import ServiceResponse from agentscope.message import Msg - - -_DEFAULT_SUMMARIZATION_PROMPT = """ -TEXT: {} -""" - -_DEFAULT_SYSTEM_PROMPT = """ -You are a helpful agent to summarize the text. -You need to keep all the key information of the text in the summary. -""" - -_DEFAULT_TOKEN_LIMIT_PROMPT = """ -Summarize the text after TEXT in less than {} tokens: -""" +from agentscope.constants import _DEFAULT_SUMMARIZATION_PROMPT +from agentscope.constants import _DEFAULT_SYSTEM_PROMPT +from agentscope.constants import _DEFAULT_TOKEN_LIMIT_PROMPT def summarization( diff --git a/src/agentscope/service/web_search/search.py b/src/agentscope/service/web_search/search.py index 8bd69a005..5df5ae9b2 100644 --- a/src/agentscope/service/web_search/search.py +++ b/src/agentscope/service/web_search/search.py @@ -4,7 +4,7 @@ from agentscope.service.service_response import ServiceResponse from agentscope.utils.common import requests_get -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus def web_search( diff --git a/src/agentscope/utils/common.py b/src/agentscope/utils/common.py index 3a8983439..99216f1ca 100644 --- a/src/agentscope/utils/common.py +++ b/src/agentscope/utils/common.py @@ -11,7 +11,7 @@ import requests from agentscope.service.service_response import ServiceResponse -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus @contextlib.contextmanager diff --git a/src/agentscope/utils/enums.py b/src/agentscope/utils/enums.py deleted file mode 100644 index 841181605..000000000 --- a/src/agentscope/utils/enums.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -""" Enums for agentscope """ - -from enum import IntEnum - - -class ResponseFormat(IntEnum): - """Enum for model response format.""" - - NONE = 0 - JSON = 1 - - -class ServiceExecStatus(IntEnum): - """Enum for service execution status.""" - - SUCCESS = 1 - ERROR = -1 - - -class PromptType(IntEnum): - """Enum for prompt types.""" - - STRING = 0 - LIST = 1 - - -class ShrinkPolicy(IntEnum): - """Enum for shrink strategies when the prompt is too long.""" - - TRUNCATE = 0 - SUMMARIZE = 1 diff --git a/tests/operate_file_test.py b/tests/operate_file_test.py index a926a2bf1..7c1b5a2e9 100644 --- a/tests/operate_file_test.py +++ b/tests/operate_file_test.py @@ -16,7 +16,7 @@ read_json_file, write_json_file, ) -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus class OperateFileTest(unittest.TestCase): diff --git a/tests/pipeline_test.py b/tests/pipeline_test.py index 72cf9b41c..1b87393e8 100644 --- a/tests/pipeline_test.py +++ b/tests/pipeline_test.py @@ -144,8 +144,8 @@ def test_if_else_pipeline(self) -> None: p = IfElsePipeline( condition_func=lambda x: "[PASS]" in x["text"], - if_body_operator=if_agent, - else_body_operator=else_agent, + if_body_operators=if_agent, + else_body_operators=else_agent, ) x = p(if_x) self.assertEqual(x["operation"], "A") @@ -167,7 +167,7 @@ def test_switch_pipeline(self) -> None: p = SwitchPipeline( condition_func=lambda x: x["text"].strip(), case_operators=case_agents, - default_operator=default_agent, + default_operators=default_agent, ) for tool in tool_types: x = {"text": f"\n\n{tool}\n\n"} @@ -183,13 +183,13 @@ def test_for_pipeline(self) -> None: # test max loop x = {"value": 0} - p = ForLoopPipeline(loop_body_operator=loop_agent, max_loop=10) + p = ForLoopPipeline(loop_body_operators=loop_agent, max_loop=10) x = p(x) self.assertEqual(x["value"], 10) x = {"value": 0} p = ForLoopPipeline( - loop_body_operator=loop_agent, + loop_body_operators=loop_agent, max_loop=10, break_func=lambda x: x["value"] > 5, ) @@ -202,7 +202,7 @@ def test_while_pipeline(self) -> None: loop_agent = Loop_while_agent("loop_agent") p = WhileLoopPipeline( - loop_body_operator=loop_agent, + loop_body_operators=loop_agent, condition_func=lambda i, x: i < 10 and not x["token_num"] > 500, ) for _ in range(50): @@ -242,14 +242,14 @@ def test_if_else_pipeline(self) -> None: if_x = ifelsepipeline( x=if_x, condition_func=lambda x: "[PASS]" in x["text"], - if_body_operator=if_agent, - else_body_operator=else_agent, + if_body_operators=if_agent, + else_body_operators=else_agent, ) else_x = ifelsepipeline( x=else_x, condition_func=lambda x: "[PASS]" in x["text"], - if_body_operator=if_agent, - else_body_operator=else_agent, + if_body_operators=if_agent, + else_body_operators=else_agent, ) self.assertEqual(if_x["operation"], "A") self.assertEqual(else_x["operation"], "B") diff --git a/tests/retrieval_from_list_test.py b/tests/retrieval_from_list_test.py index 05a2cf9dd..848b5e205 100644 --- a/tests/retrieval_from_list_test.py +++ b/tests/retrieval_from_list_test.py @@ -5,7 +5,7 @@ from typing import Any from agentscope.service import retrieve_from_list, cos_sim -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus from agentscope.message import MessageBase, Msg, Tht from agentscope.memory.temporary_memory import TemporaryMemory from agentscope.models import OpenAIEmbeddingWrapper diff --git a/tests/web_search_test.py b/tests/web_search_test.py index 16608cc52..b5e3ca1e0 100644 --- a/tests/web_search_test.py +++ b/tests/web_search_test.py @@ -5,7 +5,7 @@ from agentscope.service import ServiceResponse from agentscope.service import web_search -from agentscope.utils.enums import ServiceExecStatus +from agentscope.service.service_status import ServiceExecStatus class TestWebSearches(unittest.TestCase):