Merge branch 'mgx_ops' of https://gitlab.deepwisdomai.com/pub/MetaGPT into mgx_ops

This commit is contained in:
stellahsr 2024-04-08 21:18:13 +08:00
commit 91ec512278
6 changed files with 90 additions and 25 deletions

View file

@ -1,12 +1,14 @@
from __future__ import annotations
import asyncio
import re
from enum import Enum
from typing import Tuple
from pydantic import BaseModel
from metagpt.actions import Action
from metagpt.schema import Message
class SOPItemDef(BaseModel):
@ -77,29 +79,41 @@ DETECT_PROMPT = """
# Intentions
{intentions}
# Task
Classify user requirement into one type of the above intentions, output the name of the intention directly.
Intention name:
Classify user requirement into one type of the above intentions, output the index of the intention directly.
Intention index:
"""
REQ_WITH_SOP = """
{user_requirement}
You should follow the following Standard Operating Procedure:
## Knowledge
To meet user requirements, the following standard operating procedure(SOP) must be used.
SOP descriptions cannot be modified; user requirements can only be appended to the end of corresponding steps.
{sop}
"""
class DetectIntent(Action):
async def run(self, user_requirement: str) -> Tuple[str, str]:
intentions = "\n".join([f"{si.type_name}: {si.value.description}" for si in SOPItem])
async def run(self, with_message: Message, **kwargs) -> Tuple[str, str]:
user_requirement = with_message.content
mappings = {i + 1: si for i, si in enumerate(SOPItem)}
intentions = "\n".join([f"{i+1}. {si.type_name}: {si.value.description}" for i, si in enumerate(SOPItem)])
prompt = DETECT_PROMPT.format(user_requirement=user_requirement, intentions=intentions)
sop_type = await self._aask(prompt)
sop_type = sop_type.strip()
sop = SOPItem.get_type(sop_type).sop
rsp = await self._aask(prompt)
match = re.search(r"\d+", rsp)
index = len(SOPItem) + 1 # 1-based
if match:
index = int(match.group()) # 1-based
sop = mappings[index].value.sop if index in mappings else None
sop_type = mappings[index].type_name if index in mappings else SOPItem.OTHER.type_name
req_with_sop = (
REQ_WITH_SOP.format(user_requirement=user_requirement, sop="\n".join(sop)) if sop else user_requirement
REQ_WITH_SOP.format(
user_requirement=user_requirement, sop="\n".join([f"{i+1}. {v}" for i, v in enumerate(sop)])
)
if sop
else user_requirement
)
return req_with_sop, sop_type
@ -111,7 +125,7 @@ async def main():
detect_intent = DetectIntent()
for user_requirement in user_requirements:
req_with_sop, sop_type = await detect_intent.run(user_requirement)
req_with_sop, sop_type = await detect_intent.run(Message(role="user", content=user_requirement))
print(req_with_sop)
print(f"Detected SOP Type: {sop_type}")

View file

@ -70,6 +70,7 @@ class WriteTasks(Action):
dependencies={system_design_doc.root_relative_path},
)
await self._update_requirements(task_doc)
await self.repo.resources.api_spec_and_task.save_pdf(doc=task_doc)
return task_doc
async def _run_new_tasks(self, context):

View file

@ -135,3 +135,6 @@ AGGREGATION = "Aggregate"
# Timeout
USE_CONFIG_TIMEOUT = 0 # Using llm.timeout configuration.
LLM_API_TIMEOUT = 300
# Assistant alias
ASSISTANT_ALIAS = "response"

View file

