From 9e5c873d77754f24a7b36be0e697975d30efed04 Mon Sep 17 00:00:00 2001 From: better629 Date: Thu, 30 Nov 2023 15:10:38 +0800 Subject: [PATCH] update unittest of ser&deser --- tests/metagpt/actions/test_action.py | 17 --- tests/metagpt/roles/test_role.py | 84 +----------- .../serialize_deserialize/test_action.py | 49 +++++++ .../serialize_deserialize/test_actions.py | 26 ---- .../test_architect_deserialize.py | 2 +- .../serialize_deserialize/test_environment.py | 91 +++++++++++++ .../test_memory.py | 34 ++++- .../test_product_manager.py | 4 +- .../test_project_manager.py | 6 +- .../serialize_deserialize/test_role.py | 63 ++++++++- .../serialize_deserialize/test_schema.py | 49 +++++++ .../test_serdeser_base.py | 88 +++++++++++++ .../serialize_deserialize/test_team.py | 124 +++++++++++++----- .../serialize_deserialize/test_wrire_prd.py | 1 - .../serialize_deserialize/test_write_code.py | 2 +- tests/metagpt/test_environment.py | 44 +++---- tests/metagpt/test_role.py | 14 -- tests/metagpt/test_schema.py | 4 +- tests/metagpt/test_team.py | 22 +--- 19 files changed, 496 insertions(+), 228 deletions(-) create mode 100644 tests/metagpt/serialize_deserialize/test_action.py delete mode 100644 tests/metagpt/serialize_deserialize/test_actions.py create mode 100644 tests/metagpt/serialize_deserialize/test_environment.py rename tests/metagpt/{memory => serialize_deserialize}/test_memory.py (52%) create mode 100644 tests/metagpt/serialize_deserialize/test_schema.py create mode 100644 tests/metagpt/serialize_deserialize/test_serdeser_base.py delete mode 100644 tests/metagpt/test_role.py diff --git a/tests/metagpt/actions/test_action.py b/tests/metagpt/actions/test_action.py index 4468a6f6f..9775630cc 100644 --- a/tests/metagpt/actions/test_action.py +++ b/tests/metagpt/actions/test_action.py @@ -11,20 +11,3 @@ from metagpt.actions import Action, WritePRD, WriteTest def test_action_repr(): actions = [Action(), WriteTest(), WritePRD()] assert "WriteTest" in str(actions) - - -def test_action_serdes(): - action_info = WriteTest.ser_class() - assert action_info["action_class"] == "WriteTest" - - action_class = Action.deser_class(action_info) - assert action_class == WriteTest - - -def test_action_class_serdes(): - name = "write test" - action_info = WriteTest(name=name).serialize() - assert action_info["name"] == name - - action = Action.deserialize(action_info) - assert action.name == name diff --git a/tests/metagpt/roles/test_role.py b/tests/metagpt/roles/test_role.py index a19ad9cb5..72cd84a9a 100644 --- a/tests/metagpt/roles/test_role.py +++ b/tests/metagpt/roles/test_role.py @@ -2,84 +2,10 @@ # -*- coding: utf-8 -*- # @Desc : unittest of Role -from pathlib import Path -import shutil -import pytest - -from metagpt.roles.role import Role, RoleReactMode -from metagpt.actions.action import Action -from metagpt.schema import Message -from metagpt.actions.add_requirement import BossRequirement -from metagpt.roles.product_manager import ProductManager - -serdes_path = Path(__file__).absolute().parent.joinpath("../../data/serdes_storage") +from metagpt.roles.role import Role -def test_role_serdes(): - stg_path_prefix = serdes_path.joinpath("team/environment/roles/") - shutil.rmtree(serdes_path.joinpath("team"), ignore_errors=True) - - pm = ProductManager() - role_tag = f"{pm.__class__.__name__}_{pm.name}" - stg_path = stg_path_prefix.joinpath(role_tag) - pm.serialize(stg_path) - assert stg_path.joinpath("actions/actions_info.json").exists() - - new_pm = Role.deserialize(stg_path) - assert new_pm.name == pm.name - assert len(new_pm.get_memories(1)) == 0 - - -class ActionOK(Action): - - async def run(self, messages: list["Message"]): - return "ok" - - -class ActionRaise(Action): - - async def run(self, messages: list["Message"]): - raise RuntimeError("parse error") - - -class RoleA(Role): - - def __init__(self, - name: str = "RoleA", - profile: str = "Role A", - goal: str = "", - constraints: str = ""): - super(RoleA, self).__init__(name=name, profile=profile, goal=goal, constraints=constraints) - self._init_actions([ActionOK, ActionRaise]) - self._watch([BossRequirement]) - self._rc.react_mode = RoleReactMode.BY_ORDER - - async def run(self, message: "Message" = None, stg_path: str = None): - try: - await super(RoleA, self).run(message) - except Exception as exp: - print("exp ", exp) - self.serialize(stg_path) - - -@pytest.mark.asyncio -async def test_role_serdes_interrupt(): - role_a = RoleA() - shutil.rmtree(serdes_path.joinpath("team"), ignore_errors=True) - - stg_path = serdes_path.joinpath(f"team/environment/roles/{role_a.__class__.__name__}_{role_a.name}") - await role_a.run( - message=Message(content="demo", cause_by=BossRequirement), - stg_path=stg_path - ) - assert role_a._rc.memory.count() == 2 - - assert stg_path.joinpath("actions/todo.json").exists() - - new_role_a: Role = Role.deserialize(stg_path) - assert new_role_a._rc.state == 1 - await role_a.run( - message=Message(content="demo", cause_by=BossRequirement), - stg_path=stg_path - ) - +def test_role_desc(): + role = Role(profile="Sales", desc="Best Seller") + assert role.profile == "Sales" + assert role._setting.desc == "Best Seller" diff --git a/tests/metagpt/serialize_deserialize/test_action.py b/tests/metagpt/serialize_deserialize/test_action.py new file mode 100644 index 000000000..b624dff5a --- /dev/null +++ b/tests/metagpt/serialize_deserialize/test_action.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# @Date : 11/22/2023 11:48 AM +# @Author : stellahong (stellahong@fuzhi.ai) +# @Desc : +import pytest + +from metagpt.actions import Action, WritePRD, WriteTest +from metagpt.llm import LLM +from metagpt.provider.openai_api import OpenAIGPTAPI + + +def test_action_serialize(): + action = Action() + ser_action_dict = action.dict() + assert "name" in ser_action_dict + assert "llm" in ser_action_dict + + +@pytest.mark.asyncio +async def test_action_deserialize(): + action = Action() + serialized_data = action.dict() + assert isinstance(serialized_data["llm"], OpenAIGPTAPI) + + new_action = Action(**serialized_data) + + assert new_action.name == "" + assert new_action.llm == LLM() + assert len(await new_action._aask("who are you")) > 0 + + +def test_action_serdeser(): + action_info = WriteTest.ser_class() + assert action_info["action_class"] == "WriteTest" + + action_class = Action.deser_class(action_info) + assert action_class == WriteTest + + +def test_action_class_serdeser(): + name = "write test" + action_info = WriteTest(name=name).serialize() + assert action_info["name"] == name + + action_info = WriteTest(name=name, llm=LLM()).serialize() + assert action_info["name"] == name + + action = Action.deserialize(action_info) + assert action.name == name diff --git a/tests/metagpt/serialize_deserialize/test_actions.py b/tests/metagpt/serialize_deserialize/test_actions.py deleted file mode 100644 index 2fec2121a..000000000 --- a/tests/metagpt/serialize_deserialize/test_actions.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# @Date : 11/22/2023 11:48 AM -# @Author : stellahong (stellahong@fuzhi.ai) -# @Desc : -import pytest - -from metagpt.actions import Action -from metagpt.llm import LLM - - -def test_action_serialize(): - action = Action() - ser_action_dict = action.dict() - assert "name" in ser_action_dict - assert "llm" in ser_action_dict - - -@pytest.mark.asyncio -async def test_action_deserialize(): - action = Action() - serialized_data = action.dict() - - new_action = Action(**serialized_data) - assert new_action.name == "" - assert new_action.llm == LLM() - assert len(await new_action._aask("who are you")) > 0 diff --git a/tests/metagpt/serialize_deserialize/test_architect_deserialize.py b/tests/metagpt/serialize_deserialize/test_architect_deserialize.py index d0ee3bc99..fb58f0a3a 100644 --- a/tests/metagpt/serialize_deserialize/test_architect_deserialize.py +++ b/tests/metagpt/serialize_deserialize/test_architect_deserialize.py @@ -25,4 +25,4 @@ async def test_architect_deserialize(): assert new_role.name == "Bob" assert len(new_role._actions) == 1 assert isinstance(new_role._actions[0], Action) - await new_role._actions[0].run(context="write a cli snake game") \ No newline at end of file + await new_role._actions[0].run(context="write a cli snake game") diff --git a/tests/metagpt/serialize_deserialize/test_environment.py b/tests/metagpt/serialize_deserialize/test_environment.py new file mode 100644 index 000000000..15336eb6a --- /dev/null +++ b/tests/metagpt/serialize_deserialize/test_environment.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : + +from pathlib import Path +import shutil + +from metagpt.schema import Message +from metagpt.actions.action_output import ActionOutput +from metagpt.roles.project_manager import ProjectManager +from metagpt.actions.add_requirement import BossRequirement +from metagpt.actions.project_management import WriteTasks +from metagpt.environment import Environment +from tests.metagpt.serialize_deserialize.test_serdeser_base import RoleC, ActionOK, serdeser_path + + +def test_env_serialize(): + env = Environment() + ser_env_dict = env.dict() + assert "roles" in ser_env_dict + assert "memory" in ser_env_dict + + +def test_env_deserialize(): + env = Environment() + env.publish_message(message=Message(content="test env serialize")) + ser_env_dict = env.dict() + new_env = Environment(**ser_env_dict) + assert len(new_env.roles) == 0 + assert new_env.memory.storage[0].content == "test env serialize" + assert len(new_env.history) == 25 + + +def test_environment_serdeser(): + out_mapping = {"field1": (list[str], ...)} + out_data = {"field1": ["field1 value1", "field1 value2"]} + ic_obj = ActionOutput.create_model_class("prd", out_mapping) + + message = Message( + content="prd", + instruct_content=ic_obj(**out_data), + role="product manager", + cause_by=BossRequirement + ) + + environment = Environment() + role_c = RoleC() + environment.add_role(role_c) + environment.publish_message(message) + + ser_data = environment.dict() + assert ser_data["roles"]["Role C"]["name"] == "RoleC" + + new_env: Environment = Environment(**ser_data) + assert len(new_env.roles) == 1 + + assert new_env.memory.count() == 1 + assert new_env.memory.storage[0].instruct_content == ic_obj(**out_data) + assert list(new_env.roles.values())[0]._states == list(environment.roles.values())[0]._states + assert list(new_env.roles.values())[0]._actions == list(environment.roles.values())[0]._actions + assert isinstance(list(environment.roles.values())[0]._actions[0], ActionOK) + assert type(list(new_env.roles.values())[0]._actions[0]) == ActionOK + + +def test_environment_serdeser_v2(): + environment = Environment() + pm = ProjectManager() + environment.add_role(pm) + + ser_data = environment.dict() + + new_env: Environment = Environment(**ser_data) + role = new_env.get_role(pm.profile) + assert isinstance(role, ProjectManager) + assert isinstance(role._actions[0], WriteTasks) + assert isinstance(list(new_env.roles.values())[0]._actions[0], WriteTasks) + + +def test_environment_serdeser_save(): + environment = Environment() + role_c = RoleC() + + shutil.rmtree(serdeser_path.joinpath("team"), ignore_errors=True) + + stg_path = serdeser_path.joinpath("team/environment") + environment.add_role(role_c) + environment.serialize(stg_path) + + new_env: Environment = Environment.deserialize(stg_path) + assert len(new_env.roles) == 1 + assert type(list(new_env.roles.values())[0]._actions[0]) == ActionOK diff --git a/tests/metagpt/memory/test_memory.py b/tests/metagpt/serialize_deserialize/test_memory.py similarity index 52% rename from tests/metagpt/memory/test_memory.py rename to tests/metagpt/serialize_deserialize/test_memory.py index bda79ded1..e24f31af3 100644 --- a/tests/metagpt/memory/test_memory.py +++ b/tests/metagpt/serialize_deserialize/test_memory.py @@ -3,6 +3,7 @@ # @Desc : unittest of memory from pathlib import Path +from pydantic import BaseModel from metagpt.schema import Message from metagpt.memory.memory import Memory @@ -10,10 +11,36 @@ from metagpt.actions.action_output import ActionOutput from metagpt.actions.design_api import WriteDesign from metagpt.actions.add_requirement import BossRequirement -serdes_path = Path(__file__).absolute().parent.joinpath("../../data/serdes_storage") +from tests.metagpt.serialize_deserialize.test_serdeser_base import serdeser_path -def test_memory_serdes(): +def test_memory_serdeser(): + msg1 = Message(role="Boss", + content="write a snake game", + cause_by=BossRequirement) + + out_mapping = {"field2": (list[str], ...)} + out_data = {"field2": ["field2 value1", "field2 value2"]} + ic_obj = ActionOutput.create_model_class("system_design", out_mapping) + msg2 = Message(role="Architect", + instruct_content=ic_obj(**out_data), + content="system design content", + cause_by=WriteDesign) + + memory = Memory() + memory.add_batch([msg1, msg2]) + ser_data = memory.dict() + + new_memory = Memory(**ser_data) + assert new_memory.count() == 2 + new_msg2 = new_memory.get(2)[0] + assert isinstance(new_msg2, BaseModel) + assert isinstance(new_memory.storage[-1], BaseModel) + assert new_memory.storage[-1].cause_by == WriteDesign + assert new_msg2.role == "Boss" + + +def test_memory_serdeser_save(): msg1 = Message(role="User", content="write a 2048 game", cause_by=BossRequirement) @@ -29,7 +56,7 @@ def test_memory_serdes(): memory = Memory() memory.add_batch([msg1, msg2]) - stg_path = serdes_path.joinpath("team/environment") + stg_path = serdeser_path.joinpath("team/environment") memory.serialize(stg_path) assert stg_path.joinpath("memory.json").exists() @@ -38,5 +65,6 @@ def test_memory_serdes(): new_msg2 = new_memory.get(1)[0] assert new_msg2.instruct_content.field1 == ["field1 value1", "field1 value2"] assert new_msg2.cause_by == WriteDesign + assert len(new_memory.index) == 2 stg_path.joinpath("memory.json").unlink() diff --git a/tests/metagpt/serialize_deserialize/test_product_manager.py b/tests/metagpt/serialize_deserialize/test_product_manager.py index 2aed87a28..54584cf96 100644 --- a/tests/metagpt/serialize_deserialize/test_product_manager.py +++ b/tests/metagpt/serialize_deserialize/test_product_manager.py @@ -15,8 +15,8 @@ async def test_product_manager_deserialize(): ser_role_dict = role.dict(by_alias=True) new_role = ProductManager(**ser_role_dict) # new_role = ProductManager().deserialize(ser_role_dict) - + assert new_role.name == "Alice" assert len(new_role._actions) == 1 assert isinstance(new_role._actions[0], Action) - await new_role._actions[0].run([Message(content="write a cli snake game")]) \ No newline at end of file + await new_role._actions[0].run([Message(content="write a cli snake game")]) diff --git a/tests/metagpt/serialize_deserialize/test_project_manager.py b/tests/metagpt/serialize_deserialize/test_project_manager.py index fbc0dcc08..21fafa72e 100644 --- a/tests/metagpt/serialize_deserialize/test_project_manager.py +++ b/tests/metagpt/serialize_deserialize/test_project_manager.py @@ -6,6 +6,7 @@ import pytest from metagpt.roles.project_manager import ProjectManager from metagpt.actions.action import Action +from metagpt.actions.project_management import WriteTasks def test_project_manager_serialize(): @@ -20,9 +21,10 @@ def test_project_manager_serialize(): async def test_project_manager_deserialize(): role = ProjectManager() ser_role_dict = role.dict(by_alias=True) + new_role = ProjectManager(**ser_role_dict) - # new_role = ProjectManager().deserialize(ser_role_dict) assert new_role.name == "Eve" assert len(new_role._actions) == 1 assert isinstance(new_role._actions[0], Action) - await new_role._actions[0].run(context="write a cli snake game") \ No newline at end of file + assert isinstance(new_role._actions[0], WriteTasks) + # await new_role._actions[0].run(context="write a cli snake game") diff --git a/tests/metagpt/serialize_deserialize/test_role.py b/tests/metagpt/serialize_deserialize/test_role.py index 0e438d1a2..f260dea3a 100644 --- a/tests/metagpt/serialize_deserialize/test_role.py +++ b/tests/metagpt/serialize_deserialize/test_role.py @@ -2,12 +2,22 @@ # @Date : 11/23/2023 4:49 PM # @Author : stellahong (stellahong@fuzhi.ai) # @Desc : + +from pathlib import Path +import shutil import pytest +from metagpt.logs import logger from metagpt.roles.role import Role +from metagpt.actions import WriteCode, WriteCodeReview +from metagpt.schema import Message +from metagpt.actions.add_requirement import BossRequirement +from metagpt.roles.product_manager import ProductManager +from metagpt.const import SERDESER_PATH from metagpt.roles.engineer import Engineer +from metagpt.utils.utils import format_trackback_info -from metagpt.actions.action import Action +from tests.metagpt.serialize_deserialize.test_serdeser_base import RoleC, serdeser_path def test_role_serialize(): @@ -30,12 +40,53 @@ def test_engineer_serialize(): async def test_engineer_deserialize(): role = Engineer(use_code_review=True) ser_role_dict = role.dict(by_alias=True) - # new_role = Engineer().deserialize(ser_role_dict) - # also can be deserialized in this way: + new_role = Engineer(**ser_role_dict) assert new_role.name == "Alex" assert new_role.use_code_review is True assert len(new_role._actions) == 2 - assert isinstance(new_role._actions[0], Action) - assert isinstance(new_role._actions[1], Action) - await new_role._actions[0].run(context="write a cli snake game", filename="test_code") + assert isinstance(new_role._actions[0], WriteCode) + assert isinstance(new_role._actions[1], WriteCodeReview) + # await new_role._actions[0].run(context="write a cli snake game", filename="test_code") + + +def test_role_serdeser_save(): + stg_path_prefix = serdeser_path.joinpath("team/environment/roles/") + shutil.rmtree(serdeser_path.joinpath("team"), ignore_errors=True) + + pm = ProductManager() + role_tag = f"{pm.__class__.__name__}_{pm.name}" + stg_path = stg_path_prefix.joinpath(role_tag) + pm.serialize(stg_path) + assert stg_path.joinpath("actions/actions_info.json").exists() + + new_pm = Role.deserialize(stg_path) + assert new_pm.name == pm.name + assert len(new_pm.get_memories(1)) == 0 + + +@pytest.mark.asyncio +async def test_role_serdeser_interrupt(): + role_c = RoleC() + shutil.rmtree(SERDESER_PATH.joinpath("team"), ignore_errors=True) + + stg_path = SERDESER_PATH.joinpath(f"team/environment/roles/{role_c.__class__.__name__}_{role_c.name}") + try: + await role_c.run( + message=Message(content="demo", cause_by=BossRequirement) + ) + except Exception as exp: + logger.error(f"Exception in `role_a.run`, detail: {format_trackback_info()}") + role_c.serialize(stg_path) + + assert role_c._rc.memory.count() == 2 + + assert stg_path.joinpath("actions/todo.json").exists() + + new_role_a: Role = Role.deserialize(stg_path) + assert new_role_a._rc.state == 1 + + with pytest.raises(Exception): + await role_c.run( + message=Message(content="demo", cause_by=BossRequirement) + ) diff --git a/tests/metagpt/serialize_deserialize/test_schema.py b/tests/metagpt/serialize_deserialize/test_schema.py new file mode 100644 index 000000000..74b134cad --- /dev/null +++ b/tests/metagpt/serialize_deserialize/test_schema.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : unittest of schema ser&deser + +from metagpt.schema import Message +from metagpt.actions.action_output import ActionOutput +from metagpt.actions.write_code import WriteCode + +from tests.metagpt.serialize_deserialize.test_serdeser_base import MockMessage + + +def test_message_serdeser(): + out_mapping = {"field3": (str, ...), "field4": (list[str], ...)} + out_data = {"field3": "field3 value3", "field4": ["field4 value1", "field4 value2"]} + ic_obj = ActionOutput.create_model_class("code", out_mapping) + + message = Message( + content="code", + instruct_content=ic_obj(**out_data), + role="engineer", + cause_by=WriteCode + ) + ser_data = message.dict() + assert ser_data["cause_by"] == { + "action_class": "WriteCode", + "module_name": "metagpt.actions.write_code" + } + assert ser_data["instruct_content"]["class"] == "code" + + new_message = Message(**ser_data) + assert new_message.cause_by == WriteCode + assert new_message.cause_by in [WriteCode] + assert new_message.instruct_content == ic_obj(**out_data) + + +def test_message_without_postprocess(): + """ to explain `instruct_content` should be postprocessed """ + out_mapping = {"field1": (list[str], ...)} + out_data = {"field1": ["field1 value1", "field1 value2"]} + ic_obj = ActionOutput.create_model_class("code", out_mapping) + message = MockMessage( + content="code", + instruct_content=ic_obj(**out_data) + ) + ser_data = message.dict() + assert ser_data["instruct_content"] == {"field1": ["field1 value1", "field1 value2"]} + + new_message = MockMessage(**ser_data) + assert new_message.instruct_content != ic_obj(**out_data) diff --git a/tests/metagpt/serialize_deserialize/test_serdeser_base.py b/tests/metagpt/serialize_deserialize/test_serdeser_base.py new file mode 100644 index 000000000..35bad6cd9 --- /dev/null +++ b/tests/metagpt/serialize_deserialize/test_serdeser_base.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : base test actions / roles used in unittest + +from pydantic import BaseModel, Field +from pathlib import Path + +from metagpt.actions.action import Action +from metagpt.roles.role import Role, RoleReactMode +from metagpt.actions.add_requirement import BossRequirement + + +serdeser_path = Path(__file__).absolute().parent.joinpath("../../data/serdeser_storage") + + +class MockMessage(BaseModel): + """ to test normal dict without postprocess """ + content: str = "" + instruct_content: BaseModel = Field(default=None) + + +class ActionPass(Action): + name: str = "ActionPass" + + async def run(self, messages: list["Message"]): + return "pass" + + +class ActionOK(Action): + name: str = "ActionOK" + + async def run(self, messages: list["Message"]): + return "ok" + + +class ActionRaise(Action): + name: str = "ActionRaise" + + async def run(self, messages: list["Message"]): + raise RuntimeError("parse error in ActionRaise") + + +class RoleA(Role): + + name: str = Field(default="RoleA") + profile: str = Field(default="Role A") + goal: str = "RoleA's goal" + constraints: str = "RoleA's constraints" + + def __init__(self, **kwargs): + super(RoleA, self).__init__(**kwargs) + self._init_actions([ActionPass]) + self._watch([BossRequirement]) + + async def run(self, message: "Message" = None): + await super(RoleA, self).run(message) + + +class RoleB(Role): + name: str = Field(default="RoleB") + profile: str = Field(default="Role B") + goal: str = "RoleB's goal" + constraints: str = "RoleB's constraints" + + def __init__(self, **kwargs): + super(RoleB, self).__init__(**kwargs) + self._init_actions([ActionOK, ActionRaise]) + self._watch([ActionPass]) + self._rc.react_mode = RoleReactMode.BY_ORDER + + async def run(self, message: "Message" = None): + await super(RoleB, self).run(message) + + +class RoleC(Role): + name: str = Field(default="RoleC") + profile: str = Field(default="Role C") + goal: str = "RoleC's goal" + constraints: str = "RoleC's constraints" + + def __init__(self, **kwargs): + super(RoleC, self).__init__(**kwargs) + self._init_actions([ActionOK, ActionRaise]) + self._watch([BossRequirement]) + self._rc.react_mode = RoleReactMode.BY_ORDER + + async def run(self, message: "Message" = None): + await super(RoleC, self).run(message) diff --git a/tests/metagpt/serialize_deserialize/test_team.py b/tests/metagpt/serialize_deserialize/test_team.py index 44a75d262..e9122ebc0 100644 --- a/tests/metagpt/serialize_deserialize/test_team.py +++ b/tests/metagpt/serialize_deserialize/test_team.py @@ -2,46 +2,104 @@ # @Date : 11/27/2023 10:07 AM # @Author : stellahong (stellahong@fuzhi.ai) # @Desc : + +from pathlib import Path +import shutil import pytest -from metagpt.environment import Environment -from metagpt.schema import Message -from metagpt.software_company import SoftwareCompany from metagpt.roles import ProjectManager, ProductManager, Architect +from metagpt.team import Team +from metagpt.const import SERDESER_PATH + +from tests.metagpt.serialize_deserialize.test_serdeser_base import RoleA, RoleB, RoleC, serdeser_path -def test_env_serialize(): - env = Environment() - ser_env_dict = env.dict() - assert "roles" in ser_env_dict - assert "memory" in ser_env_dict - assert "memory" in ser_env_dict +def test_team_deserialize(): + company = Team() - -def test_env_deserialize(): - env = Environment() - env.publish_message(message=Message(content="test env serialize")) - ser_env_dict = env.dict() - new_env = Environment(**ser_env_dict) - assert len(new_env.roles) == 0 - assert new_env.memory.storage[0].content == "test env serialize" - assert len(new_env.history) == 25 - - -def test_softwarecompany_deserialize(): - team = SoftwareCompany() - team.hire( + pm = ProductManager() + arch = Architect() + company.hire( [ - ProductManager(), - Architect(), + pm, + arch, ProjectManager(), ] ) - assert len(team.environment.get_roles()) == 3 - ser_team_dict = team.dict() - new_team = SoftwareCompany(**ser_team_dict) - - assert len(new_team.environment.get_roles()) == 3 - assert new_team.environment.get_role('Product Manager') is not None - assert new_team.environment.get_role('Product Manager') is not None - assert new_team.environment.get_role('Architect') is not None + assert len(company.environment.get_roles()) == 3 + ser_company = company.dict() + new_company = Team(**ser_company) + + assert len(new_company.environment.get_roles()) == 3 + assert new_company.environment.get_role(pm.profile) is not None + + new_pm = new_company.environment.get_role(pm.profile) + assert type(new_pm) == ProductManager + assert new_company.environment.get_role(pm.profile) is not None + assert new_company.environment.get_role(arch.profile) is not None + + +def test_team_serdeser(): + company = Team() + company.hire([RoleC()]) + + stg_path = serdeser_path.joinpath("team") + shutil.rmtree(stg_path, ignore_errors=True) + + company.serialize(stg_path=stg_path) + + new_company = Team.deserialize(stg_path) + + assert len(new_company.environment.roles) == 1 + + +@pytest.mark.asyncio +async def test_team_recover(): + idea = "write a snake game" + stg_path = SERDESER_PATH.joinpath("team") + shutil.rmtree(stg_path, ignore_errors=True) + + company = Team() + company.hire([RoleC()]) + company.start_project(idea) + await company.run(n_round=4) + + ser_data = company.dict() + new_company = Team(**ser_data) + assert new_company.environment.memory.count() == 1 + assert type(list(new_company.environment.roles.values())[0]._actions[0]) == ActionOK + + new_company.start_project(idea) + await new_company.run(n_round=4) + + +@pytest.mark.asyncio +async def test_team_recover_save(): + idea = "write a 2048 web game" + stg_path = SERDESER_PATH.joinpath("team") + shutil.rmtree(stg_path, ignore_errors=True) + + company = Team() + company.hire([RoleC()]) + company.start_project(idea) + await company.run(n_round=4) + + new_company = Team.recover(stg_path) + new_company.start_project(idea) + await new_company.run(n_round=4) + + +@pytest.mark.asyncio +async def test_team_recover_multi_roles_save(): + idea = "write a snake game" + stg_path = SERDESER_PATH.joinpath("team") + shutil.rmtree(stg_path, ignore_errors=True) + + company = Team() + company.hire([RoleA(), RoleB()]) + company.start_project(idea) + await company.run(n_round=4) + + new_company = Team.recover(stg_path) + new_company.start_project(idea) + await new_company.run(n_round=4) diff --git a/tests/metagpt/serialize_deserialize/test_wrire_prd.py b/tests/metagpt/serialize_deserialize/test_wrire_prd.py index baa08ed76..96b4d19ad 100644 --- a/tests/metagpt/serialize_deserialize/test_wrire_prd.py +++ b/tests/metagpt/serialize_deserialize/test_wrire_prd.py @@ -25,4 +25,3 @@ async def test_action_deserialize(): assert new_action.name == "" assert new_action.llm == LLM() assert len(await new_action.run([Message(content="write a cli snake game")])) > 0 - diff --git a/tests/metagpt/serialize_deserialize/test_write_code.py b/tests/metagpt/serialize_deserialize/test_write_code.py index 9d659caaf..7f4799014 100644 --- a/tests/metagpt/serialize_deserialize/test_write_code.py +++ b/tests/metagpt/serialize_deserialize/test_write_code.py @@ -43,4 +43,4 @@ async def test_write_code_review_deserialize(): assert new_action.name == "WriteCodeReview" assert new_action.llm == LLM() - await new_action.run(context="write a cli snake game", code =code, filename="test_rewrite_code") \ No newline at end of file + await new_action.run(context="write a cli snake game", code=code, filename="test_rewrite_code") diff --git a/tests/metagpt/test_environment.py b/tests/metagpt/test_environment.py index 3cc2d8a7a..9f69e6189 100644 --- a/tests/metagpt/test_environment.py +++ b/tests/metagpt/test_environment.py @@ -8,17 +8,15 @@ import pytest from pathlib import Path -import shutil from metagpt.actions import BossRequirement from metagpt.environment import Environment from metagpt.logs import logger from metagpt.roles import Architect, ProductManager, Role from metagpt.schema import Message -from tests.metagpt.roles.test_role import RoleA -serdes_path = Path(__file__).absolute().parent.joinpath("../data/serdes_storage") +serdeser_path = Path(__file__).absolute().parent.joinpath("../data/serdeser_storage") @pytest.fixture @@ -27,14 +25,23 @@ def env(): def test_add_role(env: Environment): - role = ProductManager("Alice", "product manager", "create a new product", "limited resources") + role = ProductManager(name="Alice", + profile="product manager", + goal="create a new product", + constraints="limited resources") env.add_role(role) assert env.get_role(role.profile) == role def test_get_roles(env: Environment): - role1 = Role("Alice", "product manager", "create a new product", "limited resources") - role2 = Role("Bob", "engineer", "develop the new product", "short deadline") + role1 = Role(name="Alice", + profile="product manager", + goal="create a new product", + constraints="limited resources") + role2 = Role(name="Bob", + profile="engineer", + goal="develop the new product", + constraints="short deadline") env.add_role(role1) env.add_role(role2) roles = env.get_roles() @@ -43,8 +50,14 @@ def test_get_roles(env: Environment): @pytest.mark.asyncio async def test_publish_and_process_message(env: Environment): - product_manager = ProductManager("Alice", "Product Manager", "做AI Native产品", "资源有限") - architect = Architect("Bob", "Architect", "设计一个可用、高效、较低成本的系统,包括数据结构与接口", "资源有限,需要节省成本") + product_manager = ProductManager(name="Alice", + profile="Product Manager", + goal="做AI Native产品", + constraints="资源有限") + architect = Architect(name="Bob", + profile="Architect", + goal="设计一个可用、高效、较低成本的系统,包括数据结构与接口", + constraints="资源有限,需要节省成本") env.add_roles([product_manager, architect]) env.publish_message(Message(role="BOSS", content="需要一个基于LLM做总结的搜索引擎", cause_by=BossRequirement)) @@ -52,18 +65,3 @@ async def test_publish_and_process_message(env: Environment): await env.run(k=2) logger.info(f"{env.history=}") assert len(env.history) > 10 - - -def test_environment_serdes(): - environment = Environment() - role_a = RoleA() - - shutil.rmtree(serdes_path.joinpath("team"), ignore_errors=True) - - stg_path = serdes_path.joinpath("team/environment") - environment.add_role(role_a) - environment.serialize(stg_path) - - new_env: Environment = Environment() - new_env.deserialize(stg_path) - assert len(new_env.roles) == 1 diff --git a/tests/metagpt/test_role.py b/tests/metagpt/test_role.py deleted file mode 100644 index 11fd804ec..000000000 --- a/tests/metagpt/test_role.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -@Time : 2023/5/11 14:44 -@Author : alexanderwu -@File : test_role.py -""" -from metagpt.roles import Role - - -def test_role_desc(): - i = Role(profile='Sales', desc='Best Seller') - assert i.profile == 'Sales' - assert i._setting.desc == 'Best Seller' diff --git a/tests/metagpt/test_schema.py b/tests/metagpt/test_schema.py index f515326e8..c70c93cfc 100644 --- a/tests/metagpt/test_schema.py +++ b/tests/metagpt/test_schema.py @@ -25,7 +25,7 @@ def test_messages(): assert all([i in text for i in roles]) -def test_message_serdes(): +def test_message_serdeser(): out_mapping = {"field3": (str, ...), "field4": (list[str], ...)} out_data = {"field3": "field3 value3", "field4": ["field4 value1", "field4 value2"]} ic_obj = ActionOutput.create_model_class("code", out_mapping) @@ -37,7 +37,7 @@ def test_message_serdes(): cause_by=WriteCode ) message_dict = serialize_general_message(message) - assert message_dict["cause_by"] == {"action_class": "WriteCode"} + assert message_dict["cause_by"] == {"action_class": "WriteCode", "module_name": "metagpt.actions.write_code"} assert message_dict["instruct_content"] == { "class": "code", "mapping": { diff --git a/tests/metagpt/test_team.py b/tests/metagpt/test_team.py index ab201152c..efd035bb2 100644 --- a/tests/metagpt/test_team.py +++ b/tests/metagpt/test_team.py @@ -2,26 +2,12 @@ # -*- coding: utf-8 -*- # @Desc : unittest of team -from pathlib import Path -import shutil - from metagpt.team import Team - -from tests.metagpt.roles.test_role import RoleA - -serdes_path = Path(__file__).absolute().parent.joinpath("../data/serdes_storage") +from metagpt.roles.project_manager import ProjectManager -def test_team_serdes(): +def test_team(): company = Team() - company.hire([RoleA()]) + company.hire([ProjectManager()]) - stg_path = serdes_path.joinpath("team") - shutil.rmtree(stg_path, ignore_errors=True) - - company.serialize(stg_path=stg_path) - - new_company = Team() - new_company.deserialize(stg_path) - - assert len(new_company.environment.roles) == 1 + assert len(company.environment.roles) == 1