2023-04-15 12:56:23 +00:00
|
|
|
"""Logging module for Auto-GPT."""
|
|
|
|
import json
|
2023-04-07 23:05:08 +00:00
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import random
|
2023-04-12 12:39:54 +00:00
|
|
|
import re
|
2023-04-07 23:05:08 +00:00
|
|
|
import time
|
2023-04-15 12:56:23 +00:00
|
|
|
import traceback
|
2023-04-17 20:41:42 +00:00
|
|
|
from logging import LogRecord
|
2023-04-07 23:05:08 +00:00
|
|
|
|
2023-04-14 19:42:28 +00:00
|
|
|
from colorama import Fore, Style
|
2023-04-07 23:05:08 +00:00
|
|
|
|
2023-04-14 19:42:28 +00:00
|
|
|
from autogpt.config import Config, Singleton
|
2023-04-17 20:41:42 +00:00
|
|
|
from autogpt.speech import say_text
|
2023-04-07 23:05:08 +00:00
|
|
|
|
2023-04-15 12:56:23 +00:00
|
|
|
CFG = Config()
|
2023-04-07 23:05:20 +00:00
|
|
|
|
2023-04-07 23:05:08 +00:00
|
|
|
|
|
|
|
class Logger(metaclass=Singleton):
|
2023-04-15 12:56:23 +00:00
|
|
|
"""
|
|
|
|
Logger that handle titles in different colors.
|
|
|
|
Outputs logs in console, activity.log, and errors.log
|
|
|
|
For console handler: simulates typing
|
|
|
|
"""
|
|
|
|
|
2023-04-07 23:05:08 +00:00
|
|
|
def __init__(self):
|
|
|
|
# create log directory if it doesn't exist
|
2023-04-14 11:32:33 +00:00
|
|
|
this_files_dir_path = os.path.dirname(__file__)
|
2023-04-14 19:42:28 +00:00
|
|
|
log_dir = os.path.join(this_files_dir_path, "../logs")
|
2023-04-07 23:05:08 +00:00
|
|
|
if not os.path.exists(log_dir):
|
|
|
|
os.makedirs(log_dir)
|
|
|
|
|
|
|
|
log_file = "activity.log"
|
|
|
|
error_file = "error.log"
|
|
|
|
|
2023-04-14 19:42:28 +00:00
|
|
|
console_formatter = AutoGptFormatter("%(title_color)s %(message)s")
|
2023-04-12 12:39:54 +00:00
|
|
|
|
|
|
|
# Create a handler for console which simulate typing
|
|
|
|
self.typing_console_handler = TypingConsoleHandler()
|
|
|
|
self.typing_console_handler.setLevel(logging.INFO)
|
|
|
|
self.typing_console_handler.setFormatter(console_formatter)
|
|
|
|
|
|
|
|
# Create a handler for console without typing simulation
|
|
|
|
self.console_handler = ConsoleHandler()
|
|
|
|
self.console_handler.setLevel(logging.DEBUG)
|
2023-04-07 23:05:08 +00:00
|
|
|
self.console_handler.setFormatter(console_formatter)
|
|
|
|
|
|
|
|
# Info handler in activity.log
|
2023-04-16 15:38:08 +00:00
|
|
|
self.file_handler = logging.FileHandler(
|
2023-04-17 20:41:42 +00:00
|
|
|
os.path.join(log_dir, log_file), "a", "utf-8"
|
2023-04-16 15:38:08 +00:00
|
|
|
)
|
2023-04-12 17:27:49 +00:00
|
|
|
self.file_handler.setLevel(logging.DEBUG)
|
2023-04-14 19:42:28 +00:00
|
|
|
info_formatter = AutoGptFormatter(
|
|
|
|
"%(asctime)s %(levelname)s %(title)s %(message_no_color)s"
|
|
|
|
)
|
2023-04-07 23:05:08 +00:00
|
|
|
self.file_handler.setFormatter(info_formatter)
|
|
|
|
|
|
|
|
# Error handler error.log
|
2023-04-16 15:38:08 +00:00
|
|
|
error_handler = logging.FileHandler(
|
2023-04-17 20:41:42 +00:00
|
|
|
os.path.join(log_dir, error_file), "a", "utf-8"
|
2023-04-16 15:38:08 +00:00
|
|
|
)
|
2023-04-07 23:05:08 +00:00
|
|
|
error_handler.setLevel(logging.ERROR)
|
|
|
|
error_formatter = AutoGptFormatter(
|
2023-04-15 00:04:48 +00:00
|
|
|
"%(asctime)s %(levelname)s %(module)s:%(funcName)s:%(lineno)d %(title)s"
|
|
|
|
" %(message_no_color)s"
|
2023-04-14 19:42:28 +00:00
|
|
|
)
|
2023-04-07 23:05:08 +00:00
|
|
|
error_handler.setFormatter(error_formatter)
|
|
|
|
|
2023-04-14 19:42:28 +00:00
|
|
|
self.typing_logger = logging.getLogger("TYPER")
|
2023-04-12 12:39:54 +00:00
|
|
|
self.typing_logger.addHandler(self.typing_console_handler)
|
|
|
|
self.typing_logger.addHandler(self.file_handler)
|
|
|
|
self.typing_logger.addHandler(error_handler)
|
|
|
|
self.typing_logger.setLevel(logging.DEBUG)
|
|
|
|
|
2023-04-14 19:42:28 +00:00
|
|
|
self.logger = logging.getLogger("LOGGER")
|
2023-04-07 23:05:08 +00:00
|
|
|
self.logger.addHandler(self.console_handler)
|
|
|
|
self.logger.addHandler(self.file_handler)
|
|
|
|
self.logger.addHandler(error_handler)
|
2023-04-12 12:39:54 +00:00
|
|
|
self.logger.setLevel(logging.DEBUG)
|
2023-04-07 23:05:08 +00:00
|
|
|
|
2023-04-12 12:43:32 +00:00
|
|
|
def typewriter_log(
|
2023-04-17 20:41:42 +00:00
|
|
|
self, title="", title_color="", content="", speak_text=False, level=logging.INFO
|
2023-04-14 19:42:28 +00:00
|
|
|
):
|
2023-04-15 12:56:23 +00:00
|
|
|
if speak_text and CFG.speak_mode:
|
|
|
|
say_text(f"{title}. {content}")
|
2023-04-07 23:05:08 +00:00
|
|
|
|
|
|
|
if content:
|
|
|
|
if isinstance(content, list):
|
|
|
|
content = " ".join(content)
|
|
|
|
else:
|
|
|
|
content = ""
|
|
|
|
|
2023-04-14 19:42:28 +00:00
|
|
|
self.typing_logger.log(
|
|
|
|
level, content, extra={"title": title, "color": title_color}
|
|
|
|
)
|
2023-04-12 12:39:54 +00:00
|
|
|
|
|
|
|
def debug(
|
2023-04-17 20:41:42 +00:00
|
|
|
self,
|
|
|
|
message,
|
|
|
|
title="",
|
|
|
|
title_color="",
|
2023-04-12 12:39:54 +00:00
|
|
|
):
|
2023-04-12 12:43:32 +00:00
|
|
|
self._log(title, title_color, message, logging.DEBUG)
|
2023-04-12 12:39:54 +00:00
|
|
|
|
|
|
|
def warn(
|
2023-04-17 20:41:42 +00:00
|
|
|
self,
|
|
|
|
message,
|
|
|
|
title="",
|
|
|
|
title_color="",
|
2023-04-12 12:39:54 +00:00
|
|
|
):
|
2023-04-12 12:43:32 +00:00
|
|
|
self._log(title, title_color, message, logging.WARN)
|
2023-04-12 12:39:54 +00:00
|
|
|
|
2023-04-14 19:42:28 +00:00
|
|
|
def error(self, title, message=""):
|
2023-04-12 12:43:32 +00:00
|
|
|
self._log(title, Fore.RED, message, logging.ERROR)
|
2023-04-12 12:39:54 +00:00
|
|
|
|
2023-04-14 19:42:28 +00:00
|
|
|
def _log(self, title="", title_color="", message="", level=logging.INFO):
|
2023-04-12 12:39:54 +00:00
|
|
|
if message:
|
|
|
|
if isinstance(message, list):
|
|
|
|
message = " ".join(message)
|
2023-04-14 19:42:28 +00:00
|
|
|
self.logger.log(level, message, extra={"title": title, "color": title_color})
|
2023-04-07 23:05:08 +00:00
|
|
|
|
|
|
|
def set_level(self, level):
|
|
|
|
self.logger.setLevel(level)
|
2023-04-12 12:39:54 +00:00
|
|
|
self.typing_logger.setLevel(level)
|
2023-04-07 23:05:08 +00:00
|
|
|
|
2023-04-12 20:38:53 +00:00
|
|
|
def double_check(self, additionalText=None):
|
|
|
|
if not additionalText:
|
2023-04-15 00:04:48 +00:00
|
|
|
additionalText = (
|
|
|
|
"Please ensure you've setup and configured everything"
|
|
|
|
" correctly. Read https://github.com/Torantulino/Auto-GPT#readme to "
|
|
|
|
"double check. You can also create a github issue or join the discord"
|
|
|
|
" and ask there!"
|
|
|
|
)
|
2023-04-07 23:05:08 +00:00
|
|
|
|
2023-04-12 20:38:53 +00:00
|
|
|
self.typewriter_log("DOUBLE CHECK CONFIGURATION", Fore.YELLOW, additionalText)
|
2023-04-07 23:05:08 +00:00
|
|
|
|
|
|
|
|
2023-04-14 19:42:28 +00:00
|
|
|
"""
|
2023-04-07 23:05:20 +00:00
|
|
|
Output stream to console using simulated typing
|
2023-04-14 19:42:28 +00:00
|
|
|
"""
|
2023-04-07 23:05:20 +00:00
|
|
|
|
|
|
|
|
2023-04-07 23:05:08 +00:00
|
|
|
class TypingConsoleHandler(logging.StreamHandler):
|
|
|
|
def emit(self, record):
|
|
|
|
min_typing_speed = 0.05
|
|
|
|
max_typing_speed = 0.01
|
|
|
|
|
|
|
|
msg = self.format(record)
|
|
|
|
try:
|
|
|
|
words = msg.split()
|
|
|
|
for i, word in enumerate(words):
|
|
|
|
print(word, end="", flush=True)
|
|
|
|
if i < len(words) - 1:
|
|
|
|
print(" ", end="", flush=True)
|
|
|
|
typing_speed = random.uniform(min_typing_speed, max_typing_speed)
|
|
|
|
time.sleep(typing_speed)
|
|
|
|
# type faster after each word
|
|
|
|
min_typing_speed = min_typing_speed * 0.95
|
|
|
|
max_typing_speed = max_typing_speed * 0.95
|
|
|
|
print()
|
|
|
|
except Exception:
|
|
|
|
self.handleError(record)
|
|
|
|
|
2023-04-12 21:05:14 +00:00
|
|
|
|
2023-04-12 12:39:54 +00:00
|
|
|
class ConsoleHandler(logging.StreamHandler):
|
2023-04-15 00:04:48 +00:00
|
|
|
def emit(self, record) -> None:
|
2023-04-12 12:39:54 +00:00
|
|
|
msg = self.format(record)
|
|
|
|
try:
|
|
|
|
print(msg)
|
|
|
|
except Exception:
|
|
|
|
self.handleError(record)
|
2023-04-07 23:05:08 +00:00
|
|
|
|
2023-04-12 20:12:25 +00:00
|
|
|
|
2023-04-07 23:05:08 +00:00
|
|
|
class AutoGptFormatter(logging.Formatter):
|
2023-04-13 09:05:36 +00:00
|
|
|
"""
|
|
|
|
Allows to handle custom placeholders 'title_color' and 'message_no_color'.
|
|
|
|
To use this formatter, make sure to pass 'color', 'title' as log extras.
|
|
|
|
"""
|
2023-04-14 19:42:28 +00:00
|
|
|
|
2023-04-07 23:05:08 +00:00
|
|
|
def format(self, record: LogRecord) -> str:
|
2023-04-14 19:42:28 +00:00
|
|
|
if hasattr(record, "color"):
|
|
|
|
record.title_color = (
|
2023-04-17 20:41:42 +00:00
|
|
|
getattr(record, "color")
|
|
|
|
+ getattr(record, "title")
|
|
|
|
+ " "
|
|
|
|
+ Style.RESET_ALL
|
2023-04-14 19:42:28 +00:00
|
|
|
)
|
2023-04-12 12:39:54 +00:00
|
|
|
else:
|
2023-04-14 19:42:28 +00:00
|
|
|
record.title_color = getattr(record, "title")
|
|
|
|
if hasattr(record, "msg"):
|
|
|
|
record.message_no_color = remove_color_codes(getattr(record, "msg"))
|
2023-04-07 23:05:08 +00:00
|
|
|
else:
|
2023-04-14 19:42:28 +00:00
|
|
|
record.message_no_color = ""
|
2023-04-07 23:05:08 +00:00
|
|
|
return super().format(record)
|
|
|
|
|
|
|
|
|
|
|
|
def remove_color_codes(s: str) -> str:
|
2023-04-14 19:42:28 +00:00
|
|
|
ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
|
|
|
return ansi_escape.sub("", s)
|
2023-04-07 23:05:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
logger = Logger()
|
2023-04-15 12:56:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
def print_assistant_thoughts(ai_name, assistant_reply):
|
|
|
|
"""Prints the assistant's thoughts to the console"""
|
2023-04-19 00:40:14 +00:00
|
|
|
from autogpt.json_utils.json_fix_llm import (
|
|
|
|
attempt_to_fix_json_by_finding_outermost_brackets,
|
|
|
|
fix_and_parse_json,
|
|
|
|
)
|
2023-04-15 12:56:23 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
# Parse and print Assistant response
|
|
|
|
assistant_reply_json = fix_and_parse_json(assistant_reply)
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
logger.error("Error: Invalid JSON in assistant thoughts\n", assistant_reply)
|
|
|
|
assistant_reply_json = attempt_to_fix_json_by_finding_outermost_brackets(
|
|
|
|
assistant_reply
|
|
|
|
)
|
|
|
|
if isinstance(assistant_reply_json, str):
|
|
|
|
assistant_reply_json = fix_and_parse_json(assistant_reply_json)
|
|
|
|
|
|
|
|
# Check if assistant_reply_json is a string and attempt to parse
|
|
|
|
# it into a JSON object
|
|
|
|
if isinstance(assistant_reply_json, str):
|
|
|
|
try:
|
|
|
|
assistant_reply_json = json.loads(assistant_reply_json)
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
logger.error("Error: Invalid JSON\n", assistant_reply)
|
|
|
|
assistant_reply_json = (
|
|
|
|
attempt_to_fix_json_by_finding_outermost_brackets(
|
|
|
|
assistant_reply_json
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
assistant_thoughts_reasoning = None
|
|
|
|
assistant_thoughts_plan = None
|
|
|
|
assistant_thoughts_speak = None
|
|
|
|
assistant_thoughts_criticism = None
|
|
|
|
if not isinstance(assistant_reply_json, dict):
|
|
|
|
assistant_reply_json = {}
|
|
|
|
assistant_thoughts = assistant_reply_json.get("thoughts", {})
|
|
|
|
assistant_thoughts_text = assistant_thoughts.get("text")
|
|
|
|
|
|
|
|
if assistant_thoughts:
|
|
|
|
assistant_thoughts_reasoning = assistant_thoughts.get("reasoning")
|
|
|
|
assistant_thoughts_plan = assistant_thoughts.get("plan")
|
|
|
|
assistant_thoughts_criticism = assistant_thoughts.get("criticism")
|
|
|
|
assistant_thoughts_speak = assistant_thoughts.get("speak")
|
|
|
|
|
|
|
|
logger.typewriter_log(
|
|
|
|
f"{ai_name.upper()} THOUGHTS:", Fore.YELLOW, f"{assistant_thoughts_text}"
|
|
|
|
)
|
|
|
|
logger.typewriter_log(
|
|
|
|
"REASONING:", Fore.YELLOW, f"{assistant_thoughts_reasoning}"
|
|
|
|
)
|
|
|
|
|
|
|
|
if assistant_thoughts_plan:
|
|
|
|
logger.typewriter_log("PLAN:", Fore.YELLOW, "")
|
|
|
|
# If it's a list, join it into a string
|
|
|
|
if isinstance(assistant_thoughts_plan, list):
|
|
|
|
assistant_thoughts_plan = "\n".join(assistant_thoughts_plan)
|
|
|
|
elif isinstance(assistant_thoughts_plan, dict):
|
|
|
|
assistant_thoughts_plan = str(assistant_thoughts_plan)
|
|
|
|
|
|
|
|
# Split the input_string using the newline character and dashes
|
|
|
|
lines = assistant_thoughts_plan.split("\n")
|
|
|
|
for line in lines:
|
|
|
|
line = line.lstrip("- ")
|
|
|
|
logger.typewriter_log("- ", Fore.GREEN, line.strip())
|
|
|
|
|
|
|
|
logger.typewriter_log(
|
|
|
|
"CRITICISM:", Fore.YELLOW, f"{assistant_thoughts_criticism}"
|
|
|
|
)
|
|
|
|
# Speak the assistant's thoughts
|
|
|
|
if CFG.speak_mode and assistant_thoughts_speak:
|
|
|
|
say_text(assistant_thoughts_speak)
|
2023-04-15 22:32:26 +00:00
|
|
|
else:
|
2023-04-16 06:32:18 +00:00
|
|
|
logger.typewriter_log("SPEAK:", Fore.YELLOW, f"{assistant_thoughts_speak}")
|
2023-04-15 12:56:23 +00:00
|
|
|
|
|
|
|
return assistant_reply_json
|
|
|
|
except json.decoder.JSONDecodeError:
|
|
|
|
logger.error("Error: Invalid JSON\n", assistant_reply)
|
|
|
|
if CFG.speak_mode:
|
|
|
|
say_text(
|
|
|
|
"I have received an invalid JSON response from the OpenAI API."
|
|
|
|
" I cannot ignore this response."
|
|
|
|
)
|
|
|
|
|
|
|
|
# All other errors, return "Error: + error message"
|
|
|
|
except Exception:
|
|
|
|
call_stack = traceback.format_exc()
|
|
|
|
logger.error("Error: \n", call_stack)
|
2023-04-16 16:43:40 +00:00
|
|
|
|
2023-04-16 16:46:18 +00:00
|
|
|
|
2023-04-17 20:41:42 +00:00
|
|
|
def print_assistant_thoughts(
|
|
|
|
ai_name: object, assistant_reply_json_valid: object
|
|
|
|
) -> None:
|
2023-04-16 16:43:40 +00:00
|
|
|
assistant_thoughts_reasoning = None
|
|
|
|
assistant_thoughts_plan = None
|
|
|
|
assistant_thoughts_speak = None
|
|
|
|
assistant_thoughts_criticism = None
|
|
|
|
|
|
|
|
assistant_thoughts = assistant_reply_json_valid.get("thoughts", {})
|
|
|
|
assistant_thoughts_text = assistant_thoughts.get("text")
|
|
|
|
if assistant_thoughts:
|
|
|
|
assistant_thoughts_reasoning = assistant_thoughts.get("reasoning")
|
|
|
|
assistant_thoughts_plan = assistant_thoughts.get("plan")
|
|
|
|
assistant_thoughts_criticism = assistant_thoughts.get("criticism")
|
|
|
|
assistant_thoughts_speak = assistant_thoughts.get("speak")
|
|
|
|
logger.typewriter_log(
|
|
|
|
f"{ai_name.upper()} THOUGHTS:", Fore.YELLOW, f"{assistant_thoughts_text}"
|
|
|
|
)
|
2023-04-17 20:41:42 +00:00
|
|
|
logger.typewriter_log("REASONING:", Fore.YELLOW, f"{assistant_thoughts_reasoning}")
|
2023-04-16 16:43:40 +00:00
|
|
|
if assistant_thoughts_plan:
|
|
|
|
logger.typewriter_log("PLAN:", Fore.YELLOW, "")
|
|
|
|
# If it's a list, join it into a string
|
|
|
|
if isinstance(assistant_thoughts_plan, list):
|
|
|
|
assistant_thoughts_plan = "\n".join(assistant_thoughts_plan)
|
|
|
|
elif isinstance(assistant_thoughts_plan, dict):
|
|
|
|
assistant_thoughts_plan = str(assistant_thoughts_plan)
|
|
|
|
|
|
|
|
# Split the input_string using the newline character and dashes
|
|
|
|
lines = assistant_thoughts_plan.split("\n")
|
|
|
|
for line in lines:
|
|
|
|
line = line.lstrip("- ")
|
|
|
|
logger.typewriter_log("- ", Fore.GREEN, line.strip())
|
2023-04-17 20:41:42 +00:00
|
|
|
logger.typewriter_log("CRITICISM:", Fore.YELLOW, f"{assistant_thoughts_criticism}")
|
2023-04-16 16:43:40 +00:00
|
|
|
# Speak the assistant's thoughts
|
|
|
|
if CFG.speak_mode and assistant_thoughts_speak:
|
|
|
|
say_text(assistant_thoughts_speak)
|