fixbug: DebugError

This commit is contained in:
莘权 马 2023-11-24 13:30:00 +08:00
parent 10d9f33150
commit 75dcc8d534
6 changed files with 107 additions and 43 deletions

View file

@ -5,6 +5,7 @@
@Author : alexanderwu
@File : debug_error.py
"""
import re
from metagpt.actions.action import Action
from metagpt.logs import logger
@ -36,7 +37,9 @@ class DebugError(Action):
# return fixed_code
async def run(self, *args, **kwargs) -> str:
if "PASS" in self.context.output:
pattern = r"Ran (\d+) tests in ([\d.]+)s\n\nOK"
matches = re.search(pattern, self.context.output)
if matches:
return "", "the original code works fine, no need to debug"
file_name = self.context.code_filename

View file

@ -51,8 +51,14 @@ CONTEXT = """
## Running Command
{command}
## Running Output
standard output: {outs};
standard errors: {errs};
standard output:
```text
{outs}
```
standard errors:
```text
{errs}
```
"""
@ -84,10 +90,19 @@ class RunCode(Action):
additional_python_paths = ":".join(additional_python_paths)
env["PYTHONPATH"] = additional_python_paths + ":" + env.get("PYTHONPATH", "")
install_command = ["python", "-m", "pip", "install", "-r", "requirements.txt"]
logger.info(" ".join(install_command))
subprocess.run(install_command, check=True, cwd=working_directory, env=env)
install_pytest_command = ["python", "-m", "pip", "install", "pytest"]
logger.info(" ".join(install_pytest_command))
subprocess.run(install_pytest_command, check=True, cwd=working_directory, env=env)
# Start the subprocess
process = subprocess.Popen(
command, cwd=working_directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env
)
logger.info(" ".join(command))
try:
# Wait for the process to complete, with a timeout
@ -101,7 +116,11 @@ class RunCode(Action):
async def run(self, *args, **kwargs) -> str:
logger.info(f"Running {' '.join(self.context.command)}")
if self.context.mode == "script":
outs, errs = await self.run_script(command=self.context.command, **kwargs)
outs, errs = await self.run_script(
command=self.context.command,
working_directory=self.context.working_directory,
additional_python_paths=self.context.additional_python_paths,
)
elif self.context.mode == "text":
outs, errs = await self.run_text(code=self.context.code)

View file

@ -7,15 +7,13 @@
@Modified By: mashenquan, 2023-11-1. In accordance with Chapter 2.2.1 and 2.2.2 of RFC 116, modify the data
type of the `cause_by` value in the `Message` to a string, and utilize the new message filtering feature.
"""
import json
from metagpt.actions import DebugError, RunCode, WriteCode, WriteCodeReview, WriteTest
from metagpt.config import CONFIG
from metagpt.const import MESSAGE_ROUTE_TO_NONE, OUTPUTS_FILE_REPO, TEST_CODES_FILE_REPO
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Document, Message, RunCodeContext, TestingContext
from metagpt.utils.common import any_to_str_set
from metagpt.utils.common import any_to_str_set, parse_recipient
class QaEngineer(Role):
@ -64,68 +62,76 @@ class QaEngineer(Role):
code_filename=context.code_doc.filename,
test_filename=context.test_doc.filename,
working_directory=str(CONFIG.git_repo.workdir),
additional_python_paths=[CONFIG.src_workspace],
additional_python_paths=[str(CONFIG.src_workspace)],
)
msg = Message(
content=run_code_context.json(),
role=self.profile,
cause_by=WriteTest,
sent_from=self,
send_to=self,
self.publish_message(
Message(
content=run_code_context.json(),
role=self.profile,
cause_by=WriteTest,
sent_from=self,
send_to=self,
)
)
self.publish_message(msg)
logger.info(f"Done {str(tests_file_repo.workdir)} generating.")
async def _run_code(self, msg):
m = json.loads(msg.content)
run_code_context = RunCodeContext(**m)
src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace)
src_doc = await src_file_repo.get(run_code_context.code_filename)
run_code_context = RunCodeContext.loads(msg.content)
src_doc = await CONFIG.git_repo.new_file_repository(CONFIG.src_workspace).get(run_code_context.code_filename)
if not src_doc:
return
test_file_repo = CONFIG.git_repo.new_file_repository(TEST_CODES_FILE_REPO)
test_doc = await test_file_repo.get(run_code_context.test_filename)
test_doc = await CONFIG.git_repo.new_file_repository(TEST_CODES_FILE_REPO).get(run_code_context.test_filename)
if not test_doc:
return
run_code_context.code = src_doc.content
run_code_context.test_code = test_doc.content
result_msg = await RunCode(context=run_code_context, llm=self._llm).run()
outputs_file_repo = CONFIG.git_repo.new_file_repository(OUTPUTS_FILE_REPO)
run_code_context.output_filename = run_code_context.test_filename + ".log"
await outputs_file_repo.save(
run_code_context.output_filename = run_code_context.test_filename + ".md"
await CONFIG.git_repo.new_file_repository(OUTPUTS_FILE_REPO).save(
filename=run_code_context.output_filename,
content=result_msg,
dependencies={src_doc.root_relative_path, test_doc.root_relative_path},
)
run_code_context.code = None
run_code_context.test_code = None
msg = Message(
content=run_code_context.json(), role=self.profile, cause_by=RunCode, sent_from=self, send_to=self
recipient = parse_recipient(result_msg) # the recipient might be Engineer or myself
mappings = {
"Engineer": "Alex",
"QaEngineer": "Edward",
}
self.publish_message(
Message(
content=run_code_context.json(),
role=self.profile,
cause_by=RunCode,
sent_from=self,
send_to=mappings.get(recipient, MESSAGE_ROUTE_TO_NONE),
)
)
self.publish_message(msg)
async def _debug_error(self, msg):
m = json.loads(msg.context)
run_code_context = RunCodeContext(**m)
run_code_context = RunCodeContext.loads(msg.content)
output_file_repo = CONFIG.git_repo.new_file_repository(OUTPUTS_FILE_REPO)
output_doc = await output_file_repo.get(run_code_context.output_filename)
if not output_doc:
return
run_code_context.output = output_doc.content
code = await DebugError(context=run_code_context, llm=self._llm).run()
src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace)
await src_file_repo.save(filename=run_code_context.code_filename, content=code)
await CONFIG.git_repo.new_file_repository(CONFIG.src_workspace).save(
filename=run_code_context.code_filename, content=code
)
run_code_context.output = None
run_code_context.output_filename = None
msg = Message(
content=run_code_context.json(),
role=self.profile,
cause_by=DebugError,
sent_from=self,
send_to=self,
self.publish_message(
Message(
content=run_code_context.json(),
role=self.profile,
cause_by=DebugError,
sent_from=self,
send_to=self,
)
)
self.publish_message(msg)
async def _act(self) -> Message:
if self.test_round > self.test_round_allowed:
@ -154,11 +160,10 @@ class QaEngineer(Role):
# I ran my test code, time to fix bugs, if any
await self._debug_error(msg)
self.test_round += 1
result_msg = Message(
return Message(
content=f"Round {self.test_round} of tests done",
role=self.profile,
cause_by=WriteTest,
sent_from=self.profile,
send_to=MESSAGE_ROUTE_TO_NONE,
)
return result_msg

View file

@ -253,12 +253,28 @@ class CodingContext(BaseModel):
task_doc: Document
code_doc: Document
@staticmethod
def loads(val: str) -> CodingContext | None:
try:
m = json.loads(val)
return CodingContext(**m)
except Exception:
return None
class TestingContext(BaseModel):
filename: str
code_doc: Document
test_doc: Document
@staticmethod
def loads(val: str) -> TestingContext | None:
try:
m = json.loads(val)
return TestingContext(**m)
except Exception:
return None
class RunCodeContext(BaseModel):
mode: str = "script"
@ -271,3 +287,11 @@ class RunCodeContext(BaseModel):
additional_python_paths: List[str] = Field(default_factory=list)
output_filename: Optional[str]
output: Optional[str]
@staticmethod
def loads(val: str) -> RunCodeContext | None:
try:
m = json.loads(val)
return RunCodeContext(**m)
except Exception:
return None

View file

@ -304,7 +304,13 @@ def print_members(module, indent=0):
def parse_recipient(text):
pattern = r"## Send To:\s*([A-Za-z]+)\s*?" # hard code for now
recipient = re.search(pattern, text)
return recipient.group(1) if recipient else ""
if recipient:
return recipient.group(1)
pattern = r"Send To:\s*([A-Za-z]+)\s*?"
recipient = re.search(pattern, text)
if recipient:
return recipient.group(1)
return ""
def get_class_name(cls) -> str:

View file

@ -96,8 +96,15 @@ class FileRepository:
path_name = self.workdir / filename
if not path_name.exists():
return None
async with aiofiles.open(str(path_name), mode="r") as reader:
doc.content = await reader.read()
try:
async with aiofiles.open(str(path_name), mode="r") as reader:
doc.content = await reader.read()
except FileNotFoundError as e:
logger.info(f"open {str(path_name)} failed:{e}")
return None
except Exception as e:
logger.info(f"open {str(path_name)} failed:{e}")
return None
return doc
async def get_all(self) -> List[Document]: