mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-11 15:15:18 +02:00
feat: merge geekan:main
This commit is contained in:
commit
8e85dd3bc6
84 changed files with 654 additions and 367 deletions
|
|
@ -12,6 +12,7 @@ import logging
|
|||
import os
|
||||
import re
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
import aiohttp.web
|
||||
|
|
@ -270,3 +271,11 @@ def mermaid_mocker(aiohttp_mocker, mermaid_rsp_cache):
|
|||
aiohttp_mocker.rsp_cache = mermaid_rsp_cache
|
||||
aiohttp_mocker.check_funcs = check_funcs
|
||||
yield check_funcs
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def git_dir():
|
||||
"""Fixture to get the unittest directory."""
|
||||
git_dir = Path(__file__).parent / f"unittest/{uuid.uuid4().hex}"
|
||||
git_dir.mkdir(parents=True, exist_ok=True)
|
||||
return git_dir
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -149,7 +149,7 @@ sequenceDiagram
|
|||
|
||||
The requirement analysis suggests the need for a clean and intuitive interface. Since we are using a command-line interface, we need to ensure that the text-based UI is as user-friendly as possible. Further clarification on whether a graphical user interface (GUI) is expected in the future would be helpful for planning the extendability of the game."""
|
||||
|
||||
TASKS_SAMPLE = """
|
||||
TASK_SAMPLE = """
|
||||
## Required Python packages
|
||||
|
||||
- random==2.2.1
|
||||
|
|
@ -345,7 +345,7 @@ REFINED_DESIGN_JSON = {
|
|||
"Anything UNCLEAR": "",
|
||||
}
|
||||
|
||||
REFINED_TASKS_JSON = {
|
||||
REFINED_TASK_JSON = {
|
||||
"Required Python packages": ["random==2.2.1", "Tkinter==8.6"],
|
||||
"Required Other language third-party packages": ["No third-party dependencies required"],
|
||||
"Refined Logic Analysis": [
|
||||
|
|
@ -373,7 +373,14 @@ REFINED_TASKS_JSON = {
|
|||
}
|
||||
|
||||
CODE_PLAN_AND_CHANGE_SAMPLE = {
|
||||
"Code Plan And Change": '\n1. Plan for gui.py: Develop the GUI using Tkinter to replace the command-line interface. Start by setting up the main window and event handling. Then, add widgets for displaying the game status, results, and feedback. Implement interactive elements for difficulty selection and visualize the guess history. Finally, create animations for guess feedback and ensure responsiveness across different screen sizes.\n```python\nclass GUI:\n- pass\n+ def __init__(self):\n+ self.setup_window()\n+\n+ def setup_window(self):\n+ # Initialize the main window using Tkinter\n+ pass\n+\n+ def bind_events(self):\n+ # Bind button clicks and other events\n+ pass\n+\n+ def update_feedback(self, message: str):\n+ # Update the feedback label with the given message\n+ pass\n+\n+ def update_attempts(self, attempts: int):\n+ # Update the attempts label with the number of attempts\n+ pass\n+\n+ def update_history(self, history: list):\n+ # Update the history view with the list of past guesses\n+ pass\n+\n+ def show_difficulty_selector(self):\n+ # Show buttons or a dropdown for difficulty selection\n+ pass\n+\n+ def animate_guess_result(self, correct: bool):\n+ # Trigger an animation for correct or incorrect guesses\n+ pass\n```\n\n2. Plan for main.py: Modify the main.py to initialize the GUI and start the event-driven game loop. Ensure that the GUI is the primary interface for user interaction.\n```python\nclass Main:\n def main(self):\n- user_interface = UI()\n- user_interface.start()\n+ graphical_user_interface = GUI()\n+ graphical_user_interface.setup_window()\n+ graphical_user_interface.bind_events()\n+ # Start the Tkinter main loop\n+ pass\n\n if __name__ == "__main__":\n main_instance = Main()\n main_instance.main()\n```\n\n3. Plan for ui.py: Refactor ui.py to work with the new GUI class. Remove command-line interactions and delegate display and input tasks to the GUI.\n```python\nclass UI:\n- def display_message(self, message: str):\n- print(message)\n+\n+ def display_message(self, message: str):\n+ # This method will now pass the message to the GUI to display\n+ pass\n\n- def get_user_input(self, prompt: str) -> str:\n- return input(prompt)\n+\n+ def get_user_input(self, prompt: str) -> str:\n+ # This method will now trigger the GUI to get user input\n+ pass\n\n- def show_attempts(self, attempts: int):\n- print(f"Number of attempts: {attempts}")\n+\n+ def show_attempts(self, attempts: int):\n+ # This method will now update the GUI with the number of attempts\n+ pass\n\n- def show_history(self, history: list):\n- print("Guess history:")\n- for guess in history:\n- print(guess)\n+\n+ def show_history(self, history: list):\n+ # This method will now update the GUI with the guess history\n+ pass\n```\n\n4. Plan for game.py: Ensure game.py remains mostly unchanged as it contains the core game logic. However, make minor adjustments if necessary to integrate with the new GUI.\n```python\nclass Game:\n # No changes required for now\n```\n'
|
||||
"Development Plan": [
|
||||
"Develop the GUI using Tkinter to replace the command-line interface. Start by setting up the main window and event handling. Then, add widgets for displaying the game status, results, and feedback. Implement interactive elements for difficulty selection and visualize the guess history. Finally, create animations for guess feedback and ensure responsiveness across different screen sizes.",
|
||||
"Modify the main.py to initialize the GUI and start the event-driven game loop. Ensure that the GUI is the primary interface for user interaction.",
|
||||
],
|
||||
"Incremental Change": [
|
||||
"""```diff\nclass GUI:\n- pass\n+ def __init__(self):\n+ self.setup_window()\n+\n+ def setup_window(self):\n+ # Initialize the main window using Tkinter\n+ pass\n+\n+ def bind_events(self):\n+ # Bind button clicks and other events\n+ pass\n+\n+ def update_feedback(self, message: str):\n+ # Update the feedback label with the given message\n+ pass\n+\n+ def update_attempts(self, attempts: int):\n+ # Update the attempts label with the number of attempts\n+ pass\n+\n+ def update_history(self, history: list):\n+ # Update the history view with the list of past guesses\n+ pass\n+\n+ def show_difficulty_selector(self):\n+ # Show buttons or a dropdown for difficulty selection\n+ pass\n+\n+ def animate_guess_result(self, correct: bool):\n+ # Trigger an animation for correct or incorrect guesses\n+ pass\n```""",
|
||||
"""```diff\nclass Main:\n def main(self):\n- user_interface = UI()\n- user_interface.start()\n+ graphical_user_interface = GUI()\n+ graphical_user_interface.setup_window()\n+ graphical_user_interface.bind_events()\n+ # Start the Tkinter main loop\n+ pass\n\n if __name__ == "__main__":\n main_instance = Main()\n main_instance.main()\n```\n\n3. Plan for ui.py: Refactor ui.py to work with the new GUI class. Remove command-line interactions and delegate display and input tasks to the GUI.\n```python\nclass UI:\n- def display_message(self, message: str):\n- print(message)\n+\n+ def display_message(self, message: str):\n+ # This method will now pass the message to the GUI to display\n+ pass\n\n- def get_user_input(self, prompt: str) -> str:\n- return input(prompt)\n+\n+ def get_user_input(self, prompt: str) -> str:\n+ # This method will now trigger the GUI to get user input\n+ pass\n\n- def show_attempts(self, attempts: int):\n- print(f"Number of attempts: {attempts}")\n+\n+ def show_attempts(self, attempts: int):\n+ # This method will now update the GUI with the number of attempts\n+ pass\n\n- def show_history(self, history: list):\n- print("Guess history:")\n- for guess in history:\n- print(guess)\n+\n+ def show_history(self, history: list):\n+ # This method will now update the GUI with the guess history\n+ pass\n```\n\n4. Plan for game.py: Ensure game.py remains mostly unchanged as it contains the core game logic. However, make minor adjustments if necessary to integrate with the new GUI.\n```python\nclass Game:\n # No changes required for now\n```\n""",
|
||||
],
|
||||
}
|
||||
|
||||
REFINED_CODE_INPUT_SAMPLE = """
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.mi.ask_review import AskReview
|
||||
from metagpt.actions.di.ask_review import AskReview
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions.mi.debug_code import DebugCode
|
||||
from metagpt.actions.di.debug_code import DebugCode
|
||||
from metagpt.schema import Message
|
||||
|
||||
ErrorStr = """Tested passed:
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.mi.execute_nb_code import ExecuteNbCode, truncate
|
||||
from metagpt.actions.di.execute_nb_code import ExecuteNbCode, truncate
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.mi.ml_action import WriteCodeWithToolsML
|
||||
from metagpt.actions.di.ml_action import WriteCodeWithToolsML
|
||||
from metagpt.schema import Plan, Task
|
||||
|
||||
|
||||
|
|
@ -2,8 +2,8 @@ import asyncio
|
|||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions.mi.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.actions.mi.write_analysis_code import (
|
||||
from metagpt.actions.di.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.actions.di.write_analysis_code import (
|
||||
WriteCodeWithoutTools,
|
||||
WriteCodeWithTools,
|
||||
)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.mi.write_plan import (
|
||||
from metagpt.actions.di.write_plan import (
|
||||
Plan,
|
||||
Task,
|
||||
WritePlan,
|
||||
|
|
@ -37,7 +37,7 @@ DESIGN = {
|
|||
}
|
||||
|
||||
|
||||
TASKS = {
|
||||
TASK = {
|
||||
"Required Python packages": ["pygame==2.0.1"],
|
||||
"Required Other language third-party packages": ["No third-party dependencies required"],
|
||||
"Logic Analysis": [
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.design_api import WriteDesign
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import Message
|
||||
from tests.data.incremental_dev_project.mock import DESIGN_SAMPLE, REFINED_PRD_JSON
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -25,3 +27,16 @@ async def test_design_api(context):
|
|||
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))
|
||||
logger.info(result)
|
||||
|
||||
assert result
|
||||
|
|
|
|||
|
|
@ -9,13 +9,19 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.project_management import WriteTasks
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import Message
|
||||
from tests.data.incremental_dev_project.mock import (
|
||||
REFINED_DESIGN_JSON,
|
||||
REFINED_PRD_JSON,
|
||||
TASK_SAMPLE,
|
||||
)
|
||||
from tests.metagpt.actions.mock_json import DESIGN, PRD
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_design_api(context):
|
||||
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)
|
||||
|
|
@ -26,3 +32,19 @@ async def test_design_api(context):
|
|||
logger.info(result)
|
||||
|
||||
assert result
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
assert result
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@ from openai._models import BaseModel
|
|||
|
||||
from metagpt.actions.action_node import ActionNode, dict_to_markdown
|
||||
from metagpt.actions.project_management import NEW_REQ_TEMPLATE
|
||||
from metagpt.actions.project_management_an import REFINED_PM_NODE
|
||||
from metagpt.actions.project_management_an import PM_NODE, REFINED_PM_NODE
|
||||
from metagpt.llm import LLM
|
||||
from tests.data.incremental_dev_project.mock import (
|
||||
REFINED_DESIGN_JSON,
|
||||
REFINED_TASKS_JSON,
|
||||
TASKS_SAMPLE,
|
||||
REFINED_TASK_JSON,
|
||||
TASK_SAMPLE,
|
||||
)
|
||||
from tests.metagpt.actions.mock_json import TASK
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
|
|
@ -24,20 +25,40 @@ def llm():
|
|||
return LLM()
|
||||
|
||||
|
||||
def mock_refined_tasks_json():
|
||||
return REFINED_TASKS_JSON
|
||||
def mock_refined_task_json():
|
||||
return REFINED_TASK_JSON
|
||||
|
||||
|
||||
def mock_task_json():
|
||||
return TASK
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_project_management_an(mocker):
|
||||
root = ActionNode.from_children(
|
||||
"ProjectManagement", [ActionNode(key="", expected_type=str, instruction="", example="")]
|
||||
)
|
||||
root.instruct_content = BaseModel()
|
||||
root.instruct_content.model_dump = mock_task_json
|
||||
mocker.patch("metagpt.actions.project_management_an.PM_NODE.fill", return_value=root)
|
||||
|
||||
node = await PM_NODE.fill(dict_to_markdown(REFINED_DESIGN_JSON), llm)
|
||||
|
||||
assert "Logic Analysis" in node.instruct_content.model_dump()
|
||||
assert "Task list" in node.instruct_content.model_dump()
|
||||
assert "Shared Knowledge" in node.instruct_content.model_dump()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_project_management_an_inc(mocker):
|
||||
root = ActionNode.from_children(
|
||||
"RefinedProjectManagement", [ActionNode(key="", expected_type=str, instruction="", example="")]
|
||||
)
|
||||
root.instruct_content = BaseModel()
|
||||
root.instruct_content.model_dump = mock_refined_tasks_json
|
||||
root.instruct_content.model_dump = mock_refined_task_json
|
||||
mocker.patch("metagpt.actions.project_management_an.REFINED_PM_NODE.fill", return_value=root)
|
||||
|
||||
prompt = NEW_REQ_TEMPLATE.format(old_task=TASKS_SAMPLE, context=dict_to_markdown(REFINED_DESIGN_JSON))
|
||||
prompt = NEW_REQ_TEMPLATE.format(old_task=TASK_SAMPLE, context=dict_to_markdown(REFINED_DESIGN_JSON))
|
||||
node = await REFINED_PM_NODE.fill(prompt, llm)
|
||||
|
||||
assert "Refined Logic Analysis" in node.instruct_content.model_dump()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
@File : test_write_code.py
|
||||
@Modifiled By: mashenquan, 2023-12-6. According to RFC 135
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
|
@ -14,10 +14,27 @@ import pytest
|
|||
from metagpt.actions.write_code import WriteCode
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import CodingContext, Document
|
||||
from metagpt.utils.common import aread
|
||||
from metagpt.utils.common import CodeParser, aread
|
||||
from tests.data.incremental_dev_project.mock import (
|
||||
CODE_PLAN_AND_CHANGE_SAMPLE,
|
||||
REFINED_CODE_INPUT_SAMPLE,
|
||||
REFINED_DESIGN_JSON,
|
||||
REFINED_TASK_JSON,
|
||||
)
|
||||
from tests.metagpt.actions.mock_markdown import TASKS_2, WRITE_CODE_PROMPT_SAMPLE
|
||||
|
||||
|
||||
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"
|
||||
|
||||
return context
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_code(context):
|
||||
# Prerequisites
|
||||
|
|
@ -81,5 +98,66 @@ async def test_write_code_deps(context):
|
|||
assert rsp.code_doc.content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_refined_code(context, git_dir):
|
||||
# Prerequisites
|
||||
context = setup_inc_workdir(context, inc=True)
|
||||
await context.repo.docs.system_design.save(filename="1.json", content=json.dumps(REFINED_DESIGN_JSON))
|
||||
await context.repo.docs.task.save(filename="1.json", content=json.dumps(REFINED_TASK_JSON))
|
||||
await context.repo.docs.code_plan_and_change.save(
|
||||
filename="1.json", content=json.dumps(CODE_PLAN_AND_CHANGE_SAMPLE)
|
||||
)
|
||||
|
||||
# old_workspace contains the legacy code
|
||||
await context.repo.with_src_path(context.repo.old_workspace).srcs.save(
|
||||
filename="game.py", content=CodeParser.parse_code(block="", text=REFINED_CODE_INPUT_SAMPLE)
|
||||
)
|
||||
|
||||
ccontext = CodingContext(
|
||||
filename="game.py",
|
||||
design_doc=await context.repo.docs.system_design.get(filename="1.json"),
|
||||
task_doc=await context.repo.docs.task.get(filename="1.json"),
|
||||
code_plan_and_change_doc=await context.repo.docs.code_plan_and_change.get(filename="1.json"),
|
||||
code_doc=Document(filename="game.py", content="", root_path="src"),
|
||||
)
|
||||
coding_doc = Document(root_path="src", filename="game.py", content=ccontext.json())
|
||||
|
||||
action = WriteCode(i_context=coding_doc, context=context)
|
||||
rsp = await action.run()
|
||||
assert rsp
|
||||
assert rsp.code_doc.content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_codes(context):
|
||||
# Prerequisites
|
||||
context = setup_inc_workdir(context, inc=True)
|
||||
for filename in ["game.py", "ui.py"]:
|
||||
await context.repo.with_src_path(context.src_workspace).srcs.save(
|
||||
filename=filename, content=f"# {filename}\nnew code ..."
|
||||
)
|
||||
await context.repo.with_src_path(context.repo.old_workspace).srcs.save(
|
||||
filename=filename, content=f"# {filename}\nlegacy code ..."
|
||||
)
|
||||
|
||||
await context.repo.with_src_path(context.repo.old_workspace).srcs.save(
|
||||
filename="gui.py", content="# gui.py\nlegacy code ..."
|
||||
)
|
||||
await context.repo.with_src_path(context.repo.old_workspace).srcs.save(
|
||||
filename="main.py", content='# main.py\nif __name__ == "__main__":\n main()'
|
||||
)
|
||||
task_doc = Document(filename="1.json", content=json.dumps(REFINED_TASK_JSON))
|
||||
|
||||
context.repo = context.repo.with_src_path(context.src_workspace)
|
||||
# Ready to write gui.py
|
||||
codes = await WriteCode.get_codes(task_doc=task_doc, exclude="gui.py", project_repo=context.repo)
|
||||
codes_inc = await WriteCode.get_codes(task_doc=task_doc, exclude="gui.py", project_repo=context.repo, use_inc=True)
|
||||
|
||||
logger.info(codes)
|
||||
logger.info(codes_inc)
|
||||
assert codes
|
||||
assert codes_inc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
@Author : mannaandpoem
|
||||
@File : test_write_code_plan_and_change_an.py
|
||||
"""
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from openai._models import BaseModel
|
||||
|
||||
|
|
@ -14,15 +16,21 @@ from metagpt.actions.write_code_plan_and_change_an import (
|
|||
REFINED_TEMPLATE,
|
||||
WriteCodePlanAndChange,
|
||||
)
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import CodePlanAndChangeContext
|
||||
from metagpt.utils.common import CodeParser
|
||||
from tests.data.incremental_dev_project.mock import (
|
||||
CODE_PLAN_AND_CHANGE_SAMPLE,
|
||||
DESIGN_SAMPLE,
|
||||
NEW_REQUIREMENT_SAMPLE,
|
||||
REFINED_CODE_INPUT_SAMPLE,
|
||||
REFINED_CODE_SAMPLE,
|
||||
TASKS_SAMPLE,
|
||||
REFINED_DESIGN_JSON,
|
||||
REFINED_PRD_JSON,
|
||||
REFINED_TASK_JSON,
|
||||
TASK_SAMPLE,
|
||||
)
|
||||
from tests.metagpt.actions.test_write_code import setup_inc_workdir
|
||||
|
||||
|
||||
def mock_code_plan_and_change():
|
||||
|
|
@ -30,27 +38,35 @@ def mock_code_plan_and_change():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_code_plan_and_change_an(mocker):
|
||||
async def test_write_code_plan_and_change_an(mocker, context, git_dir):
|
||||
context = setup_inc_workdir(context, inc=True)
|
||||
await context.repo.docs.prd.save(filename="2.json", content=json.dumps(REFINED_PRD_JSON))
|
||||
await context.repo.docs.system_design.save(filename="2.json", content=json.dumps(REFINED_DESIGN_JSON))
|
||||
await context.repo.docs.task.save(filename="2.json", content=json.dumps(REFINED_TASK_JSON))
|
||||
|
||||
await context.repo.with_src_path(context.repo.old_workspace).srcs.save(
|
||||
filename="game.py", content=CodeParser.parse_code(block="", text=REFINED_CODE_INPUT_SAMPLE)
|
||||
)
|
||||
|
||||
root = ActionNode.from_children(
|
||||
"WriteCodePlanAndChange", [ActionNode(key="", expected_type=str, instruction="", example="")]
|
||||
)
|
||||
root.instruct_content = BaseModel()
|
||||
root.instruct_content.model_dump = mock_code_plan_and_change
|
||||
mocker.patch("metagpt.actions.write_code_plan_and_change_an.WriteCodePlanAndChange.run", return_value=root)
|
||||
|
||||
requirement = "New requirement"
|
||||
prd_filename = "prd.md"
|
||||
design_filename = "design.md"
|
||||
task_filename = "task.md"
|
||||
code_plan_and_change_context = CodePlanAndChangeContext(
|
||||
requirement=requirement,
|
||||
prd_filename=prd_filename,
|
||||
design_filename=design_filename,
|
||||
task_filename=task_filename,
|
||||
mocker.patch(
|
||||
"metagpt.actions.write_code_plan_and_change_an.WRITE_CODE_PLAN_AND_CHANGE_NODE.fill", return_value=root
|
||||
)
|
||||
node = await WriteCodePlanAndChange(i_context=code_plan_and_change_context).run()
|
||||
|
||||
assert "Code Plan And Change" in node.instruct_content.model_dump()
|
||||
code_plan_and_change_context = CodePlanAndChangeContext(
|
||||
requirement="New requirement",
|
||||
prd_filename="2.json",
|
||||
design_filename="2.json",
|
||||
task_filename="2.json",
|
||||
)
|
||||
node = await WriteCodePlanAndChange(i_context=code_plan_and_change_context, context=context).run()
|
||||
|
||||
assert "Development Plan" in node.instruct_content.model_dump()
|
||||
assert "Incremental Change" in node.instruct_content.model_dump()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -60,7 +76,7 @@ async def test_refine_code(mocker):
|
|||
user_requirement=NEW_REQUIREMENT_SAMPLE,
|
||||
code_plan_and_change=CODE_PLAN_AND_CHANGE_SAMPLE,
|
||||
design=DESIGN_SAMPLE,
|
||||
task=TASKS_SAMPLE,
|
||||
task=TASK_SAMPLE,
|
||||
code=REFINED_CODE_INPUT_SAMPLE,
|
||||
logs="",
|
||||
feedback="",
|
||||
|
|
@ -69,3 +85,25 @@ async def test_refine_code(mocker):
|
|||
)
|
||||
code = await WriteCode().write_code(prompt=prompt)
|
||||
assert "def" in code
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_old_code(context, git_dir):
|
||||
context = setup_inc_workdir(context, inc=True)
|
||||
await context.repo.with_src_path(context.repo.old_workspace).srcs.save(
|
||||
filename="game.py", content=REFINED_CODE_INPUT_SAMPLE
|
||||
)
|
||||
|
||||
code_plan_and_change_context = CodePlanAndChangeContext(
|
||||
requirement="New requirement",
|
||||
prd_filename="1.json",
|
||||
design_filename="1.json",
|
||||
task_filename="1.json",
|
||||
)
|
||||
action = WriteCodePlanAndChange(context=context, i_context=code_plan_and_change_context)
|
||||
|
||||
old_codes = await action.get_old_codes()
|
||||
logger.info(old_codes)
|
||||
|
||||
assert "def" in old_codes
|
||||
assert "class" in old_codes
|
||||
|
|
|
|||
|
|
@ -32,5 +32,28 @@ def add(a, b):
|
|||
print(f"输出内容: {captured.out}")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_code_review_inc(capfd, context):
|
||||
context.src_workspace = context.repo.workdir / "srcs"
|
||||
context.config.inc = True
|
||||
code = """
|
||||
def add(a, b):
|
||||
return a +
|
||||
"""
|
||||
code_plan_and_change = """
|
||||
def add(a, b):
|
||||
- return a +
|
||||
+ return a + b
|
||||
"""
|
||||
coding_context = CodingContext(
|
||||
filename="math.py",
|
||||
design_doc=Document(content="编写一个从a加b的函数,返回a+b"),
|
||||
code_doc=Document(content=code),
|
||||
code_plan_and_change_doc=Document(content=code_plan_and_change),
|
||||
)
|
||||
|
||||
await WriteCodeReview(i_context=coding_context, context=context).run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
@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 pytest
|
||||
|
||||
from metagpt.actions import UserRequirement, WritePRD
|
||||
|
|
@ -15,6 +16,8 @@ from metagpt.roles.product_manager import ProductManager
|
|||
from metagpt.roles.role import RoleReactMode
|
||||
from metagpt.schema import 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
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -34,5 +37,41 @@ async def test_write_prd(new_filename, context):
|
|||
assert product_manager.context.repo.docs.prd.changed_files
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
action = WritePRD(context=context)
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
|
||||
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))
|
||||
logger.info(prd)
|
||||
|
||||
# Assert the prd is not None or empty
|
||||
assert prd is not None
|
||||
assert prd.content != ""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -56,3 +56,7 @@ mock_llm_config_spark = LLMConfig(
|
|||
mock_llm_config_qianfan = LLMConfig(api_type="qianfan", access_key="xxx", secret_key="xxx", model="ERNIE-Bot-turbo")
|
||||
|
||||
mock_llm_config_dashscope = LLMConfig(api_type="dashscope", api_key="xxx", model="qwen-max")
|
||||
|
||||
mock_llm_config_anthropic = LLMConfig(
|
||||
api_type="anthropic", api_key="xxx", base_url="https://api.anthropic.com", model="claude-3-opus-20240229"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@
|
|||
# @Desc : default request & response data for provider unittest
|
||||
|
||||
|
||||
from anthropic.types import (
|
||||
ContentBlock,
|
||||
ContentBlockDeltaEvent,
|
||||
Message,
|
||||
MessageStartEvent,
|
||||
TextDelta,
|
||||
)
|
||||
from anthropic.types import Usage as AnthropicUsage
|
||||
from dashscope.api_entities.dashscope_response import (
|
||||
DashScopeAPIResponse,
|
||||
GenerationOutput,
|
||||
|
|
@ -130,6 +138,38 @@ def get_dashscope_response(name: str) -> GenerationResponse:
|
|||
)
|
||||
|
||||
|
||||
# For Anthropic
|
||||
def get_anthropic_response(name: str, stream: bool = False) -> Message:
|
||||
if stream:
|
||||
return [
|
||||
MessageStartEvent(
|
||||
message=Message(
|
||||
id="xxx",
|
||||
model=name,
|
||||
role="assistant",
|
||||
type="message",
|
||||
content=[ContentBlock(text="", type="text")],
|
||||
usage=AnthropicUsage(input_tokens=10, output_tokens=10),
|
||||
),
|
||||
type="message_start",
|
||||
),
|
||||
ContentBlockDeltaEvent(
|
||||
index=0,
|
||||
delta=TextDelta(text=resp_cont_tmpl.format(name=name), type="text_delta"),
|
||||
type="content_block_delta",
|
||||
),
|
||||
]
|
||||
else:
|
||||
return Message(
|
||||
id="xxx",
|
||||
model=name,
|
||||
role="assistant",
|
||||
type="message",
|
||||
content=[ContentBlock(text=resp_cont_tmpl.format(name=name), type="text")],
|
||||
usage=AnthropicUsage(input_tokens=10, output_tokens=10),
|
||||
)
|
||||
|
||||
|
||||
# For llm general chat functions call
|
||||
async def llm_general_chat_funcs_test(llm: BaseLLM, prompt: str, messages: list[dict], resp_cont: str):
|
||||
resp = await llm.aask(prompt, stream=False)
|
||||
|
|
|
|||
|
|
@ -2,31 +2,45 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Desc : the unittest of Claude2
|
||||
|
||||
|
||||
import pytest
|
||||
from anthropic.resources.completions import Completion
|
||||
|
||||
from metagpt.provider.anthropic_api import Claude2
|
||||
from tests.metagpt.provider.mock_llm_config import mock_llm_config
|
||||
from tests.metagpt.provider.req_resp_const import prompt, resp_cont_tmpl
|
||||
from metagpt.provider.anthropic_api import AnthropicLLM
|
||||
from tests.metagpt.provider.mock_llm_config import mock_llm_config_anthropic
|
||||
from tests.metagpt.provider.req_resp_const import (
|
||||
get_anthropic_response,
|
||||
llm_general_chat_funcs_test,
|
||||
messages,
|
||||
prompt,
|
||||
resp_cont_tmpl,
|
||||
)
|
||||
|
||||
resp_cont = resp_cont_tmpl.format(name="Claude")
|
||||
name = "claude-3-opus-20240229"
|
||||
resp_cont = resp_cont_tmpl.format(name=name)
|
||||
|
||||
|
||||
def mock_anthropic_completions_create(self, model: str, prompt: str, max_tokens_to_sample: int) -> Completion:
|
||||
return Completion(id="xx", completion=resp_cont, model="claude-2", stop_reason="stop_sequence", type="completion")
|
||||
async def mock_anthropic_messages_create(
|
||||
self, messages: list[dict], model: str, stream: bool = True, max_tokens: int = None, system: str = None
|
||||
) -> Completion:
|
||||
if stream:
|
||||
|
||||
async def aresp_iterator():
|
||||
resps = get_anthropic_response(name, stream=True)
|
||||
for resp in resps:
|
||||
yield resp
|
||||
|
||||
async def mock_anthropic_acompletions_create(self, model: str, prompt: str, max_tokens_to_sample: int) -> Completion:
|
||||
return Completion(id="xx", completion=resp_cont, model="claude-2", stop_reason="stop_sequence", type="completion")
|
||||
|
||||
|
||||
def test_claude2_ask(mocker):
|
||||
mocker.patch("anthropic.resources.completions.Completions.create", mock_anthropic_completions_create)
|
||||
assert resp_cont == Claude2(mock_llm_config).ask(prompt)
|
||||
return aresp_iterator()
|
||||
else:
|
||||
return get_anthropic_response(name)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_claude2_aask(mocker):
|
||||
mocker.patch("anthropic.resources.completions.AsyncCompletions.create", mock_anthropic_acompletions_create)
|
||||
assert resp_cont == await Claude2(mock_llm_config).aask(prompt)
|
||||
async def test_anthropic_acompletion(mocker):
|
||||
mocker.patch("anthropic.resources.messages.AsyncMessages.create", mock_anthropic_messages_create)
|
||||
|
||||
anthropic_llm = AnthropicLLM(mock_llm_config_anthropic)
|
||||
|
||||
resp = await anthropic_llm.acompletion(messages)
|
||||
assert resp.content[0].text == resp_cont
|
||||
|
||||
await llm_general_chat_funcs_test(anthropic_llm, prompt, messages, resp_cont)
|
||||
|
|
|
|||
|
|
@ -27,9 +27,15 @@ class MockBaseLLM(BaseLLM):
|
|||
def completion(self, messages: list[dict], timeout=3):
|
||||
return get_part_chat_completion(name)
|
||||
|
||||
async def _achat_completion(self, messages: list[dict], timeout=3):
|
||||
pass
|
||||
|
||||
async def acompletion(self, messages: list[dict], timeout=3):
|
||||
return get_part_chat_completion(name)
|
||||
|
||||
async def _achat_completion_stream(self, messages: list[dict], timeout: int = 3) -> str:
|
||||
pass
|
||||
|
||||
async def acompletion_text(self, messages: list[dict], stream=False, timeout=3) -> str:
|
||||
return default_resp_cont
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.logs import logger
|
||||
from metagpt.roles.mi.interpreter import Interpreter
|
||||
from metagpt.roles.di.data_interpreter import DataInterpreter
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("auto_run", [(True), (False)])
|
||||
async def test_interpreter(mocker, auto_run):
|
||||
mocker.patch("metagpt.actions.mi.execute_nb_code.ExecuteNbCode.run", return_value=("a successful run", True))
|
||||
mocker.patch("metagpt.actions.di.execute_nb_code.ExecuteNbCode.run", return_value=("a successful run", True))
|
||||
mocker.patch("builtins.input", return_value="confirm")
|
||||
|
||||
requirement = "Run data analysis on sklearn Iris dataset, include a plot"
|
||||
tools = []
|
||||
|
||||
mi = Interpreter(auto_run=auto_run, use_tools=True, tools=tools)
|
||||
rsp = await mi.run(requirement)
|
||||
di = DataInterpreter(auto_run=auto_run, use_tools=True, tools=tools)
|
||||
rsp = await di.run(requirement)
|
||||
logger.info(rsp)
|
||||
assert len(rsp.content) > 0
|
||||
|
||||
finished_tasks = mi.planner.plan.get_finished_tasks()
|
||||
finished_tasks = di.planner.plan.get_finished_tasks()
|
||||
assert len(finished_tasks) > 0
|
||||
assert len(finished_tasks[0].code) > 0 # check one task to see if code is recorded
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.actions.mi.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.actions.di.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.logs import logger
|
||||
from metagpt.roles.mi.ml_engineer import MLEngineer
|
||||
from metagpt.roles.di.ml_engineer import MLEngineer
|
||||
from metagpt.schema import Message, Plan, Task
|
||||
from metagpt.tools.tool_type import ToolType
|
||||
from tests.metagpt.actions.mi.test_debug_code import CODE, DebugContext, ErrorStr
|
||||
from tests.metagpt.actions.di.test_debug_code import CODE, DebugContext, ErrorStr
|
||||
|
||||
|
||||
def test_mle_init():
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
import nbformat
|
||||
import pytest
|
||||
|
||||
from metagpt.actions.mi.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.actions.di.execute_nb_code import ExecuteNbCode
|
||||
from metagpt.utils.common import read_json_file
|
||||
from metagpt.utils.save_code import DATA_PATH, save_code_file
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from typing import Optional, Union
|
|||
|
||||
from metagpt.config2 import config
|
||||
from metagpt.configs.llm_config import LLMType
|
||||
from metagpt.logs import log_llm_stream, logger
|
||||
from metagpt.logs import logger
|
||||
from metagpt.provider.azure_openai_api import AzureOpenAILLM
|
||||
from metagpt.provider.openai_api import OpenAILLM
|
||||
from metagpt.schema import Message
|
||||
|
|
@ -24,17 +24,8 @@ class MockLLM(OriginalLLM):
|
|||
async def acompletion_text(self, messages: list[dict], stream=False, timeout=3) -> str:
|
||||
"""Overwrite original acompletion_text to cancel retry"""
|
||||
if stream:
|
||||
resp = self._achat_completion_stream(messages, timeout=timeout)
|
||||
|
||||
collected_messages = []
|
||||
async for i in resp:
|
||||
log_llm_stream(i)
|
||||
collected_messages.append(i)
|
||||
|
||||
full_reply_content = "".join(collected_messages)
|
||||
usage = self._calc_usage(messages, full_reply_content)
|
||||
self._update_costs(usage)
|
||||
return full_reply_content
|
||||
resp = await self._achat_completion_stream(messages, timeout=timeout)
|
||||
return resp
|
||||
|
||||
rsp = await self._achat_completion(messages, timeout=timeout)
|
||||
return self.get_choice_text(rsp)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue