feat: merge geekan:main

This commit is contained in:
莘权 马 2024-03-05 16:03:57 +08:00
commit 8e85dd3bc6
84 changed files with 654 additions and 367 deletions

View file

@ -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

View file

@ -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 = """

File diff suppressed because one or more lines are too long

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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,
)

View file

@ -1,6 +1,6 @@
import pytest
from metagpt.actions.mi.write_plan import (
from metagpt.actions.di.write_plan import (
Plan,
Task,
WritePlan,

View file

@ -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": [

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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"])

View file

@ -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

View file

@ -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"])

View file

@ -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"])

View file

@ -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"
)

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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():

View file

@ -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

View 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)