From a7f805604c2e6c69aaf13dc84e09dff028455258 Mon Sep 17 00:00:00 2001 From: merwanehamadi Date: Sun, 18 Jun 2023 19:05:41 -0700 Subject: [PATCH] Pass config everywhere in order to get rid of singleton (#4666) Signed-off-by: Merwane Hamadi --- autogpt/agent/agent.py | 10 +-- autogpt/agent/agent_manager.py | 20 +++--- autogpt/command_decorator.py | 4 +- autogpt/commands/execute_code.py | 8 +-- autogpt/commands/file_operations.py | 3 +- autogpt/commands/image_gen.py | 2 +- autogpt/commands/web_selenium.py | 2 +- autogpt/config/ai_config.py | 22 +++---- autogpt/config/config.py | 5 +- autogpt/config/prompt_config.py | 12 +--- autogpt/json_utils/utilities.py | 5 +- autogpt/llm/chat.py | 7 ++- autogpt/llm/utils/__init__.py | 30 ++++----- autogpt/logs.py | 7 ++- autogpt/main.py | 38 +++++------ autogpt/memory/message_history.py | 36 ++++++----- autogpt/memory/vector/__init__.py | 18 +++--- autogpt/memory/vector/memory_item.py | 20 +++--- autogpt/memory/vector/providers/base.py | 18 +++--- autogpt/memory/vector/providers/json_file.py | 8 +-- autogpt/memory/vector/utils.py | 13 ++-- autogpt/plugins/__init__.py | 32 +++++----- autogpt/processing/text.py | 29 ++++++--- autogpt/prompts/prompt.py | 63 +++++++++---------- autogpt/setup.py | 31 ++++----- autogpt/speech/base.py | 5 +- autogpt/speech/eleven_labs.py | 17 +++-- autogpt/speech/say.py | 7 +-- autogpt/utils.py | 11 ++-- data_ingestion.py | 4 +- docs/challenges/building_challenges.md | 2 +- tests/Auto-GPT-test-cassettes | 2 +- tests/conftest.py | 2 +- tests/integration/agent_factory.py | 26 ++++---- tests/integration/goal_oriented/__init__.py | 0 .../memory/test_json_file_memory.py | 27 +++++--- tests/integration/test_setup.py | 16 ++--- tests/unit/test_agent_manager.py | 4 +- tests/unit/test_ai_config.py | 22 +++---- tests/unit/test_config.py | 6 +- tests/unit/test_file_operations.py | 7 ++- tests/unit/test_message_history.py | 7 +-- tests/unit/test_prompt_config.py | 6 +- tests/unit/test_utils.py | 9 +-- 44 files changed, 323 insertions(+), 300 deletions(-) delete mode 100644 tests/integration/goal_oriented/__init__.py diff --git a/autogpt/agent/agent.py b/autogpt/agent/agent.py index 8af9f49e..7537233e 100644 --- a/autogpt/agent/agent.py +++ b/autogpt/agent/agent.py @@ -143,7 +143,7 @@ class Agent: try: assistant_reply_json = extract_json_from_response(assistant_reply) - validate_json(assistant_reply_json) + validate_json(assistant_reply_json, self.config) except json.JSONDecodeError as e: logger.error(f"Exception while validating assistant reply JSON: {e}") assistant_reply_json = {} @@ -158,7 +158,7 @@ class Agent: # Get command name and arguments try: print_assistant_thoughts( - self.ai_name, assistant_reply_json, self.config.speak_mode + self.ai_name, assistant_reply_json, self.config ) command_name, arguments = get_command(assistant_reply_json) if self.config.speak_mode: @@ -197,10 +197,12 @@ class Agent: ) while True: if self.config.chat_messages_enabled: - console_input = clean_input("Waiting for your response...") + console_input = clean_input( + self.config, "Waiting for your response..." + ) else: console_input = clean_input( - Fore.MAGENTA + "Input:" + Style.RESET_ALL + self.config, Fore.MAGENTA + "Input:" + Style.RESET_ALL ) if console_input.lower().strip() == self.config.authorise_key: user_input = "GENERATE NEXT COMMAND JSON" diff --git a/autogpt/agent/agent_manager.py b/autogpt/agent/agent_manager.py index 8560b0ec..1f1c8a1d 100644 --- a/autogpt/agent/agent_manager.py +++ b/autogpt/agent/agent_manager.py @@ -10,12 +10,12 @@ from autogpt.singleton import Singleton class AgentManager(metaclass=Singleton): """Agent manager for managing GPT agents""" - def __init__(self): + def __init__(self, config: Config): self.next_key = 0 self.agents: dict[ int, tuple[str, list[Message], str] ] = {} # key, (task, full_message_history, model) - self.cfg = Config() + self.config = config # Create new GPT agent # TODO: Centralise use of create_chat_completion() to globally enforce token limit @@ -35,18 +35,18 @@ class AgentManager(metaclass=Singleton): """ messages = ChatSequence.for_model(model, [Message("user", creation_prompt)]) - for plugin in self.cfg.plugins: + for plugin in self.config.plugins: if not plugin.can_handle_pre_instruction(): continue if plugin_messages := plugin.pre_instruction(messages.raw()): messages.extend([Message(**raw_msg) for raw_msg in plugin_messages]) # Start GPT instance - agent_reply = create_chat_completion(prompt=messages) + agent_reply = create_chat_completion(prompt=messages, config=self.config) messages.add("assistant", agent_reply) plugins_reply = "" - for i, plugin in enumerate(self.cfg.plugins): + for i, plugin in enumerate(self.config.plugins): if not plugin.can_handle_on_instruction(): continue if plugin_result := plugin.on_instruction([m.raw() for m in messages]): @@ -62,7 +62,7 @@ class AgentManager(metaclass=Singleton): self.agents[key] = (task, list(messages), model) - for plugin in self.cfg.plugins: + for plugin in self.config.plugins: if not plugin.can_handle_post_instruction(): continue agent_reply = plugin.post_instruction(agent_reply) @@ -85,19 +85,19 @@ class AgentManager(metaclass=Singleton): messages = ChatSequence.for_model(model, messages) messages.add("user", message) - for plugin in self.cfg.plugins: + for plugin in self.config.plugins: if not plugin.can_handle_pre_instruction(): continue if plugin_messages := plugin.pre_instruction([m.raw() for m in messages]): messages.extend([Message(**raw_msg) for raw_msg in plugin_messages]) # Start GPT instance - agent_reply = create_chat_completion(prompt=messages) + agent_reply = create_chat_completion(prompt=messages, config=self.config) messages.add("assistant", agent_reply) plugins_reply = agent_reply - for i, plugin in enumerate(self.cfg.plugins): + for i, plugin in enumerate(self.config.plugins): if not plugin.can_handle_on_instruction(): continue if plugin_result := plugin.on_instruction([m.raw() for m in messages]): @@ -107,7 +107,7 @@ class AgentManager(metaclass=Singleton): if plugins_reply and plugins_reply != "": messages.add("assistant", plugins_reply) - for plugin in self.cfg.plugins: + for plugin in self.config.plugins: if not plugin.can_handle_post_instruction(): continue agent_reply = plugin.post_instruction(agent_reply) diff --git a/autogpt/command_decorator.py b/autogpt/command_decorator.py index 98f114e4..7ac4b7cd 100644 --- a/autogpt/command_decorator.py +++ b/autogpt/command_decorator.py @@ -19,10 +19,10 @@ def command( """The command decorator is used to create Command objects from ordinary functions.""" # TODO: Remove this in favor of better command management - CFG = Config() + config = Config() if callable(enabled): - enabled = enabled(CFG) + enabled = enabled(config) if not enabled: if disabled_reason is not None: logger.debug(f"Command '{name}' is disabled: {disabled_reason}") diff --git a/autogpt/commands/execute_code.py b/autogpt/commands/execute_code.py index beaae64c..663800ef 100644 --- a/autogpt/commands/execute_code.py +++ b/autogpt/commands/execute_code.py @@ -10,7 +10,6 @@ from autogpt.agent.agent import Agent from autogpt.command_decorator import command from autogpt.config import Config from autogpt.logs import logger -from autogpt.setup import CFG from autogpt.workspace.workspace import Workspace ALLOWLIST_CONTROL = "allowlist" @@ -83,7 +82,7 @@ def execute_python_file(filename: str, agent: Agent) -> str: str: The output of the file """ logger.info( - f"Executing python file '{filename}' in working directory '{CFG.workspace_path}'" + f"Executing python file '{filename}' in working directory '{agent.config.workspace_path}'" ) if not filename.endswith(".py"): @@ -105,7 +104,7 @@ def execute_python_file(filename: str, agent: Agent) -> str: ["python", str(path)], capture_output=True, encoding="utf8", - cwd=CFG.workspace_path, + cwd=agent.config.workspace_path, ) if result.returncode == 0: return result.stdout @@ -174,6 +173,7 @@ def validate_command(command: str, config: Config) -> bool: Args: command (str): The command to validate + config (Config): The config to use to validate the command Returns: bool: True if the command is allowed, False otherwise @@ -199,7 +199,7 @@ def validate_command(command: str, config: Config) -> bool: "required": True, } }, - enabled=lambda cfg: cfg.execute_local_commands, + enabled=lambda config: config.execute_local_commands, disabled_reason="You are not allowed to run local shell commands. To execute" " shell commands, EXECUTE_LOCAL_COMMANDS must be set to 'True' " "in your config file: .env - do not attempt to bypass the restriction.", diff --git a/autogpt/commands/file_operations.py b/autogpt/commands/file_operations.py index 2a932d38..ebf15ac0 100644 --- a/autogpt/commands/file_operations.py +++ b/autogpt/commands/file_operations.py @@ -81,6 +81,7 @@ def is_duplicate_operation( Args: operation: The operation to check for filename: The name of the file to check for + config: The agent config checksum: The checksum of the contents to be written Returns: @@ -137,7 +138,7 @@ def read_file(filename: str, agent: Agent) -> str: content = read_textual_file(filename, logger) # TODO: invalidate/update memory when file is edited - file_memory = MemoryItem.from_text_file(content, filename) + file_memory = MemoryItem.from_text_file(content, filename, agent.config) if len(file_memory.chunks) > 1: return file_memory.summary diff --git a/autogpt/commands/image_gen.py b/autogpt/commands/image_gen.py index 043e91d7..d6bb73d8 100644 --- a/autogpt/commands/image_gen.py +++ b/autogpt/commands/image_gen.py @@ -181,7 +181,7 @@ def generate_image_with_sd_webui( "negative_prompt": negative_prompt, "sampler_index": "DDIM", "steps": 20, - "cfg_scale": 7.0, + "config_scale": 7.0, "width": size, "height": size, "n_iter": 1, diff --git a/autogpt/commands/web_selenium.py b/autogpt/commands/web_selenium.py index 718cde71..471e203b 100644 --- a/autogpt/commands/web_selenium.py +++ b/autogpt/commands/web_selenium.py @@ -232,6 +232,6 @@ def summarize_memorize_webpage( memory = get_memory(agent.config) - new_memory = MemoryItem.from_webpage(text, url, question=question) + new_memory = MemoryItem.from_webpage(text, url, agent.config, question=question) memory.add(new_memory) return new_memory.summary diff --git a/autogpt/config/ai_config.py b/autogpt/config/ai_config.py index d118be3f..6b9e15f1 100644 --- a/autogpt/config/ai_config.py +++ b/autogpt/config/ai_config.py @@ -59,14 +59,14 @@ class AIConfig: self.command_registry: CommandRegistry | None = None @staticmethod - def load(config_file: str = SAVE_FILE) -> "AIConfig": + def load(ai_settings_file: str = SAVE_FILE) -> "AIConfig": """ Returns class object with parameters (ai_name, ai_role, ai_goals, api_budget) loaded from yaml file if yaml file exists, else returns class with no parameters. Parameters: - config_file (int): The path to the config yaml file. + ai_settings_file (int): The path to the config yaml file. DEFAULT: "../ai_settings.yaml" Returns: @@ -74,7 +74,7 @@ class AIConfig: """ try: - with open(config_file, encoding="utf-8") as file: + with open(ai_settings_file, encoding="utf-8") as file: config_params = yaml.load(file, Loader=yaml.FullLoader) or {} except FileNotFoundError: config_params = {} @@ -91,12 +91,12 @@ class AIConfig: # type: Type[AIConfig] return AIConfig(ai_name, ai_role, ai_goals, api_budget) - def save(self, config_file: str = SAVE_FILE) -> None: + def save(self, ai_settings_file: str = SAVE_FILE) -> None: """ Saves the class parameters to the specified file yaml file path as a yaml file. Parameters: - config_file(str): The path to the config yaml file. + ai_settings_file(str): The path to the config yaml file. DEFAULT: "../ai_settings.yaml" Returns: @@ -109,11 +109,11 @@ class AIConfig: "ai_goals": self.ai_goals, "api_budget": self.api_budget, } - with open(config_file, "w", encoding="utf-8") as file: + with open(ai_settings_file, "w", encoding="utf-8") as file: yaml.dump(config, file, allow_unicode=True) def construct_full_prompt( - self, prompt_generator: Optional[PromptGenerator] = None + self, config, prompt_generator: Optional[PromptGenerator] = None ) -> str: """ Returns a prompt to the user with the class information in an organized fashion. @@ -133,22 +133,20 @@ class AIConfig: "" ) - from autogpt.config import Config from autogpt.prompts.prompt import build_default_prompt_generator - cfg = Config() if prompt_generator is None: - prompt_generator = build_default_prompt_generator() + prompt_generator = build_default_prompt_generator(config) prompt_generator.goals = self.ai_goals prompt_generator.name = self.ai_name prompt_generator.role = self.ai_role prompt_generator.command_registry = self.command_registry - for plugin in cfg.plugins: + for plugin in config.plugins: if not plugin.can_handle_post_prompt(): continue prompt_generator = plugin.post_prompt(prompt_generator) - if cfg.execute_local_commands: + if config.execute_local_commands: # add OS info to prompt os_name = platform.system() os_info = ( diff --git a/autogpt/config/config.py b/autogpt/config/config.py index 92712dd7..3231f560 100644 --- a/autogpt/config/config.py +++ b/autogpt/config/config.py @@ -300,10 +300,9 @@ class Config(metaclass=Singleton): self.memory_backend = name -def check_openai_api_key() -> None: +def check_openai_api_key(config: Config) -> None: """Check if the OpenAI API key is set in config.py or as an environment variable.""" - cfg = Config() - if not cfg.openai_api_key: + if not config.openai_api_key: print( Fore.RED + "Please set your OpenAI API key in .env or as an environment variable." diff --git a/autogpt/config/prompt_config.py b/autogpt/config/prompt_config.py index 3f562c95..793bb444 100644 --- a/autogpt/config/prompt_config.py +++ b/autogpt/config/prompt_config.py @@ -6,11 +6,8 @@ import yaml from colorama import Fore from autogpt import utils -from autogpt.config.config import Config from autogpt.logs import logger -CFG = Config() - class PromptConfig: """ @@ -22,10 +19,7 @@ class PromptConfig: performance_evaluations (list): Performance evaluation list for the prompt generator. """ - def __init__( - self, - config_file: str = CFG.prompt_settings_file, - ) -> None: + def __init__(self, prompt_settings_file: str) -> None: """ Initialize a class instance with parameters (constraints, resources, performance_evaluations) loaded from yaml file if yaml file exists, @@ -39,13 +33,13 @@ class PromptConfig: None """ # Validate file - (validated, message) = utils.validate_yaml_file(config_file) + (validated, message) = utils.validate_yaml_file(prompt_settings_file) if not validated: logger.typewriter_log("FAILED FILE VALIDATION", Fore.RED, message) logger.double_check() exit(1) - with open(config_file, encoding="utf-8") as file: + with open(prompt_settings_file, encoding="utf-8") as file: config_params = yaml.load(file, Loader=yaml.FullLoader) self.constraints = config_params.get("constraints", []) diff --git a/autogpt/json_utils/utilities.py b/autogpt/json_utils/utilities.py index e492d302..62f3b3ca 100644 --- a/autogpt/json_utils/utilities.py +++ b/autogpt/json_utils/utilities.py @@ -9,7 +9,6 @@ from jsonschema import Draft7Validator from autogpt.config import Config from autogpt.logs import logger -CFG = Config() LLM_DEFAULT_RESPONSE_FORMAT = "llm_response_format_1" @@ -37,7 +36,7 @@ def llm_response_schema( def validate_json( - json_object: object, schema_name: str = LLM_DEFAULT_RESPONSE_FORMAT + json_object: object, config: Config, schema_name: str = LLM_DEFAULT_RESPONSE_FORMAT ) -> bool: """ :type schema_name: object @@ -54,7 +53,7 @@ def validate_json( for error in errors: logger.error(f"JSON Validation Error: {error}") - if CFG.debug_mode: + if config.debug_mode: logger.error( json.dumps(json_object, indent=4) ) # Replace 'json_object' with the variable containing the JSON data diff --git a/autogpt/llm/chat.py b/autogpt/llm/chat.py index 41d1b78b..0a088d06 100644 --- a/autogpt/llm/chat.py +++ b/autogpt/llm/chat.py @@ -96,7 +96,7 @@ def chat_with_ai( current_tokens_used += 500 # Reserve space for new_summary_message # Add Messages until the token limit is reached or there are no more messages to add. - for cycle in reversed(list(agent.history.per_cycle())): + for cycle in reversed(list(agent.history.per_cycle(agent.config))): messages_to_add = [msg for msg in cycle if msg is not None] tokens_to_add = count_message_tokens(messages_to_add, model) if current_tokens_used + tokens_to_add > send_token_limit: @@ -110,14 +110,14 @@ def chat_with_ai( # Update & add summary of trimmed messages if len(agent.history) > 0: new_summary_message, trimmed_messages = agent.history.trim_messages( - current_message_chain=list(message_sequence), + current_message_chain=list(message_sequence), config=agent.config ) tokens_to_add = count_message_tokens([new_summary_message], model) message_sequence.insert(insertion_index, new_summary_message) current_tokens_used += tokens_to_add - 500 # FIXME: uncomment when memory is back in use - # memory_store = get_memory(cfg) + # memory_store = get_memory(config) # for _, ai_msg, result_msg in agent.history.per_cycle(trimmed_messages): # memory_to_add = MemoryItem.from_ai_action(ai_msg, result_msg) # logger.debug(f"Storing the following memory:\n{memory_to_add.dump()}") @@ -192,6 +192,7 @@ def chat_with_ai( # temperature and other settings we care about assistant_reply = create_chat_completion( prompt=message_sequence, + config=agent.config, max_tokens=tokens_remaining, ) diff --git a/autogpt/llm/utils/__init__.py b/autogpt/llm/utils/__init__.py index aee7997a..3b0d3e17 100644 --- a/autogpt/llm/utils/__init__.py +++ b/autogpt/llm/utils/__init__.py @@ -57,18 +57,18 @@ def call_ai_function( def create_text_completion( prompt: str, + config: Config, model: Optional[str], temperature: Optional[float], max_output_tokens: Optional[int], ) -> str: - cfg = Config() if model is None: - model = cfg.fast_llm_model + model = config.fast_llm_model if temperature is None: - temperature = cfg.temperature + temperature = config.temperature - if cfg.use_azure: - kwargs = {"deployment_id": cfg.get_azure_deployment_id_for_model(model)} + if config.use_azure: + kwargs = {"deployment_id": config.get_azure_deployment_id_for_model(model)} else: kwargs = {"model": model} @@ -77,7 +77,7 @@ def create_text_completion( **kwargs, temperature=temperature, max_tokens=max_output_tokens, - api_key=cfg.openai_api_key, + api_key=config.openai_api_key, ) logger.debug(f"Response: {response}") @@ -87,6 +87,7 @@ def create_text_completion( # Overly simple abstraction until we create something better def create_chat_completion( prompt: ChatSequence, + config: Config, model: Optional[str] = None, temperature: Optional[float] = None, max_tokens: Optional[int] = None, @@ -102,11 +103,10 @@ def create_chat_completion( Returns: str: The response from the chat completion """ - cfg = Config() if model is None: model = prompt.model.name if temperature is None: - temperature = cfg.temperature + temperature = config.temperature logger.debug( f"{Fore.GREEN}Creating chat completion with model {model}, temperature {temperature}, max_tokens {max_tokens}{Fore.RESET}" @@ -117,7 +117,7 @@ def create_chat_completion( "max_tokens": max_tokens, } - for plugin in cfg.plugins: + for plugin in config.plugins: if plugin.can_handle_chat_completion( messages=prompt.raw(), **chat_completion_kwargs, @@ -129,11 +129,11 @@ def create_chat_completion( if message is not None: return message - chat_completion_kwargs["api_key"] = cfg.openai_api_key - if cfg.use_azure: - chat_completion_kwargs["deployment_id"] = cfg.get_azure_deployment_id_for_model( - model - ) + chat_completion_kwargs["api_key"] = config.openai_api_key + if config.use_azure: + chat_completion_kwargs[ + "deployment_id" + ] = config.get_azure_deployment_id_for_model(model) response = iopenai.create_chat_completion( messages=prompt.raw(), @@ -148,7 +148,7 @@ def create_chat_completion( logger.error(response.error) raise RuntimeError(response.error) - for plugin in cfg.plugins: + for plugin in config.plugins: if not plugin.can_handle_on_response(): continue resp = plugin.on_response(resp) diff --git a/autogpt/logs.py b/autogpt/logs.py index f14267fc..90d006bc 100644 --- a/autogpt/logs.py +++ b/autogpt/logs.py @@ -9,6 +9,7 @@ from typing import Any from colorama import Fore, Style +from autogpt.config import Config from autogpt.log_cycle.json_handler import JsonFileHandler, JsonFormatter from autogpt.singleton import Singleton from autogpt.speech import say_text @@ -254,7 +255,7 @@ logger = Logger() def print_assistant_thoughts( ai_name: object, assistant_reply_json_valid: object, - speak_mode: bool = False, + config: Config, ) -> None: assistant_thoughts_reasoning = None assistant_thoughts_plan = None @@ -288,7 +289,7 @@ def print_assistant_thoughts( logger.typewriter_log("CRITICISM:", Fore.YELLOW, f"{assistant_thoughts_criticism}") # Speak the assistant's thoughts if assistant_thoughts_speak: - if speak_mode: - say_text(assistant_thoughts_speak) + if config.speak_mode: + say_text(assistant_thoughts_speak, config) else: logger.typewriter_log("SPEAK:", Fore.YELLOW, f"{assistant_thoughts_speak}") diff --git a/autogpt/main.py b/autogpt/main.py index f0af9b53..43c7bcb3 100644 --- a/autogpt/main.py +++ b/autogpt/main.py @@ -53,12 +53,12 @@ def run_auto_gpt( logger.set_level(logging.DEBUG if debug else logging.INFO) logger.speak_mode = speak - cfg = Config() + config = Config() # TODO: fill in llm values here - check_openai_api_key() + check_openai_api_key(config) create_config( - cfg, + config, continuous, continuous_limit, ai_settings, @@ -74,17 +74,17 @@ def run_auto_gpt( skip_news, ) - if cfg.continuous_mode: + if config.continuous_mode: for line in get_legal_warning().split("\n"): logger.warn(markdown_to_ansi_style(line), "LEGAL:", Fore.RED) - if not cfg.skip_news: + if not config.skip_news: motd, is_new_motd = get_latest_bulletin() if motd: motd = markdown_to_ansi_style(motd) for motd_line in motd.split("\n"): logger.info(motd_line, "NEWS:", Fore.GREEN) - if is_new_motd and not cfg.chat_messages_enabled: + if is_new_motd and not config.chat_messages_enabled: input( Fore.MAGENTA + Style.BRIGHT @@ -123,7 +123,7 @@ def run_auto_gpt( # TODO: pass in the ai_settings file and the env file and have them cloned into # the workspace directory so we can bind them to the agent. workspace_directory = Workspace.make_workspace(workspace_directory) - cfg.workspace_path = str(workspace_directory) + config.workspace_path = str(workspace_directory) # HACK: doing this here to collect some globals that depend on the workspace. file_logger_path = workspace_directory / "file_logger.txt" @@ -131,17 +131,17 @@ def run_auto_gpt( with file_logger_path.open(mode="w", encoding="utf-8") as f: f.write("File Operation Logger ") - cfg.file_logger_path = str(file_logger_path) + config.file_logger_path = str(file_logger_path) - cfg.set_plugins(scan_plugins(cfg, cfg.debug_mode)) + config.set_plugins(scan_plugins(config, config.debug_mode)) # Create a CommandRegistry instance and scan default folder command_registry = CommandRegistry() logger.debug( - f"The following command categories are disabled: {cfg.disabled_command_categories}" + f"The following command categories are disabled: {config.disabled_command_categories}" ) enabled_command_categories = [ - x for x in COMMAND_CATEGORIES if x not in cfg.disabled_command_categories + x for x in COMMAND_CATEGORIES if x not in config.disabled_command_categories ] logger.debug( @@ -152,7 +152,7 @@ def run_auto_gpt( command_registry.import_commands(command_category) ai_name = "" - ai_config = construct_main_ai_config() + ai_config = construct_main_ai_config(config) ai_config.command_registry = command_registry if ai_config.ai_name: ai_name = ai_config.ai_name @@ -161,22 +161,22 @@ def run_auto_gpt( next_action_count = 0 # add chat plugins capable of report to logger - if cfg.chat_messages_enabled: - for plugin in cfg.plugins: + if config.chat_messages_enabled: + for plugin in config.plugins: if hasattr(plugin, "can_handle_report") and plugin.can_handle_report(): logger.info(f"Loaded plugin into logger: {plugin.__class__.__name__}") logger.chat_plugins.append(plugin) # Initialize memory and make sure it is empty. # this is particularly important for indexing and referencing pinecone memory - memory = get_memory(cfg) + memory = get_memory(config) memory.clear() logger.typewriter_log( "Using memory of type:", Fore.GREEN, f"{memory.__class__.__name__}" ) - logger.typewriter_log("Using Browser:", Fore.GREEN, cfg.selenium_web_browser) - system_prompt = ai_config.construct_full_prompt() - if cfg.debug_mode: + logger.typewriter_log("Using Browser:", Fore.GREEN, config.selenium_web_browser) + system_prompt = ai_config.construct_full_prompt(config) + if config.debug_mode: logger.typewriter_log("Prompt:", Fore.GREEN, system_prompt) agent = Agent( @@ -188,6 +188,6 @@ def run_auto_gpt( triggering_prompt=DEFAULT_TRIGGERING_PROMPT, workspace_directory=workspace_directory, ai_config=ai_config, - config=cfg, + config=config, ) agent.start_interaction_loop() diff --git a/autogpt/memory/message_history.py b/autogpt/memory/message_history.py index 897cee15..4dba13dd 100644 --- a/autogpt/memory/message_history.py +++ b/autogpt/memory/message_history.py @@ -47,8 +47,7 @@ class MessageHistory: return self.messages.append(message) def trim_messages( - self, - current_message_chain: list[Message], + self, current_message_chain: list[Message], config: Config ) -> tuple[Message, list[Message]]: """ Returns a list of trimmed messages: messages which are in the message history @@ -56,6 +55,7 @@ class MessageHistory: Args: current_message_chain (list[Message]): The messages currently in the context. + config (Config): The config to use. Returns: Message: A message with the new running summary after adding the trimmed messages. @@ -75,7 +75,7 @@ class MessageHistory: return self.summary_message(), [] new_summary_message = self.update_running_summary( - new_events=new_messages_not_in_chain + new_events=new_messages_not_in_chain, config=config ) # Find the index of the last message processed @@ -84,7 +84,7 @@ class MessageHistory: return new_summary_message, new_messages_not_in_chain - def per_cycle(self, messages: list[Message] | None = None): + def per_cycle(self, config: Config, messages: list[Message] | None = None): """ Yields: Message: a message containing user input @@ -118,7 +118,9 @@ class MessageHistory: f"This reminds you of these events from your past: \n{self.summary}", ) - def update_running_summary(self, new_events: list[Message]) -> Message: + def update_running_summary( + self, new_events: list[Message], config: Config + ) -> Message: """ This function takes a list of dictionaries representing new events and combines them with the current summary, focusing on key and potentially important information to remember. The updated summary is returned in a message @@ -135,8 +137,6 @@ class MessageHistory: update_running_summary(new_events) # Returns: "This reminds you of these events from your past: \nI entered the kitchen and found a scrawled note saying 7." """ - cfg = Config() - if not new_events: return self.summary_message() @@ -156,7 +156,7 @@ class MessageHistory: event.content = json.dumps(content_dict) except json.JSONDecodeError as e: logger.error(f"Error: Invalid JSON: {e}") - if cfg.debug_mode: + if config.debug_mode: logger.error(f"{event.content}") elif event.role.lower() == "system": @@ -171,23 +171,23 @@ class MessageHistory: # Assume an upper bound length for the summary prompt template, i.e. Your task is to create a concise running summary...., in summarize_batch func # TODO make this default dynamic prompt_template_length = 100 - max_tokens = OPEN_AI_CHAT_MODELS.get(cfg.fast_llm_model).max_tokens - summary_tlength = count_string_tokens(str(self.summary), cfg.fast_llm_model) + max_tokens = OPEN_AI_CHAT_MODELS.get(config.fast_llm_model).max_tokens + summary_tlength = count_string_tokens(str(self.summary), config.fast_llm_model) batch = [] batch_tlength = 0 # TODO Can put a cap on length of total new events and drop some previous events to save API cost, but need to think thru more how to do it without losing the context for event in new_events: - event_tlength = count_string_tokens(str(event), cfg.fast_llm_model) + event_tlength = count_string_tokens(str(event), config.fast_llm_model) if ( batch_tlength + event_tlength > max_tokens - prompt_template_length - summary_tlength ): # The batch is full. Summarize it and start a new one. - self.summarize_batch(batch, cfg) + self.summarize_batch(batch, config) summary_tlength = count_string_tokens( - str(self.summary), cfg.fast_llm_model + str(self.summary), config.fast_llm_model ) batch = [event] batch_tlength = event_tlength @@ -197,11 +197,11 @@ class MessageHistory: if batch: # There's an unprocessed batch. Summarize it. - self.summarize_batch(batch, cfg) + self.summarize_batch(batch, config) return self.summary_message() - def summarize_batch(self, new_events_batch, cfg): + def summarize_batch(self, new_events_batch, config): prompt = f'''Your task is to create a concise running summary of actions and information results in the provided text, focusing on key and potentially important information to remember. You will receive the current summary and your latest actions. Combine them, adding relevant key information from the latest development in 1st person past tense and keeping the summary concise. @@ -217,7 +217,9 @@ Latest Development: """ ''' - prompt = ChatSequence.for_model(cfg.fast_llm_model, [Message("user", prompt)]) + prompt = ChatSequence.for_model( + config.fast_llm_model, [Message("user", prompt)] + ) self.agent.log_cycle_handler.log_cycle( self.agent.ai_name, self.agent.created_at, @@ -226,7 +228,7 @@ Latest Development: PROMPT_SUMMARY_FILE_NAME, ) - self.summary = create_chat_completion(prompt) + self.summary = create_chat_completion(prompt, config) self.agent.log_cycle_handler.log_cycle( self.agent.ai_name, diff --git a/autogpt/memory/vector/__init__.py b/autogpt/memory/vector/__init__.py index 11c9d697..72abbb00 100644 --- a/autogpt/memory/vector/__init__.py +++ b/autogpt/memory/vector/__init__.py @@ -39,12 +39,12 @@ supported_memory = ["json_file", "no_memory"] # MilvusMemory = None -def get_memory(cfg: Config) -> VectorMemory: +def get_memory(config: Config) -> VectorMemory: memory = None - match cfg.memory_backend: + match config.memory_backend: case "json_file": - memory = JSONFileMemory(cfg) + memory = JSONFileMemory(config) case "pinecone": raise NotImplementedError( @@ -59,7 +59,7 @@ def get_memory(cfg: Config) -> VectorMemory: # " to use Pinecone as a memory backend." # ) # else: - # memory = PineconeMemory(cfg) + # memory = PineconeMemory(config) # if clear: # memory.clear() @@ -74,7 +74,7 @@ def get_memory(cfg: Config) -> VectorMemory: # " use Redis as a memory backend." # ) # else: - # memory = RedisMemory(cfg) + # memory = RedisMemory(config) case "weaviate": raise NotImplementedError( @@ -89,7 +89,7 @@ def get_memory(cfg: Config) -> VectorMemory: # " use Weaviate as a memory backend." # ) # else: - # memory = WeaviateMemory(cfg) + # memory = WeaviateMemory(config) case "milvus": raise NotImplementedError( @@ -104,18 +104,18 @@ def get_memory(cfg: Config) -> VectorMemory: # "Please install pymilvus to use Milvus or Zilliz Cloud as memory backend." # ) # else: - # memory = MilvusMemory(cfg) + # memory = MilvusMemory(config) case "no_memory": memory = NoMemory() case _: raise ValueError( - f"Unknown memory backend '{cfg.memory_backend}'. Please check your config." + f"Unknown memory backend '{config.memory_backend}'. Please check your config." ) if memory is None: - memory = JSONFileMemory(cfg) + memory = JSONFileMemory(config) return memory diff --git a/autogpt/memory/vector/memory_item.py b/autogpt/memory/vector/memory_item.py index 539ccb61..cf00cc87 100644 --- a/autogpt/memory/vector/memory_item.py +++ b/autogpt/memory/vector/memory_item.py @@ -36,19 +36,19 @@ class MemoryItem: def from_text( text: str, source_type: MemoryDocType, + config: Config, metadata: dict = {}, how_to_summarize: str | None = None, question_for_summary: str | None = None, ): - cfg = Config() logger.debug(f"Memorizing text:\n{'-'*32}\n{text}\n{'-'*32}\n") chunks = [ chunk for chunk, _ in ( - split_text(text, cfg.embedding_model) + split_text(text, config.embedding_model, config) if source_type != "code_file" - else chunk_content(text, cfg.embedding_model) + else chunk_content(text, config.embedding_model) ) ] logger.debug("Chunks: " + str(chunks)) @@ -58,6 +58,7 @@ class MemoryItem: for summary, _ in [ summarize_text( text_chunk, + config, instruction=how_to_summarize, question=question_for_summary, ) @@ -66,7 +67,7 @@ class MemoryItem: ] logger.debug("Chunk summaries: " + str(chunk_summaries)) - e_chunks = get_embedding(chunks) + e_chunks = get_embedding(chunks, config) summary = ( chunk_summaries[0] @@ -81,7 +82,7 @@ class MemoryItem: # TODO: investigate search performance of weighted average vs summary # e_average = np.average(e_chunks, axis=0, weights=[len(c) for c in chunks]) - e_summary = get_embedding(summary) + e_summary = get_embedding(summary, config) metadata["source_type"] = source_type @@ -96,8 +97,8 @@ class MemoryItem: ) @staticmethod - def from_text_file(content: str, path: str): - return MemoryItem.from_text(content, "text_file", {"location": path}) + def from_text_file(content: str, path: str, config: Config): + return MemoryItem.from_text(content, "text_file", config, {"location": path}) @staticmethod def from_code_file(content: str, path: str): @@ -137,10 +138,13 @@ class MemoryItem: ) @staticmethod - def from_webpage(content: str, url: str, question: str | None = None): + def from_webpage( + content: str, url: str, config: Config, question: str | None = None + ): return MemoryItem.from_text( text=content, source_type="webpage", + config=config, metadata={"location": url}, question_for_summary=question, ) diff --git a/autogpt/memory/vector/providers/base.py b/autogpt/memory/vector/providers/base.py index 969d8934..dc4dbf3c 100644 --- a/autogpt/memory/vector/providers/base.py +++ b/autogpt/memory/vector/providers/base.py @@ -17,25 +17,29 @@ class VectorMemoryProvider(MutableSet[MemoryItem], AbstractSingleton): def __init__(self, config: Config): pass - def get(self, query: str) -> MemoryItemRelevance | None: + def get(self, query: str, config: Config) -> MemoryItemRelevance | None: """ Gets the data from the memory that is most relevant to the given query. Args: - data: The data to compare to. + query: The query used to retrieve information. + config: The config Object. Returns: The most relevant Memory """ - result = self.get_relevant(query, 1) + result = self.get_relevant(query, 1, config) return result[0] if result else None - def get_relevant(self, query: str, k: int) -> Sequence[MemoryItemRelevance]: + def get_relevant( + self, query: str, k: int, config: Config + ) -> Sequence[MemoryItemRelevance]: """ Returns the top-k most relevant memories for the given query Args: query: the query to compare stored memories to k: the number of relevant memories to fetch + config: The config Object. Returns: list[MemoryItemRelevance] containing the top [k] relevant memories @@ -48,7 +52,7 @@ class VectorMemoryProvider(MutableSet[MemoryItem], AbstractSingleton): f"{len(self)} memories in index" ) - relevances = self.score_memories_for_relevance(query) + relevances = self.score_memories_for_relevance(query, config) logger.debug(f"Memory relevance scores: {[str(r) for r in relevances]}") # take last k items and reverse @@ -57,13 +61,13 @@ class VectorMemoryProvider(MutableSet[MemoryItem], AbstractSingleton): return [relevances[i] for i in top_k_indices] def score_memories_for_relevance( - self, for_query: str + self, for_query: str, config: Config ) -> Sequence[MemoryItemRelevance]: """ Returns MemoryItemRelevance for every memory in the index. Implementations may override this function for performance purposes. """ - e_query: Embedding = get_embedding(for_query) + e_query: Embedding = get_embedding(for_query, config) return [m.relevance_for(for_query, e_query) for m in self] def get_stats(self) -> tuple[int, int]: diff --git a/autogpt/memory/vector/providers/json_file.py b/autogpt/memory/vector/providers/json_file.py index 3ae7cd86..b85ea8e6 100644 --- a/autogpt/memory/vector/providers/json_file.py +++ b/autogpt/memory/vector/providers/json_file.py @@ -20,17 +20,17 @@ class JSONFileMemory(VectorMemoryProvider): file_path: Path memories: list[MemoryItem] - def __init__(self, cfg: Config) -> None: + def __init__(self, config: Config) -> None: """Initialize a class instance Args: - cfg: Config object + config: Config object Returns: None """ - workspace_path = Path(cfg.workspace_path) - self.file_path = workspace_path / f"{cfg.memory_index}.json" + workspace_path = Path(config.workspace_path) + self.file_path = workspace_path / f"{config.memory_index}.json" self.file_path.touch() logger.debug( f"Initialized {__class__.__name__} with index path {self.file_path}" diff --git a/autogpt/memory/vector/utils.py b/autogpt/memory/vector/utils.py index b542632b..beb2fcf9 100644 --- a/autogpt/memory/vector/utils.py +++ b/autogpt/memory/vector/utils.py @@ -22,7 +22,7 @@ def get_embedding(input: list[str] | list[TText]) -> list[Embedding]: def get_embedding( - input: str | TText | list[str] | list[TText], + input: str | TText | list[str] | list[TText], config: Config ) -> Embedding | list[Embedding]: """Get an embedding from the ada model. @@ -33,7 +33,6 @@ def get_embedding( Returns: List[float]: The embedding. """ - cfg = Config() multiple = isinstance(input, list) and all(not isinstance(i, int) for i in input) if isinstance(input, str): @@ -41,22 +40,22 @@ def get_embedding( elif multiple and isinstance(input[0], str): input = [text.replace("\n", " ") for text in input] - model = cfg.embedding_model - if cfg.use_azure: - kwargs = {"engine": cfg.get_azure_deployment_id_for_model(model)} + model = config.embedding_model + if config.use_azure: + kwargs = {"engine": config.get_azure_deployment_id_for_model(model)} else: kwargs = {"model": model} logger.debug( f"Getting embedding{f's for {len(input)} inputs' if multiple else ''}" f" with model '{model}'" - + (f" via Azure deployment '{kwargs['engine']}'" if cfg.use_azure else "") + + (f" via Azure deployment '{kwargs['engine']}'" if config.use_azure else "") ) embeddings = iopenai.create_embedding( input, **kwargs, - api_key=cfg.openai_api_key, + api_key=config.openai_api_key, ).data if not multiple: diff --git a/autogpt/plugins/__init__.py b/autogpt/plugins/__init__.py index 4d84c9b5..d0a4864c 100644 --- a/autogpt/plugins/__init__.py +++ b/autogpt/plugins/__init__.py @@ -58,7 +58,7 @@ def write_dict_to_json_file(data: dict, file_path: str) -> None: json.dump(data, file, indent=4) -def fetch_openai_plugins_manifest_and_spec(cfg: Config) -> dict: +def fetch_openai_plugins_manifest_and_spec(config: Config) -> dict: """ Fetch the manifest for a list of OpenAI plugins. Args: @@ -68,8 +68,8 @@ def fetch_openai_plugins_manifest_and_spec(cfg: Config) -> dict: """ # TODO add directory scan manifests = {} - for url in cfg.plugins_openai: - openai_plugin_client_dir = f"{cfg.plugins_dir}/openai/{urlparse(url).netloc}" + for url in config.plugins_openai: + openai_plugin_client_dir = f"{config.plugins_dir}/openai/{urlparse(url).netloc}" create_directory_if_not_exists(openai_plugin_client_dir) if not os.path.exists(f"{openai_plugin_client_dir}/ai-plugin.json"): try: @@ -134,18 +134,18 @@ def create_directory_if_not_exists(directory_path: str) -> bool: def initialize_openai_plugins( - manifests_specs: dict, cfg: Config, debug: bool = False + manifests_specs: dict, config: Config, debug: bool = False ) -> dict: """ Initialize OpenAI plugins. Args: manifests_specs (dict): per url dictionary of manifest and spec. - cfg (Config): Config instance including plugins config + config (Config): Config instance including plugins config debug (bool, optional): Enable debug logging. Defaults to False. Returns: dict: per url dictionary of manifest, spec and client. """ - openai_plugins_dir = f"{cfg.plugins_dir}/openai" + openai_plugins_dir = f"{config.plugins_dir}/openai" if create_directory_if_not_exists(openai_plugins_dir): for url, manifest_spec in manifests_specs.items(): openai_plugin_client_dir = f"{openai_plugins_dir}/{urlparse(url).hostname}" @@ -188,13 +188,13 @@ def initialize_openai_plugins( def instantiate_openai_plugin_clients( - manifests_specs_clients: dict, cfg: Config, debug: bool = False + manifests_specs_clients: dict, config: Config, debug: bool = False ) -> dict: """ Instantiates BaseOpenAIPlugin instances for each OpenAI plugin. Args: manifests_specs_clients (dict): per url dictionary of manifest, spec and client. - cfg (Config): Config instance including plugins config + config (Config): Config instance including plugins config debug (bool, optional): Enable debug logging. Defaults to False. Returns: plugins (dict): per url dictionary of BaseOpenAIPlugin instances. @@ -206,11 +206,11 @@ def instantiate_openai_plugin_clients( return plugins -def scan_plugins(cfg: Config, debug: bool = False) -> List[AutoGPTPluginTemplate]: +def scan_plugins(config: Config, debug: bool = False) -> List[AutoGPTPluginTemplate]: """Scan the plugins directory for plugins and loads them. Args: - cfg (Config): Config instance including plugins config + config (Config): Config instance including plugins config debug (bool, optional): Enable debug logging. Defaults to False. Returns: @@ -218,11 +218,11 @@ def scan_plugins(cfg: Config, debug: bool = False) -> List[AutoGPTPluginTemplate """ loaded_plugins = [] # Generic plugins - plugins_path_path = Path(cfg.plugins_dir) - plugins_config = cfg.plugins_config + plugins_path_path = Path(config.plugins_dir) + plugins_config = config.plugins_config # Directory-based plugins - for plugin_path in [f.path for f in os.scandir(cfg.plugins_dir) if f.is_dir()]: + for plugin_path in [f.path for f in os.scandir(config.plugins_dir) if f.is_dir()]: # Avoid going into __pycache__ or other hidden directories if plugin_path.startswith("__"): continue @@ -286,11 +286,11 @@ def scan_plugins(cfg: Config, debug: bool = False) -> List[AutoGPTPluginTemplate ) # OpenAI plugins - if cfg.plugins_openai: - manifests_specs = fetch_openai_plugins_manifest_and_spec(cfg) + if config.plugins_openai: + manifests_specs = fetch_openai_plugins_manifest_and_spec(config) if manifests_specs.keys(): manifests_specs_clients = initialize_openai_plugins( - manifests_specs, cfg, debug + manifests_specs, config, debug ) for url, openai_plugin_meta in manifests_specs_clients.items(): if not plugins_config.is_enabled(url): diff --git a/autogpt/processing/text.py b/autogpt/processing/text.py index aadc93ef..78eabf45 100644 --- a/autogpt/processing/text.py +++ b/autogpt/processing/text.py @@ -12,8 +12,6 @@ from autogpt.llm.utils import count_string_tokens, create_chat_completion from autogpt.logs import logger from autogpt.utils import batch -CFG = Config() - def _max_chunk_length(model: str, max: Optional[int] = None) -> int: model_max_input_tokens = OPEN_AI_MODELS[model].max_tokens - 1 @@ -60,13 +58,18 @@ def chunk_content( def summarize_text( - text: str, instruction: Optional[str] = None, question: Optional[str] = None + text: str, + config: Config, + instruction: Optional[str] = None, + question: Optional[str] = None, ) -> tuple[str, None | list[tuple[str, str]]]: """Summarize text using the OpenAI API Args: text (str): The text to summarize + config (Config): The config object instruction (str): Additional instruction for summarization, e.g. "focus on information related to polar bears", "omit personal information contained in the text" + question (str): Question to answer in the summary Returns: str: The summary of the text @@ -79,7 +82,7 @@ def summarize_text( if instruction and question: raise ValueError("Parameters 'question' and 'instructions' cannot both be set") - model = CFG.fast_llm_model + model = config.fast_llm_model if question: instruction = ( @@ -111,14 +114,18 @@ def summarize_text( logger.debug(f"Summarizing with {model}:\n{summarization_prompt.dump()}\n") summary = create_chat_completion( - summarization_prompt, temperature=0, max_tokens=500 + summarization_prompt, config, temperature=0, max_tokens=500 ) logger.debug(f"\n{'-'*16} SUMMARY {'-'*17}\n{summary}\n{'-'*42}\n") return summary.strip(), None summaries: list[str] = [] - chunks = list(split_text(text, for_model=model, max_chunk_length=max_chunk_length)) + chunks = list( + split_text( + text, for_model=model, config=config, max_chunk_length=max_chunk_length + ) + ) for i, (chunk, chunk_length) in enumerate(chunks): logger.info( @@ -138,7 +145,8 @@ def summarize_text( def split_text( text: str, - for_model: str = CFG.fast_llm_model, + for_model: str, + config: Config, with_overlap=True, max_chunk_length: Optional[int] = None, ): @@ -147,7 +155,9 @@ def split_text( Args: text (str): The text to split for_model (str): The model to chunk for; determines tokenizer and constraints - max_length (int, optional): The maximum length of each chunk + config (Config): The config object + with_overlap (bool, optional): Whether to allow overlap between chunks + max_chunk_length (int, optional): The maximum length of a chunk Yields: str: The next chunk of text @@ -155,6 +165,7 @@ def split_text( Raises: ValueError: when a sentence is longer than the maximum length """ + max_length = _max_chunk_length(for_model, max_chunk_length) # flatten paragraphs to improve performance @@ -168,7 +179,7 @@ def split_text( n_chunks = ceil(text_length / max_length) target_chunk_length = ceil(text_length / n_chunks) - nlp: spacy.language.Language = spacy.load(CFG.browse_spacy_language_model) + nlp: spacy.language.Language = spacy.load(config.browse_spacy_language_model) nlp.add_pipe("sentencizer") doc = nlp(text) sentences = [sentence.text.strip() for sentence in doc.sents] diff --git a/autogpt/prompts/prompt.py b/autogpt/prompts/prompt.py index 17d78bd1..16d5c7e7 100644 --- a/autogpt/prompts/prompt.py +++ b/autogpt/prompts/prompt.py @@ -9,12 +9,10 @@ from autogpt.prompts.generator import PromptGenerator from autogpt.setup import prompt_user from autogpt.utils import clean_input -CFG = Config() - DEFAULT_TRIGGERING_PROMPT = "Determine exactly one command to use, and respond using the JSON schema specified previously:" -def build_default_prompt_generator() -> PromptGenerator: +def build_default_prompt_generator(config: Config) -> PromptGenerator: """ This function generates a prompt string that includes various constraints, commands, resources, and performance evaluations. @@ -27,7 +25,7 @@ def build_default_prompt_generator() -> PromptGenerator: prompt_generator = PromptGenerator() # Initialize the PromptConfig object and load the file set in the main config (default: prompts_settings.yaml) - prompt_config = PromptConfig(CFG.prompt_settings_file) + prompt_config = PromptConfig(config.prompt_settings_file) # Add constraints to the PromptGenerator object for constraint in prompt_config.constraints: @@ -44,70 +42,71 @@ def build_default_prompt_generator() -> PromptGenerator: return prompt_generator -def construct_main_ai_config() -> AIConfig: +def construct_main_ai_config(config: Config) -> AIConfig: """Construct the prompt for the AI to respond to Returns: str: The prompt string """ - config = AIConfig.load(CFG.ai_settings_file) - if CFG.skip_reprompt and config.ai_name: - logger.typewriter_log("Name :", Fore.GREEN, config.ai_name) - logger.typewriter_log("Role :", Fore.GREEN, config.ai_role) - logger.typewriter_log("Goals:", Fore.GREEN, f"{config.ai_goals}") + ai_config = AIConfig.load(config.ai_settings_file) + if config.skip_reprompt and ai_config.ai_name: + logger.typewriter_log("Name :", Fore.GREEN, ai_config.ai_name) + logger.typewriter_log("Role :", Fore.GREEN, ai_config.ai_role) + logger.typewriter_log("Goals:", Fore.GREEN, f"{ai_config.ai_goals}") logger.typewriter_log( "API Budget:", Fore.GREEN, - "infinite" if config.api_budget <= 0 else f"${config.api_budget}", + "infinite" if ai_config.api_budget <= 0 else f"${ai_config.api_budget}", ) - elif config.ai_name: + elif ai_config.ai_name: logger.typewriter_log( "Welcome back! ", Fore.GREEN, - f"Would you like me to return to being {config.ai_name}?", + f"Would you like me to return to being {ai_config.ai_name}?", speak_text=True, ) should_continue = clean_input( + config, f"""Continue with the last settings? -Name: {config.ai_name} -Role: {config.ai_role} -Goals: {config.ai_goals} -API Budget: {"infinite" if config.api_budget <= 0 else f"${config.api_budget}"} -Continue ({CFG.authorise_key}/{CFG.exit_key}): """ +Name: {ai_config.ai_name} +Role: {ai_config.ai_role} +Goals: {ai_config.ai_goals} +API Budget: {"infinite" if ai_config.api_budget <= 0 else f"${ai_config.api_budget}"} +Continue ({config.authorise_key}/{config.exit_key}): """, ) - if should_continue.lower() == CFG.exit_key: - config = AIConfig() + if should_continue.lower() == config.exit_key: + ai_config = AIConfig() - if not config.ai_name: - config = prompt_user() - config.save(CFG.ai_settings_file) + if not ai_config.ai_name: + ai_config = prompt_user(config) + ai_config.save(config.ai_settings_file) - if CFG.restrict_to_workspace: + if config.restrict_to_workspace: logger.typewriter_log( "NOTE:All files/directories created by this agent can be found inside its workspace at:", Fore.YELLOW, - f"{CFG.workspace_path}", + f"{config.workspace_path}", ) # set the total api budget api_manager = ApiManager() - api_manager.set_total_budget(config.api_budget) + api_manager.set_total_budget(ai_config.api_budget) # Agent Created, print message logger.typewriter_log( - config.ai_name, + ai_config.ai_name, Fore.LIGHTBLUE_EX, "has been created with the following details:", speak_text=True, ) - # Print the ai config details + # Print the ai_config details # Name - logger.typewriter_log("Name:", Fore.GREEN, config.ai_name, speak_text=False) + logger.typewriter_log("Name:", Fore.GREEN, ai_config.ai_name, speak_text=False) # Role - logger.typewriter_log("Role:", Fore.GREEN, config.ai_role, speak_text=False) + logger.typewriter_log("Role:", Fore.GREEN, ai_config.ai_role, speak_text=False) # Goals logger.typewriter_log("Goals:", Fore.GREEN, "", speak_text=False) - for goal in config.ai_goals: + for goal in ai_config.ai_goals: logger.typewriter_log("-", Fore.GREEN, goal, speak_text=False) - return config + return ai_config diff --git a/autogpt/setup.py b/autogpt/setup.py index 67cae5da..2fe8b3a9 100644 --- a/autogpt/setup.py +++ b/autogpt/setup.py @@ -16,10 +16,8 @@ from autogpt.prompts.default_prompts import ( DEFAULT_USER_DESIRE_PROMPT, ) -CFG = Config() - -def prompt_user() -> AIConfig: +def prompt_user(config: Config) -> AIConfig: """Prompt the user for input Returns: @@ -45,7 +43,7 @@ def prompt_user() -> AIConfig: ) user_desire = utils.clean_input( - f"{Fore.LIGHTBLUE_EX}I want Auto-GPT to{Style.RESET_ALL}: " + config, f"{Fore.LIGHTBLUE_EX}I want Auto-GPT to{Style.RESET_ALL}: " ) if user_desire == "": @@ -58,11 +56,11 @@ def prompt_user() -> AIConfig: Fore.GREEN, speak_text=True, ) - return generate_aiconfig_manual() + return generate_aiconfig_manual(config) else: try: - return generate_aiconfig_automatic(user_desire) + return generate_aiconfig_automatic(user_desire, config) except Exception as e: logger.typewriter_log( "Unable to automatically generate AI Config based on user desire.", @@ -71,10 +69,10 @@ def prompt_user() -> AIConfig: speak_text=True, ) - return generate_aiconfig_manual() + return generate_aiconfig_manual(config) -def generate_aiconfig_manual() -> AIConfig: +def generate_aiconfig_manual(config: Config) -> AIConfig: """ Interactively create an AI configuration by prompting the user to provide the name, role, and goals of the AI. @@ -99,7 +97,7 @@ def generate_aiconfig_manual() -> AIConfig: logger.typewriter_log( "Name your AI: ", Fore.GREEN, "For example, 'Entrepreneur-GPT'" ) - ai_name = utils.clean_input("AI Name: ") + ai_name = utils.clean_input(config, "AI Name: ") if ai_name == "": ai_name = "Entrepreneur-GPT" @@ -114,7 +112,7 @@ def generate_aiconfig_manual() -> AIConfig: "For example, 'an AI designed to autonomously develop and run businesses with" " the sole goal of increasing your net worth.'", ) - ai_role = utils.clean_input(f"{ai_name} is: ") + ai_role = utils.clean_input(config, f"{ai_name} is: ") if ai_role == "": ai_role = "an AI designed to autonomously develop and run businesses with the" " sole goal of increasing your net worth." @@ -129,7 +127,9 @@ def generate_aiconfig_manual() -> AIConfig: logger.info("Enter nothing to load defaults, enter nothing when finished.") ai_goals = [] for i in range(5): - ai_goal = utils.clean_input(f"{Fore.LIGHTBLUE_EX}Goal{Style.RESET_ALL} {i+1}: ") + ai_goal = utils.clean_input( + config, f"{Fore.LIGHTBLUE_EX}Goal{Style.RESET_ALL} {i+1}: " + ) if ai_goal == "": break ai_goals.append(ai_goal) @@ -148,7 +148,7 @@ def generate_aiconfig_manual() -> AIConfig: ) logger.info("Enter nothing to let the AI run without monetary limit") api_budget_input = utils.clean_input( - f"{Fore.LIGHTBLUE_EX}Budget{Style.RESET_ALL}: $" + config, f"{Fore.LIGHTBLUE_EX}Budget{Style.RESET_ALL}: $" ) if api_budget_input == "": api_budget = 0.0 @@ -164,7 +164,7 @@ def generate_aiconfig_manual() -> AIConfig: return AIConfig(ai_name, ai_role, ai_goals, api_budget) -def generate_aiconfig_automatic(user_prompt) -> AIConfig: +def generate_aiconfig_automatic(user_prompt: str, config: Config) -> AIConfig: """Generates an AIConfig object from the given string. Returns: @@ -178,12 +178,13 @@ def generate_aiconfig_automatic(user_prompt) -> AIConfig: # Call LLM with the string as user input output = create_chat_completion( ChatSequence.for_model( - CFG.fast_llm_model, + config.fast_llm_model, [ Message("system", system_prompt), Message("user", prompt_ai_config_automatic), ], - ) + ), + config, ) # Debug LLM Output diff --git a/autogpt/speech/base.py b/autogpt/speech/base.py index a7570d94..a75acaed 100644 --- a/autogpt/speech/base.py +++ b/autogpt/speech/base.py @@ -2,6 +2,7 @@ import abc from threading import Lock +from autogpt.config import Config from autogpt.singleton import AbstractSingleton @@ -10,7 +11,7 @@ class VoiceBase(AbstractSingleton): Base class for all voice classes. """ - def __init__(self): + def __init__(self, config: Config): """ Initialize the voice class. """ @@ -19,7 +20,7 @@ class VoiceBase(AbstractSingleton): self._api_key = None self._voices = [] self._mutex = Lock() - self._setup() + self._setup(config) def say(self, text: str, voice_index: int = 0) -> bool: """ diff --git a/autogpt/speech/eleven_labs.py b/autogpt/speech/eleven_labs.py index 5952508d..3f3baf33 100644 --- a/autogpt/speech/eleven_labs.py +++ b/autogpt/speech/eleven_labs.py @@ -13,14 +13,13 @@ PLACEHOLDERS = {"your-voice-id"} class ElevenLabsSpeech(VoiceBase): """ElevenLabs speech class""" - def _setup(self) -> None: + def _setup(self, config: Config) -> None: """Set up the voices, API key, etc. Returns: None: None """ - cfg = Config() default_voices = ["ErXwobaYiN019PkySvjV", "EXAVITQu4vr4xnSDxMaL"] voice_options = { "Rachel": "21m00Tcm4TlvDq8ikWAM", @@ -35,15 +34,15 @@ class ElevenLabsSpeech(VoiceBase): } self._headers = { "Content-Type": "application/json", - "xi-api-key": cfg.elevenlabs_api_key, + "xi-api-key": config.elevenlabs_api_key, } self._voices = default_voices.copy() - if cfg.elevenlabs_voice_id in voice_options: - cfg.elevenlabs_voice_id = voice_options[cfg.elevenlabs_voice_id] - if cfg.elevenlabs_voice_2_id in voice_options: - cfg.elevenlabs_voice_2_id = voice_options[cfg.elevenlabs_voice_2_id] - self._use_custom_voice(cfg.elevenlabs_voice_id, 0) - self._use_custom_voice(cfg.elevenlabs_voice_2_id, 1) + if config.elevenlabs_voice_id in voice_options: + config.elevenlabs_voice_id = voice_options[config.elevenlabs_voice_id] + if config.elevenlabs_voice_2_id in voice_options: + config.elevenlabs_voice_2_id = voice_options[config.elevenlabs_voice_2_id] + self._use_custom_voice(config.elevenlabs_voice_id, 0) + self._use_custom_voice(config.elevenlabs_voice_2_id, 1) def _use_custom_voice(self, voice, voice_index) -> None: """Use a custom voice if provided and not a placeholder diff --git a/autogpt/speech/say.py b/autogpt/speech/say.py index 06f580f0..1847c631 100644 --- a/autogpt/speech/say.py +++ b/autogpt/speech/say.py @@ -14,10 +14,9 @@ _QUEUE_SEMAPHORE = Semaphore( ) # The amount of sounds to queue before blocking the main thread -def say_text(text: str, voice_index: int = 0) -> None: +def say_text(text: str, config: Config, voice_index: int = 0) -> None: """Speak the given text using the given voice index""" - cfg = Config() - default_voice_engine, voice_engine = _get_voice_engine(cfg) + default_voice_engine, voice_engine = _get_voice_engine(config) def speak() -> None: success = voice_engine.say(text, voice_index) @@ -35,7 +34,7 @@ def _get_voice_engine(config: Config) -> tuple[VoiceBase, VoiceBase]: """Get the voice engine to use for the given configuration""" tts_provider = config.text_to_speech_provider if tts_provider == "elevenlabs": - voice_engine = ElevenLabsSpeech() + voice_engine = ElevenLabsSpeech(config) elif tts_provider == "macos": voice_engine = MacOSTTS() elif tts_provider == "streamelements": diff --git a/autogpt/utils.py b/autogpt/utils.py index 91e570a0..9eb6cbe4 100644 --- a/autogpt/utils.py +++ b/autogpt/utils.py @@ -23,11 +23,10 @@ def batch(iterable, max_batch_length: int, overlap: int = 0): yield iterable[i : i + max_batch_length] -def clean_input(prompt: str = "", talk=False): +def clean_input(config: Config, prompt: str = "", talk=False): try: - cfg = Config() - if cfg.chat_messages_enabled: - for plugin in cfg.plugins: + if config.chat_messages_enabled: + for plugin in config.plugins: if not hasattr(plugin, "can_handle_user_input"): continue if not plugin.can_handle_user_input(user_input=prompt): @@ -44,14 +43,14 @@ def clean_input(prompt: str = "", talk=False): "sure", "alright", ]: - return cfg.authorise_key + return config.authorise_key elif plugin_response.lower() in [ "no", "nope", "n", "negative", ]: - return cfg.exit_key + return config.exit_key return plugin_response # ask for input, default when just pressing Enter is y diff --git a/data_ingestion.py b/data_ingestion.py index 09d5328c..ae32b47c 100644 --- a/data_ingestion.py +++ b/data_ingestion.py @@ -5,7 +5,7 @@ from autogpt.commands.file_operations import ingest_file, list_files from autogpt.config import Config from autogpt.memory.vector import VectorMemory, get_memory -cfg = Config() +config = Config() def configure_logging(): @@ -70,7 +70,7 @@ def main() -> None: args = parser.parse_args() # Initialize memory - memory = get_memory(cfg) + memory = get_memory(config) if args.init: memory.clear() logger.debug("Using memory of type: " + memory.__class__.__name__) diff --git a/docs/challenges/building_challenges.md b/docs/challenges/building_challenges.md index 0c3d89ac..f50b0ea9 100644 --- a/docs/challenges/building_challenges.md +++ b/docs/challenges/building_challenges.md @@ -52,7 +52,7 @@ def kubernetes_agent( ai_config.command_registry = command_registry system_prompt = ai_config.construct_full_prompt() - Config().set_continuous_mode(False) + agent_test_config.set_continuous_mode(False) agent = Agent( # We also give the AI a name ai_name="Kubernetes-Demo", diff --git a/tests/Auto-GPT-test-cassettes b/tests/Auto-GPT-test-cassettes index 43f536a1..058606a1 160000 --- a/tests/Auto-GPT-test-cassettes +++ b/tests/Auto-GPT-test-cassettes @@ -1 +1 @@ -Subproject commit 43f536a193a57cd76f31fa405cf7ec2309ed383a +Subproject commit 058606a1009948a91808f4ccf5fb3c4f9e522db2 diff --git a/tests/conftest.py b/tests/conftest.py index 97620e21..db0eced0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -97,7 +97,7 @@ def agent(config: Config, workspace: Workspace) -> Agent: memory_json_file = get_memory(config) memory_json_file.clear() - system_prompt = ai_config.construct_full_prompt() + system_prompt = ai_config.construct_full_prompt(config) return Agent( ai_name=ai_config.ai_name, diff --git a/tests/integration/agent_factory.py b/tests/integration/agent_factory.py index 9078a843..e6702b66 100644 --- a/tests/integration/agent_factory.py +++ b/tests/integration/agent_factory.py @@ -55,7 +55,7 @@ def browser_agent(agent_test_config, memory_none: NoMemory, workspace: Workspace ) ai_config.command_registry = command_registry - system_prompt = ai_config.construct_full_prompt() + system_prompt = ai_config.construct_full_prompt(agent_test_config) agent = Agent( ai_name="", @@ -91,8 +91,8 @@ def file_system_agents( ai_goals=[ai_goal], ) ai_config.command_registry = command_registry - system_prompt = ai_config.construct_full_prompt() - Config().set_continuous_mode(False) + system_prompt = ai_config.construct_full_prompt(agent_test_config) + agent_test_config.set_continuous_mode(False) agents.append( Agent( ai_name="File System Agent", @@ -123,7 +123,7 @@ def memory_management_agent(agent_test_config, memory_json_file, workspace: Work ) ai_config.command_registry = command_registry - system_prompt = ai_config.construct_full_prompt() + system_prompt = ai_config.construct_full_prompt(agent_test_config) agent = Agent( ai_name="Follow-Instructions-GPT", @@ -159,8 +159,8 @@ def information_retrieval_agents( ai_goals=[ai_goal], ) ai_config.command_registry = command_registry - system_prompt = ai_config.construct_full_prompt() - Config().set_continuous_mode(False) + system_prompt = ai_config.construct_full_prompt(agent_test_config) + agent_test_config.set_continuous_mode(False) agents.append( Agent( ai_name="Information Retrieval Agent", @@ -195,8 +195,8 @@ def kubernetes_agent( ) ai_config.command_registry = command_registry - system_prompt = ai_config.construct_full_prompt() - Config().set_continuous_mode(False) + system_prompt = ai_config.construct_full_prompt(agent_test_config) + agent_test_config.set_continuous_mode(False) agent = Agent( ai_name="Kubernetes-Demo", memory=memory_json_file, @@ -228,8 +228,8 @@ def get_nobel_prize_agent(agent_test_config, memory_json_file, workspace: Worksp ) ai_config.command_registry = command_registry - system_prompt = ai_config.construct_full_prompt() - Config().set_continuous_mode(False) + system_prompt = ai_config.construct_full_prompt(agent_test_config) + agent_test_config.set_continuous_mode(False) agent = Agent( ai_name="Get-PhysicsNobelPrize", @@ -254,7 +254,7 @@ def debug_code_agents(agent_test_config, memory_json_file, workspace: Workspace) "1- Run test.py using the execute_python_file command.", "2- Read code.py using the read_file command.", "3- Modify code.py using the write_to_file command." - "Repeat step 1, 2 and 3 until test.py runs without errors.", + "Repeat step 1, 2 and 3 until test.py runs without errors. Do not modify the test.py file.", ], [ "1- Run test.py.", @@ -273,8 +273,8 @@ def debug_code_agents(agent_test_config, memory_json_file, workspace: Workspace) ) command_registry = get_command_registry(agent_test_config) ai_config.command_registry = command_registry - system_prompt = ai_config.construct_full_prompt() - Config().set_continuous_mode(False) + system_prompt = ai_config.construct_full_prompt(agent_test_config) + agent_test_config.set_continuous_mode(False) agents.append( Agent( ai_name="Debug Code Agent", diff --git a/tests/integration/goal_oriented/__init__.py b/tests/integration/goal_oriented/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/memory/test_json_file_memory.py b/tests/integration/memory/test_json_file_memory.py index 41a3e174..ab3996de 100644 --- a/tests/integration/memory/test_json_file_memory.py +++ b/tests/integration/memory/test_json_file_memory.py @@ -71,11 +71,11 @@ def test_json_memory_clear(config: Config, memory_item: MemoryItem): def test_json_memory_get(config: Config, memory_item: MemoryItem, mock_get_embedding): index = JSONFileMemory(config) assert ( - index.get("test") == None + index.get("test", config) == None ), "Cannot test get() because initial index is not empty" index.add(memory_item) - retrieved = index.get("test") + retrieved = index.get("test", config) assert retrieved is not None assert retrieved.memory_item == memory_item @@ -102,20 +102,27 @@ def test_json_memory_load_index(config: Config, memory_item: MemoryItem): @requires_api_key("OPENAI_API_KEY") def test_json_memory_get_relevant(config: Config, patched_api_requestor: None) -> None: index = JSONFileMemory(config) - mem1 = MemoryItem.from_text_file("Sample text", "sample.txt") - mem2 = MemoryItem.from_text_file("Grocery list:\n- Pancake mix", "groceries.txt") - mem3 = MemoryItem.from_text_file("What is your favorite color?", "color.txt") + mem1 = MemoryItem.from_text_file("Sample text", "sample.txt", config) + mem2 = MemoryItem.from_text_file( + "Grocery list:\n- Pancake mix", "groceries.txt", config + ) + mem3 = MemoryItem.from_text_file( + "What is your favorite color?", "color.txt", config + ) lipsum = "Lorem ipsum dolor sit amet" - mem4 = MemoryItem.from_text_file(" ".join([lipsum] * 100), "lipsum.txt") + mem4 = MemoryItem.from_text_file(" ".join([lipsum] * 100), "lipsum.txt", config) index.add(mem1) index.add(mem2) index.add(mem3) index.add(mem4) - assert index.get_relevant(mem1.raw_content, 1)[0].memory_item == mem1 - assert index.get_relevant(mem2.raw_content, 1)[0].memory_item == mem2 - assert index.get_relevant(mem3.raw_content, 1)[0].memory_item == mem3 - assert [mr.memory_item for mr in index.get_relevant(lipsum, 2)] == [mem4, mem1] + assert index.get_relevant(mem1.raw_content, 1, config)[0].memory_item == mem1 + assert index.get_relevant(mem2.raw_content, 1, config)[0].memory_item == mem2 + assert index.get_relevant(mem3.raw_content, 1, config)[0].memory_item == mem3 + assert [mr.memory_item for mr in index.get_relevant(lipsum, 2, config)] == [ + mem4, + mem1, + ] def test_json_memory_get_stats(config: Config, memory_item: MemoryItem) -> None: diff --git a/tests/integration/test_setup.py b/tests/integration/test_setup.py index 5217d72a..ed7eb8fd 100644 --- a/tests/integration/test_setup.py +++ b/tests/integration/test_setup.py @@ -9,10 +9,10 @@ from tests.utils import requires_api_key @pytest.mark.vcr @requires_api_key("OPENAI_API_KEY") -def test_generate_aiconfig_automatic_default(patched_api_requestor): +def test_generate_aiconfig_automatic_default(patched_api_requestor, config): user_inputs = [""] with patch("autogpt.utils.session.prompt", side_effect=user_inputs): - ai_config = prompt_user() + ai_config = prompt_user(config) assert isinstance(ai_config, AIConfig) assert ai_config.ai_name is not None @@ -22,9 +22,9 @@ def test_generate_aiconfig_automatic_default(patched_api_requestor): @pytest.mark.vcr @requires_api_key("OPENAI_API_KEY") -def test_generate_aiconfig_automatic_typical(patched_api_requestor): +def test_generate_aiconfig_automatic_typical(patched_api_requestor, config): user_prompt = "Help me create a rock opera about cybernetic giraffes" - ai_config = generate_aiconfig_automatic(user_prompt) + ai_config = generate_aiconfig_automatic(user_prompt, config) assert isinstance(ai_config, AIConfig) assert ai_config.ai_name is not None @@ -34,7 +34,7 @@ def test_generate_aiconfig_automatic_typical(patched_api_requestor): @pytest.mark.vcr @requires_api_key("OPENAI_API_KEY") -def test_generate_aiconfig_automatic_fallback(patched_api_requestor): +def test_generate_aiconfig_automatic_fallback(patched_api_requestor, config): user_inputs = [ "T&GF£OIBECC()!*", "Chef-GPT", @@ -45,7 +45,7 @@ def test_generate_aiconfig_automatic_fallback(patched_api_requestor): "", ] with patch("autogpt.utils.session.prompt", side_effect=user_inputs): - ai_config = prompt_user() + ai_config = prompt_user(config) assert isinstance(ai_config, AIConfig) assert ai_config.ai_name == "Chef-GPT" @@ -55,7 +55,7 @@ def test_generate_aiconfig_automatic_fallback(patched_api_requestor): @pytest.mark.vcr @requires_api_key("OPENAI_API_KEY") -def test_prompt_user_manual_mode(patched_api_requestor): +def test_prompt_user_manual_mode(patched_api_requestor, config): user_inputs = [ "--manual", "Chef-GPT", @@ -66,7 +66,7 @@ def test_prompt_user_manual_mode(patched_api_requestor): "", ] with patch("autogpt.utils.session.prompt", side_effect=user_inputs): - ai_config = prompt_user() + ai_config = prompt_user(config) assert isinstance(ai_config, AIConfig) assert ai_config.ai_name == "Chef-GPT" diff --git a/tests/unit/test_agent_manager.py b/tests/unit/test_agent_manager.py index 4b0a01bc..a372b726 100644 --- a/tests/unit/test_agent_manager.py +++ b/tests/unit/test_agent_manager.py @@ -5,9 +5,9 @@ from autogpt.llm.chat import create_chat_completion @pytest.fixture -def agent_manager(): +def agent_manager(config): # Hack, real gross. Singletons are not good times. - yield AgentManager() + yield AgentManager(config) del AgentManager._instances[AgentManager] diff --git a/tests/unit/test_ai_config.py b/tests/unit/test_ai_config.py index a684373b..e3c31d5d 100644 --- a/tests/unit/test_ai_config.py +++ b/tests/unit/test_ai_config.py @@ -19,10 +19,10 @@ ai_name: McFamished ai_role: A hungry AI api_budget: 0.0 """ - config_file = tmp_path / "ai_settings.yaml" - config_file.write_text(yaml_content) + ai_settings_file = tmp_path / "ai_settings.yaml" + ai_settings_file.write_text(yaml_content) - ai_config = AIConfig.load(config_file) + ai_config = AIConfig.load(ai_settings_file) assert len(ai_config.ai_goals) == 4 assert ai_config.ai_goals[0] == "Goal 1: Make a sandwich" @@ -30,8 +30,8 @@ api_budget: 0.0 assert ai_config.ai_goals[2] == "Goal 3 - Go to sleep" assert ai_config.ai_goals[3] == "Goal 4: Wake up" - config_file.write_text("") - ai_config.save(config_file) + ai_settings_file.write_text("") + ai_config.save(ai_settings_file) yaml_content2 = """ai_goals: - 'Goal 1: Make a sandwich' @@ -42,15 +42,15 @@ ai_name: McFamished ai_role: A hungry AI api_budget: 0.0 """ - assert config_file.read_text() == yaml_content2 + assert ai_settings_file.read_text() == yaml_content2 def test_ai_config_file_not_exists(workspace): """Test if file does not exist.""" - config_file = workspace.get_path("ai_settings.yaml") + ai_settings_file = workspace.get_path("ai_settings.yaml") - ai_config = AIConfig.load(str(config_file)) + ai_config = AIConfig.load(str(ai_settings_file)) assert ai_config.ai_name == "" assert ai_config.ai_role == "" assert ai_config.ai_goals == [] @@ -62,10 +62,10 @@ def test_ai_config_file_not_exists(workspace): def test_ai_config_file_is_empty(workspace): """Test if file does not exist.""" - config_file = workspace.get_path("ai_settings.yaml") - config_file.write_text("") + ai_settings_file = workspace.get_path("ai_settings.yaml") + ai_settings_file.write_text("") - ai_config = AIConfig.load(str(config_file)) + ai_config = AIConfig.load(str(ai_settings_file)) assert ai_config.ai_name == "" assert ai_config.ai_role == "" assert ai_config.ai_goals == [] diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 9a95cef1..19f474e2 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -1,5 +1,5 @@ """ -Test cases for the Config class, which handles the configuration settings +Test cases for the config class, which handles the configuration settings for the AI and ensures it behaves as a singleton. """ from unittest import mock @@ -7,14 +7,14 @@ from unittest.mock import patch import pytest -from autogpt.config.config import Config +from autogpt.config import Config from autogpt.configurator import GPT_3_MODEL, GPT_4_MODEL, create_config from autogpt.workspace.workspace import Workspace def test_initial_values(config: Config): """ - Test if the initial values of the Config class attributes are set correctly. + Test if the initial values of the config class attributes are set correctly. """ assert config.debug_mode == False assert config.continuous_mode == False diff --git a/tests/unit/test_file_operations.py b/tests/unit/test_file_operations.py index 5761e01a..b8283111 100644 --- a/tests/unit/test_file_operations.py +++ b/tests/unit/test_file_operations.py @@ -13,6 +13,7 @@ from pytest_mock import MockerFixture import autogpt.commands.file_operations as file_ops from autogpt.agent.agent import Agent +from autogpt.config import Config from autogpt.memory.vector.memory_item import MemoryItem from autogpt.memory.vector.utils import Embedding from autogpt.workspace import Workspace @@ -24,11 +25,13 @@ def file_content(): @pytest.fixture() -def mock_MemoryItem_from_text(mocker: MockerFixture, mock_embedding: Embedding): +def mock_MemoryItem_from_text( + mocker: MockerFixture, mock_embedding: Embedding, config: Config +): mocker.patch.object( file_ops.MemoryItem, "from_text", - new=lambda content, source_type, metadata: MemoryItem( + new=lambda content, source_type, config, metadata: MemoryItem( raw_content=content, summary=f"Summary of content '{content}'", chunk_summaries=[f"Summary of content '{content}'"], diff --git a/tests/unit/test_message_history.py b/tests/unit/test_message_history.py index 6fdf75e6..14b60895 100644 --- a/tests/unit/test_message_history.py +++ b/tests/unit/test_message_history.py @@ -38,8 +38,7 @@ def agent(config: Config): return agent -def test_message_history_batch_summary(mocker, agent): - config = Config() +def test_message_history_batch_summary(mocker, agent, config): history = MessageHistory(agent) model = config.fast_llm_model message_tlength = 0 @@ -114,7 +113,7 @@ def test_message_history_batch_summary(mocker, agent): history.append(user_input_msg) # only take the last cycle of the message history, trim the rest of previous messages, and generate a summary for them - for cycle in reversed(list(history.per_cycle())): + for cycle in reversed(list(history.per_cycle(config))): messages_to_add = [msg for msg in cycle if msg is not None] message_sequence.insert(insertion_index, *messages_to_add) break @@ -127,7 +126,7 @@ def test_message_history_batch_summary(mocker, agent): # test the main trim_message function new_summary_message, trimmed_messages = history.trim_messages( - current_message_chain=list(message_sequence), + current_message_chain=list(message_sequence), config=config ) expected_call_count = math.ceil( diff --git a/tests/unit/test_prompt_config.py b/tests/unit/test_prompt_config.py index eacb0cf4..4616db97 100644 --- a/tests/unit/test_prompt_config.py +++ b/tests/unit/test_prompt_config.py @@ -23,10 +23,10 @@ performance_evaluations: - Another test performance evaluation - A third test performance evaluation """ - config_file = tmp_path / "test_prompt_settings.yaml" - config_file.write_text(yaml_content) + prompt_settings_file = tmp_path / "test_prompt_settings.yaml" + prompt_settings_file.write_text(yaml_content) - prompt_config = PromptConfig(config_file) + prompt_config = PromptConfig(prompt_settings_file) assert len(prompt_config.constraints) == 3 assert prompt_config.constraints[0] == "A test constraint" diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index f9a471c2..0258cc49 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -4,6 +4,7 @@ from unittest.mock import patch import pytest import requests +from autogpt.config import Config from autogpt.json_utils.utilities import extract_json_from_response, validate_json from autogpt.utils import ( get_bulletin_from_web, @@ -185,12 +186,12 @@ def test_get_current_git_branch_failure(mock_repo): assert branch_name == "" -def test_validate_json_valid(valid_json_response): - assert validate_json(valid_json_response) +def test_validate_json_valid(valid_json_response, config: Config): + assert validate_json(valid_json_response, config) -def test_validate_json_invalid(invalid_json_response): - assert not validate_json(valid_json_response) +def test_validate_json_invalid(invalid_json_response, config: Config): + assert not validate_json(valid_json_response, config) def test_extract_json_from_response(valid_json_response: dict):