Merge branch 'new_main' of github.com:better629/MetaGPT into new_main

This commit is contained in:
better629 2023-12-21 00:18:39 +08:00
commit 86b167da93
7 changed files with 134 additions and 12 deletions

View file

@ -4,6 +4,9 @@
@Time : 2023/12/11 18:45
@Author : alexanderwu
@File : action_node.py
NOTE: You should use typing.List instead of list to do type annotation. Because in the markdown extraction process,
we can use typing to extract the type of the node, but we cannot use built-in list to extract.
"""
import json
from typing import Any, Dict, Generic, List, Optional, Tuple, Type, TypeVar
@ -41,10 +44,10 @@ Fill in the above nodes based on the format example.
"""
def dict_to_markdown(d, prefix="-", postfix="\n"):
def dict_to_markdown(d, prefix="##", kv_sep="\n", postfix="\n"):
markdown_str = ""
for key, value in d.items():
markdown_str += f"{prefix} {key}: {value}{postfix}"
markdown_str += f"{prefix}{key}{kv_sep}{value}{postfix}"
return markdown_str

View file

@ -5,6 +5,8 @@
@Author : alexanderwu
@File : design_api_an.py
"""
from typing import List
from metagpt.actions.action_node import ActionNode
from metagpt.logs import logger
from metagpt.utils.mermaid import MMC1, MMC2
@ -22,7 +24,7 @@ PROJECT_NAME = ActionNode(
FILE_LIST = ActionNode(
key="File list",
expected_type=list[str],
expected_type=List[str],
instruction="Only need relative paths. ALWAYS write a main.py or app.py here",
example=["main.py", "game.py"],
)

View file

@ -5,26 +5,28 @@
@Author : alexanderwu
@File : project_management_an.py
"""
from typing import List
from metagpt.actions.action_node import ActionNode
from metagpt.logs import logger
REQUIRED_PYTHON_PACKAGES = ActionNode(
key="Required Python packages",
expected_type=list[str],
expected_type=List[str],
instruction="Provide required Python packages in requirements.txt format.",
example=["flask==1.1.2", "bcrypt==3.2.0"],
)
REQUIRED_OTHER_LANGUAGE_PACKAGES = ActionNode(
key="Required Other language third-party packages",
expected_type=list[str],
expected_type=List[str],
instruction="List down the required packages for languages other than Python.",
example=["No third-party dependencies required"],
)
LOGIC_ANALYSIS = ActionNode(
key="Logic Analysis",
expected_type=list[list[str]],
expected_type=List[List[str]],
instruction="Provide a list of files with the classes/methods/functions to be implemented, "
"including dependency analysis and imports.",
example=[
@ -35,7 +37,7 @@ LOGIC_ANALYSIS = ActionNode(
TASK_LIST = ActionNode(
key="Task list",
expected_type=list[str],
expected_type=List[str],
instruction="Break down the tasks into a list of filenames, prioritized by dependency order.",
example=["game.py", "main.py"],
)

View file

@ -5,6 +5,7 @@
@Author : alexanderwu
@File : write_prd_an.py
"""
from typing import List
from metagpt.actions.action_node import ActionNode
from metagpt.logs import logger
@ -39,14 +40,14 @@ PROJECT_NAME = ActionNode(
PRODUCT_GOALS = ActionNode(
key="Product Goals",
expected_type=list[str],
expected_type=List[str],
instruction="Provide up to three clear, orthogonal product goals.",
example=["Create an engaging user experience", "Improve accessibility, be responsive", "More beautiful UI"],
)
USER_STORIES = ActionNode(
key="User Stories",
expected_type=list[str],
expected_type=List[str],
instruction="Provide up to 3 to 5 scenario-based user stories.",
example=[
"As a player, I want to be able to choose difficulty levels",
@ -59,7 +60,7 @@ USER_STORIES = ActionNode(
COMPETITIVE_ANALYSIS = ActionNode(
key="Competitive Analysis",
expected_type=list[str],
expected_type=List[str],
instruction="Provide 5 to 7 competitive products.",
example=[
"2048 Game A: Simple interface, lacks responsive features",
@ -98,7 +99,7 @@ REQUIREMENT_ANALYSIS = ActionNode(
REQUIREMENT_POOL = ActionNode(
key="Requirement Pool",
expected_type=list[list[str]],
expected_type=List[List[str]],
instruction="List down the top-5 requirements with their priority (P0, P1, P2).",
example=[["P0", "The main code ..."], ["P0", "The game algorithm ..."]],
)

View file

@ -0,0 +1,38 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Author : alexanderwu
@File : write_review.py
"""
from typing import List
from metagpt.actions import Action
from metagpt.actions.action_node import ActionNode
REVIEW = ActionNode(
key="Review",
expected_type=List[str],
instruction="Act as an experienced Reviewer and review the given output. Ask a series of critical questions, "
"concisely and clearly, to help the writer improve their work.",
example=[
"This is a good PRD, but I think it can be improved by adding more details.",
],
)
LGTM = ActionNode(
key="LGTM",
expected_type=str,
instruction="LGTM/LBTM. If the output is good enough, give a LGTM (Looks Good To Me) to the writer, "
"else LBTM (Looks Bad To Me).",
example="LGTM",
)
WRITE_REVIEW_NODE = ActionNode.from_children("WRITE_REVIEW_NODE", [REVIEW, LGTM])
class WriteReview(Action):
"""This class allows LLM to further mine noteworthy details based on specific "##TOPIC"(discussion topic) and
"##RECORD" (discussion records), thereby deepening the discussion."""
async def run(self, context):
return await WRITE_REVIEW_NODE.fill(context=context, llm=self.llm, schema="markdown")

View file

@ -23,7 +23,7 @@ import traceback
import typing
from pathlib import Path
from typing import Any
from typing import List, Tuple, Union
from typing import List, Tuple, Union, get_args, get_origin
import aiofiles
import loguru
@ -135,8 +135,31 @@ class OutputParser:
parsed_data[block] = content
return parsed_data
@staticmethod
def extract_content(text, tag="CONTENT"):
# Use regular expression to extract content between [CONTENT] and [/CONTENT]
extracted_content = re.search(rf"\[{tag}\](.*?)\[/{tag}\]", text, re.DOTALL)
if extracted_content:
return extracted_content.group(1).strip()
else:
return "No content found between [CONTENT] and [/CONTENT] tags."
@staticmethod
def is_supported_list_type(i):
origin = get_origin(i)
if origin is not List:
return False
args = get_args(i)
if args == (str,) or args == (Tuple[str, str],) or args == (List[str],):
return True
return False
@classmethod
def parse_data_with_mapping(cls, data, mapping):
data = cls.extract_content(text=data)
block_dict = cls.parse_blocks(data)
parsed_data = {}
for block, content in block_dict.items():

View file

@ -0,0 +1,53 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2023/12/20 15:01
@Author : alexanderwu
@File : test_write_review.py
"""
import pytest
from metagpt.actions.write_review import WriteReview
CONTEXT = """
{
"Language": "zh_cn",
"Programming Language": "Python",
"Original Requirements": "写一个简单的2048",
"Project Name": "game_2048",
"Product Goals": [
"创建一个引人入胜的用户体验",
"确保高性能",
"提供可定制的功能"
],
"User Stories": [
"作为用户,我希望能够选择不同的难度级别",
"作为玩家,我希望在每局游戏结束后能看到我的得分"
],
"Competitive Analysis": [
"Python Snake Game: 界面简单,缺乏高级功能"
],
"Competitive Quadrant Chart": "quadrantChart\n title \"Reach and engagement of campaigns\"\n x-axis \"Low Reach\" --> \"High Reach\"\n y-axis \"Low Engagement\" --> \"High Engagement\"\n quadrant-1 \"我们应该扩展\"\n quadrant-2 \"需要推广\"\n quadrant-3 \"重新评估\"\n quadrant-4 \"可能需要改进\"\n \"Campaign A\": [0.3, 0.6]\n \"Campaign B\": [0.45, 0.23]\n \"Campaign C\": [0.57, 0.69]\n \"Campaign D\": [0.78, 0.34]\n \"Campaign E\": [0.40, 0.34]\n \"Campaign F\": [0.35, 0.78]\n \"Our Target Product\": [0.5, 0.6]",
"Requirement Analysis": "产品应该用户友好。",
"Requirement Pool": [
[
"P0",
"主要代码..."
],
[
"P0",
"游戏算法..."
]
],
"UI Design draft": "基本功能描述,简单的风格和布局。",
"Anything UNCLEAR": "..."
}
"""
@pytest.mark.asyncio
async def test_write_review():
write_review = WriteReview()
review = await write_review.run(CONTEXT)
assert review.instruct_content
assert review.get("LGTM") in ["LGTM", "LBTM"]