From e3fcdb3177ba37f165f405013128215acdf5d1cd Mon Sep 17 00:00:00 2001 From: brucemeek <113046530+brucemeek@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:06:11 -0500 Subject: [PATCH 1/5] Update debug_error.py --- metagpt/actions/debug_error.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/metagpt/actions/debug_error.py b/metagpt/actions/debug_error.py index c955f30ea..6b9fc744b 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 + From 4c85faec112d631b79fed6aab2616f4af9ed732b Mon Sep 17 00:00:00 2001 From: brucemeek <113046530+brucemeek@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:06:49 -0500 Subject: [PATCH 2/5] Update run_code.py --- metagpt/actions/run_code.py | 50 ++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/metagpt/actions/run_code.py b/metagpt/actions/run_code.py index 7fcf20975..74590968c 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 From 4b7b418d8d0136ad50fcda48a04137c07dd7e6f1 Mon Sep 17 00:00:00 2001 From: brucemeek <113046530+brucemeek@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:07:25 -0500 Subject: [PATCH 3/5] Update run_code.py --- metagpt/actions/run_code.py | 150 +++++++++++------------------------- 1 file changed, 46 insertions(+), 104 deletions(-) diff --git a/metagpt/actions/run_code.py b/metagpt/actions/run_code.py index 74590968c..60fc92154 100644 --- a/metagpt/actions/run_code.py +++ b/metagpt/actions/run_code.py @@ -1,122 +1,64 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -@Time : 2023/5/11 17:46 +@Time : 2023/5/11 22:12 @Author : alexanderwu -@File : run_code.py +@File : environment.py """ -import traceback -import os -import subprocess -from typing import List, Tuple +import asyncio +from typing import Iterable -from metagpt.logs import logger -from metagpt.actions.action import Action +from pydantic import BaseModel, Field -PROMPT_TEMPLATE = """ -Role: You are a senior development and qa engineer, your role is summarize the code running result. -If the running result does not include an error, you should explicitly approve the result. -On the other hand, if the running result indicates some error, you should point out which part, the development code or the test code, produces the error, -and give specific instructions on fixing the errors. Here is the code info: -{context} -Now you should begin your analysis ---- -## instruction: -Please summarize the cause of the errors and give correction instruction -## File To Rewrite: -Determine the ONE file to rewrite in order to fix the error, for example, xyz.py, or test_xyz.py -## Status: -Determine if all of the code works fine, if so write PASS, else FAIL, -WRITE ONLY ONE WORD, PASS OR FAIL, IN THI SECTION -## Send To: -Please write Engineer if the errors are due to problematic development codes, and QaEngineer to problematic test codes, and NoOne if there are no errors, -WRITE ONLY ONE WORD, Engineer OR QaEngineer OR NoOne, IN THIS SECTION. ---- -You should fill in necessary instruction, status, send to, and finally return all content between the --- segment line. -""" +from metagpt.memory import Memory +from metagpt.roles import Role +from metagpt.schema import Message -CONTEXT = """ -## Development Code File Name -{code_file_name} -## Development Code -```python -{code} -``` -## Test File Name -{test_file_name} -## Test Code -```python -{test_code} -``` -## Running Command -{command} -## Running Output -standard output: {outs}; -standard errors: {errs}; -""" -class RunCode(Action): - def __init__(self, name="RunCode", context=None, llm=None): - super().__init__(name, context, llm) +class Environment(BaseModel): + """Environment that carries a set of roles. Roles can publish messages to the environment, which can be observed by other roles.""" - @classmethod - async def run_text(cls, code) -> Tuple[str, str]: - try: - # We will document_store the result in this dictionary - namespace = {} - exec(code, namespace) - return namespace.get('result', ""), "" - except Exception: - # If there is an error in the code, return the error message - return "", traceback.format_exc() + roles: dict[str, Role] = Field(default_factory=dict) + memory: Memory = Field(default_factory=Memory) + history: str = Field(default='') - @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() + class Config: + arbitrary_types_allowed = True - # 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', '') + def add_role(self, role: Role): + """Add a Role to the current environment.""" + role.set_env(self) + self.roles[role.profile] = role - # Start the subprocess - process = subprocess.Popen(command, cwd=working_directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + def add_roles(self, roles: Iterable[Role]): + """Add a batch of Roles to the current environment.""" + for role in roles: + self.add_role(role) - 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') + 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, 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) + 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) - logger.info(f"{outs=}") - logger.info(f"{errs=}") + await asyncio.gather(*futures) - 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 - ) + def get_roles(self) -> dict[str, Role]: + """Get all Roles within the environment.""" + return self.roles - prompt = PROMPT_TEMPLATE.format(context=context) - rsp = await self._aask(prompt) - - result = context + rsp - - return result + def get_role(self, name: str) -> Role: + """Get a specified Role within the environment.""" + return self.roles.get(name, None) From 552c22d5da1973c3be0f4f3a9c273df84fe7987f Mon Sep 17 00:00:00 2001 From: brucemeek <113046530+brucemeek@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:10:08 -0500 Subject: [PATCH 4/5] Update llm.py --- metagpt/llm.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/metagpt/llm.py b/metagpt/llm.py index b8aefec61..c6755919d 100644 --- a/metagpt/llm.py +++ b/metagpt/llm.py @@ -1,17 +1,22 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -@Time : 2023/5/11 14:45 +@Time : 2023/6/1 12:41 @Author : alexanderwu -@File : llm.py +@File : logs.py """ -from metagpt.provider.anthropic_api import Claude2 as Claude -from metagpt.provider.openai_api import OpenAIGPTAPI as LLM +import sys -DEFAULT_LLM = LLM() -CLAUDE_LLM = Claude() +from loguru import logger as _logger -async def ai_func(prompt): - """Use LLM for Q&A.""" - return await DEFAULT_LLM.aask(prompt) +from metagpt.const import PROJECT_ROOT + +def define_log_level(print_level="INFO", logfile_level="DEBUG"): + """Adjust log level to above the specified level.""" + _logger.remove() + _logger.add(sys.stderr, level=print_level) + _logger.add(PROJECT_ROOT / 'logs/log.txt', level=logfile_level) + return _logger + +logger = define_log_level() From 57ffca3b5c4559ac28b257ad5cb45f584769440d Mon Sep 17 00:00:00 2001 From: brucemeek <113046530+brucemeek@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:10:45 -0500 Subject: [PATCH 5/5] Update llm.py pasted wrong code --- metagpt/llm.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/metagpt/llm.py b/metagpt/llm.py index c6755919d..b8aefec61 100644 --- a/metagpt/llm.py +++ b/metagpt/llm.py @@ -1,22 +1,17 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -@Time : 2023/6/1 12:41 +@Time : 2023/5/11 14:45 @Author : alexanderwu -@File : logs.py +@File : llm.py """ -import sys +from metagpt.provider.anthropic_api import Claude2 as Claude +from metagpt.provider.openai_api import OpenAIGPTAPI as LLM -from loguru import logger as _logger +DEFAULT_LLM = LLM() +CLAUDE_LLM = Claude() -from metagpt.const import PROJECT_ROOT - -def define_log_level(print_level="INFO", logfile_level="DEBUG"): - """Adjust log level to above the specified level.""" - _logger.remove() - _logger.add(sys.stderr, level=print_level) - _logger.add(PROJECT_ROOT / 'logs/log.txt', level=logfile_level) - return _logger - -logger = define_log_level() +async def ai_func(prompt): + """Use LLM for Q&A.""" + return await DEFAULT_LLM.aask(prompt)