diff --git a/src/agentscope/agents/agent.py b/src/agentscope/agents/agent.py index 4341952d9..7894345df 100644 --- a/src/agentscope/agents/agent.py +++ b/src/agentscope/agents/agent.py @@ -233,7 +233,8 @@ def get_agent_class(cls, agent_class_name: str) -> Type[AgentBase]: Type[AgentBase]: the AgentBase sub-class. """ if agent_class_name not in cls._registry: - raise ValueError(f"Agent [{agent_class_name}] not found.") + logger.error(f"Agent class <{agent_class_name}> not found.") + raise ValueError(f"Agent class <{agent_class_name}> not found.") return cls._registry[agent_class_name] # type: ignore[return-value] @classmethod @@ -374,6 +375,7 @@ def to_dist( max_timeout_seconds: int = 1800, local_mode: bool = True, lazy_launch: bool = True, + upload_source_code: bool = False, launch_server: bool = None, ) -> AgentBase: """Convert current agent instance into a distributed version. @@ -400,6 +402,12 @@ def to_dist( Only takes effect when `host` and `port` are not filled in. If `True`, launch the agent server when the agent is called, otherwise, launch the agent server immediately. + upload_source_code (`bool`, defaults to `False`): + Upload the source code of the agent to the agent server. + Only takes effect when connecting to an existing server. + When you are using an agent that doens't exist on the server + (such as your customized agent that is not officially provided + by AgentScope), please set this value to `True`. launch_server(`bool`, defaults to `None`): This field has been deprecated and will be removed in future releases. @@ -429,4 +437,5 @@ def to_dist( local_mode=local_mode, lazy_launch=lazy_launch, agent_id=self.agent_id, + upload_source_code=upload_source_code, ) diff --git a/src/agentscope/rpc/rpc_agent.proto b/src/agentscope/rpc/rpc_agent.proto index e8c1ff856..a59544fb1 100644 --- a/src/agentscope/rpc/rpc_agent.proto +++ b/src/agentscope/rpc/rpc_agent.proto @@ -41,7 +41,7 @@ message StatusResponse { message CreateAgentRequest { string agent_id = 1; bytes agent_init_args = 2; - string agent_source_code = 3; + bytes agent_source_code = 3; } message AgentIds { diff --git a/src/agentscope/rpc/rpc_agent_client.py b/src/agentscope/rpc/rpc_agent_client.py index 639fda2ec..4a7769598 100644 --- a/src/agentscope/rpc/rpc_agent_client.py +++ b/src/agentscope/rpc/rpc_agent_client.py @@ -2,6 +2,7 @@ """ Client of rpc agent server """ import threading +import json from typing import Optional, Sequence, Union from loguru import logger @@ -133,13 +134,12 @@ def create_agent( with grpc.insecure_channel(f"{self.host}:{self.port}") as channel: stub = RpcAgentStub(channel) if upload_source_code: - import inspect from agentscope.agents import AgentBase agent_class = AgentBase.get_agent_class( agent_configs["class_name"], ) - source_code = inspect.getsource(agent_class) + source_code = dill.dumps(agent_class) else: source_code = None status = stub.create_agent( @@ -211,7 +211,14 @@ def clone_agent(self, agent_id: str) -> Optional[str]: return resp.agent_ids[0] def update_placeholder(self, task_id: int) -> str: - """Update the placeholder value.""" + """Update the placeholder value. + + Args: + task_id (`int`): task_id of the PlaceholderMessage. + + Returns: + str: serialized message value. + """ with grpc.insecure_channel(f"{self.host}:{self.port}") as channel: stub = RpcAgentStub(channel) result_msg = stub.update_placeholder( @@ -219,22 +226,50 @@ def update_placeholder(self, task_id: int) -> str: ) return result_msg.value - # def get_agent_id_list(self) -> Sequence[str]: - # """ - # Get id of all agents on the server as a list. - # """ - # pass + def get_agent_id_list(self, agent_id: str) -> Sequence[str]: + """ + Get id of all agents on the server as a list. + + Returns: + Sequence[str]: list of agent_id + """ + with grpc.insecure_channel(f"{self.host}:{self.port}") as channel: + stub = RpcAgentStub(channel) + resp = stub.get_agent_id_list( + agent_pb2.AgentIds(agent_ids=[agent_id]), + ) + return resp.agent_ids + + def get_agent_info(self, agent_id: str = None) -> dict: + """Get the agent information of the specific agent_id - # def get_agent_info(self, agent_id: str = None) -> dict: - # """Get the agent information of the specific agent_id + Args: + agent_id (`str`, optional): the id of the agent. Defaults to None. - # Args: - # agent_id (`str`, optional): the id of the agent. Defaults to None. + Returns: + `dict`: the information of the agent as a `dict` + """ + with grpc.insecure_channel(f"{self.host}:{self.port}") as channel: + stub = RpcAgentStub(channel) + resp = stub.get_agent_info( + agent_pb2.AgentIds(agent_ids=[agent_id]), + ) + if not resp.ok: + logger.error( + f"Error in get_agent_info({agent_id}): {resp.message}", + ) + return {} + return json.loads(resp.message) - # Returns: - # `dict`: the information of the agent as a `dict` - # """ - # pass + def get_server_info(self) -> dict: + """Get the agent server resource usage information.""" + with grpc.insecure_channel(f"{self.host}:{self.port}") as channel: + stub = RpcAgentStub(channel) + resp = stub.get_server_info(Empty()) + if not resp.ok: + logger.error(f"Error in get_server_info: {resp.message}") + return {} + return json.loads(resp.message) class ResponseStub: diff --git a/src/agentscope/rpc/rpc_agent_pb2.py b/src/agentscope/rpc/rpc_agent_pb2.py index 647d2583f..510cb417b 100644 --- a/src/agentscope/rpc/rpc_agent_pb2.py +++ b/src/agentscope/rpc/rpc_agent_pb2.py @@ -17,7 +17,7 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( - b'\n\x0frpc_agent.proto\x1a\x1bgoogle/protobuf/empty.proto"-\n\x0eStatusResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t"Z\n\x12\x43reateAgentRequest\x12\x10\n\x08\x61gent_id\x18\x01 \x01(\t\x12\x17\n\x0f\x61gent_init_args\x18\x02 \x01(\x0c\x12\x19\n\x11\x61gent_source_code\x18\x03 \x01(\t"\x1d\n\x08\x41gentIds\x12\x11\n\tagent_ids\x18\x01 \x03(\t"/\n\x0b\x41gentStatus\x12\x10\n\x08\x61gent_id\x18\x01 \x01(\t\x12\x0e\n\x06status\x18\x02 \x01(\t"+\n\x18UpdatePlaceholderRequest\x12\x0f\n\x07task_id\x18\x01 \x01(\x03">\n\x06RpcMsg\x12\r\n\x05value\x18\x01 \x01(\t\x12\x13\n\x0btarget_func\x18\x02 \x01(\t\x12\x10\n\x08\x61gent_id\x18\x03 \x01(\t2\xd9\x03\n\x08RpcAgent\x12\x35\n\x08is_alive\x12\x16.google.protobuf.Empty\x1a\x0f.StatusResponse"\x00\x12\x36\n\x0c\x63reate_agent\x12\x13.CreateAgentRequest\x1a\x0f.StatusResponse"\x00\x12,\n\x0c\x64\x65lete_agent\x12\t.AgentIds\x1a\x0f.StatusResponse"\x00\x12%\n\x0b\x63lone_agent\x12\t.AgentIds\x1a\t.AgentIds"\x00\x12\x38\n\x11get_agent_id_list\x12\x16.google.protobuf.Empty\x1a\t.AgentIds"\x00\x12.\n\x0eget_agent_info\x12\t.AgentIds\x1a\x0f.StatusResponse"\x00\x12<\n\x0fget_server_info\x12\x16.google.protobuf.Empty\x1a\x0f.StatusResponse"\x00\x12%\n\x0f\x63\x61ll_agent_func\x12\x07.RpcMsg\x1a\x07.RpcMsg"\x00\x12:\n\x12update_placeholder\x12\x19.UpdatePlaceholderRequest\x1a\x07.RpcMsg"\x00\x62\x06proto3', + b'\n\x0frpc_agent.proto\x1a\x1bgoogle/protobuf/empty.proto"-\n\x0eStatusResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t"Z\n\x12\x43reateAgentRequest\x12\x10\n\x08\x61gent_id\x18\x01 \x01(\t\x12\x17\n\x0f\x61gent_init_args\x18\x02 \x01(\x0c\x12\x19\n\x11\x61gent_source_code\x18\x03 \x01(\x0c"\x1d\n\x08\x41gentIds\x12\x11\n\tagent_ids\x18\x01 \x03(\t"/\n\x0b\x41gentStatus\x12\x10\n\x08\x61gent_id\x18\x01 \x01(\t\x12\x0e\n\x06status\x18\x02 \x01(\t"+\n\x18UpdatePlaceholderRequest\x12\x0f\n\x07task_id\x18\x01 \x01(\x03">\n\x06RpcMsg\x12\r\n\x05value\x18\x01 \x01(\t\x12\x13\n\x0btarget_func\x18\x02 \x01(\t\x12\x10\n\x08\x61gent_id\x18\x03 \x01(\t2\xd9\x03\n\x08RpcAgent\x12\x35\n\x08is_alive\x12\x16.google.protobuf.Empty\x1a\x0f.StatusResponse"\x00\x12\x36\n\x0c\x63reate_agent\x12\x13.CreateAgentRequest\x1a\x0f.StatusResponse"\x00\x12,\n\x0c\x64\x65lete_agent\x12\t.AgentIds\x1a\x0f.StatusResponse"\x00\x12%\n\x0b\x63lone_agent\x12\t.AgentIds\x1a\t.AgentIds"\x00\x12\x38\n\x11get_agent_id_list\x12\x16.google.protobuf.Empty\x1a\t.AgentIds"\x00\x12.\n\x0eget_agent_info\x12\t.AgentIds\x1a\x0f.StatusResponse"\x00\x12<\n\x0fget_server_info\x12\x16.google.protobuf.Empty\x1a\x0f.StatusResponse"\x00\x12%\n\x0f\x63\x61ll_agent_func\x12\x07.RpcMsg\x1a\x07.RpcMsg"\x00\x12:\n\x12update_placeholder\x12\x19.UpdatePlaceholderRequest\x1a\x07.RpcMsg"\x00\x62\x06proto3', ) _globals = globals() diff --git a/src/agentscope/server/servicer.py b/src/agentscope/server/servicer.py index 03df4206d..37efd8a1b 100644 --- a/src/agentscope/server/servicer.py +++ b/src/agentscope/server/servicer.py @@ -126,17 +126,11 @@ def create_agent( ) agent_configs = dill.loads(request.agent_init_args) if len(request.agent_source_code) > 0: - exec(request.agent_source_code, globals()) - cls_name = ( - request.agent_source_code.split("class ")[1] - .split(":")[0] - .split("(")[0] - .strip() - ) + cls = dill.loads(request.agent_source_code) + cls_name = cls.__name__ logger.info( f"Load class [{cls_name}] from uploaded source code.", ) - cls = globals()[cls_name] else: cls_name = agent_configs["class_name"] cls = AgentBase.get_agent_class(cls_name) diff --git a/tests/rpc_agent_test.py b/tests/rpc_agent_test.py index b77b6cb85..4b3d6f832 100644 --- a/tests/rpc_agent_test.py +++ b/tests/rpc_agent_test.py @@ -607,3 +607,44 @@ def test_agent_nesting(self) -> None: self.assertTrue(0.5 < r2.content["time"] < 2) launcher1.shutdown() launcher2.shutdown() + + def test_customized_agent(self) -> None: + """Test customized agent""" + launcher = RpcAgentServerLauncher( + host="localhost", + port=12010, + local_mode=False, + ) + # launch without customized agent + launcher.launch() + + class CustomizedAgent(AgentBase): + """A customized agent that not supported by agent server.""" + + def __init__( # type: ignore[no-untyped-def] + self, + **kwargs, + ) -> None: + super().__init__(**kwargs) + self.id = 0 + + def reply(self, x: dict = None) -> dict: + return Msg( + name=self.name, + role="assistant", + content="Customized", + ) + + agent = CustomizedAgent(name="customized") + self.assertRaises( + Exception, + agent.to_dist, + host=launcher.host, + port=launcher.port, + ) + agent = agent.to_dist( + host=launcher.host, + port=launcher.port, + upload_source_code=True, + ) + launcher.shutdown()