mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-08 15:05:17 +02:00
Merge branch 'feature/refine_msg_content' into 'mgx_ops'
refactor: 清空了软件公司message中的无用内容 See merge request pub/MetaGPT!95
This commit is contained in:
commit
5783835364
11 changed files with 76 additions and 64 deletions
|
|
@ -13,7 +13,7 @@ import json
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from metagpt.actions import Action, ActionOutput
|
||||
from metagpt.actions import Action
|
||||
from metagpt.actions.design_api_an import (
|
||||
DATA_STRUCTURES_AND_INTERFACES,
|
||||
DESIGN_API_NODE,
|
||||
|
|
@ -24,7 +24,7 @@ from metagpt.actions.design_api_an import (
|
|||
)
|
||||
from metagpt.const import DATA_API_DESIGN_FILE_REPO, SEQ_FLOW_FILE_REPO
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import Document, Documents, Message
|
||||
from metagpt.schema import AIMessage, Document, Documents, Message
|
||||
from metagpt.utils.mermaid import mermaid_to_file
|
||||
from metagpt.utils.report import DocsReporter, GalleryReporter
|
||||
|
||||
|
|
@ -68,7 +68,15 @@ class WriteDesign(Action):
|
|||
logger.info("Nothing has changed.")
|
||||
# Wait until all files under `docs/system_designs/` are processed before sending the publish message,
|
||||
# leaving room for global optimization in subsequent steps.
|
||||
return ActionOutput(content=changed_files.model_dump_json(), instruct_content=changed_files)
|
||||
return AIMessage(
|
||||
content="Designing is complete. "
|
||||
+ "\n".join(
|
||||
list(self.repo.docs.system_design.changed_files.keys())
|
||||
+ list(self.repo.resources.data_api_design.changed_files.keys())
|
||||
+ list(self.repo.resources.seq_flow.changed_files.keys())
|
||||
),
|
||||
cause_by=self,
|
||||
)
|
||||
|
||||
async def _new_system_design(self, context):
|
||||
node = await DESIGN_API_NODE.fill(context=context, llm=self.llm, schema=self.prompt_schema)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from typing import Dict, Optional
|
|||
|
||||
from metagpt.actions import Action, UserRequirement
|
||||
from metagpt.const import REQUIREMENT_FILENAME
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import AIMessage
|
||||
from metagpt.utils.common import any_to_str
|
||||
from metagpt.utils.file_repository import FileRepository
|
||||
|
|
@ -56,6 +57,7 @@ class PrepareDocuments(Action):
|
|||
if not v or k in ["resources", "reason"]:
|
||||
continue
|
||||
self.context.kwargs.set(k, v)
|
||||
logger.info(f"{k}={v}")
|
||||
if self.context.kwargs.project_path:
|
||||
self.config.update_via_cli(
|
||||
project_path=self.context.kwargs.project_path,
|
||||
|
|
|
|||
|
|
@ -14,11 +14,10 @@ import json
|
|||
from typing import Optional
|
||||
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.actions.action_output import ActionOutput
|
||||
from metagpt.actions.project_management_an import PM_NODE, REFINED_PM_NODE
|
||||
from metagpt.const import PACKAGE_REQUIREMENTS_FILENAME
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import Document, Documents
|
||||
from metagpt.schema import AIMessage, Document, Documents
|
||||
from metagpt.utils.report import DocsReporter
|
||||
|
||||
NEW_REQ_TEMPLATE = """
|
||||
|
|
@ -55,7 +54,15 @@ class WriteTasks(Action):
|
|||
logger.info("Nothing has changed.")
|
||||
# Wait until all files under `docs/tasks/` are processed before sending the publish_message, leaving room for
|
||||
# global optimization in subsequent steps.
|
||||
return ActionOutput(content=change_files.model_dump_json(), instruct_content=change_files)
|
||||
return AIMessage(
|
||||
content="WBS is completed. "
|
||||
+ "\n".join(
|
||||
[PACKAGE_REQUIREMENTS_FILENAME]
|
||||
+ list(self.repo.docs.task.changed_files.keys())
|
||||
+ list(self.repo.resources.api_spec_and_task.changed_files.keys())
|
||||
),
|
||||
cause_by=self,
|
||||
)
|
||||
|
||||
async def _update_tasks(self, filename):
|
||||
system_design_doc = await self.repo.docs.system_design.get(filename)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ from metagpt.const import (
|
|||
REQUIREMENT_FILENAME,
|
||||
)
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import BugFixContext, Document, Documents, Message
|
||||
from metagpt.schema import AIMessage, Document, Documents, Message
|
||||
from metagpt.utils.common import CodeParser
|
||||
from metagpt.utils.file_repository import FileRepository
|
||||
from metagpt.utils.mermaid import mermaid_to_file
|
||||
|
|
@ -66,7 +66,7 @@ class WritePRD(Action):
|
|||
3. Requirement update: If the requirement is an update, the PRD document will be updated.
|
||||
"""
|
||||
|
||||
async def run(self, with_messages, *args, **kwargs) -> ActionOutput | Message:
|
||||
async def run(self, with_messages, *args, **kwargs) -> Message:
|
||||
"""Run the action."""
|
||||
req: Document = await self.repo.requirement
|
||||
docs: list[Document] = await self.repo.docs.prd.get_all()
|
||||
|
|
@ -82,22 +82,27 @@ class WritePRD(Action):
|
|||
# if requirement is related to other documents, update them, otherwise create a new one
|
||||
if related_docs := await self.get_related_docs(req, docs):
|
||||
logger.info(f"Requirement update detected: {req.content}")
|
||||
return await self._handle_requirement_update(req, related_docs)
|
||||
await self._handle_requirement_update(req, related_docs)
|
||||
else:
|
||||
logger.info(f"New requirement detected: {req.content}")
|
||||
return await self._handle_new_requirement(req)
|
||||
await self._handle_new_requirement(req)
|
||||
return AIMessage(
|
||||
content="PRD is completed. "
|
||||
+ "\n".join(
|
||||
list(self.repo.docs.prd.changed_files.keys())
|
||||
+ list(self.repo.resources.prd.changed_files.keys())
|
||||
+ list(self.repo.resources.competitive_analysis.changed_files.keys())
|
||||
),
|
||||
cause_by=self,
|
||||
)
|
||||
|
||||
async def _handle_bugfix(self, req: Document) -> Message:
|
||||
# ... bugfix logic ...
|
||||
await self.repo.docs.save(filename=BUGFIX_FILENAME, content=req.content)
|
||||
await self.repo.docs.save(filename=REQUIREMENT_FILENAME, content="")
|
||||
bug_fix = BugFixContext(filename=BUGFIX_FILENAME)
|
||||
return Message(
|
||||
content=bug_fix.model_dump_json(),
|
||||
instruct_content=bug_fix,
|
||||
role="",
|
||||
return AIMessage(
|
||||
content=f"A new issue is received: {BUGFIX_FILENAME}",
|
||||
cause_by=FixBug,
|
||||
sent_from=self,
|
||||
send_to="Alex", # the name of Engineer
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ MESSAGE_ROUTE_CAUSE_BY = "cause_by"
|
|||
MESSAGE_META_ROLE = "role"
|
||||
MESSAGE_ROUTE_TO_ALL = "<all>"
|
||||
MESSAGE_ROUTE_TO_NONE = "<none>"
|
||||
MESSAGE_ROUTE_TO_SELF = "<self>" # Add this tag to replace `ActionOutput`
|
||||
|
||||
REQUIREMENT_FILENAME = "requirement.txt"
|
||||
BUGFIX_FILENAME = "bugfix.txt"
|
||||
|
|
|
|||
|
|
@ -202,10 +202,13 @@ class Environment(ExtEnv):
|
|||
for _ in range(k):
|
||||
futures = []
|
||||
for role in self.roles.values():
|
||||
if role.is_idle:
|
||||
continue
|
||||
future = role.run()
|
||||
futures.append(future)
|
||||
|
||||
await asyncio.gather(*futures)
|
||||
if futures:
|
||||
await asyncio.gather(*futures)
|
||||
logger.debug(f"is idle: {self.is_idle}")
|
||||
|
||||
def get_roles(self) -> dict[str, "Role"]:
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ from metagpt.actions.write_code_plan_and_change_an import WriteCodePlanAndChange
|
|||
from metagpt.const import (
|
||||
BUGFIX_FILENAME,
|
||||
CODE_PLAN_AND_CHANGE_FILE_REPO,
|
||||
MESSAGE_ROUTE_TO_SELF,
|
||||
REQUIREMENT_FILENAME,
|
||||
SYSTEM_DESIGN_FILE_REPO,
|
||||
TASK_FILE_REPO,
|
||||
|
|
@ -150,12 +151,6 @@ class Engineer(Role):
|
|||
dependencies=list(dependencies),
|
||||
content=coding_context.code_doc.content,
|
||||
)
|
||||
AIMessage(
|
||||
content=coding_context.model_dump_json(),
|
||||
instruct_content=coding_context,
|
||||
cause_by=WriteCode,
|
||||
)
|
||||
|
||||
changed_files.add(coding_context.code_doc.filename)
|
||||
if not changed_files:
|
||||
logger.info("Nothing has changed.")
|
||||
|
|
@ -177,17 +172,16 @@ class Engineer(Role):
|
|||
return await self.rc.todo.run(self.rc.history)
|
||||
|
||||
async def _act_write_code(self):
|
||||
changed_files = await self._act_sp_with_cr(review=self.use_code_review)
|
||||
await self._act_sp_with_cr(review=self.use_code_review)
|
||||
return AIMessage(
|
||||
content="\n".join(changed_files),
|
||||
cause_by=WriteCodeReview if self.use_code_review else WriteCode,
|
||||
send_to=self,
|
||||
sent_from=self,
|
||||
content="", cause_by=WriteCodeReview if self.use_code_review else WriteCode, send_to=MESSAGE_ROUTE_TO_SELF
|
||||
)
|
||||
|
||||
async def _act_summarize(self):
|
||||
tasks = []
|
||||
for todo in self.summarize_todos:
|
||||
if self.n_summarize >= self.config.max_auto_summarize_code:
|
||||
break
|
||||
summary = await todo.run()
|
||||
summary_filename = Path(todo.i_context.design_filename).with_suffix(".md").name
|
||||
dependencies = {todo.i_context.design_filename, todo.i_context.task_filename}
|
||||
|
|
@ -209,19 +203,23 @@ class Engineer(Role):
|
|||
)
|
||||
else:
|
||||
await self.project_repo.docs.code_summary.delete(filename=Path(todo.i_context.design_filename).name)
|
||||
|
||||
self.summarize_todos = []
|
||||
logger.info(f"--max-auto-summarize-code={self.config.max_auto_summarize_code}")
|
||||
if not tasks or self.config.max_auto_summarize_code == 0:
|
||||
self.n_summarize = 0
|
||||
return AIMessage(
|
||||
content="",
|
||||
content="Coding is complete. "
|
||||
"\n".join(
|
||||
list(self.project_repo.resources.code_summary.changed_files.keys())
|
||||
+ list(self.project_repo.srcs.changed_files.keys())
|
||||
),
|
||||
cause_by=SummarizeCode,
|
||||
sent_from=self,
|
||||
send_to="Edward", # The name of QaEngineer
|
||||
)
|
||||
# The maximum number of times the 'SummarizeCode' action is automatically invoked, with -1 indicating unlimited.
|
||||
# This parameter is used for debugging the workflow.
|
||||
self.n_summarize += 1 if self.config.max_auto_summarize_code > self.n_summarize else 0
|
||||
return AIMessage(content=json.dumps(tasks), cause_by=SummarizeCode, send_to=self, sent_from=self)
|
||||
return AIMessage(content="", cause_by=SummarizeCode, send_to=MESSAGE_ROUTE_TO_SELF)
|
||||
|
||||
async def _act_code_plan_and_change(self):
|
||||
"""Write code plan and change that guides subsequent WriteCode and WriteCodeReview"""
|
||||
|
|
@ -243,12 +241,7 @@ class Engineer(Role):
|
|||
dependencies=dependencies,
|
||||
)
|
||||
|
||||
return AIMessage(
|
||||
content=code_plan_and_change,
|
||||
cause_by=WriteCodePlanAndChange,
|
||||
send_to=self,
|
||||
sent_from=self,
|
||||
)
|
||||
return AIMessage(content="", cause_by=WriteCodePlanAndChange, send_to=MESSAGE_ROUTE_TO_SELF)
|
||||
|
||||
async def _is_pass(self, summary) -> (str, str):
|
||||
rsp = await self.llm.aask(msg=IS_PASS_PROMPT.format(context=summary), stream=False)
|
||||
|
|
@ -416,7 +409,6 @@ class Engineer(Role):
|
|||
self.summarize_todos.append(new_summarize)
|
||||
if self.summarize_todos:
|
||||
self.set_todo(self.summarize_todos[0])
|
||||
self.summarize_todos.pop(0)
|
||||
|
||||
async def _new_code_plan_and_change_action(self, cause_by: str):
|
||||
"""Create a WriteCodePlanAndChange action for subsequent to-do actions."""
|
||||
|
|
|
|||
|
|
@ -18,10 +18,9 @@
|
|||
from metagpt.actions import DebugError, RunCode, UserRequirement, WriteTest
|
||||
from metagpt.actions.prepare_documents import PrepareDocuments
|
||||
from metagpt.actions.summarize_code import SummarizeCode
|
||||
from metagpt.const import MESSAGE_ROUTE_TO_NONE
|
||||
from metagpt.const import MESSAGE_ROUTE_TO_NONE, MESSAGE_ROUTE_TO_SELF
|
||||
from metagpt.logs import logger
|
||||
from metagpt.roles import Role
|
||||
from metagpt.utils.report import EditorReporter
|
||||
from metagpt.schema import AIMessage, Document, Message, RunCodeContext, TestingContext
|
||||
from metagpt.utils.common import (
|
||||
any_to_str,
|
||||
|
|
@ -29,6 +28,7 @@ from metagpt.utils.common import (
|
|||
init_python_folder,
|
||||
parse_recipient,
|
||||
)
|
||||
from metagpt.utils.report import EditorReporter
|
||||
|
||||
|
||||
class QaEngineer(Role):
|
||||
|
|
@ -95,12 +95,7 @@ class QaEngineer(Role):
|
|||
additional_python_paths=[str(self.context.src_workspace)],
|
||||
)
|
||||
self.publish_message(
|
||||
AIMessage(
|
||||
content=run_code_context.model_dump_json(),
|
||||
cause_by=WriteTest,
|
||||
sent_from=self,
|
||||
send_to=self,
|
||||
)
|
||||
AIMessage(content=run_code_context.model_dump_json(), cause_by=WriteTest, send_to=MESSAGE_ROUTE_TO_SELF)
|
||||
)
|
||||
|
||||
logger.info(f"Done {str(self.project_repo.tests.workdir)} generating.")
|
||||
|
|
@ -133,7 +128,6 @@ class QaEngineer(Role):
|
|||
AIMessage(
|
||||
content=run_code_context.model_dump_json(),
|
||||
cause_by=RunCode,
|
||||
sent_from=self,
|
||||
send_to=mappings.get(recipient, MESSAGE_ROUTE_TO_NONE),
|
||||
)
|
||||
)
|
||||
|
|
@ -144,12 +138,7 @@ class QaEngineer(Role):
|
|||
await self.project_repo.tests.save(filename=run_code_context.test_filename, content=code)
|
||||
run_code_context.output = None
|
||||
self.publish_message(
|
||||
AIMessage(
|
||||
content=run_code_context.model_dump_json(),
|
||||
cause_by=DebugError,
|
||||
sent_from=self,
|
||||
send_to=self,
|
||||
)
|
||||
AIMessage(content=run_code_context.model_dump_json(), cause_by=DebugError, send_to=MESSAGE_ROUTE_TO_SELF)
|
||||
)
|
||||
|
||||
async def _act(self) -> Message:
|
||||
|
|
@ -157,9 +146,9 @@ class QaEngineer(Role):
|
|||
await init_python_folder(self.project_repo.tests.workdir)
|
||||
if self.test_round > self.test_round_allowed:
|
||||
result_msg = AIMessage(
|
||||
content=f"Exceeding {self.test_round_allowed} rounds of tests, skip (writing code counts as a round, too)",
|
||||
content=f"Exceeding {self.test_round_allowed} rounds of tests, stop. "
|
||||
+ "\n".join(list(self.project_repo.tests.changed_files.keys())),
|
||||
cause_by=WriteTest,
|
||||
sent_from=self.profile,
|
||||
send_to=MESSAGE_ROUTE_TO_NONE,
|
||||
)
|
||||
return result_msg
|
||||
|
|
@ -185,7 +174,6 @@ class QaEngineer(Role):
|
|||
return AIMessage(
|
||||
content=f"Round {self.test_round} of tests done",
|
||||
cause_by=WriteTest,
|
||||
sent_from=self.profile,
|
||||
send_to=MESSAGE_ROUTE_TO_NONE,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny, model_validat
|
|||
from metagpt.actions import Action, ActionOutput
|
||||
from metagpt.actions.action_node import ActionNode
|
||||
from metagpt.actions.add_requirement import UserRequirement
|
||||
from metagpt.const import MESSAGE_ROUTE_TO_SELF
|
||||
from metagpt.context_mixin import ContextMixin
|
||||
from metagpt.logs import logger
|
||||
from metagpt.memory import Memory
|
||||
|
|
@ -408,7 +409,7 @@ class Role(SerializationMixin, ContextMixin, BaseModel):
|
|||
elif isinstance(response, Message):
|
||||
msg = response
|
||||
else:
|
||||
msg = AIMessage(content=response, cause_by=self.rc.todo, sent_from=self)
|
||||
msg = AIMessage(content=response or "", cause_by=self.rc.todo, sent_from=self)
|
||||
if self.enable_memory:
|
||||
self.rc.memory.add(msg)
|
||||
|
||||
|
|
@ -443,12 +444,19 @@ class Role(SerializationMixin, ContextMixin, BaseModel):
|
|||
"""If the role belongs to env, then the role's messages will be broadcast to env"""
|
||||
if not msg:
|
||||
return
|
||||
if MESSAGE_ROUTE_TO_SELF in msg.send_to:
|
||||
msg.send_to.add(any_to_str(self))
|
||||
msg.send_to.remove(MESSAGE_ROUTE_TO_SELF)
|
||||
if not msg.sent_from or msg.sent_from == MESSAGE_ROUTE_TO_SELF:
|
||||
msg.sent_from = any_to_str(self)
|
||||
if all(to in {any_to_str(self), self.name} for to in msg.send_to): # Message to myself
|
||||
self.put_message(msg)
|
||||
return
|
||||
if not self.rc.env:
|
||||
# If env does not exist, do not publish the message
|
||||
return
|
||||
if isinstance(msg, AIMessage) and not msg.agent:
|
||||
msg.with_agent(self._setting)
|
||||
self.rc.env.publish_message(msg)
|
||||
|
||||
def put_message(self, message):
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ class Message(BaseModel):
|
|||
"""list[<role>: <content>]"""
|
||||
|
||||
id: str = Field(default="", validate_default=True) # According to Section 2.2.3.1.1 of RFC 135
|
||||
content: str
|
||||
content: str # natural language for user or agent
|
||||
instruct_content: Optional[BaseModel] = Field(default=None, validate_default=True)
|
||||
role: str = "user" # system / user / assistant
|
||||
cause_by: str = Field(default="", validate_default=True)
|
||||
|
|
@ -748,10 +748,6 @@ class CodeSummarizeContext(BaseModel):
|
|||
return hash((self.design_filename, self.task_filename))
|
||||
|
||||
|
||||
class BugFixContext(BaseContext):
|
||||
filename: str = ""
|
||||
|
||||
|
||||
class CodePlanAndChangeContext(BaseModel):
|
||||
requirement: str = ""
|
||||
issue: str = ""
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ serdeser_path = Path(__file__).absolute().parent.joinpath("../data/serdeser_stor
|
|||
|
||||
class MockEnv(Environment):
|
||||
def publish_message(self, message: Message, peekable: bool = True) -> bool:
|
||||
logger.info(f"{message.metadata}:{message.content}")
|
||||
consumers = []
|
||||
for role, addrs in self.member_addrs.items():
|
||||
if is_send_to(message, addrs):
|
||||
|
|
@ -87,6 +88,7 @@ async def test_publish_and_process_message(env: Environment):
|
|||
assert len(env.history.storage) == 0
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("content", "send_to"),
|
||||
|
|
@ -105,7 +107,7 @@ async def test_publish_and_process_message(env: Environment):
|
|||
any_to_str(ProjectManager),
|
||||
),
|
||||
(
|
||||
"Rewrite 'main.py' of the project at '/Users/iorishinier/github/MetaGPT/workspace/snake_game'",
|
||||
"src filename is 'game.py', Uncaught SyntaxError: Identifier 'Position' has already been declared (at game.js:1:1), the project at '/Users/iorishinier/github/bak/MetaGPT/workspace/snake_game'",
|
||||
any_to_str(Engineer),
|
||||
),
|
||||
(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue