base software company roles on RoleZero

This commit is contained in:
garylin2099 2024-06-05 23:37:15 +08:00
parent 6cf7a8114c
commit a59b9e2286
6 changed files with 98 additions and 50 deletions

View file

@ -5,13 +5,12 @@
@Author : alexanderwu
@File : architect.py
"""
from metagpt.actions import WritePRD
from metagpt.actions.design_api import WriteDesign
from metagpt.roles.role import Role
from metagpt.roles.di.role_zero import RoleZero
class Architect(Role):
class Architect(RoleZero):
"""
Represents an Architect role in a software development process.
@ -30,11 +29,26 @@ class Architect(Role):
"libraries. Use same language as user requirement"
)
instruction: str = """Use WriteDesign tool to write a system design document"""
max_react_loop: int = 1 # FIXME: Read and edit files requires more steps, consider later
tools: list[str] = ["Editor:write,read,write_content", "RoleZero", "WriteDesign"]
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
# NOTE: The following init setting will only be effective when self.use_fixed_sop is changed to True
self.enable_memory = False
# Initialize actions specific to the Architect role
self.set_actions([WriteDesign])
# Set events or actions the Architect should watch or be aware of
self._watch({WritePRD})
def _update_tool_execution(self):
wd = WriteDesign()
self.tool_execution_map.update(
{
"WriteDesign.run": wd.run,
"WriteDesign": wd.run, # alias
}
)

View file

@ -1,10 +1,7 @@
from __future__ import annotations
from pydantic import model_validator
from metagpt.prompts.di.engineer2 import ENGINEER2_INSTRUCTION
from metagpt.roles.di.role_zero import RoleZero
from metagpt.tools.libs.editor import Editor
class Engineer2(RoleZero):
@ -14,18 +11,3 @@ class Engineer2(RoleZero):
instruction: str = ENGINEER2_INSTRUCTION
tools: str = ["Plan", "Editor:write,read,write_content", "RoleZero"]
editor: Editor = Editor()
@model_validator(mode="after")
def set_tool_execution(self) -> "RoleZero":
self.tool_execution_map = {
"Plan.append_task": self.planner.plan.append_task,
"Plan.reset_task": self.planner.plan.reset_task,
"Plan.replace_task": self.planner.plan.replace_task,
"Editor.write": self.editor.write,
"Editor.write_content": self.editor.write_content,
"Editor.read": self.editor.read,
"RoleZero.ask_human": self.ask_human,
"RoleZero.reply_to_human": self.reply_to_human,
}
return self

View file

@ -9,13 +9,14 @@ from pydantic import model_validator
from metagpt.actions import Action
from metagpt.actions.di.run_command import RunCommand
from metagpt.environment.mgx.mgx_env import MGXEnv
from metagpt.logs import logger
from metagpt.prompts.di.role_zero import CMD_PROMPT, ROLE_INSTRUCTION
from metagpt.roles import Role
from metagpt.schema import AIMessage, Message, UserMessage
from metagpt.strategy.experience_retriever import DummyExpRetriever, ExpRetriever
from metagpt.strategy.planner import Planner
from metagpt.tools.libs.browser import Browser
from metagpt.tools.libs.editor import Editor
from metagpt.tools.tool_recommend import BM25ToolRecommender, ToolRecommender
from metagpt.tools.tool_registry import register_tool
from metagpt.utils.common import CodeParser
@ -43,6 +44,10 @@ class RoleZero(Role):
tool_recommender: ToolRecommender = None
tool_execution_map: dict[str, callable] = {}
special_tool_commands: list[str] = ["Plan.finish_current_task", "end"]
# Equipped with three basic tools by default for optional use
editor: Editor = Editor()
browser: Browser = Browser()
# terminal: Terminal = Terminal() # FIXME: TypeError: cannot pickle '_thread.lock' object
# Experience
experience_retriever: ExpRetriever = DummyExpRetriever()
@ -64,7 +69,6 @@ class RoleZero(Role):
if self.tools and not self.tool_recommender:
self.tool_recommender = BM25ToolRecommender(tools=self.tools, force=True)
self.set_actions([RunCommand])
self._set_state(0)
# HACK: Init Planner, control it through dynamic thinking; Consider formalizing as a react mode
self.planner = Planner(goal="", working_memory=self.rc.working_memory, auto_run=True)
@ -73,7 +77,23 @@ class RoleZero(Role):
@model_validator(mode="after")
def set_tool_execution(self) -> "RoleZero":
raise NotImplementedError
# default map
self.tool_execution_map = {
"Plan.append_task": self.planner.plan.append_task,
"Plan.reset_task": self.planner.plan.reset_task,
"Plan.replace_task": self.planner.plan.replace_task,
"Editor.write": self.editor.write,
"Editor.write_content": self.editor.write_content,
"Editor.read": self.editor.read,
"RoleZero.ask_human": self.ask_human,
"RoleZero.reply_to_human": self.reply_to_human,
}
# can be updated by subclass
self._update_tool_execution()
return self
def _update_tool_execution(self):
pass
async def _think(self) -> bool:
"""Useful in 'react' mode. Use LLM to decide whether and what to do next."""
@ -82,9 +102,9 @@ class RoleZero(Role):
return await super()._think()
### 0. Preparation ###
if not self.rc.todo and not self.rc.news:
if not self.rc.todo:
return False
self._set_state(0)
if not self.planner.plan.goal:
self.user_requirement = self.get_memories()[-1].content
self.planner.plan.goal = self.user_requirement
@ -118,7 +138,7 @@ class RoleZero(Role):
instruction=self.instruction.strip(),
)
context = self.llm.format_msg(self.rc.memory.get(self.memory_k) + [UserMessage(content=prompt)])
print(*context, sep="\n" + "*" * 5 + "\n")
# print(*context, sep="\n" + "*" * 5 + "\n")
async with ThoughtReporter():
self.command_rsp = await self.llm.aask(context, system_msgs=self.system_msg)
self.rc.memory.add(AIMessage(content=self.command_rsp))
@ -146,11 +166,15 @@ class RoleZero(Role):
)
async def _react(self) -> Message:
# NOTE: Diff 1: Each time landing here means observing news, set todo to allow news processing in _think
self._set_state(0)
actions_taken = 0
rsp = AIMessage(content="No actions taken yet", cause_by=Action) # will be overwritten after Role _act
while actions_taken < self.rc.max_react_loop:
# NOTE: difference here, keep observing within react
# NOTE: Diff 2: Keep observing within _react, news will go into memory, allowing adapting to new info
await self._observe()
# think
has_todo = await self._think()
if not has_todo:
@ -215,6 +239,8 @@ class RoleZero(Role):
async def ask_human(self, question: str) -> str:
"""Use this when you fail the current task or if you are unsure of the situation encountered. Your response should contain a brief summary of your situation, ended with a clear and concise question."""
# NOTE: Can be overwritten in remote setting
from metagpt.environment.mgx.mgx_env import MGXEnv # avoid circular import
if not isinstance(self.rc.env, MGXEnv):
return "Not in MGXEnv, command will not be executed."
return await self.rc.env.get_human_input(question, sent_from=self)
@ -222,6 +248,8 @@ class RoleZero(Role):
async def reply_to_human(self, content: str) -> str:
"""Reply to human user with the content provided. Use this when you have a clear answer or solution to the user's question."""
# NOTE: Can be overwritten in remote setting
from metagpt.environment.mgx.mgx_env import MGXEnv # avoid circular import
if not isinstance(self.rc.env, MGXEnv):
return "Not in MGXEnv, command will not be executed."
return await self.rc.env.reply_to_human(content, sent_from=self)

View file

