diff --git a/metagpt/logs.py b/metagpt/logs.py index c46041af8..f102c1be3 100644 --- a/metagpt/logs.py +++ b/metagpt/logs.py @@ -8,11 +8,9 @@ from __future__ import annotations -import json 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 @@ -20,12 +18,17 @@ from pydantic import BaseModel, Field from metagpt.const import METAGPT_ROOT -class ToolOutputItem(BaseModel): +class ToolLogItem(BaseModel): type_: str = Field(alias="type", default="str", description="Data type of `value` field.") name: str value: str +TOOL_LOG_END_MARKER = ToolLogItem( + type="str", name="end_marker", value="#END#" +) # A special log item to suggest the end of a stream log + + def define_log_level(print_level="INFO", logfile_level="DEBUG", name: str = None): """Adjust the log level to above level""" current_date = datetime.now() @@ -45,13 +48,9 @@ def log_llm_stream(msg): _llm_stream_log(msg) -def log_tool_output(output: ToolOutputItem | List[ToolOutputItem], tool_name: str = ""): +def log_tool_output(output: ToolLogItem | list[ToolLogItem], 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""" - if not _tool_output_log or not output: - return - - outputs = output if isinstance(output, list) else [output] - _tool_output_log(output=json.dumps([i.model_dump() for i in outputs]), tool_name=tool_name) + _tool_output_log(output=output, tool_name=tool_name) def set_llm_stream_logfunc(func): @@ -67,8 +66,4 @@ def set_tool_output_logfunc(func): _llm_stream_log = partial(print, end="") -def _default_tool_output_log(*args, **kwargs): - print(*args, str(kwargs), end="") - - -_tool_output_log = _default_tool_output_log +_tool_output_log = lambda output, tool_name: print(output) diff --git a/metagpt/schema.py b/metagpt/schema.py index da9f55166..7bf924b97 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -46,6 +46,7 @@ from metagpt.const import ( SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO, ) +from metagpt.logs import ToolLogItem, log_tool_output, logger from metagpt.repo_parser import DotClassInfo from metagpt.utils.common import any_to_str, any_to_str_set, import_class from metagpt.utils.exceptions import handle_exception @@ -433,10 +434,8 @@ class Plan(BaseModel): final_tasks = self.tasks[:prefix_length] + new_tasks[prefix_length:] self.tasks = final_tasks - from metagpt.logs import ToolOutputItem, log_tool_output - log_tool_output( - ToolOutputItem( + ToolLogItem( name="output", value="\n\n".join([f"Task {task.task_id}: {task.instruction}" for task in self.tasks]) ), tool_name="Plan", diff --git a/metagpt/tools/libs/software_development.py b/metagpt/tools/libs/software_development.py index ab7806006..a75625702 100644 --- a/metagpt/tools/libs/software_development.py +++ b/metagpt/tools/libs/software_development.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Optional from metagpt.const import BUGFIX_FILENAME, REQUIREMENT_FILENAME -from metagpt.logs import ToolOutputItem, log_tool_output +from metagpt.logs import 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 diff --git a/metagpt/tools/libs/terminal.py b/metagpt/tools/libs/terminal.py index 2b1657bdd..a23ebb86a 100644 --- a/metagpt/tools/libs/terminal.py +++ b/metagpt/tools/libs/terminal.py @@ -1,6 +1,6 @@ import subprocess -from metagpt.logs import log_tool_output +from metagpt.logs import TOOL_LOG_END_MARKER, ToolLogItem, log_tool_output from metagpt.tools.tool_registry import register_tool @@ -11,7 +11,6 @@ class Terminal: def __init__(self): self.shell_command = ["bash"] # FIXME: should consider windows support later self.command_terminator = "\n" - self.end_marker = "#END_MARKER#" # Start a persistent shell process self.process = subprocess.Popen( @@ -40,17 +39,22 @@ class Terminal: # Send the command self.process.stdin.write(cmd + self.command_terminator) self.process.stdin.write( - f'echo "{self.end_marker}"' + self.command_terminator + f'echo "{TOOL_LOG_END_MARKER.value}"' + self.command_terminator ) # Unique marker to signal command end self.process.stdin.flush() - log_tool_output(output={"cmd": cmd + self.command_terminator}, tool_name="Terminal") # log the command + log_tool_output( + output=ToolLogItem(name="cmd", value=cmd + self.command_terminator), tool_name="Terminal" + ) # log the command # Read the output until the unique marker is found while True: line = self.process.stdout.readline() - if line.strip() == self.end_marker: + if line.strip() == TOOL_LOG_END_MARKER.value: + log_tool_output(TOOL_LOG_END_MARKER) break - log_tool_output(output={"output": line}, tool_name="Terminal") # log stdout in real-time + log_tool_output( + output=ToolLogItem(name="output", value=line), tool_name="Terminal" + ) # log stdout in real-time cmd_output.append(line) return "".join(cmd_output)