Skip to content

Commit

Permalink
feat: +tool log
Browse files Browse the repository at this point in the history
  • Loading branch information
莘权 马 committed Mar 31, 2024
1 parent 09f75cb commit 1ed64bc
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 3 deletions.
6 changes: 6 additions & 0 deletions metagpt/actions/intent_detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ async def _get_sops(self):
async def _merge(self):
self.result = IntentDetectResult(clarifications=self._dialog_intentions.clarifications)
distinct = {}
# Consolidate intentions under the same SOP.
for i in self._intent_to_sops:
if i.sop_index == 0: # 1-based index
refs = self._get_intent_ref(i.intent)
Expand All @@ -260,12 +261,14 @@ async def _merge(self):
if len(intents) > 1:
merge_intents[sop_index] = intents
continue
# Merge single intention
refs = self._get_intent_ref(intents[0])
item = IntentDetectIntentionSOP(intention=IntentDetectIntentionRef(intent=intents[0], refs=refs))
sop_index = intent_to_sops.get(intents[0])
item.sop = SOP_CONFIG[sop_index - 1] # 1-based index
self.result.intentions.append(item)

# Merge repetitive intentions into one
for sop_index, intents in merge_intents.items():
intent_ref = IntentDetectIntentionRef(intent="\n".join(intents), refs=[])
for i in intents:
Expand Down Expand Up @@ -338,6 +341,7 @@ async def _get_sops(self):
async def _merge(self):
self.result = IntentDetectResult(clarifications=[])
distinct = {}
# Consolidate intentions under the same SOP.
for i in self._intent_to_sops:
if i.sop_index == 0: # 1-based index
ref = self._get_intent_ref(i.intent)
Expand All @@ -352,13 +356,15 @@ async def _merge(self):
if len(intents) > 1:
merge_intents[sop_index] = intents
continue
# Merge single intention
ref = self._get_intent_ref(intents[0])
item = IntentDetectIntentionSOP(intention=IntentDetectIntentionRef(intent=intents[0], refs=[ref]))
sop_index = intent_to_sops.get(intents[0]) # 1-based
if sop_index:
item.sop = SOP_CONFIG[sop_index - 1] # 1-based index
self.result.intentions.append(item)

# Merge repetitive intentions into one
for sop_index, intents in merge_intents.items():
intent_ref = IntentDetectIntentionRef(intent="\n".join(intents), refs=[])
for i in intents:
Expand Down
32 changes: 30 additions & 2 deletions metagpt/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,34 @@
import sys
from datetime import datetime
from functools import partial
from typing import List

from loguru import logger as _logger
from pydantic import BaseModel, Field

from metagpt.const import METAGPT_ROOT
from metagpt.schema import BaseEnum


class ToolOutputItem(BaseModel):
type_: str = Field(alias="type", default="str", description="Data type of `value` field.")
name: str
value: str


class ToolName(str, BaseEnum):
Terminal = "Terminal"
Plan = "Plan"
Browser = "Browser"
Files = "Files"
WritePRD = "WritePRD"
WriteDesign = "WriteDesign"
WriteProjectPlan = "WriteProjectPlan"
WriteCode = "WriteCode"
WriteUntTest = "WriteUntTest"
FixBug = "FixBug"
GitArchive = "GitArchive"
ImportRepo = "ImportRepo"


def define_log_level(print_level="INFO", logfile_level="DEBUG", name: str = None):
Expand All @@ -36,9 +60,13 @@ def log_llm_stream(msg):
_llm_stream_log(msg)


def log_tool_output(output: dict, tool_name: str = ""):
def log_tool_output(output: ToolOutputItem | List[ToolOutputItem], tool_name: str = ""):
"""interface for logging tool output, can be set to log tool output in different ways to different places with set_tool_output_logfunc"""
_tool_output_log(output)
if not _tool_output_log or not output:
return

outputs = output if isinstance(output, list) else [output]
_tool_output_log(output=[i.model_dump() for i in outputs], tool_name=tool_name)


def set_llm_stream_logfunc(func):
Expand Down
4 changes: 3 additions & 1 deletion metagpt/roles/di/mgx.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ async def _intent_detect(self, user_msgs: List[Message] = None, **kwargs):
# Extract intent and sop prompt
intention_ref = ""
for i in todo.result.intentions:
if not intention_ref:
intention_ref = "\n".join(i.intention.refs)
if not i.sop:
continue
intention_ref = "\n".join(i.intention.refs)

self.intents[intention_ref] = i.sop.sop
logger.debug(f"refs: {intention_ref}, sop: {i.sop.sop}")
sop_str = "\n".join([f"- {i}" for i in i.sop.sop])
Expand Down
24 changes: 24 additions & 0 deletions metagpt/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import uuid
from abc import ABC
from asyncio import Queue, QueueEmpty, wait_for
from enum import Enum
from json import JSONDecodeError
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Type, TypeVar, Union
Expand Down Expand Up @@ -785,3 +786,26 @@ def load_dot_class_info(cls, dot_class_info: DotClassInfo) -> UMLClassView:
method.return_type = i.return_args.type_
class_view.methods.append(method)
return class_view


class BaseEnum(Enum):
"""Base class for enums."""

def __new__(cls, value, desc=None):
"""
Construct an instance of the enum member.
Args:
cls: The class.
value: The value of the enum member.
desc: The description of the enum member. Defaults to None.
"""
if issubclass(cls, str):
obj = str.__new__(cls, value)
elif issubclass(cls, int):
obj = int.__new__(cls, value)
else:
obj = object.__new__(cls)
obj._value_ = value
obj.desc = desc
return obj
63 changes: 63 additions & 0 deletions metagpt/tools/libs/software_development.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Optional

from metagpt.const import BUGFIX_FILENAME, REQUIREMENT_FILENAME
from metagpt.logs import ToolName, ToolOutputItem, log_tool_output
from metagpt.schema import BugFixContext, Message
from metagpt.tools.tool_registry import register_tool
from metagpt.utils.common import any_to_str
Expand Down Expand Up @@ -48,6 +49,17 @@ async def write_prd(idea: str, project_path: Optional[str | Path] = None) -> Pat
role = ProductManager(context=ctx)
msg = await role.run(with_message=Message(content=idea, cause_by=UserRequirement))
await role.run(with_message=msg)

outputs = [
ToolOutputItem(name="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(
ToolOutputItem(name="Competitive Analysis", value=str(ctx.repo.resources.competitive_analysis.workdir / i))
)
log_tool_output(output=outputs, tool_name=ToolName.WritePRD)

return ctx.repo.docs.prd.workdir


Expand Down Expand Up @@ -79,6 +91,21 @@ async def write_design(prd_path: str | Path) -> Path:

role = Architect(context=ctx)
await role.run(with_message=Message(content="", cause_by=WritePRD))

outputs = [
ToolOutputItem(name="Intermedia Design File", value=str(ctx.repo.docs.system_design.workdir / i))
for i in ctx.repo.docs.system_design.changed_files.keys()
]
for i in ctx.repo.resources.system_design.changed_files.keys():
outputs.append(ToolOutputItem(name="Design File", value=str(ctx.repo.resources.system_design.workdir / i)))
for i in ctx.repo.resources.data_api_design.changed_files.keys():
outputs.append(
ToolOutputItem(name="Class Diagram File", value=str(ctx.repo.resources.data_api_design.workdir / i))
)
for i in ctx.repo.resources.seq_flow.changed_files.keys():
outputs.append(ToolOutputItem(name="Sequence Diagram File", value=str(ctx.repo.resources.seq_flow.workdir / i)))
log_tool_output(output=outputs, tool_name=ToolName.WriteDesign)

return ctx.repo.docs.system_design.workdir


Expand Down Expand Up @@ -110,6 +137,13 @@ async def write_project_plan(system_design_path: str | Path) -> Path:

role = ProjectManager(context=ctx)
await role.run(with_message=Message(content="", cause_by=WriteDesign))

outputs = [
ToolOutputItem(name="Project Plan", value=str(ctx.repo.docs.task.workdir / i))
for i in ctx.repo.docs.task.changed_files.key()
]
log_tool_output(output=outputs, tool_name=ToolName.WriteProjectPlan)

return ctx.repo.docs.task.workdir


Expand Down Expand Up @@ -153,6 +187,13 @@ async def write_codes(task_path: str | Path, inc: bool = False) -> Path:
me = {any_to_str(role), role.name}
while me.intersection(msg.send_to):
msg = await role.run(with_message=msg)

outputs = [
ToolOutputItem(name="Source File", value=str(ctx.repo.srcs.workdir / i))
for i in ctx.repo.srcs.changed_files.keys()
]
log_tool_output(output=outputs, tool_name=ToolName.WriteCode)

return ctx.repo.srcs.workdir


Expand Down Expand Up @@ -192,6 +233,13 @@ async def run_qa_test(src_path: str | Path) -> Path:

while not env.is_idle:
await env.run()

outputs = [
ToolOutputItem(name="Unit Test File", value=str(ctx.repo.tests.workdir / i))
for i in ctx.repo.tests.changed_files.keys()
]
log_tool_output(output=outputs, tool_name=ToolName.WriteUntTest)

return ctx.repo.tests.workdir


Expand Down Expand Up @@ -237,6 +285,13 @@ async def fix_bug(project_path: str | Path, issue: str) -> Path:
me = {any_to_str(role), role.name}
while me.intersection(msg.send_to):
msg = await role.run(with_message=msg)

outputs = [
ToolOutputItem(name="Changed File", value=str(ctx.repo.srcs.workdir / i))
for i in ctx.repo.srcs.changed_files.keys()
]
log_tool_output(output=outputs, tool_name=ToolName.FixBug)

return project_path


Expand Down Expand Up @@ -269,6 +324,10 @@ async def git_archive(project_path: str | Path) -> str:
ctx = Context()
ctx.set_repo_dir(project_path)
ctx.git_repo.archive()

outputs = [ToolOutputItem(name="Git Commit", value=str(ctx.repo.workdir))]
log_tool_output(output=outputs, tool_name=ToolName.GitArchive)

return ctx.git_repo.log()


Expand Down Expand Up @@ -298,4 +357,8 @@ async def import_git_repo(url: str) -> Path:
ctx = Context()
action = ImportRepo(repo_path=url, context=ctx)
await action.run()

outputs = [ToolOutputItem(name="MetaGPT Project", value=str(ctx.repo.workdir))]
log_tool_output(output=outputs, tool_name=ToolName.ImportRepo)

return ctx.repo.workdir

0 comments on commit 1ed64bc

Please sign in to comment.