mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-02 14:45:17 +02:00
Merge branch 'serialization' into feat_serdes
This commit is contained in:
commit
c56a629b40
26 changed files with 625 additions and 221 deletions
|
|
@ -63,6 +63,7 @@ # If executing, ensure that NPM is installed on your system. Then install mermai
|
|||
detail installation please refer to [cli_install](https://docs.deepwisdom.ai/guide/get_started/installation.html#install-stable-version)
|
||||
|
||||
### Docker installation
|
||||
> Note: In the Windows, you need to replace "/opt/metagpt" with a directory that Docker has permission to create, such as "D:\Users\x\metagpt"
|
||||
|
||||
```bash
|
||||
# Step 1: Download metagpt official image and prepare config.yaml
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ # 如果执行,确保您的系统上安装了 NPM。并使用npm安装mermaid-
|
|||
详细的安装请安装 [cli_install](https://docs.deepwisdom.ai/guide/get_started/installation.html#install-stable-version)
|
||||
|
||||
### Docker安装
|
||||
> 注意:在Windows中,你需要将 "/opt/metagpt" 替换为Docker具有创建权限的目录,比如"D:\Users\x\metagpt"
|
||||
|
||||
```bash
|
||||
# 步骤1: 下载metagpt官方镜像并准备好config.yaml
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ # NPM がシステムにインストールされていることを確認して
|
|||
注: この方法は pdf エクスポートに対応していません。
|
||||
|
||||
### Docker によるインストール
|
||||
> Windowsでは、"/opt/metagpt"をDockerが作成する権限を持つディレクトリに置き換える必要があります。例えば、"D:\Users\x\metagpt"などです。
|
||||
|
||||
```bash
|
||||
# ステップ 1: metagpt 公式イメージをダウンロードし、config.yaml を準備する
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
"""
|
||||
import re
|
||||
from abc import ABC
|
||||
from typing import Optional
|
||||
from typing import Optional, Any
|
||||
|
||||
import importlib
|
||||
from pydantic import BaseModel, Field
|
||||
from tenacity import retry, stop_after_attempt, wait_fixed
|
||||
|
||||
from metagpt.actions.action_output import ActionOutput
|
||||
|
|
@ -20,27 +20,27 @@ from metagpt.utils.custom_decoder import CustomDecoder
|
|||
from metagpt.utils.utils import import_class
|
||||
|
||||
|
||||
class Action(ABC):
|
||||
def __init__(self, name: str = "", context=None, llm: LLM = None):
|
||||
self.name: str = name
|
||||
if llm is None:
|
||||
llm = LLM()
|
||||
self.llm = llm
|
||||
self.context = context
|
||||
self.prefix = ""
|
||||
self.profile = ""
|
||||
self.desc = ""
|
||||
self.content = ""
|
||||
self.instruct_content = None
|
||||
|
||||
class Action(BaseModel):
|
||||
name: str = ""
|
||||
llm: LLM = Field(default_factory=LLM)
|
||||
context = ""
|
||||
prefix = ""
|
||||
profile = ""
|
||||
desc = ""
|
||||
content: Optional[str] = None
|
||||
instruct_content: Optional[str] = None
|
||||
|
||||
def __init__(self, **kwargs: Any):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def set_prefix(self, prefix, profile):
|
||||
"""Set prefix for later usage"""
|
||||
self.prefix = prefix
|
||||
self.profile = profile
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
|
@ -80,15 +80,15 @@ class Action(ABC):
|
|||
system_msgs = []
|
||||
system_msgs.append(self.prefix)
|
||||
return await self.llm.aask(prompt, system_msgs)
|
||||
|
||||
|
||||
@retry(stop=stop_after_attempt(3), wait=wait_fixed(1))
|
||||
async def _aask_v1(
|
||||
self,
|
||||
prompt: str,
|
||||
output_class_name: str,
|
||||
output_data_mapping: dict,
|
||||
system_msgs: Optional[list[str]] = None,
|
||||
format="markdown", # compatible to original format
|
||||
self,
|
||||
prompt: str,
|
||||
output_class_name: str,
|
||||
output_data_mapping: dict,
|
||||
system_msgs: Optional[list[str]] = None,
|
||||
format="markdown", # compatible to original format
|
||||
) -> ActionOutput:
|
||||
"""Append default prefix"""
|
||||
if not system_msgs:
|
||||
|
|
@ -97,25 +97,25 @@ class Action(ABC):
|
|||
content = await self.llm.aask(prompt, system_msgs)
|
||||
logger.debug(content)
|
||||
output_class = ActionOutput.create_model_class(output_class_name, output_data_mapping)
|
||||
|
||||
|
||||
if format == "json":
|
||||
pattern = r"\[CONTENT\](\s*\{.*?\}\s*)\[/CONTENT\]"
|
||||
matches = re.findall(pattern, content, re.DOTALL)
|
||||
|
||||
|
||||
for match in matches:
|
||||
if match:
|
||||
content = match
|
||||
break
|
||||
|
||||
|
||||
parsed_data = CustomDecoder(strict=False).decode(content)
|
||||
|
||||
|
||||
else: # using markdown parser
|
||||
parsed_data = OutputParser.parse_data_with_mapping(content, output_data_mapping)
|
||||
|
||||
|
||||
logger.debug(parsed_data)
|
||||
instruct_content = output_class(**parsed_data)
|
||||
return ActionOutput(content, instruct_content)
|
||||
|
||||
|
||||
async def run(self, *args, **kwargs):
|
||||
"""Run action"""
|
||||
raise NotImplementedError("The run method should be implemented in a subclass.")
|
||||
|
|
|
|||
|
|
@ -7,9 +7,12 @@
|
|||
"""
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from typing import List, Optional, Any
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from metagpt.actions import Action, ActionOutput
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.const import WORKSPACE_ROOT
|
||||
from metagpt.logs import logger
|
||||
|
|
@ -150,13 +153,13 @@ OUTPUT_MAPPING = {
|
|||
|
||||
|
||||
class WriteDesign(Action):
|
||||
def __init__(self, name, context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
self.desc = (
|
||||
"Based on the PRD, think about the system design, and design the corresponding APIs, "
|
||||
"data structures, library tables, processes, and paths. Please provide your design, feedback "
|
||||
"clearly and in detail."
|
||||
)
|
||||
name: str = ""
|
||||
context: Optional[str] = None
|
||||
llm: LLM = Field(default_factory=LLM)
|
||||
desc: str = "Based on the PRD, think about the system design, and design the corresponding APIs, "
|
||||
"data structures, library tables, processes, and paths. Please provide your design, feedback "
|
||||
"clearly and in detail."
|
||||
|
||||
|
||||
def recreate_workspace(self, workspace: Path):
|
||||
try:
|
||||
|
|
@ -165,16 +168,18 @@ class WriteDesign(Action):
|
|||
pass # Folder does not exist, but we don't care
|
||||
workspace.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
async def _save_prd(self, docs_path, resources_path, context):
|
||||
prd_file = docs_path / "prd.md"
|
||||
if context[-1].instruct_content and context[-1].instruct_content.dict()["Competitive Quadrant Chart"]:
|
||||
quadrant_chart = context[-1].instruct_content.dict()["Competitive Quadrant Chart"]
|
||||
await mermaid_to_file(quadrant_chart, resources_path / "competitive_analysis")
|
||||
|
||||
|
||||
if context[-1].instruct_content:
|
||||
logger.info(f"Saving PRD to {prd_file}")
|
||||
prd_file.write_text(json_to_markdown(context[-1].instruct_content.dict()))
|
||||
|
||||
|
||||
async def _save_system_design(self, docs_path, resources_path, system_design):
|
||||
data_api_design = system_design.instruct_content.dict()[
|
||||
"Data structures and interface definitions"
|
||||
|
|
@ -188,6 +193,7 @@ class WriteDesign(Action):
|
|||
logger.info(f"Saving System Designs to {system_design_file}")
|
||||
system_design_file.write_text((json_to_markdown(system_design.instruct_content.dict())))
|
||||
|
||||
|
||||
async def _save(self, context, system_design):
|
||||
if isinstance(system_design, ActionOutput):
|
||||
ws_name = system_design.instruct_content.dict()["Python package name"]
|
||||
|
|
@ -199,9 +205,13 @@ class WriteDesign(Action):
|
|||
resources_path = workspace / "resources"
|
||||
docs_path.mkdir(parents=True, exist_ok=True)
|
||||
resources_path.mkdir(parents=True, exist_ok=True)
|
||||
await self._save_prd(docs_path, resources_path, context)
|
||||
try:
|
||||
await self._save_prd(docs_path, resources_path, context)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save PRD {e}")
|
||||
await self._save_system_design(docs_path, resources_path, system_design)
|
||||
|
||||
|
||||
async def run(self, context, format=CONFIG.prompt_format):
|
||||
prompt_template, format_example = get_template(templates, format)
|
||||
prompt = prompt_template.format(context=context, format_example=format_example)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,12 @@
|
|||
@Author : alexanderwu
|
||||
@File : project_management.py
|
||||
"""
|
||||
from typing import List
|
||||
from typing import List, Optional, Any
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.const import WORKSPACE_ROOT
|
||||
from metagpt.utils.common import CodeParser
|
||||
|
|
@ -163,21 +166,25 @@ OUTPUT_MAPPING = {
|
|||
|
||||
|
||||
class WriteTasks(Action):
|
||||
def __init__(self, name="CreateTasks", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
name: str = "CreateTasks"
|
||||
context: Optional[str] = None
|
||||
llm: LLM = Field(default_factory=LLM)
|
||||
|
||||
def _save(self, context, rsp):
|
||||
if context[-1].instruct_content:
|
||||
ws_name = context[-1].instruct_content.dict()["Python package name"]
|
||||
else:
|
||||
ws_name = CodeParser.parse_str(block="Python package name", text=context[-1].content)
|
||||
try:
|
||||
if context[-1].instruct_content:
|
||||
ws_name = context[-1].instruct_content.dict()["Python package name"]
|
||||
else:
|
||||
ws_name = CodeParser.parse_str(block="Python package name", text=context[-1].content)
|
||||
except:
|
||||
ws_name = "cli_snake_game" # fixme: 应该透传
|
||||
file_path = WORKSPACE_ROOT / ws_name / "docs/api_spec_and_tasks.md"
|
||||
file_path.write_text(json_to_markdown(rsp.instruct_content.dict()))
|
||||
|
||||
|
||||
# Write requirements.txt
|
||||
requirements_path = WORKSPACE_ROOT / ws_name / "requirements.txt"
|
||||
requirements_path.write_text("\n".join(rsp.instruct_content.dict().get("Required Python third-party packages")))
|
||||
|
||||
|
||||
async def run(self, context, format=CONFIG.prompt_format):
|
||||
prompt_template, format_example = get_template(templates, format)
|
||||
prompt = prompt_template.format(context=context, format_example=format_example)
|
||||
|
|
|
|||
|
|
@ -6,12 +6,16 @@
|
|||
@File : search_google.py
|
||||
"""
|
||||
import pydantic
|
||||
from typing import Optional, Any
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from metagpt.actions import Action
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.config import Config
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import Message
|
||||
from metagpt.tools.search_engine import SearchEngine
|
||||
from pydantic import root_validator
|
||||
|
||||
SEARCH_AND_SUMMARIZE_SYSTEM = """### Requirements
|
||||
1. Please summarize the latest dialogue based on the reference information (secondary) and dialogue history (primary). Do not include text that is irrelevant to the conversation.
|
||||
|
|
@ -54,7 +58,6 @@ SEARCH_AND_SUMMARIZE_PROMPT = """
|
|||
|
||||
"""
|
||||
|
||||
|
||||
SEARCH_AND_SUMMARIZE_SALES_SYSTEM = """## Requirements
|
||||
1. Please summarize the latest dialogue based on the reference information (secondary) and dialogue history (primary). Do not include text that is irrelevant to the conversation.
|
||||
- The context is for reference only. If it is irrelevant to the user's search request history, please reduce its reference and usage.
|
||||
|
|
@ -101,23 +104,41 @@ You are a member of a professional butler team and will provide helpful suggesti
|
|||
|
||||
|
||||
class SearchAndSummarize(Action):
|
||||
def __init__(self, name="", context=None, llm=None, engine=None, search_func=None):
|
||||
self.config = Config()
|
||||
self.engine = engine or self.config.search_engine
|
||||
name: str = ""
|
||||
content: Optional[str] = None
|
||||
llm: None = Field(default_factory=LLM)
|
||||
config: None = Field(default_factory=Config)
|
||||
engine: Optional[str] = None
|
||||
search_func: Optional[str] = None
|
||||
|
||||
try:
|
||||
self.search_engine = SearchEngine(self.engine, run_func=search_func)
|
||||
except pydantic.ValidationError:
|
||||
self.search_engine = None
|
||||
result = ""
|
||||
|
||||
|
||||
self.result = ""
|
||||
super().__init__(name, context, llm)
|
||||
@root_validator
|
||||
def validate_engine_and_run_func(cls, values):
|
||||
engine = values.get('engine')
|
||||
search_func = values.get('search_func')
|
||||
config = Config()
|
||||
|
||||
if engine is None:
|
||||
engine = config.search_engine
|
||||
config_data = {
|
||||
'engine': engine,
|
||||
'run_func': search_func
|
||||
}
|
||||
search_engine = SearchEngine(**config_data)
|
||||
|
||||
values['search_engine'] = search_engine
|
||||
return values
|
||||
|
||||
|
||||
|
||||
async def run(self, context: list[Message], system_text=SEARCH_AND_SUMMARIZE_SYSTEM) -> str:
|
||||
print(context)
|
||||
if self.search_engine is None:
|
||||
logger.warning("Configure one of SERPAPI_API_KEY, SERPER_API_KEY, GOOGLE_API_KEY to unlock full feature")
|
||||
return ""
|
||||
|
||||
|
||||
query = context[-1].content
|
||||
# logger.debug(query)
|
||||
rsp = await self.search_engine.run(query)
|
||||
|
|
@ -126,9 +147,9 @@ class SearchAndSummarize(Action):
|
|||
logger.error("empty rsp...")
|
||||
return ""
|
||||
# logger.info(rsp)
|
||||
|
||||
|
||||
system_prompt = [system_text]
|
||||
|
||||
|
||||
prompt = SEARCH_AND_SUMMARIZE_PROMPT.format(
|
||||
# PREFIX = self.prefix,
|
||||
ROLE=self.profile,
|
||||
|
|
@ -140,4 +161,7 @@ class SearchAndSummarize(Action):
|
|||
logger.debug(prompt)
|
||||
logger.debug(result)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
action = SearchAndSummarize()
|
||||
|
|
|
|||
|
|
@ -5,13 +5,18 @@
|
|||
@Author : alexanderwu
|
||||
@File : write_code.py
|
||||
"""
|
||||
from typing import List, Optional, Any
|
||||
|
||||
from pydantic import Field
|
||||
from tenacity import retry, stop_after_attempt, wait_fixed
|
||||
|
||||
from metagpt.actions import WriteDesign
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.const import WORKSPACE_ROOT
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import Message
|
||||
from metagpt.utils.common import CodeParser
|
||||
from tenacity import retry, stop_after_attempt, wait_fixed
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
NOTICE
|
||||
|
|
@ -43,9 +48,10 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc
|
|||
|
||||
|
||||
class WriteCode(Action):
|
||||
def __init__(self, name="WriteCode", context: list[Message] = None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
name: str = "WriteCode"
|
||||
context: Optional[str] = None
|
||||
llm: LLM = Field(default_factory=LLM)
|
||||
|
||||
def _is_invalid(self, filename):
|
||||
return any(i in filename for i in ["mp3", "wav"])
|
||||
|
||||
|
|
@ -79,4 +85,3 @@ class WriteCode(Action):
|
|||
# code_rsp = await self._aask_v1(prompt, "code_rsp", OUTPUT_MAPPING)
|
||||
# self._save(context, filename, code)
|
||||
return code
|
||||
|
||||
|
|
@ -5,12 +5,15 @@
|
|||
@Author : alexanderwu
|
||||
@File : write_code_review.py
|
||||
"""
|
||||
from typing import List, Optional, Any
|
||||
from pydantic import Field
|
||||
from tenacity import retry, stop_after_attempt, wait_fixed
|
||||
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import Message
|
||||
from metagpt.utils.common import CodeParser
|
||||
from tenacity import retry, stop_after_attempt, wait_fixed
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
NOTICE
|
||||
|
|
@ -62,9 +65,10 @@ FORMAT_EXAMPLE = """
|
|||
|
||||
|
||||
class WriteCodeReview(Action):
|
||||
def __init__(self, name="WriteCodeReview", context: list[Message] = None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
name: str = "WriteCodeReview"
|
||||
context: Optional[str] = None
|
||||
llm: LLM = Field(default_factory=LLM)
|
||||
|
||||
@retry(stop=stop_after_attempt(2), wait=wait_fixed(1))
|
||||
async def write_code(self, prompt):
|
||||
code_rsp = await self._aask(prompt)
|
||||
|
|
@ -79,4 +83,3 @@ class WriteCodeReview(Action):
|
|||
# code_rsp = await self._aask_v1(prompt, "code_rsp", OUTPUT_MAPPING)
|
||||
# self._save(context, filename, code)
|
||||
return code
|
||||
|
||||
|
|
@ -5,9 +5,12 @@
|
|||
@Author : alexanderwu
|
||||
@File : write_prd.py
|
||||
"""
|
||||
from typing import List
|
||||
from typing import List, Optional, Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from metagpt.actions import Action, ActionOutput
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.actions.search_and_summarize import SearchAndSummarize
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.logs import logger
|
||||
|
|
@ -219,18 +222,25 @@ OUTPUT_MAPPING = {
|
|||
|
||||
|
||||
class WritePRD(Action):
|
||||
def __init__(self, name="", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
name: str = ""
|
||||
content: Optional[str] = None
|
||||
llm: LLM = Field(default_factory=LLM)
|
||||
assistant_search_action: Action = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
async def run(self, requirements, format=CONFIG.prompt_format, *args, **kwargs) -> ActionOutput:
|
||||
sas = SearchAndSummarize()
|
||||
# rsp = await sas.run(context=requirements, system_text=SEARCH_AND_SUMMARIZE_SYSTEM_EN_US)
|
||||
rsp = ""
|
||||
info = f"### Search Results\n{sas.result}\n\n### Search Summary\n{rsp}"
|
||||
if sas.result:
|
||||
logger.info(sas.result)
|
||||
# self.assistant_search_action = SearchAndSummarize()
|
||||
if self.assistant_search_action is None:
|
||||
self.assistant_search_action = SearchAndSummarize()
|
||||
# self.assistant_search_action = SearchAndSummarize()
|
||||
rsp = await self.assistant_search_action.run(context=requirements)
|
||||
info = f"### Search Results\n{self.assistant_search_action.result}\n\n### Search Summary\n{rsp}"
|
||||
if self.assistant_search_action.result:
|
||||
logger.info(self.assistant_search_action.result)
|
||||
logger.info(rsp)
|
||||
|
||||
|
||||
prompt_template, format_example = get_template(templates, format)
|
||||
prompt = prompt_template.format(
|
||||
requirements=requirements, search_information=info, format_example=format_example
|
||||
|
|
|
|||
|
|
@ -67,11 +67,12 @@ class Environment(BaseModel):
|
|||
self.history = history.get("content")
|
||||
|
||||
def add_role(self, role: Role):
|
||||
"""增加一个在当前环境的角色
|
||||
"""增加一个在当前环境的角色, 默认为profile/role_profile
|
||||
Add a role in the current environment
|
||||
"""
|
||||
role.set_env(self)
|
||||
self.roles[role.profile] = role
|
||||
# use alias
|
||||
self.roles[role.role_profile] = role
|
||||
|
||||
def add_roles(self, roles: Iterable[Role]):
|
||||
"""增加一批在当前环境的角色
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@
|
|||
@Author : alexanderwu
|
||||
@File : architect.py
|
||||
"""
|
||||
from pydantic import Field
|
||||
|
||||
from metagpt.actions import WritePRD
|
||||
from metagpt.actions.design_api import WriteDesign
|
||||
from metagpt.roles import Role
|
||||
from metagpt.roles.role import Role
|
||||
|
||||
|
||||
class Architect(Role):
|
||||
|
|
@ -21,17 +22,16 @@ class Architect(Role):
|
|||
goal (str): Primary goal or responsibility of the architect.
|
||||
constraints (str): Constraints or guidelines for the architect.
|
||||
"""
|
||||
name: str = "Bob"
|
||||
role_profile: str = Field(default="Architect" , alias='profile')
|
||||
goal: str = "Design a concise, usable, complete python system"
|
||||
constraints: str = "Try to specify good open source tools as much as possible"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "Bob",
|
||||
profile: str = "Architect",
|
||||
goal: str = "Design a concise, usable, complete python system",
|
||||
constraints: str = "Try to specify good open source tools as much as possible",
|
||||
self,
|
||||
**kwargs
|
||||
) -> None:
|
||||
"""Initializes the Architect with given attributes."""
|
||||
super().__init__(name, profile, goal, constraints)
|
||||
|
||||
super().__init__(**kwargs)
|
||||
# Initialize actions specific to the Architect role
|
||||
self._init_actions([WriteDesign])
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@ import asyncio
|
|||
import shutil
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from pydantic import Field
|
||||
|
||||
from metagpt.actions import WriteCode, WriteCodeReview, WriteDesign, WriteTasks
|
||||
from metagpt.const import WORKSPACE_ROOT
|
||||
from metagpt.logs import logger
|
||||
from metagpt.roles import Role
|
||||
from metagpt.roles.role import Role
|
||||
from metagpt.schema import Message
|
||||
from metagpt.utils.common import CodeParser
|
||||
from metagpt.utils.special_tokens import FILENAME_CODE_SEP, MSG_SEP
|
||||
|
|
@ -23,7 +24,7 @@ async def gather_ordered_k(coros, k) -> list:
|
|||
tasks = OrderedDict()
|
||||
results = [None] * len(coros)
|
||||
done_queue = asyncio.Queue()
|
||||
|
||||
|
||||
for i, coro in enumerate(coros):
|
||||
if len(tasks) >= k:
|
||||
done, _ = await asyncio.wait(tasks.keys(), return_when=asyncio.FIRST_COMPLETED)
|
||||
|
|
@ -32,17 +33,17 @@ async def gather_ordered_k(coros, k) -> list:
|
|||
await done_queue.put((index, task.result()))
|
||||
task = asyncio.create_task(coro)
|
||||
tasks[task] = i
|
||||
|
||||
|
||||
if tasks:
|
||||
done, _ = await asyncio.wait(tasks.keys())
|
||||
for task in done:
|
||||
index = tasks[task]
|
||||
await done_queue.put((index, task.result()))
|
||||
|
||||
|
||||
while not done_queue.empty():
|
||||
index, result = await done_queue.get()
|
||||
results[index] = result
|
||||
|
||||
|
||||
return results
|
||||
|
||||
|
||||
|
|
@ -59,42 +60,42 @@ class Engineer(Role):
|
|||
use_code_review (bool): Whether to use code review.
|
||||
todos (list): List of tasks.
|
||||
"""
|
||||
|
||||
name: str = "Alex"
|
||||
role_profile: str = Field(default="Engineer", alias='profile')
|
||||
goal: str = "Write elegant, readable, extensible, efficient code"
|
||||
constraints: str = "The code should conform to standards like PEP8 and be modular and maintainable"
|
||||
n_borg: int = 1
|
||||
use_code_review: bool = False
|
||||
todos: list = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "Alex",
|
||||
profile: str = "Engineer",
|
||||
goal: str = "Write elegant, readable, extensible, efficient code",
|
||||
constraints: str = "The code should conform to standards like PEP8 and be modular and maintainable",
|
||||
n_borg: int = 1,
|
||||
use_code_review: bool = False,
|
||||
self,
|
||||
**kwargs
|
||||
) -> None:
|
||||
"""Initializes the Engineer role with given attributes."""
|
||||
super().__init__(name, profile, goal, constraints)
|
||||
self._init_actions([WriteCode])
|
||||
self.use_code_review = use_code_review
|
||||
super().__init__(**kwargs)
|
||||
|
||||
actions = [WriteCode]
|
||||
if self.use_code_review:
|
||||
self._init_actions([WriteCode, WriteCodeReview])
|
||||
actions = [WriteCode, WriteCodeReview]
|
||||
self._init_actions(actions)
|
||||
self._watch([WriteTasks])
|
||||
self.todos = []
|
||||
self.n_borg = n_borg
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_tasks(self, task_msg: Message) -> list[str]:
|
||||
if task_msg.instruct_content:
|
||||
return task_msg.instruct_content.dict().get("Task list")
|
||||
return CodeParser.parse_file_list(block="Task list", text=task_msg.content)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_code(self, code_text: str) -> str:
|
||||
return CodeParser.parse_code(block="", text=code_text)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_workspace(cls, system_design_msg: Message) -> str:
|
||||
if system_design_msg.instruct_content:
|
||||
return system_design_msg.instruct_content.dict().get("Python package name").strip().strip("'").strip('"')
|
||||
return CodeParser.parse_str(block="Python package name", text=system_design_msg.content)
|
||||
|
||||
|
||||
def get_workspace(self) -> Path:
|
||||
msg = self._rc.memory.get_by_action(WriteDesign)[-1]
|
||||
if not msg:
|
||||
|
|
@ -102,7 +103,7 @@ class Engineer(Role):
|
|||
workspace = self.parse_workspace(msg)
|
||||
# Codes are written in workspace/{package_name}/{package_name}
|
||||
return WORKSPACE_ROOT / workspace / workspace
|
||||
|
||||
|
||||
def recreate_workspace(self):
|
||||
workspace = self.get_workspace()
|
||||
try:
|
||||
|
|
@ -110,7 +111,7 @@ class Engineer(Role):
|
|||
except FileNotFoundError:
|
||||
pass # The folder does not exist, but we don't care
|
||||
workspace.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def write_file(self, filename: str, code: str):
|
||||
workspace = self.get_workspace()
|
||||
filename = filename.replace('"', "").replace("\n", "")
|
||||
|
|
@ -118,12 +119,12 @@ class Engineer(Role):
|
|||
file.parent.mkdir(parents=True, exist_ok=True)
|
||||
file.write_text(code)
|
||||
return file
|
||||
|
||||
|
||||
def recv(self, message: Message) -> None:
|
||||
self._rc.memory.add(message)
|
||||
if message in self._rc.important_memory:
|
||||
self.todos = self.parse_tasks(message)
|
||||
|
||||
|
||||
async def _act_mp(self) -> Message:
|
||||
# self.recreate_workspace()
|
||||
todo_coros = []
|
||||
|
|
@ -132,7 +133,7 @@ class Engineer(Role):
|
|||
context=self._rc.memory.get_by_actions([WriteTasks, WriteDesign]), filename=todo
|
||||
)
|
||||
todo_coros.append(todo_coro)
|
||||
|
||||
|
||||
rsps = await gather_ordered_k(todo_coros, self.n_borg)
|
||||
for todo, code_rsp in zip(self.todos, rsps):
|
||||
_ = self.parse_code(code_rsp)
|
||||
|
|
@ -142,11 +143,11 @@ class Engineer(Role):
|
|||
msg = Message(content=code_rsp, role=self.profile, cause_by=type(self._rc.todo))
|
||||
self._rc.memory.add(msg)
|
||||
del self.todos[0]
|
||||
|
||||
|
||||
logger.info(f"Done {self.get_workspace()} generating.")
|
||||
msg = Message(content="all done.", role=self.profile, cause_by=type(self._rc.todo))
|
||||
return msg
|
||||
|
||||
|
||||
async def _act_sp(self) -> Message:
|
||||
code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later
|
||||
for todo in self.todos:
|
||||
|
|
@ -157,16 +158,16 @@ class Engineer(Role):
|
|||
file_path = self.write_file(todo, code)
|
||||
msg = Message(content=code, role=self.profile, cause_by=type(self._rc.todo))
|
||||
self._rc.memory.add(msg)
|
||||
|
||||
|
||||
code_msg = todo + FILENAME_CODE_SEP + str(file_path)
|
||||
code_msg_all.append(code_msg)
|
||||
|
||||
|
||||
logger.info(f"Done {self.get_workspace()} generating.")
|
||||
msg = Message(
|
||||
content=MSG_SEP.join(code_msg_all), role=self.profile, cause_by=type(self._rc.todo), send_to="QaEngineer"
|
||||
)
|
||||
return msg
|
||||
|
||||
|
||||
async def _act_sp_precision(self) -> Message:
|
||||
code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later
|
||||
for todo in self.todos:
|
||||
|
|
@ -195,19 +196,18 @@ class Engineer(Role):
|
|||
file_path = self.write_file(todo, code)
|
||||
msg = Message(content=code, role=self.profile, cause_by=WriteCode)
|
||||
self._rc.memory.add(msg)
|
||||
|
||||
|
||||
code_msg = todo + FILENAME_CODE_SEP + str(file_path)
|
||||
code_msg_all.append(code_msg)
|
||||
|
||||
|
||||
logger.info(f"Done {self.get_workspace()} generating.")
|
||||
msg = Message(
|
||||
content=MSG_SEP.join(code_msg_all), role=self.profile, cause_by=type(self._rc.todo), send_to="QaEngineer"
|
||||
)
|
||||
return msg
|
||||
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -5,37 +5,33 @@
|
|||
@Author : alexanderwu
|
||||
@File : product_manager.py
|
||||
"""
|
||||
from pydantic import Field
|
||||
|
||||
from metagpt.actions import BossRequirement, WritePRD
|
||||
from metagpt.roles import Role
|
||||
from metagpt.roles.role import Role
|
||||
|
||||
|
||||
class ProductManager(Role):
|
||||
"""
|
||||
Represents a Product Manager role responsible for product development and management.
|
||||
Initializes the ProductManager role with given attributes.
|
||||
|
||||
Attributes:
|
||||
Args:
|
||||
name (str): Name of the product manager.
|
||||
profile (str): Role profile, default is 'Product Manager'.
|
||||
profile (str): Role profile.
|
||||
goal (str): Goal of the product manager.
|
||||
constraints (str): Constraints or limitations for the product manager.
|
||||
"""
|
||||
|
||||
name: str = "Alice"
|
||||
role_profile: str = Field(default="Product Manager", alias='profile')
|
||||
goal: str = "Efficiently create a successful product"
|
||||
constraints: str = ""
|
||||
"""
|
||||
Represents a Product Manager role responsible for product development and management.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "Alice",
|
||||
profile: str = "Product Manager",
|
||||
goal: str = "Efficiently create a successful product",
|
||||
constraints: str = "",
|
||||
self,
|
||||
**kwargs
|
||||
) -> None:
|
||||
"""
|
||||
Initializes the ProductManager role with given attributes.
|
||||
|
||||
Args:
|
||||
name (str): Name of the product manager.
|
||||
profile (str): Role profile.
|
||||
goal (str): Goal of the product manager.
|
||||
constraints (str): Constraints or limitations for the product manager.
|
||||
"""
|
||||
super().__init__(name, profile, goal, constraints)
|
||||
super().__init__(**kwargs)
|
||||
self._init_actions([WritePRD])
|
||||
self._watch([BossRequirement])
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@
|
|||
@Author : alexanderwu
|
||||
@File : project_manager.py
|
||||
"""
|
||||
from pydantic import Field
|
||||
|
||||
from metagpt.actions import WriteTasks
|
||||
from metagpt.actions.design_api import WriteDesign
|
||||
from metagpt.roles import Role
|
||||
from metagpt.roles.role import Role
|
||||
|
||||
|
||||
class ProjectManager(Role):
|
||||
|
|
@ -20,23 +22,16 @@ class ProjectManager(Role):
|
|||
goal (str): Goal of the project manager.
|
||||
constraints (str): Constraints or limitations for the project manager.
|
||||
"""
|
||||
name: str = "Eve"
|
||||
role_profile: str = Field(default="Project Manager", alias='profile')
|
||||
|
||||
goal: str = "Improve team efficiency and deliver with quality and quantity"
|
||||
constraints: str = ""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "Eve",
|
||||
profile: str = "Project Manager",
|
||||
goal: str = "Improve team efficiency and deliver with quality and quantity",
|
||||
constraints: str = "",
|
||||
self,
|
||||
**kwargs
|
||||
) -> None:
|
||||
"""
|
||||
Initializes the ProjectManager role with given attributes.
|
||||
|
||||
Args:
|
||||
name (str): Name of the project manager.
|
||||
profile (str): Role profile.
|
||||
goal (str): Goal of the project manager.
|
||||
constraints (str): Constraints or limitations for the project manager.
|
||||
"""
|
||||
super().__init__(name, profile, goal, constraints)
|
||||
super().__init__(**kwargs)
|
||||
self._init_actions([WriteTasks])
|
||||
self._watch([WriteDesign])
|
||||
|
|
|
|||
|
|
@ -5,18 +5,28 @@
|
|||
@Author : alexanderwu
|
||||
@File : role.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
from enum import Enum
|
||||
import importlib
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Iterable, Type, Union
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import (
|
||||
Dict,
|
||||
Optional,
|
||||
Union,
|
||||
Iterable,
|
||||
Type
|
||||
)
|
||||
import re
|
||||
from pydantic import BaseModel, Field
|
||||
import importlib
|
||||
from importlib import import_module
|
||||
|
||||
# from metagpt.environment import Environment
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.actions import Action, ActionOutput
|
||||
from metagpt.llm import LLM, HumanProvider
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.logs import logger
|
||||
from metagpt.memory import Memory, LongTermMemory
|
||||
from metagpt.schema import Message
|
||||
|
|
@ -60,18 +70,18 @@ class RoleReactMode(str, Enum):
|
|||
def values(cls):
|
||||
return [item.value for item in cls]
|
||||
|
||||
|
||||
class RoleSetting(BaseModel):
|
||||
"""Role Settings"""
|
||||
name: str
|
||||
profile: str
|
||||
goal: str
|
||||
constraints: str
|
||||
desc: str
|
||||
is_human: bool
|
||||
|
||||
name: str = ""
|
||||
profile: str = ""
|
||||
goal: str = ""
|
||||
constraints: str = ""
|
||||
desc: str = ""
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}({self.profile})"
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
|
@ -81,43 +91,87 @@ class RoleContext(BaseModel):
|
|||
env: 'Environment' = Field(default=None)
|
||||
memory: Memory = Field(default_factory=Memory)
|
||||
long_term_memory: LongTermMemory = Field(default_factory=LongTermMemory)
|
||||
state: int = Field(default=-1) # -1 indicates initial or termination state where todo is None
|
||||
state: int = Field(default=0)
|
||||
todo: Action = Field(default=None)
|
||||
watch: set[Type[Action]] = Field(default_factory=set)
|
||||
news: list[Type[Message]] = Field(default=[])
|
||||
react_mode: RoleReactMode = RoleReactMode.REACT # see `Role._set_react_mode` for definitions of the following two attributes
|
||||
max_react_loop: int = 1
|
||||
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
def check(self, role_id: str):
|
||||
if hasattr(CONFIG, "long_term_memory") and CONFIG.long_term_memory:
|
||||
self.long_term_memory.recover_memory(role_id, self)
|
||||
self.memory = self.long_term_memory # use memory to act as long_term_memory for unify operation
|
||||
|
||||
|
||||
@property
|
||||
def important_memory(self) -> list[Message]:
|
||||
"""Get the information corresponding to the watched actions"""
|
||||
return self.memory.get_by_actions(self.watch)
|
||||
|
||||
|
||||
@property
|
||||
def history(self) -> list[Message]:
|
||||
return self.memory.get()
|
||||
|
||||
|
||||
class Role:
|
||||
class Role(BaseModel):
|
||||
"""Role/Agent"""
|
||||
|
||||
def __init__(self, name="", profile="", goal="", constraints="", desc="", is_human=False):
|
||||
self._llm = LLM() if not is_human else HumanProvider()
|
||||
self._setting = RoleSetting(name=name, profile=profile, goal=goal,
|
||||
constraints=constraints, desc=desc, is_human=is_human)
|
||||
self._states = []
|
||||
self._actions = []
|
||||
self._role_id = str(self._setting)
|
||||
self._rc = RoleContext()
|
||||
self._recovered = False
|
||||
name: str = ""
|
||||
profile: str = ""
|
||||
goal: str = ""
|
||||
constraints: str = ""
|
||||
desc: str = ""
|
||||
_setting: RoleSetting = Field(default_factory=RoleSetting, alias="_setting")
|
||||
_setting = RoleSetting(name=name, profile=profile, goal=goal, constraints=constraints)
|
||||
_role_id: str = ""
|
||||
_states: list = Field(default=[])
|
||||
_actions: list = Field(default=[])
|
||||
_actions_type: list = Field(default=[])
|
||||
_rc: RoleContext = RoleContext()
|
||||
|
||||
_private_attributes = {
|
||||
"_setting': _setting,
|
||||
"_role_id': _role_id,
|
||||
'_states': [],
|
||||
'_actions': [],
|
||||
'_actions_type': [] # 用于记录和序列化
|
||||
}
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
# 关于私有变量的初始化 https://github.com/pydantic/pydantic/issues/655
|
||||
for key in self._private_attributes.keys():
|
||||
if key in kwargs:
|
||||
object.__setattr__(self, key, kwargs[key])
|
||||
if key =="_setting":
|
||||
_setting = RoleSetting(**kwargs[key])
|
||||
object.__setattr__(self, '_setting', _setting)
|
||||
elif key == "_rc":
|
||||
_rc = RoleContext
|
||||
object.__setattr__(self, '_rc', _rc)
|
||||
else:
|
||||
object.__setattr__(self, key, self._private_attributes[key])
|
||||
|
||||
def _reset(self):
|
||||
object.__setattr__(self, '_states', [])
|
||||
object.__setattr__(self, '_actions', [])
|
||||
|
||||
@staticmethod
|
||||
def _process_class(class_str, module_name):
|
||||
cleaned_string = re.sub(r"[<>']", "", class_str).replace("class ", "")
|
||||
package_name = "metagpt"
|
||||
file_name = cleaned_string.replace(package_name, "").replace("." + module_name, "")
|
||||
print(file_name)
|
||||
# print("\n", sys.modules)
|
||||
module_file = import_module(file_name, package=package_name)
|
||||
module = getattr(module_file, module_name)
|
||||
return module
|
||||
|
||||
def serialize(self, stg_path: Path):
|
||||
role_info_path = stg_path.joinpath("role_info.json")
|
||||
|
|
@ -216,6 +270,7 @@ class Role:
|
|||
self._reset()
|
||||
for idx, action in enumerate(actions):
|
||||
if not isinstance(action, Action):
|
||||
## 默认初始化
|
||||
i = action("", llm=self._llm)
|
||||
else:
|
||||
if self._setting.is_human and not isinstance(action.llm, HumanProvider):
|
||||
|
|
@ -225,6 +280,8 @@ class Role:
|
|||
i.set_prefix(self._get_prefix(), self.profile)
|
||||
self._actions.append(i)
|
||||
self._states.append(f"{idx}. {action}")
|
||||
action_title = action.schema()["title"]
|
||||
self._actions_type.append(action_title)
|
||||
|
||||
def set_react_mode(self, react_mode: RoleReactMode, max_react_loop: int = 1):
|
||||
self._set_react_mode(react_mode, max_react_loop)
|
||||
|
|
@ -267,11 +324,11 @@ class Role:
|
|||
self._rc.state = state
|
||||
logger.debug(self._actions)
|
||||
self._rc.todo = self._actions[self._rc.state] if state >= 0 else None
|
||||
|
||||
|
||||
def set_env(self, env: 'Environment'):
|
||||
"""Set the environment in which the role works. The role can talk to the environment and can also receive messages by observing."""
|
||||
self._rc.env = env
|
||||
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._setting.name
|
||||
|
|
@ -280,13 +337,13 @@ class Role:
|
|||
def profile(self):
|
||||
"""Get the role description (position)"""
|
||||
return self._setting.profile
|
||||
|
||||
|
||||
def _get_prefix(self):
|
||||
"""Get the role prefix"""
|
||||
if self._setting.desc:
|
||||
return self._setting.desc
|
||||
return PREFIX_TEMPLATE.format(**self._setting.dict())
|
||||
|
||||
|
||||
async def _think(self) -> None:
|
||||
"""Think about what to do and decide on the next action"""
|
||||
if len(self._actions) == 1:
|
||||
|
|
@ -301,7 +358,6 @@ class Role:
|
|||
prompt = self._get_prefix()
|
||||
prompt += STATE_TEMPLATE.format(history=self._rc.history, states="\n".join(self._states),
|
||||
n_states=len(self._states) - 1, previous_state=self._rc.state)
|
||||
# print(prompt)
|
||||
next_state = await self._llm.aask(prompt)
|
||||
logger.debug(f"{prompt=}")
|
||||
if (not next_state.isdigit() and next_state != "-1") \
|
||||
|
|
@ -313,50 +369,46 @@ class Role:
|
|||
if next_state == -1:
|
||||
logger.info(f"End actions with {next_state=}")
|
||||
self._set_state(next_state)
|
||||
|
||||
|
||||
async def _act(self) -> Message:
|
||||
# prompt = self.get_prefix()
|
||||
# prompt += ROLE_TEMPLATE.format(name=self.profile, state=self.states[self.state], result=response,
|
||||
# history=self.history)
|
||||
|
||||
logger.info(f"{self._setting}: ready to {self._rc.todo}")
|
||||
response = await self._rc.todo.run(self._rc.important_memory)
|
||||
# logger.info(response)
|
||||
if isinstance(response, ActionOutput):
|
||||
msg = Message(content=response.content, instruct_content=response.instruct_content,
|
||||
role=self.profile, cause_by=type(self._rc.todo))
|
||||
role=self.profile, cause_by=type(self._rc.todo))
|
||||
else:
|
||||
msg = Message(content=response, role=self.profile, cause_by=type(self._rc.todo))
|
||||
self._rc.memory.add(msg)
|
||||
# logger.debug(f"{response}")
|
||||
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
async def _observe(self) -> int:
|
||||
"""Observe from the environment, obtain important information, and add it to memory"""
|
||||
if not self._rc.env:
|
||||
return 0
|
||||
env_msgs = self._rc.env.memory.get()
|
||||
|
||||
|
||||
observed = self._rc.env.memory.get_by_actions(self._rc.watch)
|
||||
|
||||
self._rc.news = self._rc.memory.find_news(observed) # find news (previously unseen messages) from observed messages
|
||||
|
||||
|
||||
for i in env_msgs:
|
||||
self.recv(i)
|
||||
|
||||
|
||||
news_text = [f"{i.role}: {i.content[:20]}..." for i in self._rc.news]
|
||||
if news_text:
|
||||
logger.debug(f'{self._setting} observed: {news_text}')
|
||||
return len(self._rc.news)
|
||||
|
||||
|
||||
def _publish_message(self, msg):
|
||||
"""If the role belongs to env, then the role's messages will be broadcast to env"""
|
||||
if not self._rc.env:
|
||||
# If env does not exist, do not publish the message
|
||||
return
|
||||
self._rc.env.publish_message(msg)
|
||||
|
||||
|
||||
async def _react(self) -> Message:
|
||||
"""Think first, then act, until the Role _think it is time to stop and requires no more todo.
|
||||
This is the standard think-act loop in the ReAct paper, which alternates thinking and acting in task solving, i.e. _think -> _act -> _think -> _act -> ...
|
||||
|
|
@ -406,18 +458,18 @@ class Role:
|
|||
if message in self._rc.memory.get():
|
||||
return
|
||||
self._rc.memory.add(message)
|
||||
|
||||
|
||||
async def handle(self, message: Message) -> Message:
|
||||
"""Receive information and reply with actions"""
|
||||
# logger.debug(f"{self.name=}, {self.profile=}, {message.role=}")
|
||||
self.recv(message)
|
||||
|
||||
|
||||
return await self._react()
|
||||
|
||||
|
||||
def get_memories(self, k=0) -> list[Message]:
|
||||
"""A wrapper to return the most recent k memories of this role, return all when k=0"""
|
||||
return self._rc.memory.get(k=k)
|
||||
|
||||
|
||||
async def run(self, message=None):
|
||||
"""Observe, and think and act based on the results of the observation"""
|
||||
if message:
|
||||
|
|
@ -431,7 +483,7 @@ class Role:
|
|||
# If there is no new information, suspend and wait
|
||||
logger.debug(f"{self._setting}: no news. waiting.")
|
||||
return
|
||||
|
||||
|
||||
rsp = await self.react()
|
||||
# Publish the reply to the environment, waiting for the next subscriber to process
|
||||
self._publish_message(rsp)
|
||||
|
|
|
|||
4
tests/metagpt/serialize_deserialize/__init__.py
Normal file
4
tests/metagpt/serialize_deserialize/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 11/22/2023 11:48 AM
|
||||
# @Author : stellahong (stellahong@fuzhi.ai)
|
||||
# @Desc :
|
||||
24
tests/metagpt/serialize_deserialize/test_actions.py
Normal file
24
tests/metagpt/serialize_deserialize/test_actions.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# -*- 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
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 11/26/2023 2:04 PM
|
||||
# @Author : stellahong (stellahong@fuzhi.ai)
|
||||
# @Desc :
|
||||
import pytest
|
||||
|
||||
from metagpt.roles.architect import Architect
|
||||
from metagpt.actions.action import Action
|
||||
|
||||
def test_architect_serialize():
|
||||
role = Architect()
|
||||
ser_role_dict = role.dict(by_alias=True)
|
||||
assert "name" in ser_role_dict
|
||||
assert "_states" in ser_role_dict
|
||||
assert "_actions" in ser_role_dict
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_architect_deserialize():
|
||||
role = Architect()
|
||||
ser_role_dict = role.dict(by_alias=True)
|
||||
new_role = Architect(**ser_role_dict)
|
||||
# new_role = Architect.deserialize(ser_role_dict)
|
||||
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")
|
||||
21
tests/metagpt/serialize_deserialize/test_product_manager.py
Normal file
21
tests/metagpt/serialize_deserialize/test_product_manager.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 11/26/2023 2:07 PM
|
||||
# @Author : stellahong (stellahong@fuzhi.ai)
|
||||
# @Desc :
|
||||
import pytest
|
||||
|
||||
from metagpt.roles.product_manager import ProductManager
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.schema import Message
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_product_manager_deserialize():
|
||||
role = ProductManager()
|
||||
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")])
|
||||
26
tests/metagpt/serialize_deserialize/test_project_manager.py
Normal file
26
tests/metagpt/serialize_deserialize/test_project_manager.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 11/26/2023 2:06 PM
|
||||
# @Author : stellahong (stellahong@fuzhi.ai)
|
||||
# @Desc :
|
||||
import pytest
|
||||
|
||||
from metagpt.roles.project_manager import ProjectManager
|
||||
from metagpt.actions.action import Action
|
||||
|
||||
def test_project_manager_serialize():
|
||||
role = ProjectManager()
|
||||
ser_role_dict = role.dict(by_alias=True)
|
||||
assert "name" in ser_role_dict
|
||||
assert "_states" in ser_role_dict
|
||||
assert "_actions" in ser_role_dict
|
||||
|
||||
@pytest.mark.asyncio
|
||||
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")
|
||||
41
tests/metagpt/serialize_deserialize/test_role.py
Normal file
41
tests/metagpt/serialize_deserialize/test_role.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 11/23/2023 4:49 PM
|
||||
# @Author : stellahong (stellahong@fuzhi.ai)
|
||||
# @Desc :
|
||||
import pytest
|
||||
|
||||
from metagpt.roles.role import Role
|
||||
from metagpt.roles.engineer import Engineer
|
||||
|
||||
from metagpt.actions.action import Action
|
||||
|
||||
|
||||
def test_role_serialize():
|
||||
role = Role()
|
||||
ser_role_dict = role.dict(by_alias=True)
|
||||
assert "name" in ser_role_dict
|
||||
assert "_states" in ser_role_dict
|
||||
assert "_actions" in ser_role_dict
|
||||
|
||||
|
||||
def test_engineer_serialize():
|
||||
role = Engineer()
|
||||
ser_role_dict = role.dict(by_alias=True)
|
||||
assert "name" in ser_role_dict
|
||||
assert "_states" in ser_role_dict
|
||||
assert "_actions" in ser_role_dict
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
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 == 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")
|
||||
47
tests/metagpt/serialize_deserialize/test_team.py
Normal file
47
tests/metagpt/serialize_deserialize/test_team.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 11/27/2023 10:07 AM
|
||||
# @Author : stellahong (stellahong@fuzhi.ai)
|
||||
# @Desc :
|
||||
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
|
||||
|
||||
|
||||
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_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(
|
||||
[
|
||||
ProductManager(),
|
||||
Architect(),
|
||||
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
|
||||
28
tests/metagpt/serialize_deserialize/test_wrire_prd.py
Normal file
28
tests/metagpt/serialize_deserialize/test_wrire_prd.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 11/22/2023 1:47 PM
|
||||
# @Author : stellahong (stellahong@fuzhi.ai)
|
||||
# @Desc :
|
||||
import pytest
|
||||
|
||||
from metagpt.actions import WritePRD
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.schema import Message
|
||||
|
||||
|
||||
def test_action_serialize():
|
||||
action = WritePRD()
|
||||
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 = WritePRD()
|
||||
serialized_data = action.dict()
|
||||
new_action = WritePRD(**serialized_data)
|
||||
# new_action = WritePRD().deserialize(serialized_data)
|
||||
assert new_action.name == ""
|
||||
assert new_action.llm == LLM()
|
||||
assert len(await new_action.run([Message(content="write a cli snake game")]))>0
|
||||
|
||||
42
tests/metagpt/serialize_deserialize/test_write_code.py
Normal file
42
tests/metagpt/serialize_deserialize/test_write_code.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 11/23/2023 10:56 AM
|
||||
# @Author : stellahong (stellahong@fuzhi.ai)
|
||||
# @Desc :
|
||||
import pytest
|
||||
|
||||
from metagpt.actions import WriteCode, WriteCodeReview
|
||||
from metagpt.llm import LLM
|
||||
|
||||
def test_write_design_serialize():
|
||||
action = WriteCode()
|
||||
ser_action_dict = action.dict()
|
||||
assert ser_action_dict["name"] == "WriteCode"
|
||||
assert "llm" in ser_action_dict
|
||||
|
||||
def test_write_task_serialize():
|
||||
action = WriteCodeReview()
|
||||
ser_action_dict = action.dict()
|
||||
assert ser_action_dict["name"] == "WriteCodeReview"
|
||||
assert "llm" in ser_action_dict
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_code_deserialize():
|
||||
action = WriteCode()
|
||||
serialized_data = action.dict()
|
||||
new_action = WriteCode(**serialized_data)
|
||||
# new_action = WriteCode().deserialize(serialized_data)
|
||||
assert new_action.name == "WriteCode"
|
||||
assert new_action.llm == LLM()
|
||||
await new_action.run(context="write a cli snake game", filename="test_code")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_code_review_deserialize():
|
||||
action = WriteCodeReview()
|
||||
serialized_data = action.dict()
|
||||
new_action = WriteCodeReview(**serialized_data)
|
||||
# new_action = WriteCodeReview().deserialize(serialized_data)
|
||||
code = await WriteCode().run(context="write a cli snake game", filename="test_code")
|
||||
|
||||
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")
|
||||
39
tests/metagpt/serialize_deserialize/test_write_design.py
Normal file
39
tests/metagpt/serialize_deserialize/test_write_design.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 11/22/2023 8:19 PM
|
||||
# @Author : stellahong (stellahong@fuzhi.ai)
|
||||
# @Desc :
|
||||
import pytest
|
||||
|
||||
from metagpt.actions import WriteDesign, WriteTasks
|
||||
from metagpt.llm import LLM
|
||||
|
||||
def test_write_design_serialize():
|
||||
action = WriteDesign()
|
||||
ser_action_dict = action.dict()
|
||||
assert "name" in ser_action_dict
|
||||
assert "llm" in ser_action_dict
|
||||
|
||||
def test_write_task_serialize():
|
||||
action = WriteTasks()
|
||||
ser_action_dict = action.dict()
|
||||
assert "name" in ser_action_dict
|
||||
assert "llm" in ser_action_dict
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_design_deserialize():
|
||||
action = WriteDesign()
|
||||
serialized_data = action.dict()
|
||||
new_action = WriteDesign().deserialize(serialized_data)
|
||||
assert new_action.name == ""
|
||||
assert new_action.llm == LLM()
|
||||
await new_action.run(context="write a cli snake game")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_task_deserialize():
|
||||
action = WriteTasks()
|
||||
serialized_data = action.dict()
|
||||
new_action = WriteTasks(**serialized_data)
|
||||
# new_action = WriteTasks().deserialize(serialized_data)
|
||||
assert new_action.name == "CreateTasks"
|
||||
assert new_action.llm == LLM()
|
||||
await new_action.run(context="write a cli snake game")
|
||||
Loading…
Add table
Add a link
Reference in a new issue