feat: +SummarizeCode, refactor project_name

This commit is contained in:
莘权 马 2023-12-04 23:04:07 +08:00
parent 838b3cfcc8
commit f7fd3e4ab8
12 changed files with 219 additions and 98 deletions

View file

@ -13,6 +13,8 @@
@Modified By: mashenquan, 2023-11-27.
1. According to Section 2.2.3.1 of RFC 135, replace file data in the message with the file name.
2. According to the design in Section 2.2.3.5.5 of RFC 135, add incremental iteration functionality.
@Modified By: mashenquan, 2023-12-5. Enhance the workflow to navigate to WriteCode or QaEngineer based on the results
of SummarizeCode.
"""
from __future__ import annotations
@ -23,7 +25,8 @@ from typing import Set
from metagpt.actions import Action, WriteCode, WriteCodeReview, WriteTasks
from metagpt.actions.summarize_code import SummarizeCode
from metagpt.config import CONFIG
from metagpt.const import MESSAGE_ROUTE_TO_NONE, SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO
from metagpt.const import SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO, CODE_SUMMARIES_FILE_REPO, \
CODE_SUMMARIES_PDF_FILE_REPO
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import (
@ -33,6 +36,16 @@ from metagpt.schema import (
Documents,
Message,
)
from metagpt.utils.common import any_to_str_set, any_to_str
IS_PASS_PROMPT = """
{context}
----
Does the above log indicate anything that needs to be done?
If there are any tasks to be completed, please answer 'NO' along with the to-do list in JSON format;
otherwise, answer 'YES' in JSON format.
"""
class Engineer(Role):
@ -49,18 +62,18 @@ class Engineer(Role):
"""
def __init__(
self,
name: str = "Alex",
profile: str = "Engineer",
goal: str = "Write elegant, readable, extensible, efficient code",
constraints: str = "The code should conform to standards like PEP8 and be modular and maintainable",
n_borg: int = 1,
use_code_review: bool = False,
self,
name: str = "Alex",
profile: str = "Engineer",
goal: str = "Write elegant, readable, extensible, efficient code",
constraints: str = "The code should conform to standards like PEP8 and be modular and maintainable",
n_borg: int = 1,
use_code_review: bool = False,
) -> None:
"""Initializes the Engineer role with given attributes."""
super().__init__(name, profile, goal, constraints)
self.use_code_review = use_code_review
self._watch([WriteTasks])
self._watch([WriteTasks, SummarizeCode, WriteCode, WriteCodeReview])
self.code_todos = []
self.summarize_todos = []
self.n_borg = n_borg
@ -105,43 +118,85 @@ class Engineer(Role):
if self._rc.todo is None:
return None
if isinstance(self._rc.todo, WriteCode):
changed_files = await self._act_sp_with_cr(review=self.use_code_review)
# Unit tests only.
if CONFIG.REQA_FILENAME and CONFIG.REQA_FILENAME not in changed_files:
changed_files.add(CONFIG.REQA_FILENAME)
return Message(
content="\n".join(changed_files),
role=self.profile,
cause_by=WriteCodeReview if self.use_code_review else WriteCode,
send_to="Edward", # The name of QaEngineer
)
return await self._act_write_code()
if isinstance(self._rc.todo, SummarizeCode):
summaries = []
for todo in self.summarize_todos:
summary = await todo.run()
summaries.append(summary.json(ensure_ascii=False))
return await self._act_summarize()
return None
async def _act_write_code(self):
changed_files = await self._act_sp_with_cr(review=self.use_code_review)
return Message(
content="\n".join(changed_files),
role=self.profile,
cause_by=WriteCodeReview if self.use_code_review else WriteCode,
send_to=self,
sent_from=self
)
async def _act_summarize(self):
code_summaries_file_repo = CONFIG.git_repo.new_file_repository(CODE_SUMMARIES_FILE_REPO)
code_summaries_pdf_file_repo = CONFIG.git_repo.new_file_repository(CODE_SUMMARIES_PDF_FILE_REPO)
tasks = []
src_relative_path = CONFIG.src_workspace.relative_to(CONFIG.git_repo.workdir)
for todo in self.summarize_todos:
summary = await todo.run()
summary_filename = Path(todo.context.design_filename).with_suffix(".md").name
dependencies = {todo.context.design_filename, todo.context.task_filename}
for filename in todo.context.codes_filenames:
rpath = src_relative_path / filename
dependencies.add(str(rpath))
await code_summaries_pdf_file_repo.save(filename=summary_filename, content=summary,
dependencies=dependencies)
is_pass, reason = await self._is_pass(summary)
if not is_pass:
todo.context.reason = reason
tasks.append(todo.context.dict())
await code_summaries_file_repo.save(filename=Path(todo.context.design_filename).name,
content=todo.context.json(), dependencies=dependencies)
if not tasks:
return Message(
content="\n".join(summaries),
content="",
role=self.profile,
cause_by=SummarizeCode,
send_to=MESSAGE_ROUTE_TO_NONE,
sent_from=self,
send_to="Edward", # The name of QaEngineer
)
return None
return Message(
content=json.dumps(tasks),
role=self.profile,
cause_by=SummarizeCode,
send_to=self,
sent_from=self
)
async def _is_pass(self, summary) -> (str, str):
msgs = [{"role": "user", "content": IS_PASS_PROMPT.format(context=summary)}]
rsp = await self._llm.acompletion_text(messages=msgs, stream=False)
logger.info(rsp)
if "YES" in rsp:
return True, rsp
return False, rsp
async def _think(self) -> Action | None:
if not CONFIG.src_workspace:
CONFIG.src_workspace = CONFIG.git_repo.workdir / CONFIG.git_repo.workdir.name
if not self.code_todos:
await self._new_code_actions()
elif not self.summarize_todos:
await self._new_summarize_actions()
else:
write_code_filters = any_to_str_set([WriteTasks, SummarizeCode])
summarize_code_filters = any_to_str_set([WriteCode, WriteCodeReview])
if not self._rc.news:
return None
return self._rc.todo # For agent store
msg = self._rc.news[0]
if msg.cause_by in write_code_filters:
await self._new_code_actions()
return self._rc.todo
if msg.cause_by in summarize_code_filters and msg.sent_from == any_to_str(self):
await self._new_summarize_actions()
return self._rc.todo
return None
@staticmethod
async def _new_coding_context(
filename, src_file_repo, task_file_repo, design_file_repo, dependency
filename, src_file_repo, task_file_repo, design_file_repo, dependency
) -> CodingContext:
old_code_doc = await src_file_repo.get(filename)
if not old_code_doc:
@ -216,16 +271,16 @@ class Engineer(Role):
async def _new_summarize_actions(self):
src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace)
changed_src_files = src_file_repo.changed_files
src_files = src_file_repo.all_files
# Generate a SummarizeCode action for each pair of (system_design_doc, task_doc).
summarizations = {}
for filename in changed_src_files:
dependencies = src_file_repo.get_dependency(filename=filename)
for filename in src_files:
dependencies = await src_file_repo.get_dependency(filename=filename)
ctx = CodeSummarizeContext.loads(filenames=dependencies)
if ctx not in summarizations:
summarizations[ctx] = set()
summarizations[ctx] = []
srcs = summarizations.get(ctx)
srcs.add(filename)
srcs.append(filename)
for ctx, filenames in summarizations.items():
ctx.codes_filenames = filenames
self.summarize_todos.append(SummarizeCode(context=ctx, llm=self._llm))