@ -2,7 +2,7 @@
# @Author : stellahong (stellahong@fuzhi.ai)
# @Desc :
import asyncio
from typing import Dict, List
from typing import Dict
from metagpt.actions.di.detect_intent import DetectIntent
from metagpt.logs import logger
@ -14,9 +14,9 @@ class MGX(DataInterpreter):
use_intent: bool = True
intents: Dict = {}
async def _detect_intent(self, user_msgs: List[Message] = None, **kwargs):
async def _detect_intent(self, user_msg: Message) -> str:
todo = DetectIntent(context=self.context)
request_with_sop, sop_type = await todo.run(user_msgs)
request_with_sop, sop_type = await todo.run(user_msg)
logger.info(f"{sop_type} {request_with_sop}")
return request_with_sop
@ -24,10 +24,10 @@ class MGX(DataInterpreter):
"""first plan, then execute an action sequence, i.e. _think (of a plan) -> _act -> _act -> ... Use llm to come up with the plan dynamically."""
# create initial plan and update it until confirmation
goal = self.rc.memory.get()[-1].content # retreive latest user requirement
goal = self.rc.memory.get()[-1].content # retrieve latest user requirement
if self.use_intent: # add mode
user_message = Message(content=goal, role="user")
goal = await self._detect_intent(user_msgs=[user_message])
goal = await self._detect_intent(user_message)
logger.info(f"Goal is {goal}")
await self.planner.update_plan(goal=goal)

View file

@ -5,7 +5,7 @@ from __future__ import annotations
from pathlib import Path
from typing import Optional
from metagpt.const import BUGFIX_FILENAME, REQUIREMENT_FILENAME
from metagpt.const import ASSISTANT_ALIAS, BUGFIX_FILENAME, REQUIREMENT_FILENAME
from metagpt.logs import ToolLogItem, log_tool_output
from metagpt.schema import BugFixContext, Message
from metagpt.tools.tool_registry import register_tool
@ -42,8 +42,10 @@ async def write_prd(idea: str, project_path: Optional[str | Path] = None) -> Pat
from metagpt.context import Context
from metagpt.roles import ProductManager
log_tool_output(output=[ToolLogItem(name=ASSISTANT_ALIAS, value=write_prd.__name__)], tool_name=write_prd.__name__)
ctx = Context()
if project_path:
if project_path and Path(project_path).exists():
ctx.config.project_path = Path(project_path)
ctx.config.inc = True
role = ProductManager(context=ctx)
@ -51,13 +53,21 @@ async def write_prd(idea: str, project_path: Optional[str | Path] = None) -> Pat
await role.run(with_message=msg)
outputs = [
ToolLogItem(name="PRD File", value=str(ctx.repo.docs.prd.workdir / i))
ToolLogItem(name="Intermedia PRD File", value=str(ctx.repo.docs.prd.workdir / i))
for i in ctx.repo.docs.prd.changed_files.keys()
]
for i in ctx.repo.resources.competitive_analysis.changed_files.keys():
outputs.append(
outputs.extend(
[
ToolLogItem(name="PRD File", value=str(ctx.repo.resources.prd.workdir / i))
for i in ctx.repo.resources.prd.changed_files.keys()
]
)
outputs.extend(
[
ToolLogItem(name="Competitive Analysis", value=str(ctx.repo.resources.competitive_analysis.workdir / i))
)
for i in ctx.repo.resources.competitive_analysis.changed_files.keys()
]
)
log_tool_output(output=outputs, tool_name=write_prd.__name__)
return ctx.repo.docs.prd.workdir
@ -85,6 +95,10 @@ async def write_design(prd_path: str | Path) -> Path:
from metagpt.context import Context
from metagpt.roles import Architect
log_tool_output(
output=[ToolLogItem(name=ASSISTANT_ALIAS, value=write_design.__name__)], tool_name=write_design.__name__
)
ctx = Context()
prd_path = Path(prd_path)
project_path = (Path(prd_path) if not prd_path.is_file() else prd_path.parent) / "../.."
@ -132,6 +146,11 @@ async def write_project_plan(system_design_path: str | Path) -> Path:
from metagpt.context import Context
from metagpt.roles import ProjectManager
log_tool_output(
output=[ToolLogItem(name=ASSISTANT_ALIAS, value=write_project_plan.__name__)],
tool_name=write_project_plan.__name__,
)
ctx = Context()
system_design_path = Path(system_design_path)
project_path = (system_design_path if not system_design_path.is_file() else system_design_path.parent) / "../.."
@ -141,9 +160,15 @@ async def write_project_plan(system_design_path: str | Path) -> Path:
await role.run(with_message=Message(content="", cause_by=WriteDesign))
outputs = [
ToolLogItem(name="Project Plan", value=str(ctx.repo.docs.task.workdir / i))
ToolLogItem(name="Intermedia Project Plan", value=str(ctx.repo.docs.task.workdir / i))
for i in ctx.repo.docs.task.changed_files.keys()
]
outputs.extend(
[
ToolLogItem(name="Project Plan", value=str(ctx.repo.resources.api_spec_and_task.workdir / i))
for i in ctx.repo.resources.api_spec_and_task.changed_files.keys()
]
)
log_tool_output(output=outputs, tool_name=write_project_plan.__name__)
return ctx.repo.docs.task.workdir
@ -179,6 +204,10 @@ async def write_codes(task_path: str | Path, inc: bool = False) -> Path:
from metagpt.context import Context
from metagpt.roles import Engineer
log_tool_output(
output=[ToolLogItem(name=ASSISTANT_ALIAS, value=write_codes.__name__)], tool_name=write_codes.__name__
)
ctx = Context()
ctx.config.inc = inc
task_path = Path(task_path)
@ -222,6 +251,10 @@ async def run_qa_test(src_path: str | Path) -> Path:
from metagpt.environment import Environment
from metagpt.roles import QaEngineer
log_tool_output(
output=[ToolLogItem(name=ASSISTANT_ALIAS, value=run_qa_test.__name__)], tool_name=run_qa_test.__name__
)
ctx = Context()
src_path = Path(src_path)
project_path = (src_path if not src_path.is_file() else src_path.parent) / ".."
@ -270,6 +303,8 @@ async def fix_bug(project_path: str | Path, issue: str) -> Path:
from metagpt.context import Context
from metagpt.roles import Engineer
log_tool_output(output=[ToolLogItem(name=ASSISTANT_ALIAS, value=fix_bug.__name__)], tool_name=fix_bug.__name__)
ctx = Context()
ctx.set_repo_dir(project_path)
ctx.src_workspace = ctx.git_repo.workdir / ctx.git_repo.workdir.name
@ -325,11 +360,18 @@ async def git_archive(project_path: str | Path) -> str:
"""
from metagpt.context import Context
log_tool_output(
output=[ToolLogItem(name=ASSISTANT_ALIAS, value=git_archive.__name__)], tool_name=git_archive.__name__
)
ctx = Context()
ctx.set_repo_dir(project_path)
files = " ".join(ctx.git_repo.changed_files.keys())
outputs = [ToolLogItem(name="cmd", value=f"git add {files}")]
log_tool_output(output=outputs, tool_name=git_archive.__name__)
ctx.git_repo.archive()
outputs = [ToolLogItem(name="Git Commit", value=str(ctx.repo.workdir))]
outputs = [ToolLogItem(name="cmd", value="git commit -m 'Archive'")]
log_tool_output(output=outputs, tool_name=git_archive.__name__)
return ctx.git_repo.log()
@ -358,6 +400,10 @@ async def import_git_repo(url: str) -> Path:
from metagpt.actions.import_repo import ImportRepo
from metagpt.context import Context
log_tool_output(
output=[ToolLogItem(name=ASSISTANT_ALIAS, value=import_git_repo.__name__)], tool_name=import_git_repo.__name__
)
ctx = Context()
action = ImportRepo(repo_path=url, context=ctx)
await action.run()

View file

@ -1,6 +1,7 @@
import pytest
from metagpt.actions.di.detect_intent import DetectIntent
from metagpt.schema import Message
SOFTWARE_DEV_REQ1 = """
I'd like to create a personalized website that features the 'Game of Life' simulation.
@ -51,5 +52,5 @@ git clone 'https://github.com/spec-first/connexion' and format to MetaGPT projec
)
async def test_detect_intent(requirement, expected_intent_type):
di = DetectIntent()
_, intent_type = await di.run(requirement)
_, intent_type = await di.run(Message(role="user", content=requirement))
assert intent_type == expected_intent_type