From 26c6cfeefd785498460394256f74220935e2ee6d Mon Sep 17 00:00:00 2001 From: merwanehamadi Date: Wed, 3 May 2023 00:27:54 -0700 Subject: [PATCH] Feature/enable intuitive logs for community challenge step 1 (#3695) --- autogpt/agent/agent.py | 31 ++++++++++-- autogpt/llm/chat.py | 8 ++++ autogpt/log_cycle/__init__.py | 0 autogpt/log_cycle/json_handler.py | 20 ++++++++ autogpt/log_cycle/log_cycle.py | 80 +++++++++++++++++++++++++++++++ autogpt/logs.py | 35 +++++++++++++- 6 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 autogpt/log_cycle/__init__.py create mode 100644 autogpt/log_cycle/json_handler.py create mode 100644 autogpt/log_cycle/log_cycle.py diff --git a/autogpt/agent/agent.py b/autogpt/agent/agent.py index dbae1198..1c184aff 100644 --- a/autogpt/agent/agent.py +++ b/autogpt/agent/agent.py @@ -1,3 +1,5 @@ +from datetime import datetime + from colorama import Fore, Style from autogpt.app import execute_command, get_command @@ -6,6 +8,11 @@ from autogpt.json_utils.json_fix_llm import fix_json_using_multiple_techniques from autogpt.json_utils.utilities import LLM_DEFAULT_RESPONSE_FORMAT, validate_json from autogpt.llm import chat_with_ai, create_chat_completion, create_chat_message from autogpt.llm.token_counter import count_string_tokens +from autogpt.log_cycle.log_cycle import ( + FULL_MESSAGE_HISTORY_FILE_NAME, + NEXT_ACTION_FILE_NAME, + LogCycleHandler, +) from autogpt.logs import logger, print_assistant_thoughts from autogpt.speech import say_text from autogpt.spinner import Spinner @@ -68,22 +75,33 @@ class Agent: self.system_prompt = system_prompt self.triggering_prompt = triggering_prompt self.workspace = Workspace(workspace_directory, cfg.restrict_to_workspace) + self.created_at = datetime.now().strftime("%Y%m%d_%H%M%S") + self.cycle_count = 0 + self.log_cycle_handler = LogCycleHandler() def start_interaction_loop(self): # Interaction Loop cfg = Config() - loop_count = 0 + self.cycle_count = 0 command_name = None arguments = None user_input = "" while True: # Discontinue if continuous limit is reached - loop_count += 1 + self.cycle_count += 1 + self.log_cycle_handler.log_count_within_cycle = 0 + self.log_cycle_handler.log_cycle( + self.config.ai_name, + self.created_at, + self.cycle_count, + self.full_message_history, + FULL_MESSAGE_HISTORY_FILE_NAME, + ) if ( cfg.continuous_mode and cfg.continuous_limit > 0 - and loop_count > cfg.continuous_limit + and self.cycle_count > cfg.continuous_limit ): logger.typewriter_log( "Continuous Limit Reached: ", Fore.YELLOW, f"{cfg.continuous_limit}" @@ -122,6 +140,13 @@ class Agent: except Exception as e: logger.error("Error: \n", str(e)) + self.log_cycle_handler.log_cycle( + self.config.ai_name, + self.created_at, + self.cycle_count, + assistant_reply_json, + NEXT_ACTION_FILE_NAME, + ) if not cfg.continuous_mode and self.next_action_count == 0: # ### GET USER AUTHORIZATION TO EXECUTE COMMAND ### diff --git a/autogpt/llm/chat.py b/autogpt/llm/chat.py index b4e6b1a4..c795a3ca 100644 --- a/autogpt/llm/chat.py +++ b/autogpt/llm/chat.py @@ -8,6 +8,7 @@ from autogpt.llm.api_manager import ApiManager from autogpt.llm.base import Message from autogpt.llm.llm_utils import create_chat_completion from autogpt.llm.token_counter import count_message_tokens +from autogpt.log_cycle.log_cycle import PROMPT_NEXT_ACTION_FILE_NAME from autogpt.logs import logger from autogpt.memory_management.store_memory import ( save_memory_trimmed_from_context_window, @@ -231,6 +232,13 @@ def chat_with_ai( logger.debug(f"{message['role'].capitalize()}: {message['content']}") logger.debug("") logger.debug("----------- END OF CONTEXT ----------------") + agent.log_cycle_handler.log_cycle( + agent.config.ai_name, + agent.created_at, + agent.cycle_count, + current_context, + PROMPT_NEXT_ACTION_FILE_NAME, + ) # TODO: use a model defined elsewhere, so that model can contain # temperature and other settings we care about diff --git a/autogpt/log_cycle/__init__.py b/autogpt/log_cycle/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/autogpt/log_cycle/json_handler.py b/autogpt/log_cycle/json_handler.py new file mode 100644 index 00000000..51ae9ae0 --- /dev/null +++ b/autogpt/log_cycle/json_handler.py @@ -0,0 +1,20 @@ +import json +import logging + + +class JsonFileHandler(logging.FileHandler): + def __init__(self, filename, mode="a", encoding=None, delay=False): + super().__init__(filename, mode, encoding, delay) + + def emit(self, record): + json_data = json.loads(self.format(record)) + with open(self.baseFilename, "w", encoding="utf-8") as f: + json.dump(json_data, f, ensure_ascii=False, indent=4) + + +import logging + + +class JsonFormatter(logging.Formatter): + def format(self, record): + return record.msg diff --git a/autogpt/log_cycle/log_cycle.py b/autogpt/log_cycle/log_cycle.py new file mode 100644 index 00000000..720ca273 --- /dev/null +++ b/autogpt/log_cycle/log_cycle.py @@ -0,0 +1,80 @@ +import json +import os +from typing import Any, Dict, Union + +from autogpt.logs import logger + +DEFAULT_PREFIX = "agent" +FULL_MESSAGE_HISTORY_FILE_NAME = "full_message_history.json" +PROMPT_NEXT_ACTION_FILE_NAME = "prompt_next_action.json" +NEXT_ACTION_FILE_NAME = "next_action.json" + + +class LogCycleHandler: + """ + A class for logging cycle data. + """ + + def __init__(self): + self.log_count_within_cycle = 0 + + @staticmethod + def create_directory_if_not_exists(directory_path: str) -> None: + if not os.path.exists(directory_path): + os.makedirs(directory_path, exist_ok=True) + + def create_outer_directory(self, ai_name: str, created_at: str) -> str: + log_directory = logger.get_log_directory() + + if os.environ.get("OVERWRITE_DEBUG") == "1": + outer_folder_name = "auto_gpt" + else: + ai_name_short = ai_name[:15] if ai_name else DEFAULT_PREFIX + outer_folder_name = f"{created_at}_{ai_name_short}" + + outer_folder_path = os.path.join(log_directory, "DEBUG", outer_folder_name) + self.create_directory_if_not_exists(outer_folder_path) + + return outer_folder_path + + def create_inner_directory(self, outer_folder_path: str, cycle_count: int) -> str: + nested_folder_name = str(cycle_count).zfill(3) + nested_folder_path = os.path.join(outer_folder_path, nested_folder_name) + self.create_directory_if_not_exists(nested_folder_path) + + return nested_folder_path + + def create_nested_directory( + self, ai_name: str, created_at: str, cycle_count: int + ) -> str: + outer_folder_path = self.create_outer_directory(ai_name, created_at) + nested_folder_path = self.create_inner_directory(outer_folder_path, cycle_count) + + return nested_folder_path + + def log_cycle( + self, + ai_name: str, + created_at: str, + cycle_count: int, + data: Union[Dict[str, Any], Any], + file_name: str, + ) -> None: + """ + Log cycle data to a JSON file. + + Args: + data (Any): The data to be logged. + file_name (str): The name of the file to save the logged data. + """ + nested_folder_path = self.create_nested_directory( + ai_name, created_at, cycle_count + ) + + json_data = json.dumps(data, ensure_ascii=False, indent=4) + log_file_path = os.path.join( + nested_folder_path, f"{self.log_count_within_cycle}_{file_name}" + ) + + logger.log_json(json_data, log_file_path) + self.log_count_within_cycle += 1 diff --git a/autogpt/logs.py b/autogpt/logs.py index 1cbb784d..120db39d 100644 --- a/autogpt/logs.py +++ b/autogpt/logs.py @@ -5,9 +5,11 @@ import random import re import time from logging import LogRecord +from typing import Any from colorama import Fore, Style +from autogpt.log_cycle.json_handler import JsonFileHandler, JsonFormatter from autogpt.singleton import Singleton from autogpt.speech import say_text @@ -74,6 +76,11 @@ class Logger(metaclass=Singleton): self.logger.addHandler(error_handler) self.logger.setLevel(logging.DEBUG) + self.json_logger = logging.getLogger("JSON_LOGGER") + self.json_logger.addHandler(self.file_handler) + self.json_logger.addHandler(error_handler) + self.json_logger.setLevel(logging.DEBUG) + self.speak_mode = False self.chat_plugins = [] @@ -152,6 +159,26 @@ class Logger(metaclass=Singleton): self.typewriter_log("DOUBLE CHECK CONFIGURATION", Fore.YELLOW, additionalText) + def log_json(self, data: Any, file_name: str) -> None: + # Define log directory + this_files_dir_path = os.path.dirname(__file__) + log_dir = os.path.join(this_files_dir_path, "../logs") + + # Create a handler for JSON files + json_file_path = os.path.join(log_dir, file_name) + json_data_handler = JsonFileHandler(json_file_path) + json_data_handler.setFormatter(JsonFormatter()) + + # Log the JSON data using the custom file handler + self.json_logger.addHandler(json_data_handler) + self.json_logger.debug(data) + self.json_logger.removeHandler(json_data_handler) + + def get_log_directory(self): + this_files_dir_path = os.path.dirname(__file__) + log_dir = os.path.join(this_files_dir_path, "../logs") + return os.path.abspath(log_dir) + """ Output stream to console using simulated typing @@ -199,12 +226,16 @@ class AutoGptFormatter(logging.Formatter): if hasattr(record, "color"): record.title_color = ( getattr(record, "color") - + getattr(record, "title") + + getattr(record, "title", "") + " " + Style.RESET_ALL ) else: - record.title_color = getattr(record, "title") + record.title_color = getattr(record, "title", "") + + # Add this line to set 'title' to an empty string if it doesn't exist + record.title = getattr(record, "title", "") + if hasattr(record, "msg"): record.message_no_color = remove_color_codes(getattr(record, "msg")) else: