mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-05-27 14:25:20 +02:00
fixbug: DebugError
This commit is contained in:
parent
10d9f33150
commit
75dcc8d534
6 changed files with 107 additions and 43 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue