diff --git a/README.md b/README.md index 4e2c3280d..72ce03412 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,9 @@ # MetaGPT: The Multi-Agent Framework

## News -🚀 Feb. 08, 2024: [v0.7.0](https://github.com/geekan/MetaGPT/releases/tag/v0.7.0) released, supporting assigning different LLMs to different Roles. We also introduced [Interpreter](https://github.com/geekan/MetaGPT/blob/main/examples/mi/README.md), a powerful agent capable of solving a wide range of real-world problems. +🚀 March. 01, 2024: Our Data Interpreter paper is on arxiv. Find all design and benchmark details [here](https://arxiv.org/abs/2402.18679)! + +🚀 Feb. 08, 2024: [v0.7.0](https://github.com/geekan/MetaGPT/releases/tag/v0.7.0) released, supporting assigning different LLMs to different Roles. We also introduced [Data Interpreter](https://github.com/geekan/MetaGPT/blob/main/examples/di/README.md), a powerful agent capable of solving a wide range of real-world problems. 🚀 Jan. 16, 2024: Our paper [MetaGPT: Meta Programming for A Multi-Agent Collaborative Framework ](https://arxiv.org/abs/2308.00352) accepted for oral presentation **(top 1.2%)** at ICLR 2024, **ranking #1** in the LLM-based Agent category. diff --git a/examples/di/README.md b/examples/di/README.md new file mode 100644 index 000000000..fb4e8044b --- /dev/null +++ b/examples/di/README.md @@ -0,0 +1,18 @@ +# Data Interpreter (DI) + +## What is Data Interpreter +Data Interpreter is an agent who solves problems through codes. It understands user requirements, makes plans, writes codes for execution, and uses tools if necessary. These capabilities enable it to tackle a wide range of scenarios, please check out the examples below. + +## Example List +- Data visualization +- Machine learning modeling +- Image background removal +- Solve math problems +- Receipt OCR +- Tool usage: web page imitation +- Tool usage: web crawling +- Tool usage: text2image +- Tool usage: email summarization and response +- More on the way! + +Please see [here](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/interpreter/intro.html) for detailed explanation. \ No newline at end of file diff --git a/examples/mi/crawl_webpage.py b/examples/di/crawl_webpage.py similarity index 78% rename from examples/mi/crawl_webpage.py rename to examples/di/crawl_webpage.py index b5d2fb3d0..f06b85d9b 100644 --- a/examples/mi/crawl_webpage.py +++ b/examples/di/crawl_webpage.py @@ -5,15 +5,15 @@ @File : crawl_webpage.py """ -from metagpt.roles.mi.interpreter import Interpreter +from metagpt.roles.di.data_interpreter import DataInterpreter async def main(): prompt = """Get data from `paperlist` table in https://papercopilot.com/statistics/iclr-statistics/iclr-2024-statistics/, and save it to a csv file. paper title must include `multiagent` or `large language model`. *notice: print key variables*""" - mi = Interpreter(use_tools=True) + di = DataInterpreter(use_tools=True) - await mi.run(prompt) + await di.run(prompt) if __name__ == "__main__": diff --git a/examples/mi/data_visualization.py b/examples/di/data_visualization.py similarity index 59% rename from examples/mi/data_visualization.py rename to examples/di/data_visualization.py index 2e4acc9b4..9af72dc42 100644 --- a/examples/mi/data_visualization.py +++ b/examples/di/data_visualization.py @@ -1,11 +1,11 @@ import asyncio -from metagpt.roles.mi.interpreter import Interpreter +from metagpt.roles.di.data_interpreter import DataInterpreter async def main(requirement: str = ""): - mi = Interpreter(use_tools=False) - await mi.run(requirement) + di = DataInterpreter(use_tools=False) + await di.run(requirement) if __name__ == "__main__": diff --git a/examples/mi/email_summary.py b/examples/di/email_summary.py similarity index 90% rename from examples/mi/email_summary.py rename to examples/di/email_summary.py index e1511c5b0..af081fee2 100644 --- a/examples/mi/email_summary.py +++ b/examples/di/email_summary.py @@ -6,7 +6,7 @@ """ import os -from metagpt.roles.mi.interpreter import Interpreter +from metagpt.roles.di.data_interpreter import DataInterpreter async def main(): @@ -22,9 +22,9 @@ async def main(): Firstly, Please help me fetch the latest 5 senders and full letter contents. Then, summarize each of the 5 emails into one sentence (you can do this by yourself, no need to import other models to do this) and output them in a markdown format.""" - mi = Interpreter(use_tools=True) + di = DataInterpreter(use_tools=True) - await mi.run(prompt) + await di.run(prompt) if __name__ == "__main__": diff --git a/examples/mi/imitate_webpage.py b/examples/di/imitate_webpage.py similarity index 81% rename from examples/mi/imitate_webpage.py rename to examples/di/imitate_webpage.py index 0e9ca731d..e2c99b874 100644 --- a/examples/mi/imitate_webpage.py +++ b/examples/di/imitate_webpage.py @@ -5,7 +5,7 @@ @Author : mannaandpoem @File : imitate_webpage.py """ -from metagpt.roles.mi.interpreter import Interpreter +from metagpt.roles.di.data_interpreter import DataInterpreter async def main(): @@ -15,9 +15,9 @@ Firstly, utilize Selenium and WebDriver for rendering. Secondly, convert image to a webpage including HTML, CSS and JS in one go. Finally, save webpage in a text file. Note: All required dependencies and environments have been fully installed and configured.""" - mi = Interpreter(use_tools=True) + di = DataInterpreter(use_tools=True) - await mi.run(prompt) + await di.run(prompt) if __name__ == "__main__": diff --git a/examples/mi/machine_learning.py b/examples/di/machine_learning.py similarity index 67% rename from examples/mi/machine_learning.py rename to examples/di/machine_learning.py index a8ab5051e..a58735831 100644 --- a/examples/mi/machine_learning.py +++ b/examples/di/machine_learning.py @@ -1,12 +1,12 @@ import fire -from metagpt.roles.mi.interpreter import Interpreter +from metagpt.roles.di.data_interpreter import DataInterpreter async def main(auto_run: bool = True): requirement = "Run data analysis on sklearn Wine recognition dataset, include a plot, and train a model to predict wine class (20% as validation), and show validation accuracy." - mi = Interpreter(auto_run=auto_run) - await mi.run(requirement) + di = DataInterpreter(auto_run=auto_run) + await di.run(requirement) if __name__ == "__main__": diff --git a/examples/mi/ml_engineer_with_tools.py b/examples/di/ml_engineer_with_tools.py similarity index 94% rename from examples/mi/ml_engineer_with_tools.py rename to examples/di/ml_engineer_with_tools.py index 9d0e7f951..6119ad843 100644 --- a/examples/mi/ml_engineer_with_tools.py +++ b/examples/di/ml_engineer_with_tools.py @@ -1,6 +1,6 @@ import asyncio -from metagpt.roles.mi.ml_engineer import MLEngineer +from metagpt.roles.di.ml_engineer import MLEngineer async def main(requirement: str): diff --git a/examples/mi/ocr_receipt.py b/examples/di/ocr_receipt.py similarity index 84% rename from examples/mi/ocr_receipt.py rename to examples/di/ocr_receipt.py index f394cccec..af54d519b 100644 --- a/examples/mi/ocr_receipt.py +++ b/examples/di/ocr_receipt.py @@ -1,4 +1,4 @@ -from metagpt.roles.mi.interpreter import Interpreter +from metagpt.roles.di.data_interpreter import DataInterpreter async def main(): @@ -10,9 +10,9 @@ async def main(): coordinates and confidence levels, then recognize the total amount from ocr text content, and finally save as table. Image path: {image_path}. NOTE: The environments for Paddle and PaddleOCR are all ready and has been fully installed.""" - mi = Interpreter() + di = DataInterpreter() - await mi.run(requirement) + await di.run(requirement) if __name__ == "__main__": diff --git a/examples/mi/rm_image_background.py b/examples/di/rm_image_background.py similarity index 75% rename from examples/mi/rm_image_background.py rename to examples/di/rm_image_background.py index 57e89b103..b74a79eeb 100644 --- a/examples/mi/rm_image_background.py +++ b/examples/di/rm_image_background.py @@ -1,11 +1,11 @@ import asyncio -from metagpt.roles.mi.interpreter import Interpreter +from metagpt.roles.di.data_interpreter import DataInterpreter async def main(requirement: str = ""): - mi = Interpreter(use_tools=False) - await mi.run(requirement) + di = DataInterpreter(use_tools=False) + await di.run(requirement) if __name__ == "__main__": diff --git a/examples/mi/sd_tool_usage.py b/examples/di/sd_tool_usage.py similarity index 73% rename from examples/mi/sd_tool_usage.py rename to examples/di/sd_tool_usage.py index f8507a411..69c7df5bd 100644 --- a/examples/mi/sd_tool_usage.py +++ b/examples/di/sd_tool_usage.py @@ -4,12 +4,12 @@ # @Desc : import asyncio -from metagpt.roles.mi.interpreter import Interpreter +from metagpt.roles.di.data_interpreter import DataInterpreter async def main(requirement: str = ""): - mi = Interpreter(use_tools=True, goal=requirement) - await mi.run(requirement) + di = DataInterpreter(use_tools=True, goal=requirement) + await di.run(requirement) if __name__ == "__main__": diff --git a/examples/mi/solve_math_problems.py b/examples/di/solve_math_problems.py similarity index 70% rename from examples/mi/solve_math_problems.py rename to examples/di/solve_math_problems.py index dce2edb00..35a69c953 100644 --- a/examples/mi/solve_math_problems.py +++ b/examples/di/solve_math_problems.py @@ -1,11 +1,11 @@ import asyncio -from metagpt.roles.mi.interpreter import Interpreter +from metagpt.roles.di.data_interpreter import DataInterpreter async def main(requirement: str = ""): - mi = Interpreter(use_tools=False) - await mi.run(requirement) + di = DataInterpreter(use_tools=False) + await di.run(requirement) if __name__ == "__main__": diff --git a/examples/mi/README.md b/examples/mi/README.md deleted file mode 100644 index 1734ba388..000000000 --- a/examples/mi/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# MetaGPT Interpreter (MI) - -## What is Interpreter -Interpreter is an agent who solves problems through codes. It understands user requirements, makes plans, writes codes for execution, and uses tools if necessary. These capabilities enable it to tackle a wide range of scenarios, please check out the examples below. - -## Example List -- Data visualization -- Machine learning modeling -- Image background removal -- Solve math problems -- Receipt OCR -- Tool usage: web page imitation -- Tool usage: web crawling -- Tool usage: text2image -- Tool usage: email summarization and response -- More on the way! - -Please see [here](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/interpreter/mi_intro.html) for detailed explanation. \ No newline at end of file diff --git a/metagpt/actions/__init__.py b/metagpt/actions/__init__.py index 19a7c10d5..29af1dad1 100644 --- a/metagpt/actions/__init__.py +++ b/metagpt/actions/__init__.py @@ -22,9 +22,9 @@ from metagpt.actions.write_code_review import WriteCodeReview from metagpt.actions.write_prd import WritePRD from metagpt.actions.write_prd_review import WritePRDReview from metagpt.actions.write_test import WriteTest -from metagpt.actions.mi.execute_nb_code import ExecuteNbCode -from metagpt.actions.mi.write_analysis_code import WriteCodeWithoutTools, WriteCodeWithTools -from metagpt.actions.mi.write_plan import WritePlan +from metagpt.actions.di.execute_nb_code import ExecuteNbCode +from metagpt.actions.di.write_analysis_code import WriteCodeWithoutTools, WriteCodeWithTools +from metagpt.actions.di.write_plan import WritePlan class ActionType(Enum): diff --git a/metagpt/actions/design_api_an.py b/metagpt/actions/design_api_an.py index 35b50ef8f..5977cbd95 100644 --- a/metagpt/actions/design_api_an.py +++ b/metagpt/actions/design_api_an.py @@ -8,7 +8,6 @@ from typing import List from metagpt.actions.action_node import ActionNode -from metagpt.logs import logger from metagpt.utils.mermaid import MMC1, MMC2 IMPLEMENTATION_APPROACH = ActionNode( @@ -109,14 +108,3 @@ REFINED_NODES = [ DESIGN_API_NODE = ActionNode.from_children("DesignAPI", NODES) REFINED_DESIGN_NODE = ActionNode.from_children("RefinedDesignAPI", REFINED_NODES) - - -def main(): - prompt = DESIGN_API_NODE.compile(context="") - logger.info(prompt) - prompt = REFINED_DESIGN_NODE.compile(context="") - logger.info(prompt) - - -if __name__ == "__main__": - main() diff --git a/metagpt/actions/mi/__init__.py b/metagpt/actions/di/__init__.py similarity index 100% rename from metagpt/actions/mi/__init__.py rename to metagpt/actions/di/__init__.py diff --git a/metagpt/actions/mi/ask_review.py b/metagpt/actions/di/ask_review.py similarity index 100% rename from metagpt/actions/mi/ask_review.py rename to metagpt/actions/di/ask_review.py diff --git a/metagpt/actions/mi/debug_code.py b/metagpt/actions/di/debug_code.py similarity index 98% rename from metagpt/actions/mi/debug_code.py rename to metagpt/actions/di/debug_code.py index db3b7a9db..f589436f5 100644 --- a/metagpt/actions/mi/debug_code.py +++ b/metagpt/actions/di/debug_code.py @@ -1,6 +1,6 @@ from __future__ import annotations -from metagpt.actions.mi.write_analysis_code import BaseWriteAnalysisCode +from metagpt.actions.di.write_analysis_code import BaseWriteAnalysisCode from metagpt.logs import logger from metagpt.schema import Message from metagpt.utils.common import create_func_call_config diff --git a/metagpt/actions/mi/execute_nb_code.py b/metagpt/actions/di/execute_nb_code.py similarity index 100% rename from metagpt/actions/mi/execute_nb_code.py rename to metagpt/actions/di/execute_nb_code.py diff --git a/metagpt/actions/mi/ml_action.py b/metagpt/actions/di/ml_action.py similarity index 93% rename from metagpt/actions/mi/ml_action.py rename to metagpt/actions/di/ml_action.py index 60b2fb547..d49b7b67d 100644 --- a/metagpt/actions/mi/ml_action.py +++ b/metagpt/actions/di/ml_action.py @@ -3,14 +3,14 @@ from __future__ import annotations from typing import Tuple from metagpt.actions import Action -from metagpt.actions.mi.write_analysis_code import WriteCodeWithTools -from metagpt.prompts.mi.ml_action import ( +from metagpt.actions.di.write_analysis_code import WriteCodeWithTools +from metagpt.prompts.di.ml_action import ( ML_GENERATE_CODE_PROMPT, ML_TOOL_USAGE_PROMPT, PRINT_DATA_COLUMNS, UPDATE_DATA_COLUMNS, ) -from metagpt.prompts.mi.write_analysis_code import CODE_GENERATOR_WITH_TOOLS +from metagpt.prompts.di.write_analysis_code import CODE_GENERATOR_WITH_TOOLS from metagpt.schema import Message, Plan from metagpt.utils.common import create_func_call_config, remove_comments diff --git a/metagpt/actions/mi/write_analysis_code.py b/metagpt/actions/di/write_analysis_code.py similarity index 99% rename from metagpt/actions/mi/write_analysis_code.py rename to metagpt/actions/di/write_analysis_code.py index b3d0632b6..0c4980c2b 100644 --- a/metagpt/actions/mi/write_analysis_code.py +++ b/metagpt/actions/di/write_analysis_code.py @@ -10,7 +10,7 @@ from typing import Tuple from metagpt.actions import Action from metagpt.logs import logger -from metagpt.prompts.mi.write_analysis_code import ( +from metagpt.prompts.di.write_analysis_code import ( CODE_GENERATOR_WITH_TOOLS, SELECT_FUNCTION_TOOLS, TOOL_RECOMMENDATION_PROMPT, diff --git a/metagpt/actions/mi/write_plan.py b/metagpt/actions/di/write_plan.py similarity index 98% rename from metagpt/actions/mi/write_plan.py rename to metagpt/actions/di/write_plan.py index 8067d7b87..518dfb6c6 100644 --- a/metagpt/actions/mi/write_plan.py +++ b/metagpt/actions/di/write_plan.py @@ -12,7 +12,7 @@ from typing import Tuple from metagpt.actions import Action from metagpt.logs import logger -from metagpt.prompts.mi.write_analysis_code import ( +from metagpt.prompts.di.write_analysis_code import ( ASSIGN_TASK_TYPE_CONFIG, ASSIGN_TASK_TYPE_PROMPT, ) diff --git a/metagpt/actions/project_management_an.py b/metagpt/actions/project_management_an.py index 379a23384..0417c0ce4 100644 --- a/metagpt/actions/project_management_an.py +++ b/metagpt/actions/project_management_an.py @@ -8,7 +8,6 @@ from typing import List from metagpt.actions.action_node import ActionNode -from metagpt.logs import logger REQUIRED_PYTHON_PACKAGES = ActionNode( key="Required Python packages", @@ -119,14 +118,3 @@ REFINED_NODES = [ PM_NODE = ActionNode.from_children("PM_NODE", NODES) REFINED_PM_NODE = ActionNode.from_children("REFINED_PM_NODE", REFINED_NODES) - - -def main(): - prompt = PM_NODE.compile(context="") - logger.info(prompt) - prompt = REFINED_PM_NODE.compile(context="") - logger.info(prompt) - - -if __name__ == "__main__": - main() diff --git a/metagpt/actions/write_code_plan_and_change_an.py b/metagpt/actions/write_code_plan_and_change_an.py index 708808050..f99bffd84 100644 --- a/metagpt/actions/write_code_plan_and_change_an.py +++ b/metagpt/actions/write_code_plan_and_change_an.py @@ -6,30 +6,44 @@ @File : write_code_plan_and_change_an.py """ import os +from typing import List from pydantic import Field from metagpt.actions.action import Action from metagpt.actions.action_node import ActionNode +from metagpt.logs import logger from metagpt.schema import CodePlanAndChangeContext -CODE_PLAN_AND_CHANGE = ActionNode( - key="Code Plan And Change", - expected_type=str, - instruction="Developing comprehensive and step-by-step incremental development plan, and write Incremental " - "Change by making a code draft that how to implement incremental development including detailed steps based on the " - "context. Note: Track incremental changes using mark of '+' or '-' for add/modify/delete code, and conforms to the " - "output format of git diff", - example=""" -1. Plan for calculator.py: Enhance the functionality of `calculator.py` by extending it to incorporate methods for subtraction, multiplication, and division. Additionally, implement robust error handling for the division operation to mitigate potential issues related to division by zero. -```python +DEVELOPMENT_PLAN = ActionNode( + key="Development Plan", + expected_type=List[str], + instruction="Develop a comprehensive and step-by-step incremental development plan, providing the detail " + "changes to be implemented at each step based on the order of 'Task List'", + example=[ + "Enhance the functionality of `calculator.py` by extending it to incorporate methods for subtraction, ...", + "Update the existing codebase in main.py to incorporate new API endpoints for subtraction, ...", + ], +) + +INCREMENTAL_CHANGE = ActionNode( + key="Incremental Change", + expected_type=List[str], + instruction="Write Incremental Change by making a code draft that how to implement incremental development " + "including detailed steps based on the context. Note: Track incremental changes using the marks `+` and `-` to " + "indicate additions and deletions, and ensure compliance with the output format of `git diff`", + example=[ + '''```diff +--- Old/calculator.py ++++ New/calculator.py + class Calculator: self.result = number1 + number2 return self.result - def sub(self, number1, number2) -> float: + def subtract(self, number1: float, number2: float) -> float: -+ ''' ++ """ + Subtracts the second number from the first and returns the result. + + Args: @@ -38,13 +52,13 @@ class Calculator: + + Returns: + float: The difference of number1 and number2. -+ ''' ++ """ + self.result = number1 - number2 + return self.result + def multiply(self, number1: float, number2: float) -> float: - pass -+ ''' ++ """ + Multiplies two numbers and returns the result. + + Args: @@ -53,15 +67,15 @@ class Calculator: + + Returns: + float: The product of number1 and number2. -+ ''' ++ """ + self.result = number1 * number2 + return self.result + def divide(self, number1: float, number2: float) -> float: - pass -+ ''' ++ """ + ValueError: If the second number is zero. -+ ''' ++ """ + if number2 == 0: + raise ValueError('Cannot divide by zero') + self.result = number1 / number2 @@ -75,10 +89,11 @@ class Calculator: + print("Result is already zero, no need to clear.") + self.result = 0.0 -``` +```''', + """```diff +--- Old/main.py ++++ New/main.py -2. Plan for main.py: Integrate new API endpoints for subtraction, multiplication, and division into the existing codebase of `main.py`. Then, ensure seamless integration with the overall application architecture and maintain consistency with coding standards. -```python def add_numbers(): result = calculator.add_numbers(num1, num2) return jsonify({'result': result}), 200 @@ -106,6 +121,7 @@ def add_numbers(): if __name__ == '__main__': app.run() ```""", + ], ) CODE_PLAN_AND_CHANGE_CONTEXT = """ @@ -172,14 +188,16 @@ Role: You are a professional engineer; The main goal is to complete incremental 2. COMPLETE CODE: Your code will be part of the entire project, so please implement complete, reliable, reusable code snippets. 3. Set default value: If there is any setting, ALWAYS SET A DEFAULT VALUE, ALWAYS USE STRONG TYPE AND EXPLICIT VARIABLE. AVOID circular import. 4. Follow design: YOU MUST FOLLOW "Data structures and interfaces". DONT CHANGE ANY DESIGN. Do not use public member functions that do not exist in your design. -5. Follow Code Plan And Change: If there is any Incremental Change that is marked by the git diff format using '+' and '-' for add/modify/delete code, or Legacy Code files contain "{filename} to be rewritten", you must merge it into the code file according to the plan. +5. Follow Code Plan And Change: If there is any "Incremental Change" that is marked by the git diff format with '+' and '-' symbols, or Legacy Code files contain "{filename} to be rewritten", you must merge it into the code file according to the "Development Plan". 6. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE. 7. Before using a external variable/module, make sure you import it first. 8. Write out EVERY CODE DETAIL, DON'T LEAVE TODO. 9. Attention: Retain details that are not related to incremental development but are important for maintaining the consistency and clarity of the old code. """ -WRITE_CODE_PLAN_AND_CHANGE_NODE = ActionNode.from_children("WriteCodePlanAndChange", [CODE_PLAN_AND_CHANGE]) +CODE_PLAN_AND_CHANGE = [DEVELOPMENT_PLAN, INCREMENTAL_CHANGE] + +WRITE_CODE_PLAN_AND_CHANGE_NODE = ActionNode.from_children("WriteCodePlanAndChange", CODE_PLAN_AND_CHANGE) class WriteCodePlanAndChange(Action): @@ -192,14 +210,14 @@ class WriteCodePlanAndChange(Action): prd_doc = await self.repo.docs.prd.get(filename=self.i_context.prd_filename) design_doc = await self.repo.docs.system_design.get(filename=self.i_context.design_filename) task_doc = await self.repo.docs.task.get(filename=self.i_context.task_filename) - code_text = await self.get_old_codes() context = CODE_PLAN_AND_CHANGE_CONTEXT.format( requirement=self.i_context.requirement, prd=prd_doc.content, design=design_doc.content, task=task_doc.content, - code=code_text, + code=await self.get_old_codes(), ) + logger.info("Writing code plan and change..") return await WRITE_CODE_PLAN_AND_CHANGE_NODE.fill(context=context, llm=self.llm, schema="json") async def get_old_codes(self) -> str: diff --git a/metagpt/actions/write_prd_an.py b/metagpt/actions/write_prd_an.py index 9898be55b..5733b29da 100644 --- a/metagpt/actions/write_prd_an.py +++ b/metagpt/actions/write_prd_an.py @@ -56,7 +56,7 @@ REFINED_PRODUCT_GOALS = ActionNode( key="Refined Product Goals", expected_type=List[str], instruction="Update and expand the original product goals to reflect the evolving needs due to incremental " - "development.Ensure that the refined goals align with the current project direction and contribute to its success.", + "development. Ensure that the refined goals align with the current project direction and contribute to its success.", example=[ "Enhance user engagement through new features", "Optimize performance for scalability", diff --git a/metagpt/configs/llm_config.py b/metagpt/configs/llm_config.py index ed8840852..77bfc8466 100644 --- a/metagpt/configs/llm_config.py +++ b/metagpt/configs/llm_config.py @@ -16,6 +16,7 @@ from metagpt.utils.yaml_model import YamlModel class LLMType(Enum): OPENAI = "openai" ANTHROPIC = "anthropic" + CLAUDE = "claude" # alias name of anthropic SPARK = "spark" ZHIPUAI = "zhipuai" FIREWORKS = "fireworks" diff --git a/metagpt/document.py b/metagpt/document.py index be80f0c71..1a3c4b2f8 100644 --- a/metagpt/document.py +++ b/metagpt/document.py @@ -11,12 +11,12 @@ from pathlib import Path from typing import Optional, Union import pandas as pd -from langchain.document_loaders import ( +from langchain.text_splitter import CharacterTextSplitter +from langchain_community.document_loaders import ( TextLoader, UnstructuredPDFLoader, UnstructuredWordDocumentLoader, ) -from langchain.text_splitter import CharacterTextSplitter from pydantic import BaseModel, ConfigDict, Field from tqdm import tqdm diff --git a/metagpt/prompts/mi/__init__.py b/metagpt/prompts/di/__init__.py similarity index 100% rename from metagpt/prompts/mi/__init__.py rename to metagpt/prompts/di/__init__.py diff --git a/metagpt/prompts/mi/ml_action.py b/metagpt/prompts/di/ml_action.py similarity index 100% rename from metagpt/prompts/mi/ml_action.py rename to metagpt/prompts/di/ml_action.py diff --git a/metagpt/prompts/mi/write_analysis_code.py b/metagpt/prompts/di/write_analysis_code.py similarity index 100% rename from metagpt/prompts/mi/write_analysis_code.py rename to metagpt/prompts/di/write_analysis_code.py diff --git a/metagpt/provider/__init__.py b/metagpt/provider/__init__.py index ed49d01c9..14d5e7682 100644 --- a/metagpt/provider/__init__.py +++ b/metagpt/provider/__init__.py @@ -16,6 +16,7 @@ from metagpt.provider.human_provider import HumanProvider from metagpt.provider.spark_api import SparkLLM from metagpt.provider.qianfan_api import QianFanLLM from metagpt.provider.dashscope_api import DashScopeLLM +from metagpt.provider.anthropic_api import AnthropicLLM __all__ = [ "GeminiLLM", @@ -28,4 +29,5 @@ __all__ = [ "SparkLLM", "QianFanLLM", "DashScopeLLM", + "AnthropicLLM", ] diff --git a/metagpt/provider/anthropic_api.py b/metagpt/provider/anthropic_api.py index f31c2d04d..872f9b2c7 100644 --- a/metagpt/provider/anthropic_api.py +++ b/metagpt/provider/anthropic_api.py @@ -1,37 +1,71 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -""" -@Time : 2023/7/21 11:15 -@Author : Leo Xiao -@File : anthropic_api.py -""" -import anthropic -from anthropic import Anthropic, AsyncAnthropic +from anthropic import AsyncAnthropic +from anthropic.types import Message, Usage -from metagpt.configs.llm_config import LLMConfig +from metagpt.configs.llm_config import LLMConfig, LLMType +from metagpt.logs import log_llm_stream +from metagpt.provider.base_llm import BaseLLM +from metagpt.provider.llm_provider_registry import register_provider -class Claude2: +@register_provider([LLMType.ANTHROPIC, LLMType.CLAUDE]) +class AnthropicLLM(BaseLLM): def __init__(self, config: LLMConfig): self.config = config + self.__init_anthropic() - def ask(self, prompt: str) -> str: - client = Anthropic(api_key=self.config.api_key) + def __init_anthropic(self): + self.model = self.config.model + self.aclient: AsyncAnthropic = AsyncAnthropic(api_key=self.config.api_key, base_url=self.config.base_url) - res = client.completions.create( - model="claude-2", - prompt=f"{anthropic.HUMAN_PROMPT} {prompt} {anthropic.AI_PROMPT}", - max_tokens_to_sample=1000, - ) - return res.completion + def _const_kwargs(self, messages: list[dict], stream: bool = False) -> dict: + kwargs = { + "model": self.model, + "messages": messages, + "max_tokens": self.config.max_token, + "stream": stream, + } + if self.use_system_prompt: + # if the model support system prompt, extract and pass it + if messages[0]["role"] == "system": + kwargs["messages"] = messages[1:] + kwargs["system"] = messages[0]["content"] # set system prompt here + return kwargs - async def aask(self, prompt: str) -> str: - aclient = AsyncAnthropic(api_key=self.config.api_key) + def _update_costs(self, usage: Usage, model: str = None, local_calc_usage: bool = True): + usage = {"prompt_tokens": usage.input_tokens, "completion_tokens": usage.output_tokens} + super()._update_costs(usage, model) - res = await aclient.completions.create( - model="claude-2", - prompt=f"{anthropic.HUMAN_PROMPT} {prompt} {anthropic.AI_PROMPT}", - max_tokens_to_sample=1000, - ) - return res.completion + def get_choice_text(self, resp: Message) -> str: + return resp.content[0].text + + async def _achat_completion(self, messages: list[dict], timeout: int = 3) -> Message: + resp: Message = await self.aclient.messages.create(**self._const_kwargs(messages)) + self._update_costs(resp.usage, self.model) + return resp + + async def acompletion(self, messages: list[dict], timeout: int = 3) -> Message: + return await self._achat_completion(messages, timeout=timeout) + + async def _achat_completion_stream(self, messages: list[dict], timeout: int = 3) -> str: + stream = await self.aclient.messages.create(**self._const_kwargs(messages, stream=True)) + collected_content = [] + usage = Usage(input_tokens=0, output_tokens=0) + async for event in stream: + event_type = event.type + if event_type == "message_start": + usage.input_tokens = event.message.usage.input_tokens + usage.output_tokens = event.message.usage.output_tokens + elif event_type == "content_block_delta": + content = event.delta.text + log_llm_stream(content) + collected_content.append(content) + elif event_type == "message_delta": + usage.output_tokens = event.usage.output_tokens # update final output_tokens + + log_llm_stream("\n") + self._update_costs(usage) + full_content = "".join(collected_content) + return full_content diff --git a/metagpt/provider/base_llm.py b/metagpt/provider/base_llm.py index b0026074d..9850f4cf5 100644 --- a/metagpt/provider/base_llm.py +++ b/metagpt/provider/base_llm.py @@ -15,10 +15,18 @@ from typing import Dict, Optional, Union from openai import AsyncOpenAI from openai.types import CompletionUsage from pydantic import BaseModel +from tenacity import ( + after_log, + retry, + retry_if_exception_type, + stop_after_attempt, + wait_random_exponential, +) from metagpt.configs.llm_config import LLMConfig from metagpt.logs import logger from metagpt.schema import Message +from metagpt.utils.common import log_and_reraise from metagpt.utils.cost_manager import CostManager, Costs from metagpt.utils.exceptions import handle_exception @@ -134,6 +142,10 @@ class BaseLLM(ABC): """FIXME: No code segment filtering has been done here, and all results are actually displayed""" raise NotImplementedError + @abstractmethod + async def _achat_completion(self, messages: list[dict], timeout=3): + """_achat_completion implemented by inherited class""" + @abstractmethod async def acompletion(self, messages: list[dict], timeout=3): """Asynchronous version of completion @@ -146,8 +158,22 @@ class BaseLLM(ABC): """ @abstractmethod - async def acompletion_text(self, messages: list[dict], stream=False, timeout=3) -> str: + async def _achat_completion_stream(self, messages: list[dict], timeout: int = 3) -> str: + """_achat_completion_stream implemented by inherited class""" + + @retry( + stop=stop_after_attempt(3), + wait=wait_random_exponential(min=1, max=60), + after=after_log(logger, logger.level("WARNING").name), + retry=retry_if_exception_type(ConnectionError), + retry_error_callback=log_and_reraise, + ) + async def acompletion_text(self, messages: list[dict], stream: bool = False, timeout: int = 3) -> str: """Asynchronous version of completion. Return str. Support stream-print""" + if stream: + return await self._achat_completion_stream(messages, timeout=timeout) + resp = await self._achat_completion(messages, timeout=timeout) + return self.get_choice_text(resp) def get_choice_text(self, rsp: dict) -> str: """Required to provide the first text of choice""" diff --git a/metagpt/provider/dashscope_api.py b/metagpt/provider/dashscope_api.py index f2b3a19a1..21f3ef351 100644 --- a/metagpt/provider/dashscope_api.py +++ b/metagpt/provider/dashscope_api.py @@ -24,18 +24,10 @@ from dashscope.common.error import ( ModelRequired, UnsupportedApiProtocol, ) -from tenacity import ( - after_log, - retry, - retry_if_exception_type, - stop_after_attempt, - wait_random_exponential, -) -from metagpt.logs import log_llm_stream, logger +from metagpt.logs import log_llm_stream from metagpt.provider.base_llm import BaseLLM, LLMConfig from metagpt.provider.llm_provider_registry import LLMType, register_provider -from metagpt.provider.openai_api import log_and_reraise from metagpt.utils.cost_manager import CostManager from metagpt.utils.token_counter import DASHSCOPE_TOKEN_COSTS @@ -210,16 +202,16 @@ class DashScopeLLM(BaseLLM): self._update_costs(dict(resp.usage)) return resp.output - async def _achat_completion(self, messages: list[dict]) -> GenerationOutput: + async def _achat_completion(self, messages: list[dict], timeout: int = 3) -> GenerationOutput: resp: GenerationResponse = await self.aclient.acall(**self._const_kwargs(messages, stream=False)) self._check_response(resp) self._update_costs(dict(resp.usage)) return resp.output async def acompletion(self, messages: list[dict], timeout=3) -> GenerationOutput: - return await self._achat_completion(messages) + return await self._achat_completion(messages, timeout=timeout) - async def _achat_completion_stream(self, messages: list[dict]) -> str: + async def _achat_completion_stream(self, messages: list[dict], timeout: int = 3) -> str: resp = await self.aclient.acall(**self._const_kwargs(messages, stream=True)) collected_content = [] usage = {} @@ -233,16 +225,3 @@ class DashScopeLLM(BaseLLM): self._update_costs(usage) full_content = "".join(collected_content) return full_content - - @retry( - stop=stop_after_attempt(3), - wait=wait_random_exponential(min=1, max=60), - after=after_log(logger, logger.level("WARNING").name), - retry=retry_if_exception_type(ConnectionError), - retry_error_callback=log_and_reraise, - ) - async def acompletion_text(self, messages: list[dict], stream=False, timeout: int = 3) -> str: - if stream: - return await self._achat_completion_stream(messages) - resp = await self._achat_completion(messages) - return self.get_choice_text(resp) diff --git a/metagpt/provider/google_gemini_api.py b/metagpt/provider/google_gemini_api.py index d5b22d5ac..09e554205 100644 --- a/metagpt/provider/google_gemini_api.py +++ b/metagpt/provider/google_gemini_api.py @@ -13,19 +13,11 @@ from google.generativeai.types.generation_types import ( GenerateContentResponse, GenerationConfig, ) -from tenacity import ( - after_log, - retry, - retry_if_exception_type, - stop_after_attempt, - wait_random_exponential, -) from metagpt.configs.llm_config import LLMConfig, LLMType -from metagpt.logs import log_llm_stream, logger +from metagpt.logs import log_llm_stream from metagpt.provider.base_llm import BaseLLM from metagpt.provider.llm_provider_registry import register_provider -from metagpt.provider.openai_api import log_and_reraise class GeminiGenerativeModel(GenerativeModel): @@ -96,16 +88,16 @@ class GeminiLLM(BaseLLM): self._update_costs(usage) return resp - async def _achat_completion(self, messages: list[dict]) -> "AsyncGenerateContentResponse": + async def _achat_completion(self, messages: list[dict], timeout: int = 3) -> "AsyncGenerateContentResponse": resp: AsyncGenerateContentResponse = await self.llm.generate_content_async(**self._const_kwargs(messages)) usage = await self.aget_usage(messages, resp.text) self._update_costs(usage) return resp async def acompletion(self, messages: list[dict], timeout=3) -> dict: - return await self._achat_completion(messages) + return await self._achat_completion(messages, timeout=timeout) - async def _achat_completion_stream(self, messages: list[dict]) -> str: + async def _achat_completion_stream(self, messages: list[dict], timeout: int = 3) -> str: resp: AsyncGenerateContentResponse = await self.llm.generate_content_async( **self._const_kwargs(messages, stream=True) ) @@ -120,17 +112,3 @@ class GeminiLLM(BaseLLM): usage = await self.aget_usage(messages, full_content) self._update_costs(usage) return full_content - - @retry( - stop=stop_after_attempt(3), - wait=wait_random_exponential(min=1, max=60), - after=after_log(logger, logger.level("WARNING").name), - retry=retry_if_exception_type(ConnectionError), - retry_error_callback=log_and_reraise, - ) - async def acompletion_text(self, messages: list[dict], stream=False, timeout: int = 3) -> str: - """response in async with stream or non-stream mode""" - if stream: - return await self._achat_completion_stream(messages) - resp = await self._achat_completion(messages) - return self.get_choice_text(resp) diff --git a/metagpt/provider/human_provider.py b/metagpt/provider/human_provider.py index fe000b3a6..e5f37c5b9 100644 --- a/metagpt/provider/human_provider.py +++ b/metagpt/provider/human_provider.py @@ -35,10 +35,16 @@ class HumanProvider(BaseLLM): ) -> str: return self.ask(msg, timeout=timeout) + async def _achat_completion(self, messages: list[dict], timeout=3): + pass + async def acompletion(self, messages: list[dict], timeout=3): """dummy implementation of abstract method in base""" return [] + 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: """dummy implementation of abstract method in base""" return "" diff --git a/metagpt/provider/ollama_api.py b/metagpt/provider/ollama_api.py index 89e171cf5..f65d7e411 100644 --- a/metagpt/provider/ollama_api.py +++ b/metagpt/provider/ollama_api.py @@ -4,22 +4,12 @@ import json -from requests import ConnectionError -from tenacity import ( - after_log, - retry, - retry_if_exception_type, - stop_after_attempt, - wait_random_exponential, -) - from metagpt.configs.llm_config import LLMConfig, LLMType from metagpt.const import LLM_API_TIMEOUT -from metagpt.logs import log_llm_stream, logger +from metagpt.logs import log_llm_stream from metagpt.provider.base_llm import BaseLLM from metagpt.provider.general_api_requestor import GeneralAPIRequestor from metagpt.provider.llm_provider_registry import register_provider -from metagpt.provider.openai_api import log_and_reraise from metagpt.utils.cost_manager import TokenCostManager @@ -60,7 +50,7 @@ class OllamaLLM(BaseLLM): chunk = chunk.decode(encoding) return json.loads(chunk) - async def _achat_completion(self, messages: list[dict]) -> dict: + async def _achat_completion(self, messages: list[dict], timeout: int = 3) -> dict: resp, _, _ = await self.client.arequest( method=self.http_method, url=self.suffix_url, @@ -73,9 +63,9 @@ class OllamaLLM(BaseLLM): return resp async def acompletion(self, messages: list[dict], timeout=3) -> dict: - return await self._achat_completion(messages) + return await self._achat_completion(messages, timeout=timeout) - async def _achat_completion_stream(self, messages: list[dict]) -> str: + async def _achat_completion_stream(self, messages: list[dict], timeout: int = 3) -> str: stream_resp, _, _ = await self.client.arequest( method=self.http_method, url=self.suffix_url, @@ -101,17 +91,3 @@ class OllamaLLM(BaseLLM): self._update_costs(usage) full_content = "".join(collected_content) return full_content - - @retry( - stop=stop_after_attempt(3), - wait=wait_random_exponential(min=1, max=60), - after=after_log(logger, logger.level("WARNING").name), - retry=retry_if_exception_type(ConnectionError), - retry_error_callback=log_and_reraise, - ) - async def acompletion_text(self, messages: list[dict], stream=False, timeout: int = 3) -> str: - """response in async with stream or non-stream mode""" - if stream: - return await self._achat_completion_stream(messages) - resp = await self._achat_completion(messages) - return self.get_choice_text(resp) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index daf77e816..82206f738 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -30,7 +30,7 @@ from metagpt.provider.base_llm import BaseLLM from metagpt.provider.constant import GENERAL_FUNCTION_SCHEMA from metagpt.provider.llm_provider_registry import register_provider from metagpt.schema import Message -from metagpt.utils.common import CodeParser, decode_image +from metagpt.utils.common import CodeParser, decode_image, log_and_reraise from metagpt.utils.cost_manager import CostManager from metagpt.utils.exceptions import handle_exception from metagpt.utils.token_counter import ( @@ -40,17 +40,6 @@ from metagpt.utils.token_counter import ( ) -def log_and_reraise(retry_state): - logger.error(f"Retry attempts exhausted. Last exception: {retry_state.outcome.exception()}") - logger.warning( - """ -Recommend going to https://deepwisdom.feishu.cn/wiki/MsGnwQBjiif9c3koSJNcYaoSnu4#part-XdatdVlhEojeAfxaaEZcMV3ZniQ -See FAQ 5.8 -""" - ) - raise retry_state.outcome.exception() - - @register_provider([LLMType.OPENAI, LLMType.FIREWORKS, LLMType.OPEN_LLM, LLMType.MOONSHOT, LLMType.MISTRAL]) class OpenAILLM(BaseLLM): """Check https://platform.openai.com/examples for examples""" diff --git a/metagpt/provider/qianfan_api.py b/metagpt/provider/qianfan_api.py index 4cbb76566..50916fa3e 100644 --- a/metagpt/provider/qianfan_api.py +++ b/metagpt/provider/qianfan_api.py @@ -7,19 +7,11 @@ import os import qianfan from qianfan import ChatCompletion from qianfan.resources.typing import JsonBody -from tenacity import ( - after_log, - retry, - retry_if_exception_type, - stop_after_attempt, - wait_random_exponential, -) from metagpt.configs.llm_config import LLMConfig, LLMType -from metagpt.logs import log_llm_stream, logger +from metagpt.logs import log_llm_stream from metagpt.provider.base_llm import BaseLLM from metagpt.provider.llm_provider_registry import register_provider -from metagpt.provider.openai_api import log_and_reraise from metagpt.utils.cost_manager import CostManager from metagpt.utils.token_counter import ( QIANFAN_ENDPOINT_TOKEN_COSTS, @@ -115,15 +107,15 @@ class QianFanLLM(BaseLLM): self._update_costs(resp.body.get("usage", {})) return resp.body - async def _achat_completion(self, messages: list[dict]) -> JsonBody: + async def _achat_completion(self, messages: list[dict], timeout: int = 3) -> JsonBody: resp = await self.aclient.ado(**self._const_kwargs(messages=messages, stream=False)) self._update_costs(resp.body.get("usage", {})) return resp.body - async def acompletion(self, messages: list[dict], timeout=3) -> JsonBody: - return await self._achat_completion(messages) + async def acompletion(self, messages: list[dict], timeout: int = 3) -> JsonBody: + return await self._achat_completion(messages, timeout=timeout) - async def _achat_completion_stream(self, messages: list[dict]) -> str: + async def _achat_completion_stream(self, messages: list[dict], timeout: int = 3) -> str: resp = await self.aclient.ado(**self._const_kwargs(messages=messages, stream=True)) collected_content = [] usage = {} @@ -137,16 +129,3 @@ class QianFanLLM(BaseLLM): self._update_costs(usage) full_content = "".join(collected_content) return full_content - - @retry( - stop=stop_after_attempt(3), - wait=wait_random_exponential(min=1, max=60), - after=after_log(logger, logger.level("WARNING").name), - retry=retry_if_exception_type(ConnectionError), - retry_error_callback=log_and_reraise, - ) - async def acompletion_text(self, messages: list[dict], stream=False, timeout: int = 3) -> str: - if stream: - return await self._achat_completion_stream(messages) - resp = await self._achat_completion(messages) - return self.get_choice_text(resp) diff --git a/metagpt/provider/spark_api.py b/metagpt/provider/spark_api.py index 5e89c26d5..882c6ce85 100644 --- a/metagpt/provider/spark_api.py +++ b/metagpt/provider/spark_api.py @@ -31,12 +31,18 @@ class SparkLLM(BaseLLM): def get_choice_text(self, rsp: dict) -> str: return rsp["payload"]["choices"]["text"][-1]["content"] + 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: int = 3) -> str: # 不支持 # logger.warning("当前方法无法支持异步运行。当你使用acompletion时,并不能并行访问。") w = GetMessageFromWeb(messages, self.config) return w.run() + async def _achat_completion(self, messages: list[dict], timeout=3): + pass + async def acompletion(self, messages: list[dict], timeout=3): # 不支持异步 w = GetMessageFromWeb(messages, self.config) diff --git a/metagpt/provider/zhipuai_api.py b/metagpt/provider/zhipuai_api.py index 360e41f80..14ad1a36b 100644 --- a/metagpt/provider/zhipuai_api.py +++ b/metagpt/provider/zhipuai_api.py @@ -5,21 +5,12 @@ from enum import Enum from typing import Optional -from requests import ConnectionError -from tenacity import ( - after_log, - retry, - retry_if_exception_type, - stop_after_attempt, - wait_random_exponential, -) from zhipuai.types.chat.chat_completion import Completion from metagpt.configs.llm_config import LLMConfig, LLMType -from metagpt.logs import log_llm_stream, logger +from metagpt.logs import log_llm_stream from metagpt.provider.base_llm import BaseLLM from metagpt.provider.llm_provider_registry import register_provider -from metagpt.provider.openai_api import log_and_reraise from metagpt.provider.zhipuai.zhipu_model_api import ZhiPuModelAPI from metagpt.utils.cost_manager import CostManager @@ -87,17 +78,3 @@ class ZhiPuAILLM(BaseLLM): self._update_costs(usage) full_content = "".join(collected_content) return full_content - - @retry( - stop=stop_after_attempt(3), - wait=wait_random_exponential(min=1, max=60), - after=after_log(logger, logger.level("WARNING").name), - retry=retry_if_exception_type(ConnectionError), - retry_error_callback=log_and_reraise, - ) - async def acompletion_text(self, messages: list[dict], stream=False, timeout=3) -> str: - """response in async with stream or non-stream mode""" - if stream: - return await self._achat_completion_stream(messages) - resp = await self._achat_completion(messages) - return self.get_choice_text(resp) diff --git a/metagpt/roles/mi/__init__.py b/metagpt/roles/di/__init__.py similarity index 100% rename from metagpt/roles/mi/__init__.py rename to metagpt/roles/di/__init__.py diff --git a/metagpt/roles/mi/interpreter.py b/metagpt/roles/di/data_interpreter.py similarity index 91% rename from metagpt/roles/mi/interpreter.py rename to metagpt/roles/di/data_interpreter.py index fa50098e9..b3a2e789f 100644 --- a/metagpt/roles/mi/interpreter.py +++ b/metagpt/roles/di/data_interpreter.py @@ -2,9 +2,9 @@ from __future__ import annotations from pydantic import Field -from metagpt.actions.mi.ask_review import ReviewConst -from metagpt.actions.mi.execute_nb_code import ExecuteNbCode -from metagpt.actions.mi.write_analysis_code import ( +from metagpt.actions.di.ask_review import ReviewConst +from metagpt.actions.di.execute_nb_code import ExecuteNbCode +from metagpt.actions.di.write_analysis_code import ( WriteCodeWithoutTools, WriteCodeWithTools, ) @@ -13,9 +13,9 @@ from metagpt.roles import Role from metagpt.schema import Message, Task, TaskResult -class Interpreter(Role): - name: str = "Ivy" - profile: str = "Interpreter" +class DataInterpreter(Role): + name: str = "David" + profile: str = "DataInterpreter" auto_run: bool = True use_tools: bool = False execute_code: ExecuteNbCode = Field(default_factory=ExecuteNbCode, exclude=True) diff --git a/metagpt/roles/mi/ml_engineer.py b/metagpt/roles/di/ml_engineer.py similarity index 89% rename from metagpt/roles/mi/ml_engineer.py rename to metagpt/roles/di/ml_engineer.py index 78d605d3e..b33b166cf 100644 --- a/metagpt/roles/mi/ml_engineer.py +++ b/metagpt/roles/di/ml_engineer.py @@ -1,13 +1,13 @@ -from metagpt.actions.mi.debug_code import DebugCode -from metagpt.actions.mi.execute_nb_code import ExecuteNbCode -from metagpt.actions.mi.ml_action import UpdateDataColumns, WriteCodeWithToolsML +from metagpt.actions.di.debug_code import DebugCode +from metagpt.actions.di.execute_nb_code import ExecuteNbCode +from metagpt.actions.di.ml_action import UpdateDataColumns, WriteCodeWithToolsML from metagpt.logs import logger -from metagpt.roles.mi.interpreter import Interpreter +from metagpt.roles.di.data_interpreter import DataInterpreter from metagpt.tools.tool_type import ToolType from metagpt.utils.common import any_to_str -class MLEngineer(Interpreter): +class MLEngineer(DataInterpreter): name: str = "Mark" profile: str = "MLEngineer" debug_context: list = [] diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index ebcff0354..329b21553 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -204,7 +204,6 @@ class Engineer(Role): async def _act_code_plan_and_change(self): """Write code plan and change that guides subsequent WriteCode and WriteCodeReview""" - logger.info("Writing code plan and change..") node = await self.rc.todo.run() code_plan_and_change = node.instruct_content.model_dump_json() dependencies = { diff --git a/metagpt/strategy/planner.py b/metagpt/strategy/planner.py index 99d16f78b..44294be00 100644 --- a/metagpt/strategy/planner.py +++ b/metagpt/strategy/planner.py @@ -4,8 +4,8 @@ import json from pydantic import BaseModel, Field -from metagpt.actions.mi.ask_review import AskReview, ReviewConst -from metagpt.actions.mi.write_plan import ( +from metagpt.actions.di.ask_review import AskReview, ReviewConst +from metagpt.actions.di.write_plan import ( WritePlan, precheck_update_plan_from_rsp, update_plan_from_rsp, diff --git a/metagpt/strategy/solver.py b/metagpt/strategy/solver.py index ab16d82bd..e532f736b 100644 --- a/metagpt/strategy/solver.py +++ b/metagpt/strategy/solver.py @@ -49,8 +49,8 @@ class TOTSolver(BaseSolver): raise NotImplementedError -class InterpreterSolver(BaseSolver): - """InterpreterSolver: Write&Run code in the graph""" +class DataInterpreterSolver(BaseSolver): + """DataInterpreterSolver: Write&Run code in the graph""" async def solve(self): raise NotImplementedError diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py index 7bf64f05c..b6296c4fa 100644 --- a/metagpt/utils/common.py +++ b/metagpt/utils/common.py @@ -810,3 +810,14 @@ def decode_image(img_url_or_b64: str) -> Image: img_data = BytesIO(base64.b64decode(b64_data)) img = Image.open(img_data) return img + + +def log_and_reraise(retry_state: RetryCallState): + logger.error(f"Retry attempts exhausted. Last exception: {retry_state.outcome.exception()}") + logger.warning( + """ +Recommend going to https://deepwisdom.feishu.cn/wiki/MsGnwQBjiif9c3koSJNcYaoSnu4#part-XdatdVlhEojeAfxaaEZcMV3ZniQ +See FAQ 5.8 +""" + ) + raise retry_state.outcome.exception() diff --git a/metagpt/utils/project_repo.py b/metagpt/utils/project_repo.py index 6859c43b1..bb18b520c 100644 --- a/metagpt/utils/project_repo.py +++ b/metagpt/utils/project_repo.py @@ -136,6 +136,7 @@ class ProjectRepo(FileRepository): code_files = self.with_src_path(path=git_workdir / git_workdir.name).srcs.all_files if not code_files: return False + return bool(code_files) def with_src_path(self, path: str | Path) -> ProjectRepo: try: diff --git a/metagpt/utils/token_counter.py b/metagpt/utils/token_counter.py index e655398c8..f7c53706b 100644 --- a/metagpt/utils/token_counter.py +++ b/metagpt/utils/token_counter.py @@ -43,6 +43,11 @@ TOKEN_COSTS = { "mistral-small-latest": {"prompt": 0.002, "completion": 0.006}, "mistral-medium-latest": {"prompt": 0.0027, "completion": 0.0081}, "mistral-large-latest": {"prompt": 0.008, "completion": 0.024}, + "claude-instant-1.2": {"prompt": 0.0008, "completion": 0.0024}, + "claude-2.0": {"prompt": 0.008, "completion": 0.024}, + "claude-2.1": {"prompt": 0.008, "completion": 0.024}, + "claude-3-sonnet-20240229": {"prompt": 0.003, "completion": 0.015}, + "claude-3-opus-20240229": {"prompt": 0.015, "completion": 0.075}, } @@ -166,6 +171,11 @@ TOKEN_MAX = { "mistral-small-latest": 32768, "mistral-medium-latest": 32768, "mistral-large-latest": 32768, + "claude-instant-1.2": 100000, + "claude-2.0": 100000, + "claude-2.1": 200000, + "claude-3-sonnet-20240229": 200000, + "claude-3-opus-20240229": 200000, } diff --git a/requirements.txt b/requirements.txt index cf50cf255..64b174913 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,7 @@ typer==0.9.0 # google_api_python_client==2.93.0 # Used by search_engine.py lancedb==0.4.0 langchain==0.1.8 +sqlalchemy==2.0.0 # along with langchain loguru==0.6.0 meilisearch==0.21.0 numpy>=1.24.3,<1.25.0 @@ -33,7 +34,7 @@ tqdm==4.65.0 #unstructured[local-inference] # selenium>4 # webdriver_manager<3.9 -anthropic==0.8.1 +anthropic==0.18.1 typing-inspect==0.8.0 libcst==1.0.1 qdrant-client==1.7.0 diff --git a/setup.py b/setup.py index ed3d5f78f..2044e577a 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ extras_require["dev"] = (["pylint~=3.0.3", "black~=23.3.0", "isort~=5.12.0", "pr setup( name="metagpt", - version="0.7.2", + version="0.7.4", description="The Multi-Agent Framework", long_description=long_description, long_description_content_type="text/markdown", diff --git a/tests/conftest.py b/tests/conftest.py index f2379a833..efd782417 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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 diff --git a/tests/data/incremental_dev_project/Gomoku.zip b/tests/data/incremental_dev_project/Gomoku.zip index a110ccdd2..5877b8b81 100644 Binary files a/tests/data/incremental_dev_project/Gomoku.zip and b/tests/data/incremental_dev_project/Gomoku.zip differ diff --git a/tests/data/incremental_dev_project/dice_simulator_new.zip b/tests/data/incremental_dev_project/dice_simulator_new.zip index 377eaa4f5..f6956cadf 100644 Binary files a/tests/data/incremental_dev_project/dice_simulator_new.zip and b/tests/data/incremental_dev_project/dice_simulator_new.zip differ diff --git a/tests/data/incremental_dev_project/mock.py b/tests/data/incremental_dev_project/mock.py index f2eb71359..e9f5cb123 100644 --- a/tests/data/incremental_dev_project/mock.py +++ b/tests/data/incremental_dev_project/mock.py @@ -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 = """ diff --git a/tests/data/incremental_dev_project/number_guessing_game.zip b/tests/data/incremental_dev_project/number_guessing_game.zip index 9d192c1e6..f5d983d6c 100644 Binary files a/tests/data/incremental_dev_project/number_guessing_game.zip and b/tests/data/incremental_dev_project/number_guessing_game.zip differ diff --git a/tests/data/incremental_dev_project/pygame_2048.zip b/tests/data/incremental_dev_project/pygame_2048.zip index d589a59b3..35cd74259 100644 Binary files a/tests/data/incremental_dev_project/pygame_2048.zip and b/tests/data/incremental_dev_project/pygame_2048.zip differ diff --git a/tests/data/incremental_dev_project/simple_add_calculator.zip b/tests/data/incremental_dev_project/simple_add_calculator.zip index 945e2af5d..57975c8f3 100644 Binary files a/tests/data/incremental_dev_project/simple_add_calculator.zip and b/tests/data/incremental_dev_project/simple_add_calculator.zip differ diff --git a/tests/data/incremental_dev_project/snake_game.zip b/tests/data/incremental_dev_project/snake_game.zip index 739c2a1d1..2c7b01b7c 100644 Binary files a/tests/data/incremental_dev_project/snake_game.zip and b/tests/data/incremental_dev_project/snake_game.zip differ diff --git a/tests/data/incremental_dev_project/word_cloud.zip b/tests/data/incremental_dev_project/word_cloud.zip index 01f83f053..a929fdeaf 100644 Binary files a/tests/data/incremental_dev_project/word_cloud.zip and b/tests/data/incremental_dev_project/word_cloud.zip differ diff --git a/tests/data/rsp_cache.json b/tests/data/rsp_cache.json index d3058fbd0..ba144ae39 100644 --- a/tests/data/rsp_cache.json +++ b/tests/data/rsp_cache.json @@ -392,5 +392,14 @@ "reflection": "The implementation failed the test case where the input array is [1, 5, 2, 3, 4]. The issue arises because the code does not handle the sorting of elements with the same number of ones in their binary representation correctly. According to the context, for similar number of ones, the elements should be sorted based on their decimal value in ascending order. However, the output [1, 2, 4, 3, 5] shows that 3 and 4 are not in the correct order, indicating that the sorting by decimal value is not functioning as intended. To fix this issue, we should ensure that the sorting is stable, so that when two elements have the same number of ones, they maintain their relative order if their decimal values are already in ascending order.", "improved_impl": "def sort_array(arr):\n # Helper function to count the number of ones in the binary representation\n def count_ones(n):\n return bin(n).count('1')\n \n # Sort the array using a custom key function\n # The key function returns a tuple (number of ones, value) for each element\n # This ensures that if two elements have the same number of ones, they are sorted by their value\n # We use a stable sorting algorithm to maintain the relative order of elements with the same number of ones\n sorted_arr = sorted(arr, key=lambda x: (count_ones(x), x), reverse=True)\n sorted_arr = sorted(sorted_arr, key=count_ones)\n \n return sorted_arr\n" }, - "\n## context\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"invoice\": \"False\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- invoice: # if it's a invoice file, return True else False\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"invoice\": \"True\"\n}\n[/CONTENT]" + "\n## context\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"invoice\": \"False\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- invoice: # if it's a invoice file, return True else False\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"invoice\": \"True\"\n}\n[/CONTENT]", + "\n## context\n\n### Legacy Content\n\n## Implementation approach\n\nWe will create a Python-based number guessing game with a simple command-line interface. For the user interface, we will use the built-in 'input' and 'print' functions for interaction. The random library will be used for generating random numbers. We will structure the code to be modular and easily extendable, separating the game logic from the user interface.\n\n## File list\n\n- main.py\n- game.py\n- ui.py\n\n## Data structures and interfaces\n\n\nclassDiagram\n class Game {\n -int secret_number\n -int min_range\n -int max_range\n -list attempts\n +__init__(difficulty: str)\n +start_game()\n +check_guess(guess: int) str\n +get_attempts() int\n +get_history() list\n }\n class UI {\n +start()\n +display_message(message: str)\n +get_user_input(prompt: str) str\n +show_attempts(attempts: int)\n +show_history(history: list)\n +select_difficulty() str\n }\n class Main {\n +main()\n }\n Main --> UI\n UI --> Game\n\n\n## Program call flow\n\n\nsequenceDiagram\n participant M as Main\n participant UI as UI\n participant G as Game\n M->>UI: start()\n UI->>UI: select_difficulty()\n UI-->>G: __init__(difficulty)\n G->>G: start_game()\n loop Game Loop\n UI->>UI: get_user_input(\"Enter your guess:\")\n UI-->>G: check_guess(guess)\n G->>UI: display_message(feedback)\n G->>UI: show_attempts(attempts)\n G->>UI: show_history(history)\n end\n G->>UI: display_message(\"Correct! Game over.\")\n UI->>M: main() # Game session ends\n\n\n## Anything UNCLEAR\n\nThe 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.\n\n### New Requirements\n{'Language': 'en_us', 'Programming Language': 'Python', 'Refined Requirements': 'Adding graphical interface functionality to enhance the user experience in the number-guessing game.', 'Project Name': 'number_guessing_game', 'Refined Product Goals': ['Ensure a user-friendly interface for the game with the new graphical interface', 'Provide a challenging yet enjoyable game experience with visual enhancements', 'Design the game to be easily extendable for future features, including graphical elements'], 'Refined User Stories': ['As a player, I want to interact with a graphical interface to guess numbers and receive visual feedback on my guesses', 'As a player, I want to easily select the difficulty level through the graphical interface', 'As a player, I want to visually track my previous guesses and the number of attempts in the graphical interface', 'As a player, I want to be congratulated with a visually appealing message when I guess the number correctly'], 'Competitive Analysis': ['Guess The Number Game A: Basic text interface, no difficulty levels', 'Number Master B: Has difficulty levels, but cluttered interface', 'Quick Guess C: Sleek design, but lacks performance tracking', 'NumGuess D: Good performance tracking, but not mobile-friendly', 'GuessIt E: Mobile-friendly, but too many ads', 'Perfect Guess F: Offers hints, but the hints are not very helpful', 'SmartGuesser G: Has a learning mode, but lacks a competitive edge', 'Graphical Guess H: Graphical interface, but poor user experience due to complex design'], 'Competitive Quadrant Chart': 'quadrantChart\\n title \"User Engagement and Game Complexity with Graphical Interface\"\\n x-axis \"Low Complexity\" --> \"High Complexity\"\\n y-axis \"Low Engagement\" --> \"High Engagement\"\\n quadrant-1 \"Too Simple\"\\n quadrant-2 \"Niche Appeal\"\\n quadrant-3 \"Complex & Unengaging\"\\n quadrant-4 \"Sweet Spot\"\\n \"Guess The Number Game A\": [0.2, 0.4]\\n \"Number Master B\": [0.5, 0.3]\\n \"Quick Guess C\": [0.6, 0.7]\\n \"NumGuess D\": [0.4, 0.6]\\n \"GuessIt E\": [0.7, 0.5]\\n \"Perfect Guess F\": [0.6, 0.4]\\n \"SmartGuesser G\": [0.8, 0.6]\\n \"Graphical Guess H\": [0.7, 0.3]\\n \"Our Target Product\": [0.5, 0.9]', 'Refined Requirement Analysis': ['The game should maintain its simplicity while integrating a graphical interface for enhanced engagement.', 'Immediate visual feedback is crucial for user satisfaction in the graphical interface.', 'The interface must be intuitive, allowing for easy navigation and selection of game options.', \"The graphical design should be clean and not detract from the game's core guessing mechanic.\"], 'Refined Requirement Pool': [['P0', 'Implement a graphical user interface (GUI) to replace the command-line interaction'], ['P0', 'Design a user interface that displays the game status, results, and feedback clearly with graphical elements'], ['P1', 'Incorporate interactive elements for selecting difficulty levels'], ['P1', \"Visualize the history of the player's guesses and the number of attempts within the game session\"], ['P2', 'Create animations for correct or incorrect guesses to enhance user feedback'], ['P2', 'Ensure the GUI is responsive and compatible with various screen sizes'], ['P2', \"Store and show the history of the player's guesses during a game session\"]], 'UI Design draft': 'The UI will feature a modern and minimalist design with a graphical number input field, a submit button with animations, and a dedicated area for visual feedback. It will include interactive elements to select the difficulty level and a visual display for the number of attempts and history of past guesses.', 'Anything UNCLEAR': ''}\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Refined Implementation Approach\": \"We will refine ...\",\n \"Refined File list\": [\n \"main.py\",\n \"game.py\",\n \"new_feature.py\"\n ],\n \"Refined Data structures and interfaces\": \"\\nclassDiagram\\n class Main {\\n -SearchEngine search_engine\\n +main() str\\n }\\n class SearchEngine {\\n -Index index\\n -Ranking ranking\\n -Summary summary\\n +search(query: str) str\\n }\\n class Index {\\n -KnowledgeBase knowledge_base\\n +create_index(data: dict)\\n +query_index(query: str) list\\n }\\n class Ranking {\\n +rank_results(results: list) list\\n }\\n class Summary {\\n +summarize_results(results: list) str\\n }\\n class KnowledgeBase {\\n +update(data: dict)\\n +fetch_data(query: str) dict\\n }\\n Main --> SearchEngine\\n SearchEngine --> Index\\n SearchEngine --> Ranking\\n SearchEngine --> Summary\\n Index --> KnowledgeBase\\n\",\n \"Refined Program call flow\": \"\\nsequenceDiagram\\n participant M as Main\\n participant SE as SearchEngine\\n participant I as Index\\n participant R as Ranking\\n participant S as Summary\\n participant KB as KnowledgeBase\\n M->>SE: search(query)\\n SE->>I: query_index(query)\\n I->>KB: fetch_data(query)\\n KB-->>I: return data\\n I-->>SE: return results\\n SE->>R: rank_results(results)\\n R-->>SE: return ranked_results\\n SE->>S: summarize_results(ranked_results)\\n S-->>SE: return summary\\n SE-->>M: return summary\\n\",\n \"Anything UNCLEAR\": \"Clarification needed on third-party API integration, ...\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Refined Implementation Approach: # Update and extend the original implementation approach to reflect the evolving challenges and requirements due to incremental development. Outline the steps involved in the implementation process with the detailed strategies.\n- Refined File list: typing.List[str] # Update and expand the original file list including only relative paths. Up to 2 files can be added.Ensure that the refined file list reflects the evolving structure of the project.\n- Refined Data structures and interfaces: # Update and extend the existing mermaid classDiagram code syntax to incorporate new classes, methods (including __init__), and functions with precise type annotations. Delineate additional relationships between classes, ensuring clarity and adherence to PEP8 standards.Retain content that is not related to incremental development but important for consistency and clarity.\n- Refined Program call flow: # Extend the existing sequenceDiagram code syntax with detailed information, accurately covering theCRUD and initialization of each object. Ensure correct syntax usage and reflect the incremental changes introducedin the classes and API defined above. Retain content that is not related to incremental development but important for consistency and clarity.\n- Anything UNCLEAR: # Mention unclear project aspects, then try to clarify it.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Refined Implementation Approach\": \"To accommodate the new graphical interface requirements, we will integrate the Tkinter library for Python to develop the GUI components. The implementation will involve designing the layout, creating interactive widgets for user input, and displaying visual feedback. We will also implement event-driven programming to handle user actions. The game logic will be encapsulated within the Game class, and the UI class will be responsible for rendering the game state graphically.\",\n \"Refined File list\": [\n \"main.py\",\n \"game.py\",\n \"ui.py\",\n \"gui.py\"\n ],\n \"Refined Data structures and interfaces\": \"\\nclassDiagram\\n class Game {\\n -int secret_number\\n -int min_range\\n -int max_range\\n -list attempts\\n +__init__(difficulty: str)\\n +start_game()\\n +check_guess(guess: int) str\\n +get_attempts() int\\n +get_history() list\\n }\\n class UI {\\n +start()\\n +display_message(message: str)\\n +get_user_input(prompt: str) str\\n +show_attempts(attempts: int)\\n +show_history(history: list)\\n +select_difficulty() str\\n }\\n class GUI {\\n +__init__(game: Game)\\n +render_main_window()\\n +update_feedback(message: str)\\n +update_attempts(attempts: int)\\n +update_history(history: list)\\n +get_difficulty_selection() str\\n +get_guess_from_user() int\\n }\\n class Main {\\n +main()\\n }\\n Main --> UI\\n UI --> Game\\n UI --> GUI\\n GUI --> Game\\n\",\n \"Refined Program call flow\": \"\\nsequenceDiagram\\n participant M as Main\\n participant UI as UI\\n participant G as Game\\n participant GUI as GUI\\n M->>UI: start()\\n UI->>GUI: render_main_window()\\n GUI->>GUI: get_difficulty_selection()\\n GUI-->>G: __init__(difficulty)\\n G->>G: start_game()\\n loop Game Loop\\n GUI->>GUI: get_guess_from_user()\\n GUI-->>G: check_guess(guess)\\n G->>GUI: update_feedback(feedback)\\n G->>GUI: update_attempts(attempts)\\n G->>GUI: update_history(history)\\n end\\n G->>GUI: update_feedback(\\\"Correct! Game over.\\\")\\n GUI->>M: main() # Game session ends\\n\",\n \"Anything UNCLEAR\": \"Clarification needed on whether the GUI should support touch input for devices with touch screens, and if so, what gestures need to be supported.\"\n}\n[/CONTENT]", + "\n## context\n\n### Legacy Content\n\n## Required Python packages\n\n- random==2.2.1\n\n## Required Other language third-party packages\n\n- No third-party dependencies required\n\n## Logic Analysis\n\n- ['game.py', 'Contains Game class with methods __init__, start_game, check_guess, get_attempts, get_history and uses random library for generating secret_number']\n- ['ui.py', 'Contains UI class with methods start, display_message, get_user_input, show_attempts, show_history, select_difficulty and interacts with Game class']\n- ['main.py', 'Contains Main class with method main that initializes UI class and starts the game loop']\n\n## Task list\n\n- game.py\n- ui.py\n- main.py\n\n## Full API spec\n\n\n\n## Shared Knowledge\n\n`game.py` contains the core game logic and is used by `ui.py` to interact with the user. `main.py` serves as the entry point to start the game.\n\n## Anything UNCLEAR\n\nThe 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.\n\n### New Requirements\n{'Refined Implementation Approach': 'To accommodate the new graphical user interface (GUI) requirements, we will leverage the Tkinter library, which is included with Python and supports the creation of a user-friendly GUI. The game logic will remain in Python, with Tkinter handling the rendering of the interface. We will ensure that the GUI is responsive and provides immediate visual feedback. The main game loop will be event-driven, responding to user inputs such as button clicks and difficulty selection.', 'Refined File list': ['main.py', 'game.py', 'ui.py', 'gui.py'], 'Refined Data structures and interfaces': '\\nclassDiagram\\n class Game {\\n -int secret_number\\n -int min_range\\n -int max_range\\n -list attempts\\n +__init__(difficulty: str)\\n +start_game()\\n +check_guess(guess: int) str\\n +get_attempts() int\\n +get_history() list\\n }\\n class UI {\\n +start()\\n +display_message(message: str)\\n +get_user_input(prompt: str) str\\n +show_attempts(attempts: int)\\n +show_history(history: list)\\n +select_difficulty() str\\n }\\n class GUI {\\n +__init__()\\n +setup_window()\\n +bind_events()\\n +update_feedback(message: str)\\n +update_attempts(attempts: int)\\n +update_history(history: list)\\n +show_difficulty_selector()\\n +animate_guess_result(correct: bool)\\n }\\n class Main {\\n +main()\\n }\\n Main --> UI\\n UI --> Game\\n UI --> GUI\\n GUI --> Game\\n', 'Refined Program call flow': '\\nsequenceDiagram\\n participant M as Main\\n participant UI as UI\\n participant G as Game\\n participant GU as GUI\\n M->>UI: start()\\n UI->>GU: setup_window()\\n GU->>GU: bind_events()\\n GU->>UI: select_difficulty()\\n UI-->>G: __init__(difficulty)\\n G->>G: start_game()\\n loop Game Loop\\n GU->>GU: show_difficulty_selector()\\n GU->>UI: get_user_input(\"Enter your guess:\")\\n UI-->>G: check_guess(guess)\\n G->>GU: update_feedback(feedback)\\n G->>GU: update_attempts(attempts)\\n G->>GU: update_history(history)\\n GU->>GU: animate_guess_result(correct)\\n end\\n G->>GU: update_feedback(\"Correct! Game over.\")\\n GU->>M: main() # Game session ends\\n', 'Anything UNCLEAR': ''}\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Required Python packages\": [\n \"flask==1.1.2\",\n \"bcrypt==3.2.0\"\n ],\n \"Required Other language third-party packages\": [\n \"No third-party dependencies required\"\n ],\n \"Refined Logic Analysis\": [\n [\n \"game.py\",\n \"Contains Game class and ... functions\"\n ],\n [\n \"main.py\",\n \"Contains main function, from game import Game\"\n ],\n [\n \"new_feature.py\",\n \"Introduces NewFeature class and related functions\"\n ],\n [\n \"utils.py\",\n \"Modifies existing utility functions to support incremental changes\"\n ]\n ],\n \"Refined Task list\": [\n \"new_feature.py\",\n \"utils\",\n \"game.py\",\n \"main.py\"\n ],\n \"Full API spec\": \"openapi: 3.0.0 ...\",\n \"Refined Shared Knowledge\": \"`new_module.py` enhances shared utility functions for improved code reusability and collaboration.\",\n \"Anything UNCLEAR\": \"Clarification needed on how to start and initialize third-party libraries.\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Required Python packages: typing.List[str] # Provide required Python packages in requirements.txt format.\n- Required Other language third-party packages: typing.List[str] # List down the required packages for languages other than Python.\n- Refined Logic Analysis: typing.List[typing.List[str]] # Review and refine the logic analysis by merging the Legacy Content and Incremental Content. Provide a comprehensive list of files with classes/methods/functions to be implemented or modified incrementally. Include dependency analysis, consider potential impacts on existing code, and document necessary imports.\n- Refined Task list: typing.List[str] # Review and refine the combined task list after the merger of Legacy Content and Incremental Content, and consistent with Refined File List. Ensure that tasks are organized in a logical and prioritized order, considering dependencies for a streamlined and efficient development process. \n- Full API spec: # Describe all APIs using OpenAPI 3.0 spec that may be used by both frontend and backend. If front-end and back-end communication is not required, leave it blank.\n- Refined Shared Knowledge: # Update and expand shared knowledge to reflect any new elements introduced. This includes common utility functions, configuration variables for team collaboration. Retain content that is not related to incremental development but important for consistency and clarity.\n- Anything UNCLEAR: # Mention any unclear aspects in the project management context and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Required Python packages\": [\n \"random==2.2.1\",\n \"tkinter==8.6\"\n ],\n \"Required Other language third-party packages\": [\n \"No third-party dependencies required\"\n ],\n \"Refined Logic Analysis\": [\n [\n \"game.py\",\n \"Contains Game class with methods __init__, start_game, check_guess, get_attempts, get_history and uses random library for generating secret_number\"\n ],\n [\n \"ui.py\",\n \"Contains UI class with methods start, display_message, get_user_input, show_attempts, show_history, select_difficulty and interacts with Game class\"\n ],\n [\n \"gui.py\",\n \"Contains GUI class with methods __init__, setup_window, bind_events, update_feedback, update_attempts, update_history, show_difficulty_selector, animate_guess_result and interacts with Game class\"\n ],\n [\n \"main.py\",\n \"Contains Main class with method main that initializes UI class and starts the game loop\"\n ]\n ],\n \"Refined Task list\": [\n \"game.py\",\n \"ui.py\",\n \"gui.py\",\n \"main.py\"\n ],\n \"Full API spec\": \"\",\n \"Refined Shared Knowledge\": \"`game.py` contains the core game logic and is used by `ui.py` to interact with the user. `main.py` serves as the entry point to start the game. `gui.py` will handle the graphical user interface using the Tkinter library, providing a responsive and user-friendly interface for the game.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]", + "\nNOTICE\nRole: You are a professional engineer; The main goal is to complete incremental development by combining legacy code and plan and Incremental Change, ensuring the integration of new features.\n\n# Context\n## User New Requirements\n\n\n## Code Plan And Change\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\"]}\n\n## Design\n{\"Refined Implementation Approach\": \"To accommodate the new graphical user interface (GUI) requirements, we will leverage the Tkinter library, which is included with Python and supports the creation of a user-friendly GUI. The game logic will remain in Python, with Tkinter handling the rendering of the interface. We will ensure that the GUI is responsive and provides immediate visual feedback. The main game loop will be event-driven, responding to user inputs such as button clicks and difficulty selection.\", \"Refined File list\": [\"main.py\", \"game.py\", \"ui.py\", \"gui.py\"], \"Refined Data structures and interfaces\": \"\\nclassDiagram\\n class Game {\\n -int secret_number\\n -int min_range\\n -int max_range\\n -list attempts\\n +__init__(difficulty: str)\\n +start_game()\\n +check_guess(guess: int) str\\n +get_attempts() int\\n +get_history() list\\n }\\n class UI {\\n +start()\\n +display_message(message: str)\\n +get_user_input(prompt: str) str\\n +show_attempts(attempts: int)\\n +show_history(history: list)\\n +select_difficulty() str\\n }\\n class GUI {\\n +__init__()\\n +setup_window()\\n +bind_events()\\n +update_feedback(message: str)\\n +update_attempts(attempts: int)\\n +update_history(history: list)\\n +show_difficulty_selector()\\n +animate_guess_result(correct: bool)\\n }\\n class Main {\\n +main()\\n }\\n Main --> UI\\n UI --> Game\\n UI --> GUI\\n GUI --> Game\\n\", \"Refined Program call flow\": \"\\nsequenceDiagram\\n participant M as Main\\n participant UI as UI\\n participant G as Game\\n participant GU as GUI\\n M->>UI: start()\\n UI->>GU: setup_window()\\n GU->>GU: bind_events()\\n GU->>UI: select_difficulty()\\n UI-->>G: __init__(difficulty)\\n G->>G: start_game()\\n loop Game Loop\\n GU->>GU: show_difficulty_selector()\\n GU->>UI: get_user_input(\\\"Enter your guess:\\\")\\n UI-->>G: check_guess(guess)\\n G->>GU: update_feedback(feedback)\\n G->>GU: update_attempts(attempts)\\n G->>GU: update_history(history)\\n GU->>GU: animate_guess_result(correct)\\n end\\n G->>GU: update_feedback(\\\"Correct! Game over.\\\")\\n GU->>M: main() # Game session ends\\n\", \"Anything UNCLEAR\": \"\"}\n\n## Task\n{\"Required Python packages\": [\"random==2.2.1\", \"Tkinter==8.6\"], \"Required Other language third-party packages\": [\"No third-party dependencies required\"], \"Refined Logic Analysis\": [[\"game.py\", \"Contains Game class with methods __init__, start_game, check_guess, get_attempts, get_history and uses random library for generating secret_number\"], [\"ui.py\", \"Contains UI class with methods start, display_message, get_user_input, show_attempts, show_history, select_difficulty and interacts with Game class\"], [\"gui.py\", \"Contains GUI class with methods __init__, setup_window, bind_events, update_feedback, update_attempts, update_history, show_difficulty_selector, animate_guess_result and interacts with Game class for GUI rendering\"], [\"main.py\", \"Contains Main class with method main that initializes UI class and starts the event-driven game loop\"]], \"Refined Task list\": [\"game.py\", \"ui.py\", \"gui.py\", \"main.py\"], \"Full API spec\": \"\", \"Refined Shared Knowledge\": \"`game.py` contains the core game logic and is used by `ui.py` to interact with the user. `main.py` serves as the entry point to start the game. `gui.py` is introduced to handle the graphical user interface using Tkinter, which will interact with both `game.py` and `ui.py` for a responsive and user-friendly experience.\", \"Anything UNCLEAR\": \"\"}\n\n## Legacy Code\n```Code\n-----Now, game.py to be rewritten\n```game.py\n\nimport random\n\nclass Game:\n def __init__(self, difficulty: str = 'medium'):\n self.min_range, self.max_range = self._set_difficulty(difficulty)\n self.secret_number = random.randint(self.min_range, self.max_range)\n self.attempts = []\n\n def _set_difficulty(self, difficulty: str):\n difficulties = {\n 'easy': (1, 10),\n 'medium': (1, 100),\n 'hard': (1, 1000)\n }\n return difficulties.get(difficulty, (1, 100))\n\n def start_game(self):\n self.secret_number = random.randint(self.min_range, self.max_range)\n self.attempts = []\n\n def check_guess(self, guess: int) -> str:\n self.attempts.append(guess)\n if guess < self.secret_number:\n return \"It's higher.\"\n elif guess > self.secret_number:\n return \"It's lower.\"\n else:\n return \"Correct! Game over.\"\n\n def get_attempts(self) -> int:\n return len(self.attempts)\n\n def get_history(self) -> list:\n return self.attempts```\n=====\n```\n\n## Debug logs\n```text\n\n\n\n```\n\n## Bug Feedback logs\n```text\n\n```\n\n# Format example\n## Code: game.py\n```python\n## game.py\n...\n```\n\n# Instruction: Based on the context, follow \"Format example\", write or rewrite code.\n## Write/Rewrite Code: Only write one file game.py, write or rewrite complete code using triple quotes based on the following attentions and context.\n1. Only One file: do your best to implement THIS ONLY ONE FILE.\n2. COMPLETE CODE: Your code will be part of the entire project, so please implement complete, reliable, reusable code snippets.\n3. Set default value: If there is any setting, ALWAYS SET A DEFAULT VALUE, ALWAYS USE STRONG TYPE AND EXPLICIT VARIABLE. AVOID circular import.\n4. Follow design: YOU MUST FOLLOW \"Data structures and interfaces\". DONT CHANGE ANY DESIGN. Do not use public member functions that do not exist in your design.\n5. Follow Code Plan And Change: If there is any \"Incremental Change\" that is marked by the git diff format with '+' and '-' symbols, or Legacy Code files contain \"game.py to be rewritten\", you must merge it into the code file according to the \"Development Plan\". \n6. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE.\n7. Before using a external variable/module, make sure you import it first.\n8. Write out EVERY CODE DETAIL, DON'T LEAVE TODO.\n9. Attention: Retain details that are not related to incremental development but are important for maintaining the consistency and clarity of the old code.\n": "```python\n## game.py\n\nimport random\n\nclass Game:\n def __init__(self, difficulty: str = 'medium'):\n self._min_range, self._max_range = self._set_difficulty(difficulty)\n self._secret_number = random.randint(self._min_range, self._max_range)\n self._attempts = []\n\n def _set_difficulty(self, difficulty: str):\n difficulties = {\n 'easy': (1, 10),\n 'medium': (1, 100),\n 'hard': (1, 1000)\n }\n return difficulties.get(difficulty, (1, 100))\n\n def start_game(self):\n self._secret_number = random.randint(self._min_range, self._max_range)\n self._attempts = []\n\n def check_guess(self, guess: int) -> str:\n self._attempts.append(guess)\n if guess < self._secret_number:\n return \"It's higher.\"\n elif guess > self._secret_number:\n return \"It's lower.\"\n else:\n return \"Correct! Game over.\"\n\n def get_attempts(self) -> int:\n return len(self._attempts)\n\n def get_history(self) -> list:\n return self._attempts\n```", + "\n# System\nRole: You are a professional software engineer, and your main task is to review and revise the code. You need to ensure that the code conforms to the google-style standards, is elegantly designed and modularized, easy to read and maintain.\nLanguage: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.\nATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced \"Format example\".\n\n# Context\n## User New Requirements\nNone\n\n## Code Plan And Change\n\n def add(a, b):\n- return a + \n+ return a + b\n \n\n## System Design\n编写一个从a加b的函数,返回a+b\n\n## Task\n\n\n## Code Files\n\n\n\n## Code to be Reviewed: \n```Code\n\n def add(a, b):\n return a + \n \n```\n\n\n\n# Format example 1\n## Code Review: \n1. No, we should fix the logic of class A due to ...\n2. ...\n3. ...\n4. No, function B is not implemented, ...\n5. ...\n6. ...\n\n## Actions\n1. Fix the `handle_events` method to update the game state only if a move is successful.\n ```python\n def handle_events(self):\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n return False\n if event.type == pygame.KEYDOWN:\n moved = False\n if event.key == pygame.K_UP:\n moved = self.game.move('UP')\n elif event.key == pygame.K_DOWN:\n moved = self.game.move('DOWN')\n elif event.key == pygame.K_LEFT:\n moved = self.game.move('LEFT')\n elif event.key == pygame.K_RIGHT:\n moved = self.game.move('RIGHT')\n if moved:\n # Update the game state only if a move was successful\n self.render()\n return True\n ```\n2. Implement function B\n\n## Code Review Result\nLBTM\n\n# Format example 2\n## Code Review: \n1. Yes.\n2. Yes.\n3. Yes.\n4. Yes.\n5. Yes.\n6. Yes.\n\n## Actions\npass\n\n## Code Review Result\nLGTM\n\n\n\n# Instruction: Based on the actual code situation, follow one of the \"Format example\". Return only 1 file under review.\n\n## Code Review: Ordered List. Based on the \"Code to be Reviewed\", provide key, clear, concise, and specific answer. If any answer is no, explain how to fix it step by step.\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyse it step by step.\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n3. Does the existing code follow the \"Data structures and interfaces\"?\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported\n6. Are methods from other files being reused correctly?\n\n## Actions: Ordered List. Things that should be done after CR, such as implementing class A and function B\n\n## Code Review Result: str. If the code doesn't have bugs, we don't need to rewrite it, so answer LGTM and stop. ONLY ANSWER LGTM/LBTM.\nLGTM/LBTM\n\n": "## Code Review:\n1. 是的,代码实现了按照要求从a加b的功能。\n2. 不完全正确,代码逻辑存在错误。在`add`函数中,`return`语句没有完整地返回a和b的和,缺少了变量b。\n3. 是的,现有代码遵循了\"数据结构和接口\"的原则。\n4. 所有函数都已实现,但`add`函数的实现不完整。\n5. 由于这是一个简单的加法函数,不需要导入额外的依赖。\n6. 代码中没有使用其他文件的方法。\n\n## Actions:\n1. 修正`add`函数,确保它可以正确返回a和b的和。\n ```python\n def add(a, b):\n return a + b\n ```\n\n## Code Review Result:\nLBTM", + "\n# System\nRole: You are a professional software engineer, and your main task is to review and revise the code. You need to ensure that the code conforms to the google-style standards, is elegantly designed and modularized, easy to read and maintain.\nLanguage: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.\nATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced \"Format example\".\n\n# Context\n## User New Requirements\nNone\n\n## Code Plan And Change\n\n def add(a, b):\n- return a + \n+ return a + b\n \n\n## System Design\n编写一个从a加b的函数,返回a+b\n\n## Task\n\n\n## Code Files\n\n\n\n## Code to be Reviewed: \n```Code\n\n def add(a, b):\n return a + \n \n```\n\n## Code Review:\n1. 是的,代码实现了按照要求从a加b的功能。\n2. 不完全正确,代码逻辑存在错误。在`add`函数中,`return`语句没有完整地返回a和b的和,缺少了变量b。\n3. 是的,现有代码遵循了\"数据结构和接口\"的原则。\n4. 所有函数都已实现,但`add`函数的实现不完整。\n5. 由于这是一个简单的加法函数,不需要导入额外的依赖。\n6. 代码中没有使用其他文件的方法。\n\n## Actions:\n1. 修正`add`函数,确保它可以正确返回a和b的和。\n ```python\n def add(a, b):\n return a + b\n ```\n\n## Code Review Result:\nLBTM\n\n# Instruction: rewrite code based on the Code Review and Actions\n## Rewrite Code: CodeBlock. If it still has some bugs, rewrite with triple quotes. Do your utmost to optimize THIS SINGLE FILE. Return all completed codes and prohibit the return of unfinished codes.\n```Code\n## \n...\n```\n": "## 重写代码:代码块\n```python\ndef add(a, b):\n return a + b\n```", + "\n# System\nRole: You are a professional software engineer, and your main task is to review and revise the code. You need to ensure that the code conforms to the google-style standards, is elegantly designed and modularized, easy to read and maintain.\nLanguage: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.\nATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced \"Format example\".\n\n# Context\n## User New Requirements\nNone\n\n## Code Plan And Change\n\n def add(a, b):\n- return a + \n+ return a + b\n \n\n## System Design\n编写一个从a加b的函数,返回a+b\n\n## Task\n\n\n## Code Files\n\n\n\n## Code to be Reviewed: \n```Code\ndef add(a, b):\n return a + b\n\n```\n\n\n\n# Format example 1\n## Code Review: \n1. No, we should fix the logic of class A due to ...\n2. ...\n3. ...\n4. No, function B is not implemented, ...\n5. ...\n6. ...\n\n## Actions\n1. Fix the `handle_events` method to update the game state only if a move is successful.\n ```python\n def handle_events(self):\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n return False\n if event.type == pygame.KEYDOWN:\n moved = False\n if event.key == pygame.K_UP:\n moved = self.game.move('UP')\n elif event.key == pygame.K_DOWN:\n moved = self.game.move('DOWN')\n elif event.key == pygame.K_LEFT:\n moved = self.game.move('LEFT')\n elif event.key == pygame.K_RIGHT:\n moved = self.game.move('RIGHT')\n if moved:\n # Update the game state only if a move was successful\n self.render()\n return True\n ```\n2. Implement function B\n\n## Code Review Result\nLBTM\n\n# Format example 2\n## Code Review: \n1. Yes.\n2. Yes.\n3. Yes.\n4. Yes.\n5. Yes.\n6. Yes.\n\n## Actions\npass\n\n## Code Review Result\nLGTM\n\n\n\n# Instruction: Based on the actual code situation, follow one of the \"Format example\". Return only 1 file under review.\n\n## Code Review: Ordered List. Based on the \"Code to be Reviewed\", provide key, clear, concise, and specific answer. If any answer is no, explain how to fix it step by step.\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyse it step by step.\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n3. Does the existing code follow the \"Data structures and interfaces\"?\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported\n6. Are methods from other files being reused correctly?\n\n## Actions: Ordered List. Things that should be done after CR, such as implementing class A and function B\n\n## Code Review Result: str. If the code doesn't have bugs, we don't need to rewrite it, so answer LGTM and stop. ONLY ANSWER LGTM/LBTM.\nLGTM/LBTM\n\n": "## Code Review: \n1. 是的,代码按照要求实现了从a加b的功能。\n2. 是的,代码逻辑完全正确。\n3. 是的,现有代码遵循了“数据结构和接口”原则。\n4. 是的,所有功能都已实现。\n5. 是的,此代码片段不需要额外的依赖导入,因为它使用的是Python的内置操作。\n6. 是的,此代码片段没有从其他文件重用方法,因此不适用。\n\n## Actions\npass\n\n## Code Review Result\nLGTM", + "\n## context\nPlease fix the bug in the code.\n\n-----\n\n## format example\n[CONTENT]\n{\n \"issue_type\": \"BUG\",\n \"reason\": \"...\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- issue_type: # Answer BUG/REQUIREMENT. If it is a bugfix, answer BUG, otherwise answer Requirement\n- reason: # Explain the reasoning process from question to answer\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"issue_type\": \"BUG\",\n \"reason\": \"The user has requested to fix a bug in the code, but has not provided the code or described the bug. Without this information, it is not possible to identify or fix the bug.\"\n}\n[/CONTENT]", + "\n## context\n\n### Legacy Content\n\n## Language\n\nen_us\n\n## Programming Language\n\nPython\n\n## Original Requirements\n\nMake a simple number guessing game\n\n## Product Goals\n\n- Ensure a user-friendly interface for the game\n- Provide a challenging yet enjoyable game experience\n- Design the game to be easily extendable for future features\n\n## User Stories\n\n- As a player, I want to guess numbers and receive feedback on whether my guess is too high or too low\n- As a player, I want to be able to set the difficulty level by choosing the range of possible numbers\n- As a player, I want to see my previous guesses to strategize my next guess\n- As a player, I want to know how many attempts it took me to guess the number once I get it right\n\n## Competitive Analysis\n\n- Guess The Number Game A: Basic text interface, no difficulty levels\n- Number Master B: Has difficulty levels, but cluttered interface\n- Quick Guess C: Sleek design, but lacks performance tracking\n- NumGuess D: Good performance tracking, but not mobile-friendly\n- GuessIt E: Mobile-friendly, but too many ads\n- Perfect Guess F: Offers hints, but the hints are not very helpful\n- SmartGuesser G: Has a learning mode, but lacks a competitive edge\n\n## Competitive Quadrant Chart\n\nquadrantChart\n title \"User Engagement and Game Complexity\"\n x-axis \"Low Complexity\" --> \"High Complexity\"\n y-axis \"Low Engagement\" --> \"High Engagement\"\n quadrant-1 \"Too Simple\"\n quadrant-2 \"Niche Appeal\"\n quadrant-3 \"Complex & Unengaging\"\n quadrant-4 \"Sweet Spot\"\n \"Guess The Number Game A\": [0.2, 0.4]\n \"Number Master B\": [0.5, 0.3]\n \"Quick Guess C\": [0.6, 0.7]\n \"NumGuess D\": [0.4, 0.6]\n \"GuessIt E\": [0.7, 0.5]\n \"Perfect Guess F\": [0.6, 0.4]\n \"SmartGuesser G\": [0.8, 0.6]\n \"Our Target Product\": [0.5, 0.8]\n\n## Requirement Analysis\n\nThe game should be simple yet engaging, allowing players of different skill levels to enjoy it. It should provide immediate feedback and track the player's performance. The game should also be designed with a clean and intuitive interface, and it should be easy to add new features in the future.\n\n## Requirement Pool\n\n- ['P0', 'Implement the core game logic to randomly select a number and allow the user to guess it']\n- ['P0', 'Design a user interface that displays the game status and results clearly']\n- ['P1', 'Add difficulty levels by varying the range of possible numbers']\n- ['P1', 'Keep track of and display the number of attempts for each game session']\n- ['P2', \"Store and show the history of the player's guesses during a game session\"]\n\n## UI Design draft\n\nThe UI will feature a clean and minimalist design with a number input field, submit button, and messages area to provide feedback. There will be options to select the difficulty level and a display showing the number of attempts and history of past guesses.\n\n## Anything UNCLEAR\n\n### New Requirements\n\nAdding graphical interface functionality to enhance the user experience in the number-guessing game. The existing number-guessing game currently relies on command-line input for numbers. The goal is to introduce a graphical interface to improve the game's usability and visual appeal\n\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"is_relative\": \"YES\",\n \"reason\": \"...\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- is_relative: # Answer YES/NO. If the requirement is related to the old PRD, answer YES, otherwise NO\n- reason: # Explain the reasoning process from question to answer\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"is_relative\": \"YES\",\n \"reason\": \"The new requirement to add a graphical interface functionality is related to the original PRD's goal of ensuring a user-friendly interface for the game. The addition of a graphical interface aligns with the original product goals of providing a challenging yet enjoyable game experience and designing the game to be easily extendable for future features. It specifically addresses the competitive analysis findings by enhancing usability and visual appeal, which were identified as shortcomings in some of the competitive products.\"\n}\n[/CONTENT]", + "\n## context\n\n### Legacy Content\n\n## Language\n\nen_us\n\n## Programming Language\n\nPython\n\n## Original Requirements\n\nMake a simple number guessing game\n\n## Product Goals\n\n- Ensure a user-friendly interface for the game\n- Provide a challenging yet enjoyable game experience\n- Design the game to be easily extendable for future features\n\n## User Stories\n\n- As a player, I want to guess numbers and receive feedback on whether my guess is too high or too low\n- As a player, I want to be able to set the difficulty level by choosing the range of possible numbers\n- As a player, I want to see my previous guesses to strategize my next guess\n- As a player, I want to know how many attempts it took me to guess the number once I get it right\n\n## Competitive Analysis\n\n- Guess The Number Game A: Basic text interface, no difficulty levels\n- Number Master B: Has difficulty levels, but cluttered interface\n- Quick Guess C: Sleek design, but lacks performance tracking\n- NumGuess D: Good performance tracking, but not mobile-friendly\n- GuessIt E: Mobile-friendly, but too many ads\n- Perfect Guess F: Offers hints, but the hints are not very helpful\n- SmartGuesser G: Has a learning mode, but lacks a competitive edge\n\n## Competitive Quadrant Chart\n\nquadrantChart\n title \"User Engagement and Game Complexity\"\n x-axis \"Low Complexity\" --> \"High Complexity\"\n y-axis \"Low Engagement\" --> \"High Engagement\"\n quadrant-1 \"Too Simple\"\n quadrant-2 \"Niche Appeal\"\n quadrant-3 \"Complex & Unengaging\"\n quadrant-4 \"Sweet Spot\"\n \"Guess The Number Game A\": [0.2, 0.4]\n \"Number Master B\": [0.5, 0.3]\n \"Quick Guess C\": [0.6, 0.7]\n \"NumGuess D\": [0.4, 0.6]\n \"GuessIt E\": [0.7, 0.5]\n \"Perfect Guess F\": [0.6, 0.4]\n \"SmartGuesser G\": [0.8, 0.6]\n \"Our Target Product\": [0.5, 0.8]\n\n## Requirement Analysis\n\nThe game should be simple yet engaging, allowing players of different skill levels to enjoy it. It should provide immediate feedback and track the player's performance. The game should also be designed with a clean and intuitive interface, and it should be easy to add new features in the future.\n\n## Requirement Pool\n\n- ['P0', 'Implement the core game logic to randomly select a number and allow the user to guess it']\n- ['P0', 'Design a user interface that displays the game status and results clearly']\n- ['P1', 'Add difficulty levels by varying the range of possible numbers']\n- ['P1', 'Keep track of and display the number of attempts for each game session']\n- ['P2', \"Store and show the history of the player's guesses during a game session\"]\n\n## UI Design draft\n\nThe UI will feature a clean and minimalist design with a number input field, submit button, and messages area to provide feedback. There will be options to select the difficulty level and a display showing the number of attempts and history of past guesses.\n\n## Anything UNCLEAR\n\n### New Requirements\n\nAdding graphical interface functionality to enhance the user experience in the number-guessing game. The existing number-guessing game currently relies on command-line input for numbers. The goal is to introduce a graphical interface to improve the game's usability and visual appeal\n\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Refined Requirements\": \"Create a 2048 game with a new feature that ...\",\n \"Project Name\": \"game_2048\",\n \"Refined Product Goals\": [\n \"Enhance user engagement through new features\",\n \"Optimize performance for scalability\",\n \"Integrate innovative UI enhancements\"\n ],\n \"Refined User Stories\": [\n \"As a player, I want to choose difficulty levels to challenge my skills\",\n \"As a player, I want a visually appealing score display after each game for a better gaming experience\",\n \"As a player, I want a convenient restart button displayed when I lose to quickly start a new game\",\n \"As a player, I want an enhanced and aesthetically pleasing UI to elevate the overall gaming experience\",\n \"As a player, I want the ability to play the game seamlessly on my mobile phone for on-the-go entertainment\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"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 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\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]\",\n \"Refined Requirement Analysis\": [\n \"Require add/update/modify ...\"\n ],\n \"Refined Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Refined Requirements: # Place the New user's original requirements here.\n- Project Name: # According to the content of \"Original Requirements,\" name the project using snake case style , like 'game_2048' or 'simple_crm.\n- Refined Product Goals: typing.List[str] # Update and expand the original product goals to reflect the evolving needs due to incremental development. Ensure that the refined goals align with the current project direction and contribute to its success.\n- Refined User Stories: typing.List[str] # Update and expand the original scenario-based user stories to reflect the evolving needs due to incremental development. Ensure that the refined user stories capture incremental features and improvements. \n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Refined Requirement Analysis: typing.List[str] # Review and refine the existing requirement analysis to align with the evolving needs of the project due to incremental development. Ensure the analysis comprehensively covers the new features and enhancements required for the refined project scope.\n- Refined Requirement Pool: typing.List[typing.List[str]] # List down the top 5 to 7 requirements with their priority (P0, P1, P2). Cover both legacy content and incremental content. Retain content unrelated to incremental development\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Refined Requirements\": \"Adding graphical interface functionality to enhance the user experience in the number-guessing game. The existing number-guessing game currently relies on command-line input for numbers. The goal is to introduce a graphical interface to improve the game's usability and visual appeal.\",\n \"Project Name\": \"graphical_number_guesser\",\n \"Refined Product Goals\": [\n \"Introduce a graphical user interface to make the game more accessible and visually appealing\",\n \"Maintain a user-friendly experience while transitioning from a command-line interface to a graphical interface\",\n \"Ensure the game remains challenging and enjoyable with the addition of the new interface\",\n \"Design the graphical interface to be easily extendable for future enhancements\"\n ],\n \"Refined User Stories\": [\n \"As a player, I want to interact with a graphical interface to input my guesses and receive feedback\",\n \"As a player, I want to easily select difficulty levels through the graphical interface\",\n \"As a player, I want to see a visual representation of my previous guesses and the number of attempts\",\n \"As a player, I want to enjoy a visually engaging experience that enhances the overall gameplay\"\n ],\n \"Competitive Analysis\": [\n \"Guess The Number Game A: Basic text interface, no difficulty levels\",\n \"Number Master B: Has difficulty levels, but cluttered interface\",\n \"Quick Guess C: Sleek design, but lacks performance tracking\",\n \"NumGuess D: Good performance tracking, but not mobile-friendly\",\n \"GuessIt E: Mobile-friendly, but too many ads\",\n \"Perfect Guess F: Offers hints, but the hints are not very helpful\",\n \"SmartGuesser G: Has a learning mode, but lacks a competitive edge\",\n \"Graphical Number Guesser: Our new game with a graphical interface and user-friendly design\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"User Engagement and Game Complexity\\\"\\n x-axis \\\"Low Complexity\\\" --> \\\"High Complexity\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"Too Simple\\\"\\n quadrant-2 \\\"Niche Appeal\\\"\\n quadrant-3 \\\"Complex & Unengaging\\\"\\n quadrant-4 \\\"Sweet Spot\\\"\\n \\\"Guess The Number Game A\\\": [0.2, 0.4]\\n \\\"Number Master B\\\": [0.5, 0.3]\\n \\\"Quick Guess C\\\": [0.6, 0.7]\\n \\\"NumGuess D\\\": [0.4, 0.6]\\n \\\"GuessIt E\\\": [0.7, 0.5]\\n \\\"Perfect Guess F\\\": [0.6, 0.4]\\n \\\"SmartGuesser G\\\": [0.8, 0.6]\\n \\\"Graphical Number Guesser\\\": [0.5, 0.8]\",\n \"Refined Requirement Analysis\": [\n \"The game must provide an intuitive graphical interface for user interaction\",\n \"The difficulty level selection should be seamlessly integrated into the graphical interface\",\n \"Performance tracking must be visually represented within the game interface\",\n \"The game should be designed to allow for easy updates and feature additions\"\n ],\n \"Refined Requirement Pool\": [\n [\"P0\", \"Implement a graphical user interface for the game\"],\n [\"P0\", \"Ensure the graphical interface provides feedback on guesses\"],\n [\"P1\", \"Incorporate difficulty level selection into the graphical interface\"],\n [\"P1\", \"Display a visual representation of guess history and attempt count\"],\n [\"P2\", \"Design the interface to be extensible for future game features\"]\n ],\n \"UI Design draft\": \"The graphical interface will feature a main game window with an input field for number guesses, buttons for submitting guesses and selecting difficulty, and panels for displaying guess history, attempt count, and feedback messages.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]" } \ No newline at end of file diff --git a/tests/metagpt/actions/mi/test_ask_review.py b/tests/metagpt/actions/di/test_ask_review.py similarity index 84% rename from tests/metagpt/actions/mi/test_ask_review.py rename to tests/metagpt/actions/di/test_ask_review.py index 92e8bd046..6bb1accf5 100644 --- a/tests/metagpt/actions/mi/test_ask_review.py +++ b/tests/metagpt/actions/di/test_ask_review.py @@ -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 diff --git a/tests/metagpt/actions/mi/test_debug_code.py b/tests/metagpt/actions/di/test_debug_code.py similarity index 96% rename from tests/metagpt/actions/mi/test_debug_code.py rename to tests/metagpt/actions/di/test_debug_code.py index 24cfcef10..67f72ad63 100644 --- a/tests/metagpt/actions/mi/test_debug_code.py +++ b/tests/metagpt/actions/di/test_debug_code.py @@ -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: diff --git a/tests/metagpt/actions/mi/test_execute_nb_code.py b/tests/metagpt/actions/di/test_execute_nb_code.py similarity index 98% rename from tests/metagpt/actions/mi/test_execute_nb_code.py rename to tests/metagpt/actions/di/test_execute_nb_code.py index 59a814054..b491dd212 100644 --- a/tests/metagpt/actions/mi/test_execute_nb_code.py +++ b/tests/metagpt/actions/di/test_execute_nb_code.py @@ -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 diff --git a/tests/metagpt/actions/mi/test_ml_action.py b/tests/metagpt/actions/di/test_ml_action.py similarity index 95% rename from tests/metagpt/actions/mi/test_ml_action.py rename to tests/metagpt/actions/di/test_ml_action.py index 27d47b0e3..826a7fcf2 100644 --- a/tests/metagpt/actions/mi/test_ml_action.py +++ b/tests/metagpt/actions/di/test_ml_action.py @@ -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 diff --git a/tests/metagpt/actions/mi/test_write_analysis_code.py b/tests/metagpt/actions/di/test_write_analysis_code.py similarity index 99% rename from tests/metagpt/actions/mi/test_write_analysis_code.py rename to tests/metagpt/actions/di/test_write_analysis_code.py index 6c2228222..d6bbae1c6 100644 --- a/tests/metagpt/actions/mi/test_write_analysis_code.py +++ b/tests/metagpt/actions/di/test_write_analysis_code.py @@ -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, ) diff --git a/tests/metagpt/actions/mi/test_write_plan.py b/tests/metagpt/actions/di/test_write_plan.py similarity index 95% rename from tests/metagpt/actions/mi/test_write_plan.py rename to tests/metagpt/actions/di/test_write_plan.py index 97632ea44..80b3399b8 100644 --- a/tests/metagpt/actions/mi/test_write_plan.py +++ b/tests/metagpt/actions/di/test_write_plan.py @@ -1,6 +1,6 @@ import pytest -from metagpt.actions.mi.write_plan import ( +from metagpt.actions.di.write_plan import ( Plan, Task, WritePlan, diff --git a/tests/metagpt/actions/mock_json.py b/tests/metagpt/actions/mock_json.py index 875d74d3c..2b354ca6f 100644 --- a/tests/metagpt/actions/mock_json.py +++ b/tests/metagpt/actions/mock_json.py @@ -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": [ diff --git a/tests/metagpt/actions/test_design_api.py b/tests/metagpt/actions/test_design_api.py index 7d3efa7ff..9924a2e84 100644 --- a/tests/metagpt/actions/test_design_api.py +++ b/tests/metagpt/actions/test_design_api.py @@ -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 diff --git a/tests/metagpt/actions/test_project_management.py b/tests/metagpt/actions/test_project_management.py index f3bb405ca..5d0d11efb 100644 --- a/tests/metagpt/actions/test_project_management.py +++ b/tests/metagpt/actions/test_project_management.py @@ -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 diff --git a/tests/metagpt/actions/test_project_management_an.py b/tests/metagpt/actions/test_project_management_an.py index ddbb56569..5a65e50c9 100644 --- a/tests/metagpt/actions/test_project_management_an.py +++ b/tests/metagpt/actions/test_project_management_an.py @@ -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() diff --git a/tests/metagpt/actions/test_write_code.py b/tests/metagpt/actions/test_write_code.py index ee05e0f7d..1709e1f5b 100644 --- a/tests/metagpt/actions/test_write_code.py +++ b/tests/metagpt/actions/test_write_code.py @@ -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"]) diff --git a/tests/metagpt/actions/test_write_code_plan_and_change_an.py b/tests/metagpt/actions/test_write_code_plan_and_change_an.py index 9cd51398f..5c262b4b7 100644 --- a/tests/metagpt/actions/test_write_code_plan_and_change_an.py +++ b/tests/metagpt/actions/test_write_code_plan_and_change_an.py @@ -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 diff --git a/tests/metagpt/actions/test_write_code_review.py b/tests/metagpt/actions/test_write_code_review.py index a08dd07bc..047d5e6ca 100644 --- a/tests/metagpt/actions/test_write_code_review.py +++ b/tests/metagpt/actions/test_write_code_review.py @@ -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"]) diff --git a/tests/metagpt/actions/test_write_prd.py b/tests/metagpt/actions/test_write_prd.py index 31d20018e..43aa336b7 100644 --- a/tests/metagpt/actions/test_write_prd.py +++ b/tests/metagpt/actions/test_write_prd.py @@ -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"]) diff --git a/tests/metagpt/provider/mock_llm_config.py b/tests/metagpt/provider/mock_llm_config.py index e75acf68f..0c56cc8ea 100644 --- a/tests/metagpt/provider/mock_llm_config.py +++ b/tests/metagpt/provider/mock_llm_config.py @@ -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" +) diff --git a/tests/metagpt/provider/req_resp_const.py b/tests/metagpt/provider/req_resp_const.py index 802962013..7e4c1a49c 100644 --- a/tests/metagpt/provider/req_resp_const.py +++ b/tests/metagpt/provider/req_resp_const.py @@ -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) diff --git a/tests/metagpt/provider/test_anthropic_api.py b/tests/metagpt/provider/test_anthropic_api.py index 93cfd7dbc..b8a3fe289 100644 --- a/tests/metagpt/provider/test_anthropic_api.py +++ b/tests/metagpt/provider/test_anthropic_api.py @@ -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) diff --git a/tests/metagpt/provider/test_base_llm.py b/tests/metagpt/provider/test_base_llm.py index cf44343bc..bff8dbde4 100644 --- a/tests/metagpt/provider/test_base_llm.py +++ b/tests/metagpt/provider/test_base_llm.py @@ -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 diff --git a/tests/metagpt/roles/mi/test_interpreter.py b/tests/metagpt/roles/di/test_data_interpreter.py similarity index 65% rename from tests/metagpt/roles/mi/test_interpreter.py rename to tests/metagpt/roles/di/test_data_interpreter.py index 3bae4a1ac..ba50f473b 100644 --- a/tests/metagpt/roles/mi/test_interpreter.py +++ b/tests/metagpt/roles/di/test_data_interpreter.py @@ -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 diff --git a/tests/metagpt/roles/mi/test_ml_engineer.py b/tests/metagpt/roles/di/test_ml_engineer.py similarity index 94% rename from tests/metagpt/roles/mi/test_ml_engineer.py rename to tests/metagpt/roles/di/test_ml_engineer.py index 921ac8822..08b92cd27 100644 --- a/tests/metagpt/roles/mi/test_ml_engineer.py +++ b/tests/metagpt/roles/di/test_ml_engineer.py @@ -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(): diff --git a/tests/metagpt/utils/test_save_code.py b/tests/metagpt/utils/test_save_code.py index 9df2650f3..aceecec3b 100644 --- a/tests/metagpt/utils/test_save_code.py +++ b/tests/metagpt/utils/test_save_code.py @@ -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 diff --git a/tests/mock/mock_llm.py b/tests/mock/mock_llm.py index 50e75dabf..b2052e2b3 100644 --- a/tests/mock/mock_llm.py +++ b/tests/mock/mock_llm.py @@ -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)