From 076ce7bee6dcd24cf9cf62d41663d75b1d0062ab Mon Sep 17 00:00:00 2001 From: brucemeek <113046530+brucemeek@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:15:39 -0500 Subject: [PATCH] conflict resolutions --- .gitignore | 3 +- metagpt/actions/debug_error.py | 18 +----- metagpt/actions/run_code.py | 50 ++++++++++++++-- metagpt/actions/write_test.py | 104 +++++++++++++++++---------------- metagpt/roles/qa_engineer.py | 29 --------- metagpt/roles/role.py | 9 +-- metagpt/schema.py | 2 + metagpt/utils/common.py | 22 ++++++- 8 files changed, 132 insertions(+), 105 deletions(-) diff --git a/.gitignore b/.gitignore index 6abc90b43..6352a90e5 100644 --- a/.gitignore +++ b/.gitignore @@ -162,4 +162,5 @@ examples/nb/ workspace/* *.mmd tmp -output.wav \ No newline at end of file +output.wav +metagpt/roles/idea_agent.py diff --git a/metagpt/actions/debug_error.py b/metagpt/actions/debug_error.py index c955f30ea..78a9e2409 100644 --- a/metagpt/actions/debug_error.py +++ b/metagpt/actions/debug_error.py @@ -27,21 +27,7 @@ Now you should start rewriting the code: class DebugError(Action): def __init__(self, name="DebugError", context=None, llm=None): super().__init__(name, context, llm) - -<<<<<<< main - async def run(self, code, error): - prompt = f"Here is a piece of Python code:\n\n{code}\n\nThe following error occurred during execution:" \ - f"\n\n{error}\n\nPlease try to fix the error in this code." - fixed_code = await self._aask(prompt) - return fixed_code - -======= - # async def run(self, code, error): - # prompt = f"Here is a piece of Python code:\n\n{code}\n\nThe following error occurred during execution:" \ - # f"\n\n{error}\n\nPlease try to fix the error in this code." - # fixed_code = await self._aask(prompt) - # return fixed_code - + async def run(self, context): if "PASS" in context: return "", "the original code works fine, no need to debug" @@ -57,4 +43,4 @@ class DebugError(Action): code = CodeParser.parse_code(block="", text=rsp) return file_name, code ->>>>>>> main + \ No newline at end of file diff --git a/metagpt/actions/run_code.py b/metagpt/actions/run_code.py index 7fcf20975..0c8149c11 100644 --- a/metagpt/actions/run_code.py +++ b/metagpt/actions/run_code.py @@ -68,13 +68,55 @@ class RunCode(Action): return namespace.get('result', ""), "" except Exception: # If there is an error in the code, return the error message -<<<<<<< main - return traceback.format_exc() - -======= return "", traceback.format_exc() @classmethod async def run_script(cls, working_directory, additional_python_paths=[], command=[]) -> Tuple[str, str]: working_directory = str(working_directory) additional_python_paths = [str(path) for path in additional_python_paths] + # Copy the current environment variables + env = os.environ.copy() + + # Modify the PYTHONPATH environment variable + additional_python_paths = [working_directory] + additional_python_paths + additional_python_paths = ":".join(additional_python_paths) + env['PYTHONPATH'] = additional_python_paths + ':' + env.get('PYTHONPATH', '') + + # Start the subprocess + process = subprocess.Popen(command, cwd=working_directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + + try: + # Wait for the process to complete, with a timeout + stdout, stderr = process.communicate(timeout=10) + except subprocess.TimeoutExpired: + logger.info("The command did not complete within the given timeout.") + process.kill() # Kill the process if it times out + stdout, stderr = process.communicate() + return stdout.decode('utf-8'), stderr.decode('utf-8') + + async def run( + self, code, mode="script", code_file_name="", test_code="", test_file_name="", command=[], **kwargs + ) -> str: + logger.info(f"Running {' '.join(command)}") + if mode == "script": + outs, errs = await self.run_script(command=command, **kwargs) + elif mode == "text": + outs, errs = await self.run_text(code=code) + + logger.info(f"{outs=}") + logger.info(f"{errs=}") + + context = CONTEXT.format( + code=code, code_file_name=code_file_name, + test_code=test_code, test_file_name=test_file_name, + command=" ".join(command), + outs=outs[:500], # outs might be long but they are not important, truncate them to avoid token overflow + errs=errs[:10000] # truncate errors to avoid token overflow + ) + + prompt = PROMPT_TEMPLATE.format(context=context) + rsp = await self._aask(prompt) + + result = context + rsp + + return result \ No newline at end of file diff --git a/metagpt/actions/write_test.py b/metagpt/actions/write_test.py index 73d4c119d..60fc92154 100644 --- a/metagpt/actions/write_test.py +++ b/metagpt/actions/write_test.py @@ -1,58 +1,64 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -@Time : 2023/5/11 17:45 +@Time : 2023/5/11 22:12 @Author : alexanderwu -@File : write_test.py +@File : environment.py """ -from metagpt.logs import logger -from metagpt.actions.action import Action -from metagpt.utils.common import CodeParser +import asyncio +from typing import Iterable -PROMPT_TEMPLATE = """ -NOTICE -1. Role: You are a QA engineer; the main goal is to design, develop, and execute PEP8 compliant, well-structured, maintainable test cases and scripts for Python 3.9. Your focus should be on ensuring the product quality of the entire project through systematic testing. -2. Requirement: Based on the context, develop a comprehensive test suite that adequately covers all relevant aspects of the code file under review. Your test suite will be part of the overall project QA, so please develop complete, robust, and reusable test cases. -3. Attention1: Use '##' to split sections, not '#', and '## ' SHOULD WRITE BEFORE the test case or script. -4. Attention2: If there are any settings in your tests, ALWAYS SET A DEFAULT VALUE, ALWAYS USE STRONG TYPE AND EXPLICIT VARIABLE. -5. Attention3: YOU MUST FOLLOW "Data structures and interface definitions". DO NOT CHANGE ANY DESIGN. Make sure your tests respect the existing design and ensure its validity. -6. Think before writing: What should be tested and validated in this document? What edge cases could exist? What might fail? -7. CAREFULLY CHECK THAT YOU DON'T MISS ANY NECESSARY TEST CASES/SCRIPTS IN THIS FILE. -Attention: Use '##' to split sections, not '#', and '## ' SHOULD WRITE BEFORE the test case or script and triple quotes. ------ -## Given the following code, please write appropriate test cases using Python's unittest framework to verify the correctness and robustness of this code: -```python -{code_to_test} -``` -Note that the code to test is at {source_file_path}, we will put your test code at {workspace}/tests/{test_file_name}, and run your test code from {workspace}, -you should correctly import the necessary classes based on these file locations! -## {test_file_name}: Write test code with triple quoto. Do your best to implement THIS ONLY ONE FILE. -""" +from pydantic import BaseModel, Field -class WriteTest(Action): - def __init__(self, name="WriteTest", context=None, llm=None): - super().__init__(name, context, llm) +from metagpt.memory import Memory +from metagpt.roles import Role +from metagpt.schema import Message - async def write_code(self, prompt): - code_rsp = await self._aask(prompt) - code = CodeParser.parse_code(block="", text=code_rsp) - return code -<<<<<<< main - async def run(self, code): - self.code = code - prompt = self.test_prompt_template.format(code=self.code) - test_cases = await self._aask(prompt) - return test_cases - -======= - async def run(self, code_to_test, test_file_name, source_file_path, workspace): - prompt = PROMPT_TEMPLATE.format( - code_to_test=code_to_test, - test_file_name=test_file_name, - source_file_path=source_file_path, - workspace=workspace - ) - code = await self.write_code(prompt) - return code ->>>>>>> main +class Environment(BaseModel): + """Environment that carries a set of roles. Roles can publish messages to the environment, which can be observed by other roles.""" + + roles: dict[str, Role] = Field(default_factory=dict) + memory: Memory = Field(default_factory=Memory) + history: str = Field(default='') + + class Config: + arbitrary_types_allowed = True + + def add_role(self, role: Role): + """Add a Role to the current environment.""" + role.set_env(self) + self.roles[role.profile] = role + + def add_roles(self, roles: Iterable[Role]): + """Add a batch of Roles to the current environment.""" + for role in roles: + self.add_role(role) + + def publish_message(self, message: Message): + """Publish a message to the current environment.""" + # self.message_queue.put(message) + self.memory.add(message) + self.history += f"\n{message}" + + async def run(self, k=1): + """Process the run of all Roles once.""" + # while not self.message_queue.empty(): + # message = self.message_queue.get() + # rsp = await self.manager.handle(message, self) + # self.message_queue.put(rsp) + for _ in range(k): + futures = [] + for role in self.roles.values(): + future = role.run() + futures.append(future) + + await asyncio.gather(*futures) + + def get_roles(self) -> dict[str, Role]: + """Get all Roles within the environment.""" + return self.roles + + def get_role(self, name: str) -> Role: + """Get a specified Role within the environment.""" + return self.roles.get(name, None) diff --git a/metagpt/roles/qa_engineer.py b/metagpt/roles/qa_engineer.py index fe7f037b4..5e12a1abd 100644 --- a/metagpt/roles/qa_engineer.py +++ b/metagpt/roles/qa_engineer.py @@ -20,34 +20,6 @@ from metagpt.utils.common import CodeParser, parse_recipient from metagpt.utils.special_tokens import MSG_SEP, FILENAME_CODE_SEP class QaEngineer(Role): -<<<<<<< main - """ - Represents a Quality Assurance (QA) Engineer role responsible for writing tests to ensure software quality. - - Attributes: - name (str): Name of the QA engineer. - profile (str): Role profile. - goal (str): Goal of the QA engineer. - constraints (str): Constraints or limitations for the QA engineer. - """ - - def __init__(self, - name: str, - profile: str, - goal: str, - constraints: str) -> None: - """ - Initializes the QaEngineer role with given attributes. - - Args: - name (str): Name of the QA engineer. - profile (str): Role profile. - goal (str): Goal of the QA engineer. - constraints (str): Constraints or limitations for the QA engineer. - """ - super().__init__(name, profile, goal, constraints) - self._init_actions([WriteTest]) -======= def __init__(self, name="Edward", profile="QaEngineer", goal="Write comprehensive and robust tests to ensure codes will work as expected without bugs", constraints="The test code you write should conform to code standard like PEP8, be modular, easy to read and maintain", @@ -192,4 +164,3 @@ class QaEngineer(Role): role=self.profile, cause_by=WriteTest, sent_from=self.profile, send_to="" ) return result_msg ->>>>>>> main diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index fa2971ae7..b1ae51cf5 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -70,6 +70,7 @@ class RoleContext(BaseModel): state: int = Field(default=0) todo: Action = Field(default=None) watch: set[Type[Action]] = Field(default_factory=set) + news: list[Type[Message]] = Field(default=[]) class Config: arbitrary_types_allowed = True @@ -183,16 +184,16 @@ class Role: env_msgs = self._rc.env.memory.get() observed = self._rc.env.memory.get_by_actions(self._rc.watch) - - news = self._rc.memory.remember(observed) # remember recent exact or similar memories + + self._rc.news = self._rc.memory.remember(observed) # remember recent exact or similar memories for i in env_msgs: self.recv(i) - news_text = [f"{i.role}: {i.content[:20]}..." for i in news] + news_text = [f"{i.role}: {i.content[:20]}..." for i in self._rc.news] if news_text: logger.debug(f'{self._setting} observed: {news_text}') - return len(news) + return len(self._rc.news) def _publish_message(self, msg): """If the role belongs to env, then the role's messages will be broadcast to env""" diff --git a/metagpt/schema.py b/metagpt/schema.py index ebcd71d8a..8db2c4ac1 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -27,6 +27,8 @@ class Message: instruct_content: BaseModel = field(default=None) role: str = field(default='user') # system / user / assistant cause_by: Type["Action"] = field(default="") + sent_from: str = field(default="") + send_to: str = field(default="") def __str__(self): # prefix = '-'.join([self.role, str(self.cause_by)]) diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py index b61dbc2eb..f65b38366 100644 --- a/metagpt/utils/common.py +++ b/metagpt/utils/common.py @@ -6,6 +6,7 @@ @File : common.py """ import ast +import contextlib import inspect import os import re @@ -78,6 +79,23 @@ class OutputParser: else: tasks = text.split("\n") return tasks + + @staticmethod + def parse_python_code(text: str) -> str: + for pattern in ( + r'(.*?```python.*?\s+)?(?P.*)(```.*?)', + r'(.*?```python.*?\s+)?(?P.*)', + ): + match = re.search(pattern, text, re.DOTALL) + if not match: + continue + code = match.group("code") + if not code: + continue + with contextlib.suppress(Exception): + ast.parse(code) + return code + raise ValueError("Invalid python code") @classmethod def parse_data(cls, data): @@ -183,7 +201,7 @@ class CodeParser: def parse_file_list(cls, block: str, text: str, lang: str = "") -> list[str]: # Regular expression pattern to find the tasks list. code = cls.parse_code(block, text, lang) - print(code) + # print(code) pattern = r'\s*(.*=.*)?(\[.*\])' # Extract tasks list string using regex. @@ -229,4 +247,4 @@ def print_members(module, indent=0): elif inspect.isfunction(obj): print(f'{prefix}Function: {name}') elif inspect.ismethod(obj): - print(f'{prefix}Method: {name}') \ No newline at end of file + print(f'{prefix}Method: {name}')