@ -1,7 +1,5 @@
from __future__ import annotations
from pydantic import model_validator
from metagpt.actions.di.run_command import RunCommand
from metagpt.prompts.di.team_leader import (
FINISH_CURRENT_TASK_CMD,
@ -26,17 +24,13 @@ class TeamLeader(RoleZero):
experience_retriever: ExpRetriever = SimpleExpRetriever()
@model_validator(mode="after")
def set_tool_execution(self) -> "RoleZero":
self.tool_execution_map = {
"Plan.append_task": self.planner.plan.append_task,
"Plan.reset_task": self.planner.plan.reset_task,
"Plan.replace_task": self.planner.plan.replace_task,
"RoleZero.ask_human": self.ask_human,
"RoleZero.reply_to_human": self.reply_to_human,
"TeamLeader.publish_team_message": self.publish_team_message,
}
return self
def _update_tool_execution(self):
self.tool_execution_map.update(
{
"TeamLeader.publish_team_message": self.publish_team_message,
"TeamLeader.publish_message": self.publish_team_message, # alias
}
)
def set_instruction(self):
team_info = ""

View file

@ -7,15 +7,15 @@
@Modified By: mashenquan, 2023/11/27. Add `PrepareDocuments` action according to Section 2.2.3.5.1 of RFC 135.
"""
from metagpt.actions import UserRequirement, WritePRD
from metagpt.actions.prepare_documents import PrepareDocuments
from metagpt.roles.role import Role, RoleReactMode
from metagpt.roles.di.role_zero import RoleZero
from metagpt.roles.role import RoleReactMode
from metagpt.utils.common import any_to_name, any_to_str
from metagpt.utils.git_repository import GitRepository
class ProductManager(Role):
class ProductManager(RoleZero):
"""
Represents a Product Manager role responsible for product development and management.
@ -30,18 +30,35 @@ class ProductManager(Role):
profile: str = "Product Manager"
goal: str = "efficiently create a successful product that meets market demands and user expectations"
constraints: str = "utilize the same language as the user requirements for seamless communication"
todo_action: str = ""
todo_action: str = any_to_name(WritePRD)
instruction: str = """Use WritePRD tool to write PRD"""
max_react_loop: int = 1 # FIXME: Read and edit files requires more steps, consider later
tools: list[str] = ["Editor:write,read,write_content", "RoleZero", "WritePRD"]
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
# NOTE: The following init setting will only be effective when self.use_fixed_sop is changed to True
self.enable_memory = False
self.set_actions([PrepareDocuments(send_to=any_to_str(self)), WritePRD])
self._watch([UserRequirement, PrepareDocuments])
self.rc.react_mode = RoleReactMode.BY_ORDER
self.todo_action = any_to_name(WritePRD)
if self.use_fixed_sop:
self.rc.react_mode = RoleReactMode.BY_ORDER
def _update_tool_execution(self):
wp = WritePRD()
self.tool_execution_map.update(
{
"WritePRD.run": wp.run,
"WritePRD": wp.run, # alias
}
)
async def _think(self) -> bool:
"""Decide what to do"""
if not self.use_fixed_sop:
return await super()._think()
if GitRepository.is_git_dir(self.config.project_path) and not self.config.git_reinit:
self._set_state(1)
else:

View file

@ -5,13 +5,12 @@
@Author : alexanderwu
@File : project_manager.py
"""
from metagpt.actions import WriteTasks
from metagpt.actions.design_api import WriteDesign
from metagpt.roles.role import Role
from metagpt.roles.di.role_zero import RoleZero
class ProjectManager(Role):
class ProjectManager(RoleZero):
"""
Represents a Project Manager role responsible for overseeing project execution and team efficiency.
@ -30,8 +29,22 @@ class ProjectManager(Role):
)
constraints: str = "use same language as user requirement"
instruction: str = """Use WriteTasks tool to write a project task list"""
max_react_loop: int = 1 # FIXME: Read and edit files requires more steps, consider later
tools: list[str] = ["Editor:write,read,write_content", "RoleZero", "WriteTasks"]
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
# NOTE: The following init setting will only be effective when self.use_fixed_sop is changed to True
self.enable_memory = False
self.set_actions([WriteTasks])
self._watch([WriteDesign])
def _update_tool_execution(self):
wt = WriteTasks()
self.tool_execution_map.update(
{
"WriteTasks.run": wt.run,
"WriteTasks": wt.run, # alias
}
)