diff --git a/README.md b/README.md index 70460ceb4..d2ca26cdd 100644 --- a/README.md +++ b/README.md @@ -270,12 +270,12 @@ ### Usage ### Code walkthrough ```python -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team from metagpt.roles import ProjectManager, ProductManager, Architect, Engineer async def startup(idea: str, investment: float = 3.0, n_round: int = 5): """Run a startup. Be a boss.""" - company = SoftwareCompany() + company = Team() company.hire([ProductManager(), Architect(), ProjectManager(), Engineer()]) company.invest(investment) company.start_project(idea) @@ -299,8 +299,8 @@ ## Citation ```bibtex @misc{hong2023metagpt, - title={MetaGPT: Meta Programming for Multi-Agent Collaborative Framework}, - author={Sirui Hong and Xiawu Zheng and Jonathan Chen and Yuheng Cheng and Jinlin Wang and Ceyao Zhang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu}, + title={MetaGPT: Meta Programming for A Multi-Agent Collaborative Framework}, + author={Sirui Hong and Mingchen Zhuge and Jonathan Chen and Xiawu Zheng and Yuheng Cheng and Ceyao Zhang and Jinlin Wang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu and Jürgen Schmidhuber}, year={2023}, eprint={2308.00352}, archivePrefix={arXiv}, diff --git a/docs/README_CN.md b/docs/README_CN.md index 9d6f34c11..3aa0cb05d 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -190,12 +190,12 @@ ### 使用 ### 代码实现 ```python -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team from metagpt.roles import ProjectManager, ProductManager, Architect, Engineer async def startup(idea: str, investment: float = 3.0, n_round: int = 5): """运行一个创业公司。做一个老板""" - company = SoftwareCompany() + company = Team() company.hire([ProductManager(), Architect(), ProjectManager(), Engineer()]) company.invest(investment) company.start_project(idea) diff --git a/docs/README_JA.md b/docs/README_JA.md index 2b2c35a62..1ced60b83 100644 --- a/docs/README_JA.md +++ b/docs/README_JA.md @@ -270,12 +270,12 @@ ### 使用方法 ### コードウォークスルー ```python -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team from metagpt.roles import ProjectManager, ProductManager, Architect, Engineer async def startup(idea: str, investment: float = 3.0, n_round: int = 5): """スタートアップを実行する。ボスになる。""" - company = SoftwareCompany() + company = Team() company.hire([ProductManager(), Architect(), ProjectManager(), Engineer()]) company.invest(investment) company.start_project(idea) @@ -299,8 +299,8 @@ ## 引用 ```bibtex @misc{hong2023metagpt, - title={MetaGPT: Meta Programming for Multi-Agent Collaborative Framework}, - author={Sirui Hong and Xiawu Zheng and Jonathan Chen and Yuheng Cheng and Jinlin Wang and Ceyao Zhang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu}, + title={MetaGPT: Meta Programming for A Multi-Agent Collaborative Framework}, + author={Sirui Hong and Mingchen Zhuge and Jonathan Chen and Xiawu Zheng and Yuheng Cheng and Ceyao Zhang and Jinlin Wang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu and Jürgen Schmidhuber}, year={2023}, eprint={2308.00352}, archivePrefix={arXiv}, diff --git a/examples/debate.py b/examples/debate.py index 05db28070..4db7567f0 100644 --- a/examples/debate.py +++ b/examples/debate.py @@ -7,14 +7,14 @@ import asyncio import platform import fire -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team from metagpt.actions import Action, BossRequirement from metagpt.roles import Role from metagpt.schema import Message from metagpt.logs import logger -class ShoutOut(Action): - """Action: Shout out loudly in a debate (quarrel)""" +class SpeakAloud(Action): + """Action: Speak out aloud in a debate (quarrel)""" PROMPT_TEMPLATE = """ ## BACKGROUND @@ -27,7 +27,7 @@ class ShoutOut(Action): craft a strong and emotional response in 80 words, in {name}'s rhetoric and viewpoints, your will argue: """ - def __init__(self, name="ShoutOut", context=None, llm=None): + def __init__(self, name="SpeakAloud", context=None, llm=None): super().__init__(name, context, llm) async def run(self, context: str, name: str, opponent_name: str): @@ -39,96 +39,55 @@ class ShoutOut(Action): return rsp -class Trump(Role): +class Debator(Role): def __init__( self, - name: str = "Trump", - profile: str = "Republican", + name: str, + profile: str, + opponent_name: str, **kwargs, ): super().__init__(name, profile, **kwargs) - self._init_actions([ShoutOut]) - self._watch([ShoutOut]) - self.name = "Trump" - self.opponent_name = "Biden" + self._init_actions([SpeakAloud]) + self._watch([BossRequirement, SpeakAloud]) + self.name = name + self.opponent_name = opponent_name async def _observe(self) -> int: await super()._observe() # accept messages sent (from opponent) to self, disregard own messages from the last round - self._rc.news = [msg for msg in self._rc.news if msg.send_to == self.name] + self._rc.news = [msg for msg in self._rc.news if msg.send_to == self.name] return len(self._rc.news) async def _act(self) -> Message: logger.info(f"{self._setting}: ready to {self._rc.todo}") + todo = self._rc.todo # An instance of SpeakAloud - msg_history = self._rc.memory.get_by_actions([ShoutOut]) - context = [] - for m in msg_history: - context.append(str(m)) - context = "\n".join(context) + memories = self.get_memories() + context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories) + # print(context) - rsp = await ShoutOut().run(context=context, name=self.name, opponent_name=self.opponent_name) + rsp = await todo.run(context=context, name=self.name, opponent_name=self.opponent_name) msg = Message( content=rsp, role=self.profile, - cause_by=ShoutOut, + cause_by=type(todo), sent_from=self.name, send_to=self.opponent_name, ) return msg -class Biden(Role): - def __init__( - self, - name: str = "Biden", - profile: str = "Democrat", - **kwargs, - ): - super().__init__(name, profile, **kwargs) - self._init_actions([ShoutOut]) - self._watch([BossRequirement, ShoutOut]) - self.name = "Biden" - self.opponent_name = "Trump" - - async def _observe(self) -> int: - await super()._observe() - # accept the very first human instruction (the debate topic) or messages sent (from opponent) to self, - # disregard own messages from the last round - self._rc.news = [msg for msg in self._rc.news if msg.cause_by == BossRequirement or msg.send_to == self.name] - return len(self._rc.news) - - async def _act(self) -> Message: - logger.info(f"{self._setting}: ready to {self._rc.todo}") - - msg_history = self._rc.memory.get_by_actions([BossRequirement, ShoutOut]) - context = [] - for m in msg_history: - context.append(str(m)) - context = "\n".join(context) - - rsp = await ShoutOut().run(context=context, name=self.name, opponent_name=self.opponent_name) - - msg = Message( - content=rsp, - role=self.profile, - cause_by=ShoutOut, - sent_from=self.name, - send_to=self.opponent_name, - ) - - return msg - -async def startup(idea: str, investment: float = 3.0, n_round: int = 5, - code_review: bool = False, run_tests: bool = False): - """We reuse the startup paradigm for roles to interact with each other. - Now we run a startup of presidents and watch they quarrel. :) """ - company = SoftwareCompany() - company.hire([Biden(), Trump()]) - company.invest(investment) - company.start_project(idea) - await company.run(n_round=n_round) +async def debate(idea: str, investment: float = 3.0, n_round: int = 5): + """Run a team of presidents and watch they quarrel. :) """ + Biden = Debator(name="Biden", profile="Democrat", opponent_name="Trump") + Trump = Debator(name="Trump", profile="Republican", opponent_name="Biden") + team = Team() + team.hire([Biden, Trump]) + team.invest(investment) + team.start_project(idea, send_to="Biden") # send debate topic to Biden and let him speak first + await team.run(n_round=n_round) def main(idea: str, investment: float = 3.0, n_round: int = 10): @@ -141,7 +100,7 @@ def main(idea: str, investment: float = 3.0, n_round: int = 10): """ if platform.system() == "Windows": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) - asyncio.run(startup(idea, investment, n_round)) + asyncio.run(debate(idea, investment, n_round)) if __name__ == '__main__': diff --git a/metagpt/const.py b/metagpt/const.py index 7f3f87dfa..407ce803a 100644 --- a/metagpt/const.py +++ b/metagpt/const.py @@ -6,7 +6,7 @@ @File : const.py """ from pathlib import Path - +from loguru import logger def get_project_root(): """Search upwards to find the project root directory.""" @@ -17,10 +17,15 @@ def get_project_root(): or (current_path / ".project_root").exists() or (current_path / ".gitignore").exists() ): + # use metagpt with git clone will land here + logger.info(f"PROJECT_ROOT set to {str(current_path)}") return current_path parent_path = current_path.parent if parent_path == current_path: - raise Exception("Project root not found.") + # use metagpt with pip install will land here + cwd = Path.cwd() + logger.info(f"PROJECT_ROOT set to current working directory: {str(cwd)}") + return cwd current_path = parent_path diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index 6d65575a8..1f6685b38 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -207,6 +207,7 @@ class Engineer(Role): async def _act(self) -> Message: """Determines the mode of action based on whether code review is used.""" + logger.info(f"{self._setting}: ready to WriteCode") if self.use_code_review: return await self._act_sp_precision() return await self._act_sp() diff --git a/metagpt/software_company.py b/metagpt/software_company.py index b2bd18c58..d44a0068a 100644 --- a/metagpt/software_company.py +++ b/metagpt/software_company.py @@ -5,58 +5,9 @@ @Author : alexanderwu @File : software_company.py """ -from pydantic import BaseModel, Field +from metagpt.team import Team as SoftwareCompany -from metagpt.actions import BossRequirement -from metagpt.config import CONFIG -from metagpt.environment import Environment -from metagpt.logs import logger -from metagpt.roles import Role -from metagpt.schema import Message -from metagpt.utils.common import NoMoneyException - - -class SoftwareCompany(BaseModel): - """ - Software Company: Possesses a team, SOP (Standard Operating Procedures), and a platform for instant messaging, - dedicated to writing executable code. - """ - environment: Environment = Field(default_factory=Environment) - investment: float = Field(default=10.0) - idea: str = Field(default="") - - class Config: - arbitrary_types_allowed = True - - def hire(self, roles: list[Role]): - """Hire roles to cooperate""" - self.environment.add_roles(roles) - - def invest(self, investment: float): - """Invest company. raise NoMoneyException when exceed max_budget.""" - self.investment = investment - CONFIG.max_budget = investment - logger.info(f'Investment: ${investment}.') - - def _check_balance(self): - if CONFIG.total_cost > CONFIG.max_budget: - raise NoMoneyException(CONFIG.total_cost, f'Insufficient funds: {CONFIG.max_budget}') - - def start_project(self, idea): - """Start a project from publishing boss requirement.""" - self.idea = idea - self.environment.publish_message(Message(role="BOSS", content=idea, cause_by=BossRequirement)) - - def _save(self): - logger.info(self.json()) - - async def run(self, n_round=3): - """Run company until target round or no money""" - while n_round > 0: - # self._save() - n_round -= 1 - logger.debug(f"{n_round=}") - self._check_balance() - await self.environment.run() - return self.environment.history - \ No newline at end of file +import warnings +warnings.warn("metagpt.software_company is deprecated and will be removed in the future" + "Please use metagpt.team instead. SoftwareCompany class is now named as Team.", + DeprecationWarning, 2) diff --git a/metagpt/team.py b/metagpt/team.py new file mode 100644 index 000000000..67d3ecec8 --- /dev/null +++ b/metagpt/team.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/5/12 00:30 +@Author : alexanderwu +@File : software_company.py +""" +from pydantic import BaseModel, Field + +from metagpt.actions import BossRequirement +from metagpt.config import CONFIG +from metagpt.environment import Environment +from metagpt.logs import logger +from metagpt.roles import Role +from metagpt.schema import Message +from metagpt.utils.common import NoMoneyException + + +class Team(BaseModel): + """ + Team: Possesses one or more roles (agents), SOP (Standard Operating Procedures), and a platform for instant messaging, + dedicated to perform any multi-agent activity, such as collaboratively writing executable code. + """ + environment: Environment = Field(default_factory=Environment) + investment: float = Field(default=10.0) + idea: str = Field(default="") + + class Config: + arbitrary_types_allowed = True + + def hire(self, roles: list[Role]): + """Hire roles to cooperate""" + self.environment.add_roles(roles) + + def invest(self, investment: float): + """Invest company. raise NoMoneyException when exceed max_budget.""" + self.investment = investment + CONFIG.max_budget = investment + logger.info(f'Investment: ${investment}.') + + def _check_balance(self): + if CONFIG.total_cost > CONFIG.max_budget: + raise NoMoneyException(CONFIG.total_cost, f'Insufficient funds: {CONFIG.max_budget}') + + def start_project(self, idea, send_to: str = ""): + """Start a project from publishing boss requirement.""" + self.idea = idea + self.environment.publish_message(Message(role="Human", content=idea, cause_by=BossRequirement, send_to=send_to)) + + def _save(self): + logger.info(self.json()) + + async def run(self, n_round=3): + """Run company until target round or no money""" + while n_round > 0: + # self._save() + n_round -= 1 + logger.debug(f"{n_round=}") + self._check_balance() + await self.environment.run() + return self.environment.history + \ No newline at end of file diff --git a/startup.py b/startup.py index e2a903c9b..e9fbf94d3 100644 --- a/startup.py +++ b/startup.py @@ -11,7 +11,7 @@ from metagpt.roles import ( ProjectManager, QaEngineer, ) -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team async def startup( @@ -23,7 +23,7 @@ async def startup( implement: bool = True, ): """Run a startup. Be a boss.""" - company = SoftwareCompany() + company = Team() company.hire( [ ProductManager(), diff --git a/tests/metagpt/roles/test_ui.py b/tests/metagpt/roles/test_ui.py index 285bff323..d58d31bd9 100644 --- a/tests/metagpt/roles/test_ui.py +++ b/tests/metagpt/roles/test_ui.py @@ -2,7 +2,7 @@ # @Date : 2023/7/22 02:40 # @Author : stellahong (stellahong@fuzhi.ai) # -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team from metagpt.roles import ProductManager from tests.metagpt.roles.ui_role import UI @@ -15,7 +15,7 @@ def test_add_ui(): async def test_ui_role(idea: str, investment: float = 3.0, n_round: int = 5): """Run a startup. Be a boss.""" - company = SoftwareCompany() + company = Team() company.hire([ProductManager(), UI()]) company.invest(investment) company.start_project(idea) diff --git a/tests/metagpt/test_software_company.py b/tests/metagpt/test_software_company.py index 00538442c..4fc651f52 100644 --- a/tests/metagpt/test_software_company.py +++ b/tests/metagpt/test_software_company.py @@ -8,12 +8,12 @@ import pytest from metagpt.logs import logger -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team @pytest.mark.asyncio -async def test_software_company(): - company = SoftwareCompany() +async def test_team(): + company = Team() company.start_project("做一个基础搜索引擎,可以支持知识库") history = await company.run(n_round=5) logger.info(history)