View file

@ -11,10 +11,13 @@
WriteTest/RunCode/DebugError object, rather than passing them in when calling the run function.
2. According to Section 2.2.3.5.7 of RFC 135, change the method of transferring files from using the Message
to using file references.
@Modified By: mashenquan, 2023-12-5. Enhance the workflow to navigate to WriteCode or QaEngineer based on the results
of SummarizeCode.
"""
from metagpt.actions import DebugError, RunCode, WriteCode, WriteCodeReview, WriteTest
# from metagpt.const import WORKSPACE_ROOT
from metagpt.actions.summarize_code import SummarizeCode
from metagpt.config import CONFIG
from metagpt.const import (
MESSAGE_ROUTE_TO_NONE,
@ -40,13 +43,16 @@ class QaEngineer(Role):
self._init_actions(
[WriteTest]
) # FIXME: a bit hack here, only init one action to circumvent _think() logic, will overwrite _think() in future updates
self._watch([WriteCode, WriteCodeReview, WriteTest, RunCode, DebugError])
self._watch([SummarizeCode, WriteTest, RunCode, DebugError])
self.test_round = 0
self.test_round_allowed = test_round_allowed
async def _write_test(self, message: Message) -> None:
changed_files = message.content.splitlines()
src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace)
changed_files = set(src_file_repo.changed_files.keys())
# Unit tests only.
if CONFIG.reqa_file and CONFIG.reqa_file not in changed_files:
changed_files.add(CONFIG.reqa_file)
tests_file_repo = CONFIG.git_repo.new_file_repository(TEST_CODES_FILE_REPO)
for filename in changed_files:
# write tests
@ -146,7 +152,7 @@ class QaEngineer(Role):
)
return result_msg
code_filters = any_to_str_set({WriteCode, WriteCodeReview})
code_filters = any_to_str_set({SummarizeCode})
test_filters = any_to_str_set({WriteTest, DebugError})
run_filters = any_to_str_set({RunCode})
for msg in self._rc.news:

View file

@ -284,9 +284,10 @@ class Role:
instruct_content=response.instruct_content,
role=self.profile,
cause_by=self._rc.todo,
sent_from=self,
)
else:
msg = Message(content=response, role=self.profile, cause_by=self._rc.todo)
msg = Message(content=response, role=self.profile, cause_by=self._rc.todo, sent_from=self)
self._rc.memory.add(msg)
return msg