Merge branch 'feature/explit_io' into 'mgx_ops'

feat: Implemenet of RFC236 #3

See merge request pub/MetaGPT!110
This commit is contained in:
林义章 2024-06-05 06:47:30 +00:00
commit fccbc9d9da
34 changed files with 1060 additions and 432 deletions

View file

@ -303,5 +303,4 @@ def test_action_node_from_pydantic_and_print_everything():
if __name__ == "__main__":
test_create_model_class()
test_create_model_class_with_mapping()
pytest.main([__file__, "-s"])

View file

@ -6,37 +6,104 @@
@File : test_design_api.py
@Modifiled By: mashenquan, 2023-12-6. According to RFC 135
"""
from pathlib import Path
import pytest
from metagpt.actions.design_api import WriteDesign
from metagpt.llm import LLM
from metagpt.const import DEFAULT_WORKSPACE_ROOT, METAGPT_ROOT
from metagpt.logs import logger
from metagpt.schema import Message
from metagpt.schema import AIMessage, Message
from metagpt.utils.project_repo import ProjectRepo
from tests.data.incremental_dev_project.mock import DESIGN_SAMPLE, REFINED_PRD_JSON
@pytest.mark.asyncio
async def test_design_api(context):
inputs = ["我们需要一个音乐播放器,它应该有播放、暂停、上一曲、下一曲等功能。"] # PRD_SAMPLE
for prd in inputs:
await context.repo.docs.prd.save(filename="new_prd.txt", content=prd)
async def test_design(context):
# Mock new design env
prd = "我们需要一个音乐播放器,它应该有播放、暂停、上一曲、下一曲等功能。"
context.kwargs.project_path = context.config.project_path
context.kwargs.inc = False
filename = "prd.txt"
repo = ProjectRepo(context.kwargs.project_path)
await repo.docs.prd.save(filename=filename, content=prd)
kvs = {
"project_path": str(context.kwargs.project_path),
"changed_prd_filenames": [str(repo.docs.prd.workdir / filename)],
}
instruct_content = AIMessage.create_instruct_value(kvs=kvs, class_name="WritePRDOutput")
design_api = WriteDesign(context=context)
result = await design_api.run(Message(content=prd, instruct_content=None))
logger.info(result)
assert result
@pytest.mark.asyncio
async def test_refined_design_api(context):
await context.repo.docs.prd.save(filename="1.txt", content=str(REFINED_PRD_JSON))
await context.repo.docs.system_design.save(filename="1.txt", content=DESIGN_SAMPLE)
design_api = WriteDesign(context=context, llm=LLM())
result = await design_api.run(Message(content="", instruct_content=None))
design_api = WriteDesign(context=context)
result = await design_api.run([Message(content=prd, instruct_content=instruct_content)])
logger.info(result)
assert result
assert isinstance(result, AIMessage)
assert result.instruct_content
assert repo.docs.system_design.changed_files
# Mock incremental design env
context.kwargs.inc = True
await repo.docs.prd.save(filename=filename, content=str(REFINED_PRD_JSON))
await repo.docs.system_design.save(filename=filename, content=DESIGN_SAMPLE)
result = await design_api.run([Message(content="", instruct_content=instruct_content)])
logger.info(result)
assert result
assert isinstance(result, AIMessage)
assert result.instruct_content
assert repo.docs.system_design.changed_files
@pytest.mark.parametrize(
("user_requirement", "prd_filename", "legacy_design_filename"),
[
("我们需要一个音乐播放器,它应该有播放、暂停、上一曲、下一曲等功能。", None, None),
("write 2048 game", str(METAGPT_ROOT / "tests/data/prd.json"), None),
(
"write 2048 game",
str(METAGPT_ROOT / "tests/data/prd.json"),
str(METAGPT_ROOT / "tests/data/system_design.json"),
),
],
)
@pytest.mark.asyncio
async def test_design_api(context, user_requirement, prd_filename, legacy_design_filename):
action = WriteDesign()
result = await action.run(
user_requirement=user_requirement, prd_filename=prd_filename, legacy_design_filename=legacy_design_filename
)
assert isinstance(result, AIMessage)
assert result.content
assert str(DEFAULT_WORKSPACE_ROOT) in result.content
@pytest.mark.parametrize(
("user_requirement", "prd_filename", "legacy_design_filename"),
[
("我们需要一个音乐播放器,它应该有播放、暂停、上一曲、下一曲等功能。", None, None),
("write 2048 game", str(METAGPT_ROOT / "tests/data/prd.json"), None),
(
"write 2048 game",
str(METAGPT_ROOT / "tests/data/prd.json"),
str(METAGPT_ROOT / "tests/data/system_design.json"),
),
],
)
@pytest.mark.asyncio
async def test_design_api_dir(context, user_requirement, prd_filename, legacy_design_filename):
action = WriteDesign()
result = await action.run(
user_requirement=user_requirement,
prd_filename=prd_filename,
legacy_design_filename=legacy_design_filename,
output_pathname=str(Path(context.config.project_path) / "1.txt"),
)
assert isinstance(result, AIMessage)
assert result.content
assert str(context.config.project_path) in result.content
assert result.instruct_content
assert result.instruct_content.changed_system_design_filenames
if __name__ == "__main__":
pytest.main([__file__, "-s"])

View file

@ -5,13 +5,15 @@
@Author : alexanderwu
@File : test_project_management.py
"""
import json
import pytest
from metagpt.actions.project_management import WriteTasks
from metagpt.llm import LLM
from metagpt.const import METAGPT_ROOT
from metagpt.logs import logger
from metagpt.schema import Message
from metagpt.schema import AIMessage, Message
from metagpt.utils.project_repo import ProjectRepo
from tests.data.incremental_dev_project.mock import (
REFINED_DESIGN_JSON,
REFINED_PRD_JSON,
@ -22,29 +24,46 @@ from tests.metagpt.actions.mock_json import DESIGN, PRD
@pytest.mark.asyncio
async def test_task(context):
await context.repo.docs.prd.save("1.txt", content=str(PRD))
await context.repo.docs.system_design.save("1.txt", content=str(DESIGN))
logger.info(context.git_repo)
# Mock write tasks env
context.kwargs.project_path = context.config.project_path
context.kwargs.inc = False
repo = ProjectRepo(context.kwargs.project_path)
filename = "1.txt"
await repo.docs.prd.save(filename=filename, content=str(PRD))
await repo.docs.system_design.save(filename=filename, content=str(DESIGN))
kvs = {
"project_path": context.kwargs.project_path,
"changed_system_design_filenames": [str(repo.docs.system_design.workdir / filename)],
}
instruct_content = AIMessage.create_instruct_value(kvs=kvs, class_name="WriteDesignOutput")
action = WriteTasks(context=context)
result = await action.run(Message(content="", instruct_content=None))
result = await action.run([Message(content="", instruct_content=instruct_content)])
logger.info(result)
assert result
assert result.instruct_content.changed_task_filenames
# Mock incremental env
context.kwargs.inc = True
await repo.docs.prd.save(filename=filename, content=str(REFINED_PRD_JSON))
await repo.docs.system_design.save(filename=filename, content=str(REFINED_DESIGN_JSON))
await repo.docs.task.save(filename=filename, content=TASK_SAMPLE)
result = await action.run([Message(content="", instruct_content=instruct_content)])
logger.info(result)
assert result
assert result.instruct_content.changed_task_filenames
@pytest.mark.asyncio
async def test_refined_task(context):
await context.repo.docs.prd.save("2.txt", content=str(REFINED_PRD_JSON))
await context.repo.docs.system_design.save("2.txt", content=str(REFINED_DESIGN_JSON))
await context.repo.docs.task.save("2.txt", content=TASK_SAMPLE)
logger.info(context.git_repo)
action = WriteTasks(context=context, llm=LLM())
result = await action.run(Message(content="", instruct_content=None))
logger.info(result)
async def test_task_api(context):
action = WriteTasks()
result = await action.run(design_filename=str(METAGPT_ROOT / "tests/data/system_design.json"))
assert result
assert result.content
m = json.loads(result.content)
assert m
if __name__ == "__main__":
pytest.main([__file__, "-s"])

View file

@ -26,12 +26,7 @@ from tests.metagpt.actions.mock_markdown import TASKS_2, WRITE_CODE_PROMPT_SAMPL
def setup_inc_workdir(context, inc: bool = False):
"""setup incremental workdir for testing"""
context.src_workspace = context.git_repo.workdir / "src"
if inc:
context.config.inc = inc
context.repo.old_workspace = context.repo.git_repo.workdir / "old"
context.config.project_path = "old"
context.config.inc = inc
return context

View file

@ -6,25 +6,26 @@
@File : test_write_prd.py
@Modified By: mashenquan, 2023-11-1. According to Chapter 2.2.1 and 2.2.2 of RFC 116, replace `handle` with `run`.
"""
import uuid
from pathlib import Path
import pytest
from metagpt.actions import UserRequirement, WritePRD
from metagpt.const import REQUIREMENT_FILENAME
from metagpt.const import DEFAULT_WORKSPACE_ROOT, REQUIREMENT_FILENAME
from metagpt.logs import logger
from metagpt.roles.product_manager import ProductManager
from metagpt.roles.role import RoleReactMode
from metagpt.schema import Message
from metagpt.schema import AIMessage, Message
from metagpt.utils.common import any_to_str
from tests.data.incremental_dev_project.mock import NEW_REQUIREMENT_SAMPLE, PRD_SAMPLE
from tests.metagpt.actions.test_write_code import setup_inc_workdir
from metagpt.utils.project_repo import ProjectRepo
from tests.data.incremental_dev_project.mock import NEW_REQUIREMENT_SAMPLE
@pytest.mark.asyncio
async def test_write_prd(new_filename, context):
product_manager = ProductManager(context=context)
requirements = "开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结"
await context.repo.docs.save(filename=REQUIREMENT_FILENAME, content=requirements)
product_manager.rc.react_mode = RoleReactMode.BY_ORDER
prd = await product_manager.run(Message(content=requirements, cause_by=UserRequirement))
assert prd.cause_by == any_to_str(WritePRD)
@ -34,38 +35,39 @@ async def test_write_prd(new_filename, context):
# Assert the prd is not None or empty
assert prd is not None
assert prd.content != ""
assert product_manager.context.repo.docs.prd.changed_files
repo = ProjectRepo(context.kwargs.project_path)
assert repo.docs.prd.changed_files
repo.git_repo.archive()
@pytest.mark.asyncio
async def test_write_prd_inc(new_filename, context, git_dir):
context = setup_inc_workdir(context, inc=True)
await context.repo.docs.prd.save("1.txt", PRD_SAMPLE)
await context.repo.docs.save(filename=REQUIREMENT_FILENAME, content=NEW_REQUIREMENT_SAMPLE)
# Mock incremental requirement
context.config.inc = True
context.config.project_path = context.kwargs.project_path
repo = ProjectRepo(context.config.project_path)
await repo.docs.save(filename=REQUIREMENT_FILENAME, content=NEW_REQUIREMENT_SAMPLE)
action = WritePRD(context=context)
prd = await action.run(Message(content=NEW_REQUIREMENT_SAMPLE, instruct_content=None))
prd = await action.run([Message(content=NEW_REQUIREMENT_SAMPLE, instruct_content=None)])
logger.info(NEW_REQUIREMENT_SAMPLE)
logger.info(prd)
# Assert the prd is not None or empty
assert prd is not None
assert prd.content != ""
assert "Refined Requirements" in prd.content
assert repo.git_repo.changed_files
@pytest.mark.asyncio
async def test_fix_debug(new_filename, context, git_dir):
context.src_workspace = context.git_repo.workdir / context.git_repo.workdir.name
# Mock legacy project
context.kwargs.project_path = str(git_dir)
repo = ProjectRepo(context.kwargs.project_path)
repo.with_src_path(git_dir.name)
await repo.srcs.save(filename="main.py", content='if __name__ == "__main__":\nmain()')
requirements = "ValueError: undefined variable `st`."
await repo.docs.save(filename=REQUIREMENT_FILENAME, content=requirements)
await context.repo.with_src_path(context.src_workspace).srcs.save(
filename="main.py", content='if __name__ == "__main__":\nmain()'
)
requirements = "Please fix the bug in the code."
await context.repo.docs.save(filename=REQUIREMENT_FILENAME, content=requirements)
action = WritePRD(context=context)
prd = await action.run(Message(content=requirements, instruct_content=None))
prd = await action.run([Message(content=requirements, instruct_content=None)])
logger.info(prd)
# Assert the prd is not None or empty
@ -73,5 +75,40 @@ async def test_fix_debug(new_filename, context, git_dir):
assert prd.content != ""
@pytest.mark.asyncio
async def test_write_prd_api(context):
action = WritePRD()
result = await action.run(user_requirement="write a snake game.")
assert isinstance(result, AIMessage)
assert result.content
assert str(DEFAULT_WORKSPACE_ROOT) in result.content
result = await action.run(
user_requirement="write a snake game.",
output_pathname=str(Path(context.config.project_path) / f"{uuid.uuid4().hex}.json"),
)
assert isinstance(result, AIMessage)
assert result.content
assert result.instruct_content
assert str(context.config.project_path) in result.content
legacy_prd_filename = result.instruct_content.changed_prd_filenames[-1]
result = await action.run(user_requirement="Add moving enemy.", legacy_prd_filename=legacy_prd_filename)
assert isinstance(result, AIMessage)
assert result.content
assert str(DEFAULT_WORKSPACE_ROOT) in result.content
result = await action.run(
user_requirement="Add moving enemy.",
output_pathname=str(Path(context.config.project_path) / f"{uuid.uuid4().hex}.json"),
legacy_prd_filename=legacy_prd_filename,
)
assert isinstance(result, AIMessage)
assert result.content
assert result.instruct_content
assert str(context.config.project_path) in result.content
if __name__ == "__main__":
pytest.main([__file__, "-s"])

View file

@ -392,5 +392,11 @@ async def test_parse_resources(context, content: str, key_descriptions):
assert k in result
@pytest.mark.parametrize(("name", "value"), [("c1", {"age": 10, "name": "Alice"}), ("", {"path": __file__})])
def test_create_instruct_value(name, value):
obj = Message.create_instruct_value(kvs=value, class_name=name)
assert obj.model_dump() == value
if __name__ == "__main__":
pytest.main([__file__, "-s"])