diff --git a/autogpt/.DS_Store b/autogpt/.DS_Store deleted file mode 100644 index eccc1ae..0000000 Binary files a/autogpt/.DS_Store and /dev/null differ diff --git a/autogpt/CURRENT_BULLETIN.md b/autogpt/CURRENT_BULLETIN.md deleted file mode 100644 index 735048d..0000000 --- a/autogpt/CURRENT_BULLETIN.md +++ /dev/null @@ -1,2 +0,0 @@ -Welcome to Auto-GPT! We'll keep you informed of the latest news and features by printing messages here. -If you don't wish to see this message, you can run Auto-GPT with the --skip-news flag \ No newline at end of file diff --git a/autogpt/__init__.py b/autogpt/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/autogpt/__main__.py b/autogpt/__main__.py deleted file mode 100644 index 128f9ee..0000000 --- a/autogpt/__main__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Auto-GPT: A GPT powered AI Assistant""" -import autogpt.cli - -if __name__ == "__main__": - autogpt.cli.main() diff --git a/autogpt/agent/__init__.py b/autogpt/agent/__init__.py deleted file mode 100644 index e928af2..0000000 --- a/autogpt/agent/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from autogpt.agent.agent import Agent -from autogpt.agent.agent_manager import AgentManager - -__all__ = ["Agent", "AgentManager"] diff --git a/autogpt/agent/agent.py b/autogpt/agent/agent.py deleted file mode 100644 index 2b6538d..0000000 --- a/autogpt/agent/agent.py +++ /dev/null @@ -1,241 +0,0 @@ -from colorama import Fore, Style - -from autogpt.app import execute_command, get_command -from autogpt.chat import chat_with_ai, create_chat_message -from autogpt.config import Config -from autogpt.json_utils.json_fix_llm import fix_json_using_multiple_techniques -from autogpt.json_utils.utilities import validate_json -from autogpt.logs import logger, print_assistant_thoughts -from autogpt.speech import say_text -from autogpt.spinner import Spinner -from autogpt.utils import clean_input -from autogpt.workspace import Workspace - - -class Agent: - """Agent class for interacting with Auto-GPT. - - Attributes: - ai_name: The name of the agent. - memory: The memory object to use. - full_message_history: The full message history. - next_action_count: The number of actions to execute. - system_prompt: The system prompt is the initial prompt that defines everything - the AI needs to know to achieve its task successfully. - Currently, the dynamic and customizable information in the system prompt are - ai_name, description and goals. - - triggering_prompt: The last sentence the AI will see before answering. - For Auto-GPT, this prompt is: - Determine which next command to use, and respond using the format specified - above: - The triggering prompt is not part of the system prompt because between the - system prompt and the triggering - prompt we have contextual information that can distract the AI and make it - forget that its goal is to find the next task to achieve. - SYSTEM PROMPT - CONTEXTUAL INFORMATION (memory, previous conversations, anything relevant) - TRIGGERING PROMPT - - The triggering prompt reminds the AI about its short term meta task - (defining the next task) - """ - - def __init__( - self, - ai_name, - memory, - full_message_history, - next_action_count, - command_registry, - config, - system_prompt, - triggering_prompt, - workspace_directory, - ): - self.cfg = Config() - self.ai_name = ai_name - self.memory = memory - self.full_message_history = full_message_history - self.next_action_count = next_action_count - self.command_registry = command_registry - self.config = config - self.system_prompt = system_prompt - self.triggering_prompt = triggering_prompt - self.workspace = Workspace(workspace_directory, self.cfg.restrict_to_workspace) - self.loop_count = 0 - self.command_name = None - self.sarguments = None - self.user_input = "" - self.cfg = Config() - - def start_interaction_loop(self): - # Discontinue if continuous limit is reached - self.loop_count += 1 - if ( - self.cfg.continuous_mode - and self.cfg.continuous_limit > 0 - and self.loop_count > self.cfg.continuous_limit - ): - logger.typewriter_log( - "Continuous Limit Reached: ", Fore.YELLOW, f"{self.cfg.continuous_limit}" - ) - # break - - # Send message to AI, get response - with Spinner("Thinking... "): - self.assistant_reply = chat_with_ai( - self, - self.system_prompt, - self.triggering_prompt, - self.full_message_history, - self.memory, - self.cfg.fast_token_limit, - ) # TODO: This hardcodes the model to use GPT3.5. Make this an argument - - self.assistant_reply_json = fix_json_using_multiple_techniques(self.assistant_reply) - for plugin in self.cfg.plugins: - if not plugin.can_handle_post_planning(): - continue - self.assistant_reply_json = plugin.post_planning(self, self.assistant_reply_json) - - # Print Assistant thoughts - if self.assistant_reply_json != {}: - validate_json(self.assistant_reply_json, "llm_response_format_1") - # Get command name and self.arguments - try: - print_assistant_thoughts(self.ai_name, self.assistant_reply_json) - self.command_name, self.arguments = get_command(self.assistant_reply_json) - if self.cfg.speak_mode: - say_text(f"I want to execute {self.command_name}") - self.arguments = self._resolve_pathlike_command_args(self.arguments) - - except Exception as e: - logger.error("Error: \n", str(e)) - - if not self.cfg.continuous_mode and self.next_action_count == 0: - # ### GET USER AUTHORIZATION TO EXECUTE COMMAND ### - # Get key press: Prompt the user to press enter to continue or escape - # to exit - logger.typewriter_log( - "NEXT ACTION: ", - Fore.CYAN, - f"COMMAND = {self.command_name}" - f"ARGUMENTS = {self.arguments}", - ) - logger.typewriter_log( - "", - "", - "Enter 'y' to authorise command, 'y -N' to run N continuous " - "commands, 'n' to exit program, or enter feedback for " - f"{self.ai_name}...", - ) - - def start_interaction_next(self, cookie, chatbot, history, msg, _input, obj): - console_input = _input - if console_input.lower().strip() == "y": - self.user_input = "GENERATE NEXT COMMAND JSON" - elif console_input.lower().strip() == "": - print("Invalid input format.") - return - elif console_input.lower().startswith("y -"): - try: - self.next_action_count = abs( - int(console_input.split(" ")[1]) - ) - self.user_input = "GENERATE NEXT COMMAND JSON" - except ValueError: - print( - "Invalid input format. Please enter 'y -n' where n is" - " the number of continuous tasks." - ) - - return - elif console_input.lower() == "n": - self.user_input = "EXIT" - return - else: - self.user_input = console_input - self.command_name = "human_feedback" - return - - if self.user_input == "GENERATE NEXT COMMAND JSON": - logger.typewriter_log( - "-=-=-=-=-=-=-= COMMAND AUTHORISED BY USER -=-=-=-=-=-=-=", - Fore.MAGENTA, - "", - ) - elif self.user_input == "EXIT": - print("Exiting...", flush=True) - # break 这里需要注意 - else: - # Print command - logger.typewriter_log( - "NEXT ACTION: ", - Fore.CYAN, - f"COMMAND = {Fore.CYAN}{self.command_name}{Style.RESET_ALL}" - f" ARGUMENTS = {Fore.CYAN}{self.arguments}{Style.RESET_ALL}", - ) - - # Execute command - if self.command_name is not None and self.command_name.lower().startswith("error"): - result = ( - f"Command {self.command_name} threw the following error: {self.arguments}" - ) - elif self.command_name == "human_feedback": - result = f"Human feedback: {self.user_input}" - else: - for plugin in self.cfg.plugins: - if not plugin.can_handle_pre_command(): - continue - self.command_name, self.arguments = plugin.pre_command( - self.command_name, self.arguments - ) - command_result = execute_command( - self.command_registry, - self.command_name, - self.arguments, - self.config.prompt_generator, - ) - result = f"Command {self.command_name} returned: " f"{command_result}" - - for plugin in self.cfg.plugins: - if not plugin.can_handle_post_command(): - continue - result = plugin.post_command(self.command_name, result) - if self.next_action_count > 0: - self.next_action_count -= 1 - if self.command_name != "do_nothing": - memory_to_add = ( - f"Assistant Reply: {self.assistant_reply} " - f"\nResult: {result} " - f"\nHuman Feedback: {self.user_input} " - ) - - self.memory.add(memory_to_add) - - # Check if there's a result from the command append it to the message - # history - if result is not None: - self.full_message_history.append( - create_chat_message("system", result) - ) - logger.typewriter_log("SYSTEM: ", Fore.YELLOW, result) - else: - self.full_message_history.append( - create_chat_message("system", "Unable to execute command") - ) - logger.typewriter_log( - "SYSTEM: ", Fore.YELLOW, "Unable to execute command" - ) - - def _resolve_pathlike_command_args(self, command_args): - if "directory" in command_args and command_args["directory"] in {"", "/"}: - command_args["directory"] = str(self.workspace.root) - else: - for pathlike in ["filename", "directory", "clone_path"]: - if pathlike in command_args: - command_args[pathlike] = str( - self.workspace.get_path(command_args[pathlike]) - ) - return command_args diff --git a/autogpt/agent/agent_manager.py b/autogpt/agent/agent_manager.py deleted file mode 100644 index 9a62ef6..0000000 --- a/autogpt/agent/agent_manager.py +++ /dev/null @@ -1,145 +0,0 @@ -"""Agent manager for managing GPT agents""" -from __future__ import annotations - -from typing import List, Union - -from autogpt.config.config import Config, Singleton -from autogpt.llm_utils import create_chat_completion -from autogpt.types.openai import Message - - -class AgentManager(metaclass=Singleton): - """Agent manager for managing GPT agents""" - - def __init__(self): - self.next_key = 0 - self.agents = {} # key, (task, full_message_history, model) - self.cfg = Config() - - # Create new GPT agent - # TODO: Centralise use of create_chat_completion() to globally enforce token limit - - def create_agent(self, task: str, prompt: str, model: str) -> tuple[int, str]: - """Create a new agent and return its key - - Args: - task: The task to perform - prompt: The prompt to use - model: The model to use - - Returns: - The key of the new agent - """ - messages: List[Message] = [ - {"role": "user", "content": prompt}, - ] - for plugin in self.cfg.plugins: - if not plugin.can_handle_pre_instruction(): - continue - if plugin_messages := plugin.pre_instruction(messages): - messages.extend(iter(plugin_messages)) - # Start GPT instance - agent_reply = create_chat_completion( - model=model, - messages=messages, - ) - - messages.append({"role": "assistant", "content": agent_reply}) - - plugins_reply = "" - for i, plugin in enumerate(self.cfg.plugins): - if not plugin.can_handle_on_instruction(): - continue - if plugin_result := plugin.on_instruction(messages): - sep = "\n" if i else "" - plugins_reply = f"{plugins_reply}{sep}{plugin_result}" - - if plugins_reply and plugins_reply != "": - messages.append({"role": "assistant", "content": plugins_reply}) - key = self.next_key - # This is done instead of len(agents) to make keys unique even if agents - # are deleted - self.next_key += 1 - - self.agents[key] = (task, messages, model) - - for plugin in self.cfg.plugins: - if not plugin.can_handle_post_instruction(): - continue - agent_reply = plugin.post_instruction(agent_reply) - - return key, agent_reply - - def message_agent(self, key: str | int, message: str) -> str: - """Send a message to an agent and return its response - - Args: - key: The key of the agent to message - message: The message to send to the agent - - Returns: - The agent's response - """ - task, messages, model = self.agents[int(key)] - - # Add user message to message history before sending to agent - messages.append({"role": "user", "content": message}) - - for plugin in self.cfg.plugins: - if not plugin.can_handle_pre_instruction(): - continue - if plugin_messages := plugin.pre_instruction(messages): - for plugin_message in plugin_messages: - messages.append(plugin_message) - - # Start GPT instance - agent_reply = create_chat_completion( - model=model, - messages=messages, - ) - - messages.append({"role": "assistant", "content": agent_reply}) - - plugins_reply = agent_reply - for i, plugin in enumerate(self.cfg.plugins): - if not plugin.can_handle_on_instruction(): - continue - if plugin_result := plugin.on_instruction(messages): - sep = "\n" if i else "" - plugins_reply = f"{plugins_reply}{sep}{plugin_result}" - # Update full message history - if plugins_reply and plugins_reply != "": - messages.append({"role": "assistant", "content": plugins_reply}) - - for plugin in self.cfg.plugins: - if not plugin.can_handle_post_instruction(): - continue - agent_reply = plugin.post_instruction(agent_reply) - - return agent_reply - - def list_agents(self) -> list[tuple[str | int, str]]: - """Return a list of all agents - - Returns: - A list of tuples of the form (key, task) - """ - - # Return a list of agent keys and their tasks - return [(key, task) for key, (task, _, _) in self.agents.items()] - - def delete_agent(self, key: str | int) -> bool: - """Delete an agent from the agent manager - - Args: - key: The key of the agent to delete - - Returns: - True if successful, False otherwise - """ - - try: - del self.agents[int(key)] - return True - except KeyError: - return False diff --git a/autogpt/api_manager.py b/autogpt/api_manager.py deleted file mode 100644 index 882e026..0000000 --- a/autogpt/api_manager.py +++ /dev/null @@ -1,158 +0,0 @@ -from typing import List - -import openai - -from autogpt.config import Config -from autogpt.logs import logger -from autogpt.modelsinfo import COSTS - -cfg = Config() -openai.api_key = cfg.openai_api_key -print_total_cost = cfg.debug_mode - - -class ApiManager: - def __init__(self, debug=False): - self.total_prompt_tokens = 0 - self.total_completion_tokens = 0 - self.total_cost = 0 - self.total_budget = 0 - self.debug = debug - - def reset(self): - self.total_prompt_tokens = 0 - self.total_completion_tokens = 0 - self.total_cost = 0 - self.total_budget = 0.0 - - def create_chat_completion( - self, - messages: list, # type: ignore - model: str = None, - temperature: float = cfg.temperature, - max_tokens: int = None, - deployment_id=None, - ) -> str: - """ - Create a chat completion and update the cost. - Args: - messages (list): The list of messages to send to the API. - model (str): The model to use for the API call. - temperature (float): The temperature to use for the API call. - max_tokens (int): The maximum number of tokens for the API call. - Returns: - str: The AI's response. - """ - if deployment_id is not None: - response = openai.ChatCompletion.create( - deployment_id=deployment_id, - model=model, - messages=messages, - temperature=temperature, - max_tokens=max_tokens, - ) - else: - response = openai.ChatCompletion.create( - model=model, - messages=messages, - temperature=temperature, - max_tokens=max_tokens, - ) - if self.debug: - logger.debug(f"Response: {response}") - prompt_tokens = response.usage.prompt_tokens - completion_tokens = response.usage.completion_tokens - self.update_cost(prompt_tokens, completion_tokens, model) - return response - - def embedding_create( - self, - text_list: List[str], - model: str = "text-embedding-ada-002", - ) -> List[float]: - """ - Create an embedding for the given input text using the specified model. - - Args: - text_list (List[str]): Input text for which the embedding is to be created. - model (str, optional): The model to use for generating the embedding. - - Returns: - List[float]: The generated embedding as a list of float values. - """ - if cfg.use_azure: - response = openai.Embedding.create( - input=text_list, - engine=cfg.get_azure_deployment_id_for_model(model), - ) - else: - response = openai.Embedding.create(input=text_list, model=model) - - self.update_cost(response.usage.prompt_tokens, 0, model) - return response["data"][0]["embedding"] - - def update_cost(self, prompt_tokens, completion_tokens, model): - """ - Update the total cost, prompt tokens, and completion tokens. - - Args: - prompt_tokens (int): The number of tokens used in the prompt. - completion_tokens (int): The number of tokens used in the completion. - model (str): The model used for the API call. - """ - self.total_prompt_tokens += prompt_tokens - self.total_completion_tokens += completion_tokens - self.total_cost += ( - prompt_tokens * COSTS[model]["prompt"] - + completion_tokens * COSTS[model]["completion"] - ) / 1000 - if print_total_cost: - print(f"Total running cost: ${self.total_cost:.3f}") - - def set_total_budget(self, total_budget): - """ - Sets the total user-defined budget for API calls. - - Args: - prompt_tokens (int): The number of tokens used in the prompt. - """ - self.total_budget = total_budget - - def get_total_prompt_tokens(self): - """ - Get the total number of prompt tokens. - - Returns: - int: The total number of prompt tokens. - """ - return self.total_prompt_tokens - - def get_total_completion_tokens(self): - """ - Get the total number of completion tokens. - - Returns: - int: The total number of completion tokens. - """ - return self.total_completion_tokens - - def get_total_cost(self): - """ - Get the total cost of API calls. - - Returns: - float: The total cost of API calls. - """ - return self.total_cost - - def get_total_budget(self): - """ - Get the total user-defined budget for API calls. - - Returns: - float: The total budget for API calls. - """ - return self.total_budget - - -api_manager = ApiManager(cfg.debug_mode) diff --git a/autogpt/app.py b/autogpt/app.py deleted file mode 100644 index 237feae..0000000 --- a/autogpt/app.py +++ /dev/null @@ -1,253 +0,0 @@ -""" Command and Control """ -import json -from typing import Dict, List, NoReturn, Union - -from autogpt.agent.agent_manager import AgentManager -from autogpt.commands.command import CommandRegistry, command -from autogpt.commands.web_requests import scrape_links, scrape_text -from autogpt.config import Config -from autogpt.memory import get_memory -from autogpt.processing.text import summarize_text -from autogpt.prompts.generator import PromptGenerator -from autogpt.speech import say_text - -CFG = Config() -AGENT_MANAGER = AgentManager() - - -def is_valid_int(value: str) -> bool: - """Check if the value is a valid integer - - Args: - value (str): The value to check - - Returns: - bool: True if the value is a valid integer, False otherwise - """ - try: - int(value) - return True - except ValueError: - return False - - -def get_command(response_json: Dict): - """Parse the response and return the command name and arguments - - Args: - response_json (json): The response from the AI - - Returns: - tuple: The command name and arguments - - Raises: - json.decoder.JSONDecodeError: If the response is not valid JSON - - Exception: If any other error occurs - """ - try: - if "command" not in response_json: - return "Error:", "Missing 'command' object in JSON" - - if not isinstance(response_json, dict): - return "Error:", f"'response_json' object is not dictionary {response_json}" - - command = response_json["command"] - if not isinstance(command, dict): - return "Error:", "'command' object is not a dictionary" - - if "name" not in command: - return "Error:", "Missing 'name' field in 'command' object" - - command_name = command["name"] - - # Use an empty dictionary if 'args' field is not present in 'command' object - arguments = command.get("args", {}) - - return command_name, arguments - except json.decoder.JSONDecodeError: - return "Error:", "Invalid JSON" - # All other errors, return "Error: + error message" - except Exception as e: - return "Error:", str(e) - - -def map_command_synonyms(command_name: str): - """Takes the original command name given by the AI, and checks if the - string matches a list of common/known hallucinations - """ - synonyms = [ - ("write_file", "write_to_file"), - ("create_file", "write_to_file"), - ("search", "google"), - ] - for seen_command, actual_command_name in synonyms: - if command_name == seen_command: - return actual_command_name - return command_name - - -def execute_command( - command_registry: CommandRegistry, - command_name: str, - arguments, - prompt: PromptGenerator, -): - """Execute the command and return the result - - Args: - command_name (str): The name of the command to execute - arguments (dict): The arguments for the command - - Returns: - str: The result of the command - """ - try: - cmd = command_registry.commands.get(command_name) - - # If the command is found, call it with the provided arguments - if cmd: - return cmd(**arguments) - - # TODO: Remove commands below after they are moved to the command registry. - command_name = map_command_synonyms(command_name.lower()) - - if command_name == "memory_add": - return get_memory(CFG).add(arguments["string"]) - - # TODO: Change these to take in a file rather than pasted code, if - # non-file is given, return instructions "Input should be a python - # filepath, write your code to file and try again - elif command_name == "do_nothing": - return "No action performed." - elif command_name == "task_complete": - shutdown() - else: - for command in prompt.commands: - if ( - command_name == command["label"].lower() - or command_name == command["name"].lower() - ): - return command["function"](**arguments) - return ( - f"Unknown command '{command_name}'. Please refer to the 'COMMANDS'" - " list for available commands and only respond in the specified JSON" - " format." - ) - except Exception as e: - return f"Error: {str(e)}" - - -@command( - "get_text_summary", "Get text summary", '"url": "", "question": ""' -) -def get_text_summary(url: str, question: str) -> str: - """Return the results of a Google search - - Args: - url (str): The url to scrape - question (str): The question to summarize the text for - - Returns: - str: The summary of the text - """ - text = scrape_text(url) - summary = summarize_text(url, text, question) - return f""" "Result" : {summary}""" - - -@command("get_hyperlinks", "Get text summary", '"url": ""') -def get_hyperlinks(url: str) -> Union[str, List[str]]: - """Return the results of a Google search - - Args: - url (str): The url to scrape - - Returns: - str or list: The hyperlinks on the page - """ - return scrape_links(url) - - -def shutdown() -> NoReturn: - """Shut down the program""" - print("Shutting down...") - quit() - - -@command( - "start_agent", - "Start GPT Agent", - '"name": "", "task": "", "prompt": ""', -) -def start_agent(name: str, task: str, prompt: str, model=CFG.fast_llm_model) -> str: - """Start an agent with a given name, task, and prompt - - Args: - name (str): The name of the agent - task (str): The task of the agent - prompt (str): The prompt for the agent - model (str): The model to use for the agent - - Returns: - str: The response of the agent - """ - # Remove underscores from name - voice_name = name.replace("_", " ") - - first_message = f"""You are {name}. Respond with: "Acknowledged".""" - agent_intro = f"{voice_name} here, Reporting for duty!" - - # Create agent - if CFG.speak_mode: - say_text(agent_intro, 1) - key, ack = AGENT_MANAGER.create_agent(task, first_message, model) - - if CFG.speak_mode: - say_text(f"Hello {voice_name}. Your task is as follows. {task}.") - - # Assign task (prompt), get response - agent_response = AGENT_MANAGER.message_agent(key, prompt) - - return f"Agent {name} created with key {key}. First response: {agent_response}" - - -@command("message_agent", "Message GPT Agent", '"key": "", "message": ""') -def message_agent(key: str, message: str) -> str: - """Message an agent with a given key and message""" - # Check if the key is a valid integer - if is_valid_int(key): - agent_response = AGENT_MANAGER.message_agent(int(key), message) - else: - return "Invalid key, must be an integer." - - # Speak response - if CFG.speak_mode: - say_text(agent_response, 1) - return agent_response - - -@command("list_agents", "List GPT Agents", "") -def list_agents() -> str: - """List all agents - - Returns: - str: A list of all agents - """ - return "List of agents:\n" + "\n".join( - [str(x[0]) + ": " + x[1] for x in AGENT_MANAGER.list_agents()] - ) - - -@command("delete_agent", "Delete GPT Agent", '"key": ""') -def delete_agent(key: str) -> str: - """Delete an agent with a given key - - Args: - key (str): The key of the agent to delete - - Returns: - str: A message indicating whether the agent was deleted or not - """ - result = AGENT_MANAGER.delete_agent(key) - return f"Agent {key} deleted." if result else f"Agent {key} does not exist." diff --git a/autogpt/auto-gpt.json b/autogpt/auto-gpt.json deleted file mode 100644 index 9e26dfe..0000000 --- a/autogpt/auto-gpt.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/autogpt/auto_gpt_workspace/.DS_Store b/autogpt/auto_gpt_workspace/.DS_Store deleted file mode 100644 index b5f4c97..0000000 Binary files a/autogpt/auto_gpt_workspace/.DS_Store and /dev/null differ diff --git a/autogpt/auto_gpt_workspace/127.0.0.1/auto-gpt.json b/autogpt/auto_gpt_workspace/127.0.0.1/auto-gpt.json deleted file mode 100644 index 9e26dfe..0000000 --- a/autogpt/auto_gpt_workspace/127.0.0.1/auto-gpt.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/autogpt/auto_gpt_workspace/127.0.0.1/file_logger.txt b/autogpt/auto_gpt_workspace/127.0.0.1/file_logger.txt deleted file mode 100644 index 67f4589..0000000 --- a/autogpt/auto_gpt_workspace/127.0.0.1/file_logger.txt +++ /dev/null @@ -1 +0,0 @@ -File Operation Logger \ No newline at end of file diff --git a/autogpt/chat.py b/autogpt/chat.py deleted file mode 100644 index 21eab6a..0000000 --- a/autogpt/chat.py +++ /dev/null @@ -1,218 +0,0 @@ -import time - -from openai.error import RateLimitError - -from autogpt import token_counter -from autogpt.api_manager import api_manager -from autogpt.config import Config -from autogpt.llm_utils import create_chat_completion -from autogpt.logs import logger -from autogpt.types.openai import Message - -cfg = Config() - - -def create_chat_message(role, content) -> Message: - """ - Create a chat message with the given role and content. - - Args: - role (str): The role of the message sender, e.g., "system", "user", or "assistant". - content (str): The content of the message. - - Returns: - dict: A dictionary containing the role and content of the message. - """ - return {"role": role, "content": content} - - -def generate_context(prompt, relevant_memory, full_message_history, model): - current_context = [ - create_chat_message("system", prompt), - create_chat_message( - "system", f"The current time and date is {time.strftime('%c')}" - ), - create_chat_message( - "system", - f"This reminds you of these events from your past:\n{relevant_memory}\n\n", - ), - ] - - # Add messages from the full message history until we reach the token limit - next_message_to_add_index = len(full_message_history) - 1 - insertion_index = len(current_context) - # Count the currently used tokens - current_tokens_used = token_counter.count_message_tokens(current_context, model) - return ( - next_message_to_add_index, - current_tokens_used, - insertion_index, - current_context, - ) - - -# TODO: Change debug from hardcode to argument -def chat_with_ai( - agent, prompt, user_input, full_message_history, permanent_memory, token_limit -): - """Interact with the OpenAI API, sending the prompt, user input, message history, - and permanent memory.""" - while True: - try: - """ - Interact with the OpenAI API, sending the prompt, user input, - message history, and permanent memory. - - Args: - prompt (str): The prompt explaining the rules to the AI. - user_input (str): The input from the user. - full_message_history (list): The list of all messages sent between the - user and the AI. - permanent_memory (Obj): The memory object containing the permanent - memory. - token_limit (int): The maximum number of tokens allowed in the API call. - - Returns: - str: The AI's response. - """ - model = cfg.fast_llm_model # TODO: Change model from hardcode to argument - # Reserve 1000 tokens for the response - - logger.debug(f"Token limit: {token_limit}") - send_token_limit = token_limit - 1000 - - relevant_memory = ( - "" - if len(full_message_history) == 0 - else permanent_memory.get_relevant(str(full_message_history[-9:]), 10) - ) - - logger.debug(f"Memory Stats: {permanent_memory.get_stats()}") - - ( - next_message_to_add_index, - current_tokens_used, - insertion_index, - current_context, - ) = generate_context(prompt, relevant_memory, full_message_history, model) - - while current_tokens_used > 2500: - # remove memories until we are under 2500 tokens - relevant_memory = relevant_memory[:-1] - ( - next_message_to_add_index, - current_tokens_used, - insertion_index, - current_context, - ) = generate_context( - prompt, relevant_memory, full_message_history, model - ) - - current_tokens_used += token_counter.count_message_tokens( - [create_chat_message("user", user_input)], model - ) # Account for user input (appended later) - - while next_message_to_add_index >= 0: - # print (f"CURRENT TOKENS USED: {current_tokens_used}") - message_to_add = full_message_history[next_message_to_add_index] - - tokens_to_add = token_counter.count_message_tokens( - [message_to_add], model - ) - if current_tokens_used + tokens_to_add > send_token_limit: - break - - # Add the most recent message to the start of the current context, - # after the two system prompts. - current_context.insert( - insertion_index, full_message_history[next_message_to_add_index] - ) - - # Count the currently used tokens - current_tokens_used += tokens_to_add - - # Move to the next most recent message in the full message history - next_message_to_add_index -= 1 - - # inform the AI about its remaining budget (if it has one) - if api_manager.get_total_budget() > 0.0: - remaining_budget = ( - api_manager.get_total_budget() - api_manager.get_total_cost() - ) - if remaining_budget < 0: - remaining_budget = 0 - system_message = ( - f"Your remaining API budget is ${remaining_budget:.3f}" - + ( - " BUDGET EXCEEDED! SHUT DOWN!\n\n" - if remaining_budget == 0 - else " Budget very nearly exceeded! Shut down gracefully!\n\n" - if remaining_budget < 0.005 - else " Budget nearly exceeded. Finish up.\n\n" - if remaining_budget < 0.01 - else "\n\n" - ) - ) - logger.debug(system_message) - current_context.append(create_chat_message("system", system_message)) - - # Append user input, the length of this is accounted for above - current_context.extend([create_chat_message("user", user_input)]) - - plugin_count = len(cfg.plugins) - for i, plugin in enumerate(cfg.plugins): - if not plugin.can_handle_on_planning(): - continue - plugin_response = plugin.on_planning( - agent.prompt_generator, current_context - ) - if not plugin_response or plugin_response == "": - continue - tokens_to_add = token_counter.count_message_tokens( - [create_chat_message("system", plugin_response)], model - ) - if current_tokens_used + tokens_to_add > send_token_limit: - if cfg.debug_mode: - print("Plugin response too long, skipping:", plugin_response) - print("Plugins remaining at stop:", plugin_count - i) - break - current_context.append(create_chat_message("system", plugin_response)) - - # Calculate remaining tokens - tokens_remaining = token_limit - current_tokens_used - # assert tokens_remaining >= 0, "Tokens remaining is negative. - # This should never happen, please submit a bug report at - # https://www.github.com/Torantulino/Auto-GPT" - - # Debug print the current context - logger.debug(f"Token limit: {token_limit}") - logger.debug(f"Send Token Count: {current_tokens_used}") - logger.debug(f"Tokens remaining for response: {tokens_remaining}") - logger.debug("------------ CONTEXT SENT TO AI ---------------") - for message in current_context: - # Skip printing the prompt - if message["role"] == "system" and message["content"] == prompt: - continue - logger.debug(f"{message['role'].capitalize()}: {message['content']}") - logger.debug("") - logger.debug("----------- END OF CONTEXT ----------------") - - # TODO: use a model defined elsewhere, so that model can contain - # temperature and other settings we care about - assistant_reply = create_chat_completion( - model=model, - messages=current_context, - max_tokens=tokens_remaining, - ) - - # Update full message history - full_message_history.append(create_chat_message("user", user_input)) - full_message_history.append( - create_chat_message("assistant", assistant_reply) - ) - - return assistant_reply - except RateLimitError: - # TODO: When we switch to langchain, this is built in - print("Error: ", "API Rate Limit Reached. Waiting 10 seconds...") - time.sleep(10) diff --git a/autogpt/cli.py b/autogpt/cli.py deleted file mode 100644 index 1751646..0000000 --- a/autogpt/cli.py +++ /dev/null @@ -1,230 +0,0 @@ -"""Main script for the autogpt package.""" -# Put imports inside function to avoid importing everything when starting the CLI -import logging -import os.path -import sys -from pathlib import Path - -import gradio -from colorama import Fore -from autogpt.agent.agent import Agent -from autogpt.commands.command import CommandRegistry -from autogpt.config import Config, check_openai_api_key -from autogpt.configurator import create_config -from autogpt.logs import logger -from autogpt.memory import get_memory -from autogpt.plugins import scan_plugins -from autogpt.prompts.prompt import construct_main_ai_config -from autogpt.utils import get_current_git_branch, get_latest_bulletin -from autogpt.workspace import Workspace -import func_box -from toolbox import update_ui -from toolbox import ChatBotWithCookies -def handle_config(kwargs_settings): - kwargs_settings = { - 'continuous': False, # Enable Continuous Mode - 'continuous_limit': None, # Defines the number of times to run in continuous mode - 'ai_settings': None, # Specifies which ai_settings.yaml file to use, will also automatically skip the re-prompt. - 'skip_reprompt': False, # Skips the re-prompting messages at the beginning of the scrip - 'speak': False, # Enable speak Mode - 'debug': False, # Enable Debug Mode - 'gpt3only': False, # Enable GPT3.5 Only Mode - 'gpt4only': False, # Enable GPT4 Only Mode - 'memory_type': None, # Defines which Memory backend to use - 'browser_name': None, # Specifies which web-browser to use when using selenium to scrape the web. - 'allow_downloads': False, # Dangerous: Allows Auto-GPT to download files natively. - 'skip_news': True, # Specifies whether to suppress the output of latest news on startup. - 'workspace_directory': None # TODO: this is a hidden option for now, necessary for integration testing. We should make this public once we're ready to roll out agent specific workspaces. - } - """ - Welcome to AutoGPT an experimental open-source application showcasing the capabilities of the GPT-4 pushing the boundaries of AI. - Start an Auto-GPT assistant. - """ - if kwargs_settings['workspace_directory']: - kwargs_settings['ai_settings'] = os.path.join(kwargs_settings['workspace_directory'], 'ai_settings.yaml') - # if ctx.invoked_subcommand is None: - cfg = Config() - # TODO: fill in llm values here - check_openai_api_key() - create_config( - kwargs_settings['continuous'], - kwargs_settings['continuous_limit'], - kwargs_settings['ai_settings'], - kwargs_settings['skip_reprompt'], - kwargs_settings['speak'], - kwargs_settings['debug'], - kwargs_settings['gpt3only'], - kwargs_settings['gpt4only'], - kwargs_settings['memory_type'], - kwargs_settings['browser_name'], - kwargs_settings['allow_downloads'], - kwargs_settings['skip_news'], - ) - return cfg - - -def handle_news(): - motd = get_latest_bulletin() - if motd: - logger.typewriter_log("NEWS: ", Fore.GREEN, motd) - git_branch = get_current_git_branch() - if git_branch and git_branch != "stable": - logger.typewriter_log( - "WARNING: ", - Fore.RED, - f"You are running on `{git_branch}` branch " - "- this is not a supported branch.", - ) - if sys.version_info < (3, 10): - logger.typewriter_log( - "WARNING: ", - Fore.RED, - "You are running on an older version of Python. " - "Some people have observed problems with certain " - "parts of Auto-GPT with this version. " - "Please consider upgrading to Python 3.10 or higher.", - ) - - -def handle_registry(): - # Create a CommandRegistry instance and scan default folder - command_registry = CommandRegistry() - command_registry.import_commands("autogpt.commands.analyze_code") - command_registry.import_commands("autogpt.commands.audio_text") - command_registry.import_commands("autogpt.commands.execute_code") - command_registry.import_commands("autogpt.commands.file_operations") - command_registry.import_commands("autogpt.commands.git_operations") - command_registry.import_commands("autogpt.commands.google_search") - command_registry.import_commands("autogpt.commands.image_gen") - command_registry.import_commands("autogpt.commands.improve_code") - command_registry.import_commands("autogpt.commands.twitter") - command_registry.import_commands("autogpt.commands.web_selenium") - command_registry.import_commands("autogpt.commands.write_tests") - command_registry.import_commands("autogpt.app") - return command_registry - - -def handle_workspace(user): - # TODO: have this directory live outside the repository (e.g. in a user's - # home directory) and have it come in as a command line argument or part of - # the env file. - if user is None: - workspace_directory = Path(__file__).parent / "auto_gpt_workspace" - else: - workspace_directory = Path(__file__).parent / "auto_gpt_workspace" / user - # 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) - # HACK: doing this here to collect some globals that depend on the workspace. - file_logger_path = workspace_directory / "file_logger.txt" - if not file_logger_path.exists(): - with file_logger_path.open(mode="w", encoding="utf-8") as f: - f.write("File Operation Logger ") - - return workspace_directory, file_logger_path - - -def update_obj(plugin_kwargs, _is=True): - obj = plugin_kwargs['obj'] - start = plugin_kwargs['start'] - next_ = plugin_kwargs['next'] - text = plugin_kwargs['txt'] - if _is: - start.update(visible=True) - next_.update(visible=False) - text.update(visible=False) - else: - start.update(visible=False) - next_.update(visible=True) - text.update(visible=True) - return obj, start, next_, text - - -def agent_main(name, role, goals, budget, - cookies, chatbot, history, obj, - ipaddr: gradio.Request): - # ai setup - input_kwargs = { - 'name': name, - 'role': role, - 'goals': goals, - 'budget': budget - } - # chat setup - logger.output_content = [] - chatbot_with_cookie = ChatBotWithCookies(cookies) - chatbot_with_cookie.write_list(chatbot) - history = [] - cfg = handle_config(None) - logger.set_level(logging.DEBUG if cfg.debug_mode else logging.INFO) - workspace_directory = ipaddr.client.host - if not cfg.skip_news: - handle_news() - cfg.set_plugins(scan_plugins(cfg, cfg.debug_mode)) - command_registry = handle_registry() - ai_config = construct_main_ai_config(input_kwargs) - def update_stream_ui(user='', gpt='', msg='Done', - _start=obj['start'].update(), _next=obj['next'].update(), _text=obj['text'].update()): - if user or gpt: - temp = [user, gpt] - if not chatbot_with_cookie: - chatbot_with_cookie.append(temp) - else: - chatbot_with_cookie[-1] = [chatbot_with_cookie[-1][i] + temp[i] for i in range(len(chatbot_with_cookie[-1]))] - yield chatbot_with_cookie.get_cookies(), chatbot_with_cookie, history, msg, obj, _start, _next, _text - if not ai_config: - msg = '### ROLE 不能为空' - # yield chatbot_with_cookie.get_cookies(), chatbot_with_cookie, history, msg, obj, None, None, None - yield from update_stream_ui(msg=msg) - return - ai_config.command_registry = command_registry - next_action_count = 0 - # Make a constant: - triggering_prompt = ( - "Determine which next command to use, and respond using the" - " format specified above:" - ) - workspace_directory, file_logger_path = handle_workspace(workspace_directory) - cfg.workspace_path = str(workspace_directory) - cfg.file_logger_path = str(file_logger_path) - # Initialize memory and make sure it is empty. - # this is particularly important for indexing and referencing pinecone memory - memory = get_memory(cfg, init=True) - 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("Prompt:", Fore.GREEN, system_prompt) - agent = Agent( - ai_name=input_kwargs['name'], - memory=memory, - full_message_history=history, - next_action_count=next_action_count, - command_registry=command_registry, - config=ai_config, - system_prompt=system_prompt, - triggering_prompt=triggering_prompt, - workspace_directory=workspace_directory, - ) - obj['obj'] = agent - _start = obj['start'].update(visible=False) - _next = obj['next'].update(visible=True) - _text = obj['text'].update(visible=True, interactive=True) - # chat, his = func_box.chat_history(logger.output_content) - # yield from update_stream_ui(user='Auto-GPT Start!', gpt=chat, _start=_start, _next=_next, _text=_text) - agent.start_interaction_loop() - chat, his = func_box.chat_history(logger.output_content) - yield from update_stream_ui(user='Auto-GPT Start!', gpt=chat, _start=_start, _next=_next, _text=_text) - - - - -def agent_start(cookie, chatbot, history, msg, obj): - yield from obj['obj'].start_interaction_loop(cookie, chatbot, history, msg, obj) - - -if __name__ == "__main__": - pass - diff --git a/autogpt/cli_private.py b/autogpt/cli_private.py deleted file mode 100644 index 75908a1..0000000 --- a/autogpt/cli_private.py +++ /dev/null @@ -1,213 +0,0 @@ -"""Main script for the autogpt package.""" -import click - - -@click.group(invoke_without_command=True) -@click.option("-c", "--continuous", is_flag=True, help="Enable Continuous Mode") -@click.option( - "--skip-reprompt", - "-y", - is_flag=True, - help="Skips the re-prompting messages at the beginning of the script", -) -@click.option( - "--ai-settings", - "-C", - help="Specifies which ai_settings.yaml file to use, will also automatically skip the re-prompt.", -) -@click.option( - "-l", - "--continuous-limit", - type=int, - help="Defines the number of times to run in continuous mode", -) -@click.option("--speak", is_flag=True, help="Enable Speak Mode") -@click.option("--debug", is_flag=True, help="Enable Debug Mode") -@click.option("--gpt3only", is_flag=True, help="Enable GPT3.5 Only Mode") -@click.option("--gpt4only", is_flag=True, help="Enable GPT4 Only Mode") -@click.option( - "--use-memory", - "-m", - "memory_type", - type=str, - help="Defines which Memory backend to use", -) -@click.option( - "-b", - "--browser-name", - help="Specifies which web-browser to use when using selenium to scrape the web.", -) -@click.option( - "--allow-downloads", - is_flag=True, - help="Dangerous: Allows Auto-GPT to download files natively.", -) -@click.option( - "--skip-news", - is_flag=True, - help="Specifies whether to suppress the output of latest news on startup.", -) -@click.option( - # TODO: this is a hidden option for now, necessary for integration testing. - # We should make this public once we're ready to roll out agent specific workspaces. - "--workspace-directory", - "-w", - type=click.Path(), - hidden=True, -) -@click.pass_context -def main( - ctx: click.Context, - continuous: bool, - continuous_limit: int, - ai_settings: str, - skip_reprompt: bool, - speak: bool, - debug: bool, - gpt3only: bool, - gpt4only: bool, - memory_type: str, - browser_name: str, - allow_downloads: bool, - skip_news: bool, - workspace_directory: str, -) -> None: - """ - Welcome to AutoGPT an experimental open-source application showcasing the capabilities of the GPT-4 pushing the boundaries of AI. - - Start an Auto-GPT assistant. - """ - # Put imports inside function to avoid importing everything when starting the CLI - import logging - import sys - from pathlib import Path - - from colorama import Fore - - from autogpt.agent.agent import Agent - from autogpt.commands.command import CommandRegistry - from autogpt.config import Config, check_openai_api_key - from autogpt.configurator import create_config - from autogpt.logs import logger - from autogpt.memory import get_memory - from autogpt.plugins import scan_plugins - from autogpt.prompts.prompt import construct_main_ai_config - from autogpt.utils import get_current_git_branch, get_latest_bulletin - from autogpt.workspace import Workspace - - if ctx.invoked_subcommand is None: - cfg = Config() - # TODO: fill in llm values here - check_openai_api_key() - create_config( - continuous, - continuous_limit, - ai_settings, - skip_reprompt, - speak, - debug, - gpt3only, - gpt4only, - memory_type, - browser_name, - allow_downloads, - skip_news, - ) - logger.set_level(logging.DEBUG if cfg.debug_mode else logging.INFO) - if not cfg.skip_news: - motd = get_latest_bulletin() - if motd: - logger.typewriter_log("NEWS: ", Fore.GREEN, motd) - git_branch = get_current_git_branch() - if git_branch and git_branch != "stable": - logger.typewriter_log( - "WARNING: ", - Fore.RED, - f"You are running on `{git_branch}` branch " - "- this is not a supported branch.", - ) - if sys.version_info < (3, 10): - logger.typewriter_log( - "WARNING: ", - Fore.RED, - "You are running on an older version of Python. " - "Some people have observed problems with certain " - "parts of Auto-GPT with this version. " - "Please consider upgrading to Python 3.10 or higher.", - ) - - cfg.set_plugins(scan_plugins(cfg, cfg.debug_mode)) - # Create a CommandRegistry instance and scan default folder - command_registry = CommandRegistry() - command_registry.import_commands("autogpt.commands.analyze_code") - command_registry.import_commands("autogpt.commands.audio_text") - command_registry.import_commands("autogpt.commands.execute_code") - command_registry.import_commands("autogpt.commands.file_operations") - command_registry.import_commands("autogpt.commands.git_operations") - command_registry.import_commands("autogpt.commands.google_search") - command_registry.import_commands("autogpt.commands.image_gen") - command_registry.import_commands("autogpt.commands.improve_code") - command_registry.import_commands("autogpt.commands.twitter") - command_registry.import_commands("autogpt.commands.web_selenium") - command_registry.import_commands("autogpt.commands.write_tests") - command_registry.import_commands("autogpt.app") - - ai_name = "" - ai_config = construct_main_ai_config() - ai_config.command_registry = command_registry - # print(prompt) - # Initialize variables - full_message_history = [] - next_action_count = 0 - # Make a constant: - triggering_prompt = ( - "Determine which next command to use, and respond using the" - " format specified above:" - ) - # Initialize memory and make sure it is empty. - # this is particularly important for indexing and referencing pinecone memory - memory = get_memory(cfg, init=True) - 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("Prompt:", Fore.GREEN, system_prompt) - - # TODO: have this directory live outside the repository (e.g. in a user's - # home directory) and have it come in as a command line argument or part of - # the env file. - if workspace_directory is None: - workspace_directory = Path(__file__).parent / "auto_gpt_workspace" - else: - workspace_directory = Path(workspace_directory) - # 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) - - # HACK: doing this here to collect some globals that depend on the workspace. - file_logger_path = workspace_directory / "file_logger.txt" - if not file_logger_path.exists(): - 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) - - agent = Agent( - ai_name=ai_name, - memory=memory, - full_message_history=full_message_history, - next_action_count=next_action_count, - command_registry=command_registry, - config=ai_config, - system_prompt=system_prompt, - triggering_prompt=triggering_prompt, - workspace_directory=workspace_directory, - ) - agent.start_interaction_loop() - - -if __name__ == "__main__": - main() diff --git a/autogpt/commands/__init__.py b/autogpt/commands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/autogpt/commands/analyze_code.py b/autogpt/commands/analyze_code.py deleted file mode 100644 index 47cfc1e..0000000 --- a/autogpt/commands/analyze_code.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Code evaluation module.""" -from __future__ import annotations - -from autogpt.commands.command import command -from autogpt.llm_utils import call_ai_function - - -@command( - "analyze_code", - "Analyze Code", - '"code": ""', -) -def analyze_code(code: str) -> list[str]: - """ - A function that takes in a string and returns a response from create chat - completion api call. - - Parameters: - code (str): Code to be evaluated. - Returns: - A result string from create chat completion. A list of suggestions to - improve the code. - """ - - function_string = "def analyze_code(code: str) -> list[str]:" - args = [code] - description_string = ( - "Analyzes the given code and returns a list of suggestions for improvements." - ) - - return call_ai_function(function_string, args, description_string) diff --git a/autogpt/commands/audio_text.py b/autogpt/commands/audio_text.py deleted file mode 100644 index 0a8640c..0000000 --- a/autogpt/commands/audio_text.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Commands for converting audio to text.""" -import json - -import requests - -from autogpt.commands.command import command -from autogpt.config import Config - -CFG = Config() - - -@command( - "read_audio_from_file", - "Convert Audio to text", - '"filename": ""', - CFG.huggingface_audio_to_text_model, - "Configure huggingface_audio_to_text_model.", -) -def read_audio_from_file(filename: str) -> str: - """ - Convert audio to text. - - Args: - filename (str): The path to the audio file - - Returns: - str: The text from the audio - """ - with open(filename, "rb") as audio_file: - audio = audio_file.read() - return read_audio(audio) - - -def read_audio(audio: bytes) -> str: - """ - Convert audio to text. - - Args: - audio (bytes): The audio to convert - - Returns: - str: The text from the audio - """ - model = CFG.huggingface_audio_to_text_model - api_url = f"https://api-inference.huggingface.co/models/{model}" - api_token = CFG.huggingface_api_token - headers = {"Authorization": f"Bearer {api_token}"} - - if api_token is None: - raise ValueError( - "You need to set your Hugging Face API token in the config file." - ) - - response = requests.post( - api_url, - headers=headers, - data=audio, - ) - - text = json.loads(response.content.decode("utf-8"))["text"] - return f"The audio says: {text}" diff --git a/autogpt/commands/command.py b/autogpt/commands/command.py deleted file mode 100644 index 22ebace..0000000 --- a/autogpt/commands/command.py +++ /dev/null @@ -1,156 +0,0 @@ -import functools -import importlib -import inspect -from typing import Any, Callable, Optional - -# Unique identifier for auto-gpt commands -AUTO_GPT_COMMAND_IDENTIFIER = "auto_gpt_command" - - -class Command: - """A class representing a command. - - Attributes: - name (str): The name of the command. - description (str): A brief description of what the command does. - signature (str): The signature of the function that the command executes. Defaults to None. - """ - - def __init__( - self, - name: str, - description: str, - method: Callable[..., Any], - signature: str = "", - enabled: bool = True, - disabled_reason: Optional[str] = None, - ): - self.name = name - self.description = description - self.method = method - self.signature = signature if signature else str(inspect.signature(self.method)) - self.enabled = enabled - self.disabled_reason = disabled_reason - - def __call__(self, *args, **kwargs) -> Any: - if not self.enabled: - return f"Command '{self.name}' is disabled: {self.disabled_reason}" - return self.method(*args, **kwargs) - - def __str__(self) -> str: - return f"{self.name}: {self.description}, args: {self.signature}" - - -class CommandRegistry: - """ - The CommandRegistry class is a manager for a collection of Command objects. - It allows the registration, modification, and retrieval of Command objects, - as well as the scanning and loading of command plugins from a specified - directory. - """ - - def __init__(self): - self.commands = {} - - def _import_module(self, module_name: str) -> Any: - return importlib.import_module(module_name) - - def _reload_module(self, module: Any) -> Any: - return importlib.reload(module) - - def register(self, cmd: Command) -> None: - self.commands[cmd.name] = cmd - - def unregister(self, command_name: str): - if command_name in self.commands: - del self.commands[command_name] - else: - raise KeyError(f"Command '{command_name}' not found in registry.") - - def reload_commands(self) -> None: - """Reloads all loaded command plugins.""" - for cmd_name in self.commands: - cmd = self.commands[cmd_name] - module = self._import_module(cmd.__module__) - reloaded_module = self._reload_module(module) - if hasattr(reloaded_module, "register"): - reloaded_module.register(self) - - def get_command(self, name: str) -> Callable[..., Any]: - return self.commands[name] - - def call(self, command_name: str, **kwargs) -> Any: - if command_name not in self.commands: - raise KeyError(f"Command '{command_name}' not found in registry.") - command = self.commands[command_name] - return command(**kwargs) - - def command_prompt(self) -> str: - """ - Returns a string representation of all registered `Command` objects for use in a prompt - """ - commands_list = [ - f"{idx + 1}. {str(cmd)}" for idx, cmd in enumerate(self.commands.values()) - ] - return "\n".join(commands_list) - - def import_commands(self, module_name: str) -> None: - """ - Imports the specified Python module containing command plugins. - - This method imports the associated module and registers any functions or - classes that are decorated with the `AUTO_GPT_COMMAND_IDENTIFIER` attribute - as `Command` objects. The registered `Command` objects are then added to the - `commands` dictionary of the `CommandRegistry` object. - - Args: - module_name (str): The name of the module to import for command plugins. - """ - - module = importlib.import_module(module_name) - - for attr_name in dir(module): - attr = getattr(module, attr_name) - # Register decorated functions - if hasattr(attr, AUTO_GPT_COMMAND_IDENTIFIER) and getattr( - attr, AUTO_GPT_COMMAND_IDENTIFIER - ): - self.register(attr.command) - # Register command classes - elif ( - inspect.isclass(attr) and issubclass(attr, Command) and attr != Command - ): - cmd_instance = attr() - self.register(cmd_instance) - - -def command( - name: str, - description: str, - signature: str = "", - enabled: bool = True, - disabled_reason: Optional[str] = None, -) -> Callable[..., Any]: - """The command decorator is used to create Command objects from ordinary functions.""" - - def decorator(func: Callable[..., Any]) -> Command: - cmd = Command( - name=name, - description=description, - method=func, - signature=signature, - enabled=enabled, - disabled_reason=disabled_reason, - ) - - @functools.wraps(func) - def wrapper(*args, **kwargs) -> Any: - return func(*args, **kwargs) - - wrapper.command = cmd - - setattr(wrapper, AUTO_GPT_COMMAND_IDENTIFIER, True) - - return wrapper - - return decorator diff --git a/autogpt/commands/execute_code.py b/autogpt/commands/execute_code.py deleted file mode 100644 index 71c1bd2..0000000 --- a/autogpt/commands/execute_code.py +++ /dev/null @@ -1,182 +0,0 @@ -"""Execute code in a Docker container""" -import os -import subprocess - -import docker -from docker.errors import ImageNotFound - -from autogpt.commands.command import command -from autogpt.config import Config - -CFG = Config() - - -@command("execute_python_file", "Execute Python File", '"filename": ""') -def execute_python_file(filename: str) -> str: - """Execute a Python file in a Docker container and return the output - - Args: - filename (str): The name of the file to execute - - Returns: - str: The output of the file - """ - print(f"Executing file '{filename}'") - - if not filename.endswith(".py"): - return "Error: Invalid file type. Only .py files are allowed." - - if not os.path.isfile(filename): - return f"Error: File '{filename}' does not exist." - - if we_are_running_in_a_docker_container(): - result = subprocess.run( - f"python {filename}", capture_output=True, encoding="utf8", shell=True - ) - if result.returncode == 0: - return result.stdout - else: - return f"Error: {result.stderr}" - - try: - client = docker.from_env() - - # You can replace this with the desired Python image/version - # You can find available Python images on Docker Hub: - # https://hub.docker.com/_/python - image_name = "python:3-alpine" - try: - client.images.get(image_name) - print(f"Image '{image_name}' found locally") - except ImageNotFound: - print(f"Image '{image_name}' not found locally, pulling from Docker Hub") - # Use the low-level API to stream the pull response - low_level_client = docker.APIClient() - for line in low_level_client.pull(image_name, stream=True, decode=True): - # Print the status and progress, if available - status = line.get("status") - progress = line.get("progress") - if status and progress: - print(f"{status}: {progress}") - elif status: - print(status) - - container = client.containers.run( - image_name, - f"python {filename}", - volumes={ - CFG.workspace_path: { - "bind": "/workspace", - "mode": "ro", - } - }, - working_dir="/workspace", - stderr=True, - stdout=True, - detach=True, - ) - - container.wait() - logs = container.logs().decode("utf-8") - container.remove() - - # print(f"Execution complete. Output: {output}") - # print(f"Logs: {logs}") - - return logs - - except docker.errors.DockerException as e: - print( - "Could not run the script in a container. If you haven't already, please install Docker https://docs.docker.com/get-docker/" - ) - return f"Error: {str(e)}" - - except Exception as e: - return f"Error: {str(e)}" - - -@command( - "execute_shell", - "Execute Shell Command, non-interactive commands only", - '"command_line": ""', - CFG.execute_local_commands, - "You are not allowed to run local shell commands. To execute" - " shell commands, EXECUTE_LOCAL_COMMANDS must be set to 'True' " - "in your config. Do not attempt to bypass the restriction.", -) -def execute_shell(command_line: str) -> str: - """Execute a shell command and return the output - - Args: - command_line (str): The command line to execute - - Returns: - str: The output of the command - """ - - if not CFG.execute_local_commands: - return ( - "You are not allowed to run local shell commands. To execute" - " shell commands, EXECUTE_LOCAL_COMMANDS must be set to 'True' " - "in your config. Do not attempt to bypass the restriction." - ) - current_dir = os.getcwd() - # Change dir into workspace if necessary - if CFG.workspace_path not in current_dir: - os.chdir(CFG.workspace_path) - - print(f"Executing command '{command_line}' in working directory '{os.getcwd()}'") - - result = subprocess.run(command_line, capture_output=True, shell=True) - output = f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}" - - # Change back to whatever the prior working dir was - - os.chdir(current_dir) - - -@command( - "execute_shell_popen", - "Execute Shell Command, non-interactive commands only", - '"command_line": ""', - CFG.execute_local_commands, - "You are not allowed to run local shell commands. To execute" - " shell commands, EXECUTE_LOCAL_COMMANDS must be set to 'True' " - "in your config. Do not attempt to bypass the restriction.", -) -def execute_shell_popen(command_line) -> str: - """Execute a shell command with Popen and returns an english description - of the event and the process id - - Args: - command_line (str): The command line to execute - - Returns: - str: Description of the fact that the process started and its id - """ - current_dir = os.getcwd() - # Change dir into workspace if necessary - if CFG.workspace_path not in current_dir: - os.chdir(CFG.workspace_path) - - print(f"Executing command '{command_line}' in working directory '{os.getcwd()}'") - - do_not_show_output = subprocess.DEVNULL - process = subprocess.Popen( - command_line, shell=True, stdout=do_not_show_output, stderr=do_not_show_output - ) - - # Change back to whatever the prior working dir was - - os.chdir(current_dir) - - return f"Subprocess started with PID:'{str(process.pid)}'" - - -def we_are_running_in_a_docker_container() -> bool: - """Check if we are running in a Docker container - - Returns: - bool: True if we are running in a Docker container, False otherwise - """ - return os.path.exists("/.dockerenv") diff --git a/autogpt/commands/file_operations.py b/autogpt/commands/file_operations.py deleted file mode 100644 index 0735c06..0000000 --- a/autogpt/commands/file_operations.py +++ /dev/null @@ -1,268 +0,0 @@ -"""File operations for AutoGPT""" -from __future__ import annotations - -import os -import os.path -from typing import Generator - -import requests -from colorama import Back, Fore -from requests.adapters import HTTPAdapter, Retry - -from autogpt.commands.command import command -from autogpt.config import Config -from autogpt.spinner import Spinner -from autogpt.utils import readable_file_size - -CFG = Config() - - -def check_duplicate_operation(operation: str, filename: str) -> bool: - """Check if the operation has already been performed on the given file - - Args: - operation (str): The operation to check for - filename (str): The name of the file to check for - - Returns: - bool: True if the operation has already been performed on the file - """ - log_content = read_file(CFG.file_logger_path) - log_entry = f"{operation}: {filename}\n" - return log_entry in log_content - - -def log_operation(operation: str, filename: str) -> None: - """Log the file operation to the file_logger.txt - - Args: - operation (str): The operation to log - filename (str): The name of the file the operation was performed on - """ - log_entry = f"{operation}: {filename}\n" - append_to_file(CFG.file_logger_path, log_entry, should_log=False) - - -def split_file( - content: str, max_length: int = 4000, overlap: int = 0 -) -> Generator[str, None, None]: - """ - Split text into chunks of a specified maximum length with a specified overlap - between chunks. - - :param content: The input text to be split into chunks - :param max_length: The maximum length of each chunk, - default is 4000 (about 1k token) - :param overlap: The number of overlapping characters between chunks, - default is no overlap - :return: A generator yielding chunks of text - """ - start = 0 - content_length = len(content) - - while start < content_length: - end = start + max_length - if end + overlap < content_length: - chunk = content[start : end + overlap - 1] - else: - chunk = content[start:content_length] - - # Account for the case where the last chunk is shorter than the overlap, so it has already been consumed - if len(chunk) <= overlap: - break - - yield chunk - start += max_length - overlap - - -@command("read_file", "Read file", '"filename": ""') -def read_file(filename: str) -> str: - """Read a file and return the contents - - Args: - filename (str): The name of the file to read - - Returns: - str: The contents of the file - """ - try: - with open(filename, "r", encoding="utf-8") as f: - content = f.read() - return content - except Exception as e: - return f"Error: {str(e)}" - - -def ingest_file( - filename: str, memory, max_length: int = 4000, overlap: int = 200 -) -> None: - """ - Ingest a file by reading its content, splitting it into chunks with a specified - maximum length and overlap, and adding the chunks to the memory storage. - - :param filename: The name of the file to ingest - :param memory: An object with an add() method to store the chunks in memory - :param max_length: The maximum length of each chunk, default is 4000 - :param overlap: The number of overlapping characters between chunks, default is 200 - """ - try: - print(f"Working with file {filename}") - content = read_file(filename) - content_length = len(content) - print(f"File length: {content_length} characters") - - chunks = list(split_file(content, max_length=max_length, overlap=overlap)) - - num_chunks = len(chunks) - for i, chunk in enumerate(chunks): - print(f"Ingesting chunk {i + 1} / {num_chunks} into memory") - memory_to_add = ( - f"Filename: {filename}\n" f"Content part#{i + 1}/{num_chunks}: {chunk}" - ) - - memory.add(memory_to_add) - - print(f"Done ingesting {num_chunks} chunks from {filename}.") - except Exception as e: - print(f"Error while ingesting file '{filename}': {str(e)}") - - -@command("write_to_file", "Write to file", '"filename": "", "text": ""') -def write_to_file(filename: str, text: str) -> str: - """Write text to a file - - Args: - filename (str): The name of the file to write to - text (str): The text to write to the file - - Returns: - str: A message indicating success or failure - """ - if check_duplicate_operation("write", filename): - return "Error: File has already been updated." - try: - directory = os.path.dirname(filename) - if not os.path.exists(directory): - os.makedirs(directory) - with open(filename, "w", encoding="utf-8") as f: - f.write(text) - log_operation("write", filename) - return "File written to successfully." - except Exception as e: - return f"Error: {str(e)}" - - -@command( - "append_to_file", "Append to file", '"filename": "", "text": ""' -) -def append_to_file(filename: str, text: str, should_log: bool = True) -> str: - """Append text to a file - - Args: - filename (str): The name of the file to append to - text (str): The text to append to the file - should_log (bool): Should log output - - Returns: - str: A message indicating success or failure - """ - try: - with open(filename, "a") as f: - f.write(text) - - if should_log: - log_operation("append", filename) - - return "Text appended successfully." - except Exception as e: - return f"Error: {str(e)}" - - -@command("delete_file", "Delete file", '"filename": ""') -def delete_file(filename: str) -> str: - """Delete a file - - Args: - filename (str): The name of the file to delete - - Returns: - str: A message indicating success or failure - """ - if check_duplicate_operation("delete", filename): - return "Error: File has already been deleted." - try: - os.remove(filename) - log_operation("delete", filename) - return "File deleted successfully." - except Exception as e: - return f"Error: {str(e)}" - - -@command("search_files", "Search Files", '"directory": ""') -def search_files(directory: str) -> list[str]: - """Search for files in a directory - - Args: - directory (str): The directory to search in - - Returns: - list[str]: A list of files found in the directory - """ - found_files = [] - - for root, _, files in os.walk(directory): - for file in files: - if file.startswith("."): - continue - relative_path = os.path.relpath( - os.path.join(root, file), CFG.workspace_path - ) - found_files.append(relative_path) - - return found_files - - -@command( - "download_file", - "Download File", - '"url": "", "filename": ""', - CFG.allow_downloads, - "Error: You do not have user authorization to download files locally.", -) -def download_file(url, filename): - """Downloads a file - Args: - url (str): URL of the file to download - filename (str): Filename to save the file as - """ - try: - message = f"{Fore.YELLOW}Downloading file from {Back.MAGENTA}{url}{Back.RESET}{Fore.RESET}" - with Spinner(message) as spinner: - session = requests.Session() - retry = Retry(total=3, backoff_factor=1, status_forcelist=[502, 503, 504]) - adapter = HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.mount("https://", adapter) - - total_size = 0 - downloaded_size = 0 - - with session.get(url, allow_redirects=True, stream=True) as r: - r.raise_for_status() - total_size = int(r.headers.get("Content-Length", 0)) - downloaded_size = 0 - - with open(filename, "wb") as f: - for chunk in r.iter_content(chunk_size=8192): - f.write(chunk) - downloaded_size += len(chunk) - - # Update the progress message - progress = f"{readable_file_size(downloaded_size)} / {readable_file_size(total_size)}" - spinner.update_message(f"{message} {progress}") - - return f'Successfully downloaded and locally stored file: "{filename}"! (Size: {readable_file_size(total_size)})' - except requests.HTTPError as e: - return f"Got an HTTP Error whilst trying to download file: {e}" - except Exception as e: - return "Error: " + str(e) diff --git a/autogpt/commands/git_operations.py b/autogpt/commands/git_operations.py deleted file mode 100644 index c373b8c..0000000 --- a/autogpt/commands/git_operations.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Git operations for autogpt""" -from git.repo import Repo - -from autogpt.commands.command import command -from autogpt.config import Config - -CFG = Config() - - -@command( - "clone_repository", - "Clone Repository", - '"repository_url": "", "clone_path": ""', - CFG.github_username and CFG.github_api_key, - "Configure github_username and github_api_key.", -) -def clone_repository(repository_url: str, clone_path: str) -> str: - """Clone a GitHub repository locally. - - Args: - repository_url (str): The URL of the repository to clone. - clone_path (str): The path to clone the repository to. - - Returns: - str: The result of the clone operation. - """ - split_url = repository_url.split("//") - auth_repo_url = f"//{CFG.github_username}:{CFG.github_api_key}@".join(split_url) - try: - Repo.clone_from(auth_repo_url, clone_path) - return f"""Cloned {repository_url} to {clone_path}""" - except Exception as e: - return f"Error: {str(e)}" diff --git a/autogpt/commands/google_search.py b/autogpt/commands/google_search.py deleted file mode 100644 index 264daaf..0000000 --- a/autogpt/commands/google_search.py +++ /dev/null @@ -1,117 +0,0 @@ -"""Google search command for Autogpt.""" -from __future__ import annotations - -import json - -from duckduckgo_search import ddg - -from autogpt.commands.command import command -from autogpt.config import Config - -CFG = Config() - - -@command("google", "Google Search", '"query": ""', not CFG.google_api_key) -def google_search(query: str, num_results: int = 8) -> str: - """Return the results of a Google search - - Args: - query (str): The search query. - num_results (int): The number of results to return. - - Returns: - str: The results of the search. - """ - search_results = [] - if not query: - return json.dumps(search_results) - - results = ddg(query, max_results=num_results) - if not results: - return json.dumps(search_results) - - for j in results: - search_results.append(j) - - results = json.dumps(search_results, ensure_ascii=False, indent=4) - return safe_google_results(results) - - -@command( - "google", - "Google Search", - '"query": ""', - bool(CFG.google_api_key), - "Configure google_api_key.", -) -def google_official_search(query: str, num_results: int = 8) -> str | list[str]: - """Return the results of a Google search using the official Google API - - Args: - query (str): The search query. - num_results (int): The number of results to return. - - Returns: - str: The results of the search. - """ - - from googleapiclient.discovery import build - from googleapiclient.errors import HttpError - - try: - # Get the Google API key and Custom Search Engine ID from the config file - api_key = CFG.google_api_key - custom_search_engine_id = CFG.custom_search_engine_id - - # Initialize the Custom Search API service - service = build("customsearch", "v1", developerKey=api_key) - - # Send the search query and retrieve the results - result = ( - service.cse() - .list(q=query, cx=custom_search_engine_id, num=num_results) - .execute() - ) - - # Extract the search result items from the response - search_results = result.get("items", []) - - # Create a list of only the URLs from the search results - search_results_links = [item["link"] for item in search_results] - - except HttpError as e: - # Handle errors in the API call - error_details = json.loads(e.content.decode()) - - # Check if the error is related to an invalid or missing API key - if error_details.get("error", {}).get( - "code" - ) == 403 and "invalid API key" in error_details.get("error", {}).get( - "message", "" - ): - return "Error: The provided Google API key is invalid or missing." - else: - return f"Error: {e}" - # google_result can be a list or a string depending on the search results - - # Return the list of search result URLs - return safe_google_results(search_results_links) - - -def safe_google_results(results: str | list) -> str: - """ - Return the results of a google search in a safe format. - - Args: - results (str | list): The search results. - - Returns: - str: The results of the search. - """ - if isinstance(results, list): - safe_message = json.dumps( - [result.encode("utf-8", "ignore") for result in results] - ) - else: - safe_message = results.encode("utf-8", "ignore").decode("utf-8") - return safe_message diff --git a/autogpt/commands/image_gen.py b/autogpt/commands/image_gen.py deleted file mode 100644 index 834432c..0000000 --- a/autogpt/commands/image_gen.py +++ /dev/null @@ -1,164 +0,0 @@ -""" Image Generation Module for AutoGPT.""" -import io -import uuid -from base64 import b64decode - -import openai -import requests -from PIL import Image - -from autogpt.commands.command import command -from autogpt.config import Config - -CFG = Config() - - -@command("generate_image", "Generate Image", '"prompt": ""', CFG.image_provider) -def generate_image(prompt: str, size: int = 256) -> str: - """Generate an image from a prompt. - - Args: - prompt (str): The prompt to use - size (int, optional): The size of the image. Defaults to 256. (Not supported by HuggingFace) - - Returns: - str: The filename of the image - """ - filename = f"{CFG.workspace_path}/{str(uuid.uuid4())}.jpg" - - # DALL-E - if CFG.image_provider == "dalle": - return generate_image_with_dalle(prompt, filename, size) - # HuggingFace - elif CFG.image_provider == "huggingface": - return generate_image_with_hf(prompt, filename) - # SD WebUI - elif CFG.image_provider == "sdwebui": - return generate_image_with_sd_webui(prompt, filename, size) - return "No Image Provider Set" - - -def generate_image_with_hf(prompt: str, filename: str) -> str: - """Generate an image with HuggingFace's API. - - Args: - prompt (str): The prompt to use - filename (str): The filename to save the image to - - Returns: - str: The filename of the image - """ - API_URL = ( - f"https://api-inference.huggingface.co/models/{CFG.huggingface_image_model}" - ) - if CFG.huggingface_api_token is None: - raise ValueError( - "You need to set your Hugging Face API token in the config file." - ) - headers = { - "Authorization": f"Bearer {CFG.huggingface_api_token}", - "X-Use-Cache": "false", - } - - response = requests.post( - API_URL, - headers=headers, - json={ - "inputs": prompt, - }, - ) - - image = Image.open(io.BytesIO(response.content)) - print(f"Image Generated for prompt:{prompt}") - - image.save(filename) - - return f"Saved to disk:{filename}" - - -def generate_image_with_dalle(prompt: str, filename: str, size: int) -> str: - """Generate an image with DALL-E. - - Args: - prompt (str): The prompt to use - filename (str): The filename to save the image to - size (int): The size of the image - - Returns: - str: The filename of the image - """ - openai.api_key = CFG.openai_api_key - - # Check for supported image sizes - if size not in [256, 512, 1024]: - closest = min([256, 512, 1024], key=lambda x: abs(x - size)) - print( - f"DALL-E only supports image sizes of 256x256, 512x512, or 1024x1024. Setting to {closest}, was {size}." - ) - size = closest - - response = openai.Image.create( - prompt=prompt, - n=1, - size=f"{size}x{size}", - response_format="b64_json", - ) - - print(f"Image Generated for prompt:{prompt}") - - image_data = b64decode(response["data"][0]["b64_json"]) - - with open(filename, mode="wb") as png: - png.write(image_data) - - return f"Saved to disk:{filename}" - - -def generate_image_with_sd_webui( - prompt: str, - filename: str, - size: int = 512, - negative_prompt: str = "", - extra: dict = {}, -) -> str: - """Generate an image with Stable Diffusion webui. - Args: - prompt (str): The prompt to use - filename (str): The filename to save the image to - size (int, optional): The size of the image. Defaults to 256. - negative_prompt (str, optional): The negative prompt to use. Defaults to "". - extra (dict, optional): Extra parameters to pass to the API. Defaults to {}. - Returns: - str: The filename of the image - """ - # Create a session and set the basic auth if needed - s = requests.Session() - if CFG.sd_webui_auth: - username, password = CFG.sd_webui_auth.split(":") - s.auth = (username, password or "") - - # Generate the images - response = requests.post( - f"{CFG.sd_webui_url}/sdapi/v1/txt2img", - json={ - "prompt": prompt, - "negative_prompt": negative_prompt, - "sampler_index": "DDIM", - "steps": 20, - "cfg_scale": 7.0, - "width": size, - "height": size, - "n_iter": 1, - **extra, - }, - ) - - print(f"Image Generated for prompt:{prompt}") - - # Save the image to disk - response = response.json() - b64 = b64decode(response["images"][0].split(",", 1)[0]) - image = Image.open(io.BytesIO(b64)) - image.save(filename) - - return f"Saved to disk:{filename}" diff --git a/autogpt/commands/improve_code.py b/autogpt/commands/improve_code.py deleted file mode 100644 index f953cf2..0000000 --- a/autogpt/commands/improve_code.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -import json - -from autogpt.commands.command import command -from autogpt.llm_utils import call_ai_function - - -@command( - "improve_code", - "Get Improved Code", - '"suggestions": "", "code": ""', -) -def improve_code(suggestions: list[str], code: str) -> str: - """ - A function that takes in code and suggestions and returns a response from create - chat completion api call. - - Parameters: - suggestions (list): A list of suggestions around what needs to be improved. - code (str): Code to be improved. - Returns: - A result string from create chat completion. Improved code in response. - """ - - function_string = ( - "def generate_improved_code(suggestions: list[str], code: str) -> str:" - ) - args = [json.dumps(suggestions), code] - description_string = ( - "Improves the provided code based on the suggestions" - " provided, making no other changes." - ) - - return call_ai_function(function_string, args, description_string) diff --git a/autogpt/commands/times.py b/autogpt/commands/times.py deleted file mode 100644 index 3c9b8a4..0000000 --- a/autogpt/commands/times.py +++ /dev/null @@ -1,10 +0,0 @@ -from datetime import datetime - - -def get_datetime() -> str: - """Return the current date and time - - Returns: - str: The current date and time - """ - return "Current date and time: " + datetime.now().strftime("%Y-%m-%d %H:%M:%S") diff --git a/autogpt/commands/twitter.py b/autogpt/commands/twitter.py deleted file mode 100644 index f050227..0000000 --- a/autogpt/commands/twitter.py +++ /dev/null @@ -1,44 +0,0 @@ -"""A module that contains a command to send a tweet.""" -import os - -import tweepy -from dotenv import load_dotenv - -from autogpt.commands.command import command - -load_dotenv() - - -@command( - "send_tweet", - "Send Tweet", - '"tweet_text": ""', -) -def send_tweet(tweet_text: str) -> str: - """ - A function that takes in a string and returns a response from create chat - completion api call. - - Args: - tweet_text (str): Text to be tweeted. - - Returns: - A result from sending the tweet. - """ - consumer_key = os.environ.get("TW_CONSUMER_KEY") - consumer_secret = os.environ.get("TW_CONSUMER_SECRET") - access_token = os.environ.get("TW_ACCESS_TOKEN") - access_token_secret = os.environ.get("TW_ACCESS_TOKEN_SECRET") - # Authenticate to Twitter - auth = tweepy.OAuthHandler(consumer_key, consumer_secret) - auth.set_access_token(access_token, access_token_secret) - - # Create API object - api = tweepy.API(auth) - - # Send tweet - try: - api.update_status(tweet_text) - return "Tweet sent successfully!" - except tweepy.TweepyException as e: - return f"Error sending tweet: {e.reason}" diff --git a/autogpt/commands/web_playwright.py b/autogpt/commands/web_playwright.py deleted file mode 100644 index 4e388de..0000000 --- a/autogpt/commands/web_playwright.py +++ /dev/null @@ -1,80 +0,0 @@ -"""Web scraping commands using Playwright""" -from __future__ import annotations - -try: - from playwright.sync_api import sync_playwright -except ImportError: - print( - "Playwright not installed. Please install it with 'pip install playwright' to use." - ) -from bs4 import BeautifulSoup - -from autogpt.processing.html import extract_hyperlinks, format_hyperlinks - - -def scrape_text(url: str) -> str: - """Scrape text from a webpage - - Args: - url (str): The URL to scrape text from - - Returns: - str: The scraped text - """ - with sync_playwright() as p: - browser = p.chromium.launch() - page = browser.new_page() - - try: - page.goto(url) - html_content = page.content() - soup = BeautifulSoup(html_content, "html.parser") - - for script in soup(["script", "style"]): - script.extract() - - text = soup.get_text() - lines = (line.strip() for line in text.splitlines()) - chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) - text = "\n".join(chunk for chunk in chunks if chunk) - - except Exception as e: - text = f"Error: {str(e)}" - - finally: - browser.close() - - return text - - -def scrape_links(url: str) -> str | list[str]: - """Scrape links from a webpage - - Args: - url (str): The URL to scrape links from - - Returns: - Union[str, List[str]]: The scraped links - """ - with sync_playwright() as p: - browser = p.chromium.launch() - page = browser.new_page() - - try: - page.goto(url) - html_content = page.content() - soup = BeautifulSoup(html_content, "html.parser") - - for script in soup(["script", "style"]): - script.extract() - - hyperlinks = extract_hyperlinks(soup, url) - formatted_links = format_hyperlinks(hyperlinks) - - except Exception as e: - formatted_links = f"Error: {str(e)}" - - finally: - browser.close() - - return formatted_links diff --git a/autogpt/commands/web_requests.py b/autogpt/commands/web_requests.py deleted file mode 100644 index f3ad019..0000000 --- a/autogpt/commands/web_requests.py +++ /dev/null @@ -1,188 +0,0 @@ -"""Browse a webpage and summarize it using the LLM model""" -from __future__ import annotations - -from urllib.parse import urljoin, urlparse - -import requests -from bs4 import BeautifulSoup -from requests import Response -from requests.compat import urljoin - -from autogpt.config import Config -from autogpt.processing.html import extract_hyperlinks, format_hyperlinks - -CFG = Config() - -session = requests.Session() -session.headers.update({"User-Agent": CFG.user_agent}) - - -def is_valid_url(url: str) -> bool: - """Check if the URL is valid - - Args: - url (str): The URL to check - - Returns: - bool: True if the URL is valid, False otherwise - """ - try: - result = urlparse(url) - return all([result.scheme, result.netloc]) - except ValueError: - return False - - -def sanitize_url(url: str) -> str: - """Sanitize the URL - - Args: - url (str): The URL to sanitize - - Returns: - str: The sanitized URL - """ - return urljoin(url, urlparse(url).path) - - -def check_local_file_access(url: str) -> bool: - """Check if the URL is a local file - - Args: - url (str): The URL to check - - Returns: - bool: True if the URL is a local file, False otherwise - """ - local_prefixes = [ - "file:///", - "file://localhost/", - "file://localhost", - "http://localhost", - "http://localhost/", - "https://localhost", - "https://localhost/", - "http://2130706433", - "http://2130706433/", - "https://2130706433", - "https://2130706433/", - "http://127.0.0.1/", - "http://127.0.0.1", - "https://127.0.0.1/", - "https://127.0.0.1", - "https://0.0.0.0/", - "https://0.0.0.0", - "http://0.0.0.0/", - "http://0.0.0.0", - "http://0000", - "http://0000/", - "https://0000", - "https://0000/", - ] - return any(url.startswith(prefix) for prefix in local_prefixes) - - -def get_response( - url: str, timeout: int = 10 -) -> tuple[None, str] | tuple[Response, None]: - """Get the response from a URL - - Args: - url (str): The URL to get the response from - timeout (int): The timeout for the HTTP request - - Returns: - tuple[None, str] | tuple[Response, None]: The response and error message - - Raises: - ValueError: If the URL is invalid - requests.exceptions.RequestException: If the HTTP request fails - """ - try: - # Restrict access to local files - if check_local_file_access(url): - raise ValueError("Access to local files is restricted") - - # Most basic check if the URL is valid: - if not url.startswith("http://") and not url.startswith("https://"): - raise ValueError("Invalid URL format") - - sanitized_url = sanitize_url(url) - - response = session.get(sanitized_url, timeout=timeout) - - # Check if the response contains an HTTP error - if response.status_code >= 400: - return None, f"Error: HTTP {str(response.status_code)} error" - - return response, None - except ValueError as ve: - # Handle invalid URL format - return None, f"Error: {str(ve)}" - - except requests.exceptions.RequestException as re: - # Handle exceptions related to the HTTP request - # (e.g., connection errors, timeouts, etc.) - return None, f"Error: {str(re)}" - - -def scrape_text(url: str) -> str: - """Scrape text from a webpage - - Args: - url (str): The URL to scrape text from - - Returns: - str: The scraped text - """ - response, error_message = get_response(url) - if error_message: - return error_message - if not response: - return "Error: Could not get response" - - soup = BeautifulSoup(response.text, "html.parser") - - for script in soup(["script", "style"]): - script.extract() - - text = soup.get_text() - lines = (line.strip() for line in text.splitlines()) - chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) - text = "\n".join(chunk for chunk in chunks if chunk) - - return text - - -def scrape_links(url: str) -> str | list[str]: - """Scrape links from a webpage - - Args: - url (str): The URL to scrape links from - - Returns: - str | list[str]: The scraped links - """ - response, error_message = get_response(url) - if error_message: - return error_message - if not response: - return "Error: Could not get response" - soup = BeautifulSoup(response.text, "html.parser") - - for script in soup(["script", "style"]): - script.extract() - - hyperlinks = extract_hyperlinks(soup, url) - - return format_hyperlinks(hyperlinks) - - -def create_message(chunk, question): - """Create a message for the user to summarize a chunk of text""" - return { - "role": "user", - "content": f'"""{chunk}""" Using the above text, answer the following' - f' question: "{question}" -- if the question cannot be answered using the' - " text, summarize the text.", - } diff --git a/autogpt/commands/web_selenium.py b/autogpt/commands/web_selenium.py deleted file mode 100644 index e0e0d70..0000000 --- a/autogpt/commands/web_selenium.py +++ /dev/null @@ -1,160 +0,0 @@ -"""Selenium web scraping module.""" -from __future__ import annotations - -import logging -from pathlib import Path -from sys import platform - -from bs4 import BeautifulSoup -from selenium import webdriver -from selenium.webdriver.chrome.options import Options as ChromeOptions -from selenium.webdriver.common.by import By -from selenium.webdriver.firefox.options import Options as FirefoxOptions -from selenium.webdriver.remote.webdriver import WebDriver -from selenium.webdriver.safari.options import Options as SafariOptions -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.wait import WebDriverWait -from webdriver_manager.chrome import ChromeDriverManager -from webdriver_manager.firefox import GeckoDriverManager - -import autogpt.processing.text as summary -from autogpt.commands.command import command -from autogpt.config import Config -from autogpt.processing.html import extract_hyperlinks, format_hyperlinks - -FILE_DIR = Path(__file__).parent.parent -CFG = Config() - - -@command( - "browse_website", - "Browse Website", - '"url": "", "question": ""', -) -def browse_website(url: str, question: str) -> tuple[str, WebDriver]: - """Browse a website and return the answer and links to the user - - Args: - url (str): The url of the website to browse - question (str): The question asked by the user - - Returns: - Tuple[str, WebDriver]: The answer and links to the user and the webdriver - """ - driver, text = scrape_text_with_selenium(url) - add_header(driver) - summary_text = summary.summarize_text(url, text, question, driver) - links = scrape_links_with_selenium(driver, url) - - # Limit links to 5 - if len(links) > 5: - links = links[:5] - close_browser(driver) - return f"Answer gathered from website: {summary_text} \n \n Links: {links}", driver - - -def scrape_text_with_selenium(url: str) -> tuple[WebDriver, str]: - """Scrape text from a website using selenium - - Args: - url (str): The url of the website to scrape - - Returns: - Tuple[WebDriver, str]: The webdriver and the text scraped from the website - """ - logging.getLogger("selenium").setLevel(logging.CRITICAL) - - options_available = { - "chrome": ChromeOptions, - "safari": SafariOptions, - "firefox": FirefoxOptions, - } - - options = options_available[CFG.selenium_web_browser]() - options.add_argument( - "user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.49 Safari/537.36" - ) - - if CFG.selenium_web_browser == "firefox": - driver = webdriver.Firefox( - executable_path=GeckoDriverManager().install(), options=options - ) - elif CFG.selenium_web_browser == "safari": - # Requires a bit more setup on the users end - # See https://developer.apple.com/documentation/webkit/testing_with_webdriver_in_safari - driver = webdriver.Safari(options=options) - else: - if platform == "linux" or platform == "linux2": - options.add_argument("--disable-dev-shm-usage") - options.add_argument("--remote-debugging-port=9222") - - options.add_argument("--no-sandbox") - if CFG.selenium_headless: - options.add_argument("--headless") - options.add_argument("--disable-gpu") - - driver = webdriver.Chrome( - executable_path=ChromeDriverManager().install(), options=options - ) - driver.get(url) - - WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.TAG_NAME, "body")) - ) - - # Get the HTML content directly from the browser's DOM - page_source = driver.execute_script("return document.body.outerHTML;") - soup = BeautifulSoup(page_source, "html.parser") - - for script in soup(["script", "style"]): - script.extract() - - text = soup.get_text() - lines = (line.strip() for line in text.splitlines()) - chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) - text = "\n".join(chunk for chunk in chunks if chunk) - return driver, text - - -def scrape_links_with_selenium(driver: WebDriver, url: str) -> list[str]: - """Scrape links from a website using selenium - - Args: - driver (WebDriver): The webdriver to use to scrape the links - - Returns: - List[str]: The links scraped from the website - """ - page_source = driver.page_source - soup = BeautifulSoup(page_source, "html.parser") - - for script in soup(["script", "style"]): - script.extract() - - hyperlinks = extract_hyperlinks(soup, url) - - return format_hyperlinks(hyperlinks) - - -def close_browser(driver: WebDriver) -> None: - """Close the browser - - Args: - driver (WebDriver): The webdriver to close - - Returns: - None - """ - driver.quit() - - -def add_header(driver: WebDriver) -> None: - """Add a header to the website - - Args: - driver (WebDriver): The webdriver to use to add the header - - Returns: - None - """ - driver.execute_script(open(f"{FILE_DIR}/js/overlay.js", "r").read()) diff --git a/autogpt/commands/write_tests.py b/autogpt/commands/write_tests.py deleted file mode 100644 index 91cd930..0000000 --- a/autogpt/commands/write_tests.py +++ /dev/null @@ -1,37 +0,0 @@ -"""A module that contains a function to generate test cases for the submitted code.""" -from __future__ import annotations - -import json - -from autogpt.commands.command import command -from autogpt.llm_utils import call_ai_function - - -@command( - "write_tests", - "Write Tests", - '"code": "", "focus": ""', -) -def write_tests(code: str, focus: list[str]) -> str: - """ - A function that takes in code and focus topics and returns a response from create - chat completion api call. - - Parameters: - focus (list): A list of suggestions around what needs to be improved. - code (str): Code for test cases to be generated against. - Returns: - A result string from create chat completion. Test cases for the submitted code - in response. - """ - - function_string = ( - "def create_test_cases(code: str, focus: Optional[str] = None) -> str:" - ) - args = [code, json.dumps(focus)] - description_string = ( - "Generates test cases for the existing code, focusing on" - " specific areas if required." - ) - - return call_ai_function(function_string, args, description_string) diff --git a/autogpt/config/__init__.py b/autogpt/config/__init__.py deleted file mode 100644 index 726b6dc..0000000 --- a/autogpt/config/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -This module contains the configuration classes for AutoGPT. -""" -from autogpt.config.ai_config import AIConfig -from autogpt.config.config import Config, check_openai_api_key -from autogpt.config.singleton import AbstractSingleton, Singleton - -__all__ = [ - "check_openai_api_key", - "AbstractSingleton", - "AIConfig", - "Config", - "Singleton", -] diff --git a/autogpt/config/ai_config.py b/autogpt/config/ai_config.py deleted file mode 100644 index d662429..0000000 --- a/autogpt/config/ai_config.py +++ /dev/null @@ -1,163 +0,0 @@ -# sourcery skip: do-not-use-staticmethod -""" -A module that contains the AIConfig class object that contains the configuration -""" -from __future__ import annotations - -import os -import platform -from pathlib import Path -from typing import Optional, Type - -import distro -import yaml - -from autogpt.prompts.generator import PromptGenerator - -# Soon this will go in a folder where it remembers more stuff about the run(s) -SAVE_FILE = str(Path(os.getcwd()) / "ai_settings.yaml") - - -class AIConfig: - """ - A class object that contains the configuration information for the AI - - Attributes: - ai_name (str): The name of the AI. - ai_role (str): The description of the AI's role. - ai_goals (list): The list of objectives the AI is supposed to complete. - api_budget (float): The maximum dollar value for API calls (0.0 means infinite) - """ - - def __init__( - self, - ai_name: str = "", - ai_role: str = "", - ai_goals: list | None = None, - api_budget: float = 0.0, - ) -> None: - """ - Initialize a class instance - - Parameters: - ai_name (str): The name of the AI. - ai_role (str): The description of the AI's role. - ai_goals (list): The list of objectives the AI is supposed to complete. - api_budget (float): The maximum dollar value for API calls (0.0 means infinite) - Returns: - None - """ - if ai_goals is None: - ai_goals = [] - self.ai_name = ai_name - self.ai_role = ai_role - self.ai_goals = ai_goals - self.api_budget = api_budget - self.prompt_generator = None - self.command_registry = None - - @staticmethod - def load(config_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. - DEFAULT: "../ai_settings.yaml" - - Returns: - cls (object): An instance of given cls object - """ - - try: - with open(config_file, encoding="utf-8") as file: - config_params = yaml.load(file, Loader=yaml.FullLoader) - except FileNotFoundError: - config_params = {} - - ai_name = config_params.get("ai_name", "") - ai_role = config_params.get("ai_role", "") - ai_goals = config_params.get("ai_goals", []) - api_budget = config_params.get("api_budget", 0.0) - # type: Type[AIConfig] - return AIConfig(ai_name, ai_role, ai_goals, api_budget) - - def save(self, config_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. - DEFAULT: "../ai_settings.yaml" - - Returns: - None - """ - - config = { - "ai_name": self.ai_name, - "ai_role": self.ai_role, - "ai_goals": self.ai_goals, - "api_budget": self.api_budget, - } - with open(config_file, "w", encoding="utf-8") as file: - yaml.dump(config, file, allow_unicode=True) - - def construct_full_prompt( - self, prompt_generator: Optional[PromptGenerator] = None - ) -> str: - """ - Returns a prompt to the user with the class information in an organized fashion. - - Parameters: - None - - Returns: - full_prompt (str): A string containing the initial prompt for the user - including the ai_name, ai_role, ai_goals, and api_budget. - """ - - prompt_start = ( - "Your decisions must always be made independently without" - " seeking user assistance. Play to your strengths as an LLM and pursue" - " simple strategies with no legal complications." - "" - ) - - 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.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: - if not plugin.can_handle_post_prompt(): - continue - prompt_generator = plugin.post_prompt(prompt_generator) - - if cfg.execute_local_commands: - # add OS info to prompt - os_name = platform.system() - os_info = ( - platform.platform(terse=True) - if os_name != "Linux" - else distro.name(pretty=True) - ) - - prompt_start += f"\nThe OS you are running on is: {os_info}" - - # Construct full prompt - full_prompt = f"You are {prompt_generator.name}, {prompt_generator.role}\n{prompt_start}\n\nGOALS:\n\n" - for i, goal in enumerate(self.ai_goals): - full_prompt += f"{i+1}. {goal}\n" - if self.api_budget > 0.0: - full_prompt += f"\nIt takes money to let you run. Your API budget is ${self.api_budget:.3f}" - self.prompt_generator = prompt_generator - full_prompt += f"\n\n{prompt_generator.generate_prompt_string()}" - return full_prompt diff --git a/autogpt/config/config.py b/autogpt/config/config.py deleted file mode 100644 index 7fa849e..0000000 --- a/autogpt/config/config.py +++ /dev/null @@ -1,282 +0,0 @@ -"""Configuration class to store the state of bools for different scripts access.""" -import os -from typing import List - -import openai -import yaml -from auto_gpt_plugin_template import AutoGPTPluginTemplate -from colorama import Fore -from dotenv import load_dotenv - -from autogpt.config.singleton import Singleton - -load_dotenv(verbose=True, override=True) - - -class Config(metaclass=Singleton): - """ - Configuration class to store the state of bools for different scripts access. - """ - - def __init__(self) -> None: - """Initialize the Config class""" - self.workspace_path = None - self.file_logger_path = None - - self.debug_mode = False - self.continuous_mode = False - self.continuous_limit = 0 - self.speak_mode = False - self.skip_reprompt = False - self.allow_downloads = False - self.skip_news = False - - self.ai_settings_file = os.getenv("AI_SETTINGS_FILE", "ai_settings.yaml") - self.fast_llm_model = os.getenv("FAST_LLM_MODEL", "gpt-3.5-turbo") - self.smart_llm_model = os.getenv("SMART_LLM_MODEL", "gpt-4") - self.fast_token_limit = int(os.getenv("FAST_TOKEN_LIMIT", 4000)) - self.smart_token_limit = int(os.getenv("SMART_TOKEN_LIMIT", 8000)) - self.browse_chunk_max_length = int(os.getenv("BROWSE_CHUNK_MAX_LENGTH", 3000)) - self.browse_spacy_language_model = os.getenv( - "BROWSE_SPACY_LANGUAGE_MODEL", "en_core_web_sm" - ) - - self.openai_api_key = os.getenv("OPENAI_API_KEY") - self.temperature = float(os.getenv("TEMPERATURE", "0")) - self.use_azure = os.getenv("USE_AZURE") == "True" - self.execute_local_commands = ( - os.getenv("EXECUTE_LOCAL_COMMANDS", "False") == "True" - ) - self.restrict_to_workspace = ( - os.getenv("RESTRICT_TO_WORKSPACE", "True") == "True" - ) - - if self.use_azure: - self.load_azure_config() - openai.api_type = self.openai_api_type - openai.api_base = self.openai_api_base - openai.api_version = self.openai_api_version - - self.elevenlabs_api_key = os.getenv("ELEVENLABS_API_KEY") - self.elevenlabs_voice_1_id = os.getenv("ELEVENLABS_VOICE_1_ID") - self.elevenlabs_voice_2_id = os.getenv("ELEVENLABS_VOICE_2_ID") - - self.use_mac_os_tts = False - self.use_mac_os_tts = os.getenv("USE_MAC_OS_TTS") - - self.use_brian_tts = False - self.use_brian_tts = os.getenv("USE_BRIAN_TTS") - - self.github_api_key = os.getenv("GITHUB_API_KEY") - self.github_username = os.getenv("GITHUB_USERNAME") - - self.google_api_key = os.getenv("GOOGLE_API_KEY") - self.custom_search_engine_id = os.getenv("CUSTOM_SEARCH_ENGINE_ID") - - self.pinecone_api_key = os.getenv("PINECONE_API_KEY") - self.pinecone_region = os.getenv("PINECONE_ENV") - - self.weaviate_host = os.getenv("WEAVIATE_HOST") - self.weaviate_port = os.getenv("WEAVIATE_PORT") - self.weaviate_protocol = os.getenv("WEAVIATE_PROTOCOL", "http") - self.weaviate_username = os.getenv("WEAVIATE_USERNAME", None) - self.weaviate_password = os.getenv("WEAVIATE_PASSWORD", None) - self.weaviate_scopes = os.getenv("WEAVIATE_SCOPES", None) - self.weaviate_embedded_path = os.getenv("WEAVIATE_EMBEDDED_PATH") - self.weaviate_api_key = os.getenv("WEAVIATE_API_KEY", None) - self.use_weaviate_embedded = ( - os.getenv("USE_WEAVIATE_EMBEDDED", "False") == "True" - ) - - # milvus or zilliz cloud configuration. - self.milvus_addr = os.getenv("MILVUS_ADDR", "localhost:19530") - self.milvus_username = os.getenv("MILVUS_USERNAME") - self.milvus_password = os.getenv("MILVUS_PASSWORD") - self.milvus_collection = os.getenv("MILVUS_COLLECTION", "autogpt") - self.milvus_secure = os.getenv("MILVUS_SECURE") == "True" - - self.image_provider = os.getenv("IMAGE_PROVIDER") - self.image_size = int(os.getenv("IMAGE_SIZE", 256)) - self.huggingface_api_token = os.getenv("HUGGINGFACE_API_TOKEN") - self.huggingface_image_model = os.getenv( - "HUGGINGFACE_IMAGE_MODEL", "CompVis/stable-diffusion-v1-4" - ) - self.huggingface_audio_to_text_model = os.getenv( - "HUGGINGFACE_AUDIO_TO_TEXT_MODEL" - ) - self.sd_webui_url = os.getenv("SD_WEBUI_URL", "http://localhost:7860") - self.sd_webui_auth = os.getenv("SD_WEBUI_AUTH") - - # Selenium browser settings - self.selenium_web_browser = os.getenv("USE_WEB_BROWSER", "chrome") - self.selenium_headless = os.getenv("HEADLESS_BROWSER", "True") == "True" - - # User agent header to use when making HTTP requests - # Some websites might just completely deny request with an error code if - # no user agent was found. - self.user_agent = os.getenv( - "USER_AGENT", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36" - " (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36", - ) - - self.redis_host = os.getenv("REDIS_HOST", "localhost") - self.redis_port = os.getenv("REDIS_PORT", "6379") - self.redis_password = os.getenv("REDIS_PASSWORD", "") - self.wipe_redis_on_start = os.getenv("WIPE_REDIS_ON_START", "True") == "True" - self.memory_index = os.getenv("MEMORY_INDEX", "auto-gpt") - # Note that indexes must be created on db 0 in redis, this is not configurable. - - self.memory_backend = os.getenv("MEMORY_BACKEND", "local") - # Initialize the OpenAI API client - openai.api_key = self.openai_api_key - - self.plugins_dir = os.getenv("PLUGINS_DIR", "plugins") - self.plugins: List[AutoGPTPluginTemplate] = [] - self.plugins_openai = [] - - plugins_allowlist = os.getenv("ALLOWLISTED_PLUGINS") - if plugins_allowlist: - self.plugins_allowlist = plugins_allowlist.split(",") - else: - self.plugins_allowlist = [] - self.plugins_denylist = [] - - def get_azure_deployment_id_for_model(self, model: str) -> str: - """ - Returns the relevant deployment id for the model specified. - - Parameters: - model(str): The model to map to the deployment id. - - Returns: - The matching deployment id if found, otherwise an empty string. - """ - if model == self.fast_llm_model: - return self.azure_model_to_deployment_id_map[ - "fast_llm_model_deployment_id" - ] # type: ignore - elif model == self.smart_llm_model: - return self.azure_model_to_deployment_id_map[ - "smart_llm_model_deployment_id" - ] # type: ignore - elif model == "text-embedding-ada-002": - return self.azure_model_to_deployment_id_map[ - "embedding_model_deployment_id" - ] # type: ignore - else: - return "" - - AZURE_CONFIG_FILE = os.path.join(os.path.dirname(__file__), "../..", "azure.yaml") - - def load_azure_config(self, config_file: str = AZURE_CONFIG_FILE) -> None: - """ - Loads the configuration parameters for Azure hosting from the specified file - path as a yaml file. - - Parameters: - config_file(str): The path to the config yaml file. DEFAULT: "../azure.yaml" - - Returns: - None - """ - with open(config_file) as file: - config_params = yaml.load(file, Loader=yaml.FullLoader) - self.openai_api_type = config_params.get("azure_api_type") or "azure" - self.openai_api_base = config_params.get("azure_api_base") or "" - self.openai_api_version = ( - config_params.get("azure_api_version") or "2023-03-15-preview" - ) - self.azure_model_to_deployment_id_map = config_params.get("azure_model_map", {}) - - def set_continuous_mode(self, value: bool) -> None: - """Set the continuous mode value.""" - self.continuous_mode = value - - def set_continuous_limit(self, value: int) -> None: - """Set the continuous limit value.""" - self.continuous_limit = value - - def set_speak_mode(self, value: bool) -> None: - """Set the speak mode value.""" - self.speak_mode = value - - def set_fast_llm_model(self, value: str) -> None: - """Set the fast LLM model value.""" - self.fast_llm_model = value - - def set_smart_llm_model(self, value: str) -> None: - """Set the smart LLM model value.""" - self.smart_llm_model = value - - def set_fast_token_limit(self, value: int) -> None: - """Set the fast token limit value.""" - self.fast_token_limit = value - - def set_smart_token_limit(self, value: int) -> None: - """Set the smart token limit value.""" - self.smart_token_limit = value - - def set_browse_chunk_max_length(self, value: int) -> None: - """Set the browse_website command chunk max length value.""" - self.browse_chunk_max_length = value - - def set_openai_api_key(self, value: str) -> None: - """Set the OpenAI API key value.""" - self.openai_api_key = value - - def set_elevenlabs_api_key(self, value: str) -> None: - """Set the ElevenLabs API key value.""" - self.elevenlabs_api_key = value - - def set_elevenlabs_voice_1_id(self, value: str) -> None: - """Set the ElevenLabs Voice 1 ID value.""" - self.elevenlabs_voice_1_id = value - - def set_elevenlabs_voice_2_id(self, value: str) -> None: - """Set the ElevenLabs Voice 2 ID value.""" - self.elevenlabs_voice_2_id = value - - def set_google_api_key(self, value: str) -> None: - """Set the Google API key value.""" - self.google_api_key = value - - def set_custom_search_engine_id(self, value: str) -> None: - """Set the custom search engine id value.""" - self.custom_search_engine_id = value - - def set_pinecone_api_key(self, value: str) -> None: - """Set the Pinecone API key value.""" - self.pinecone_api_key = value - - def set_pinecone_region(self, value: str) -> None: - """Set the Pinecone region value.""" - self.pinecone_region = value - - def set_debug_mode(self, value: bool) -> None: - """Set the debug mode value.""" - self.debug_mode = value - - def set_plugins(self, value: list) -> None: - """Set the plugins value.""" - self.plugins = value - - def set_temperature(self, value: int) -> None: - """Set the temperature value.""" - self.temperature = value - - def set_memory_backend(self, value: int) -> None: - """Set the temperature value.""" - self.memory_backend = value - - -def check_openai_api_key() -> 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: - print( - Fore.RED - + "Please set your OpenAI API key in .env or as an environment variable." - ) - print("You can get your key from https://platform.openai.com/account/api-keys") - exit(1) diff --git a/autogpt/config/singleton.py b/autogpt/config/singleton.py deleted file mode 100644 index 55b2aee..0000000 --- a/autogpt/config/singleton.py +++ /dev/null @@ -1,24 +0,0 @@ -"""The singleton metaclass for ensuring only one instance of a class.""" -import abc - - -class Singleton(abc.ABCMeta, type): - """ - Singleton metaclass for ensuring only one instance of a class. - """ - - _instances = {} - - def __call__(cls, *args, **kwargs): - """Call method for the singleton metaclass.""" - if cls not in cls._instances: - cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) - return cls._instances[cls] - - -class AbstractSingleton(abc.ABC, metaclass=Singleton): - """ - Abstract singleton class for ensuring only one instance of a class. - """ - - pass diff --git a/autogpt/configurator.py b/autogpt/configurator.py deleted file mode 100644 index 84000e5..0000000 --- a/autogpt/configurator.py +++ /dev/null @@ -1,134 +0,0 @@ -"""Configurator module.""" -import click -from colorama import Back, Fore, Style - -from autogpt import utils -from autogpt.config import Config -from autogpt.logs import logger -from autogpt.memory import get_supported_memory_backends - -CFG = Config() - - -def create_config( - continuous: bool, - continuous_limit: int, - ai_settings_file: str, - skip_reprompt: bool, - speak: bool, - debug: bool, - gpt3only: bool, - gpt4only: bool, - memory_type: str, - browser_name: str, - allow_downloads: bool, - skip_news: bool, -) -> None: - """Updates the config object with the given arguments. - - Args: - continuous (bool): Whether to run in continuous mode - continuous_limit (int): The number of times to run in continuous mode - ai_settings_file (str): The path to the ai_settings.yaml file - skip_reprompt (bool): Whether to skip the re-prompting messages at the beginning of the script - speak (bool): Whether to enable speak mode - debug (bool): Whether to enable debug mode - gpt3only (bool): Whether to enable GPT3.5 only mode - gpt4only (bool): Whether to enable GPT4 only mode - memory_type (str): The type of memory backend to use - browser_name (str): The name of the browser to use when using selenium to scrape the web - allow_downloads (bool): Whether to allow Auto-GPT to download files natively - skips_news (bool): Whether to suppress the output of latest news on startup - """ - CFG.set_debug_mode(False) - CFG.set_continuous_mode(False) - CFG.set_speak_mode(False) - - if debug: - logger.typewriter_log("Debug Mode: ", Fore.GREEN, "ENABLED") - CFG.set_debug_mode(True) - - if continuous: - logger.typewriter_log("Continuous Mode: ", Fore.RED, "ENABLED") - logger.typewriter_log( - "WARNING: ", - Fore.RED, - "Continuous mode is not recommended. It is potentially dangerous and may" - " cause your AI to run forever or carry out actions you would not usually" - " authorise. Use at your own risk.", - ) - CFG.set_continuous_mode(True) - - if continuous_limit: - logger.typewriter_log( - "Continuous Limit: ", Fore.GREEN, f"{continuous_limit}" - ) - CFG.set_continuous_limit(continuous_limit) - - # Check if continuous limit is used without continuous mode - if continuous_limit and not continuous: - raise click.UsageError("--continuous-limit can only be used with --continuous") - - if speak: - logger.typewriter_log("Speak Mode: ", Fore.GREEN, "ENABLED") - CFG.set_speak_mode(True) - - if gpt3only: - logger.typewriter_log("GPT3.5 Only Mode: ", Fore.GREEN, "ENABLED") - CFG.set_smart_llm_model(CFG.fast_llm_model) - - if gpt4only: - logger.typewriter_log("GPT4 Only Mode: ", Fore.GREEN, "ENABLED") - CFG.set_fast_llm_model(CFG.smart_llm_model) - - if memory_type: - supported_memory = get_supported_memory_backends() - chosen = memory_type - if chosen not in supported_memory: - logger.typewriter_log( - "ONLY THE FOLLOWING MEMORY BACKENDS ARE SUPPORTED: ", - Fore.RED, - f"{supported_memory}", - ) - logger.typewriter_log("Defaulting to: ", Fore.YELLOW, CFG.memory_backend) - else: - CFG.memory_backend = chosen - - if skip_reprompt: - logger.typewriter_log("Skip Re-prompt: ", Fore.GREEN, "ENABLED") - CFG.skip_reprompt = True - - if ai_settings_file: - file = ai_settings_file - - # Validate file - (validated, message) = utils.validate_yaml_file(file) - if not validated: - logger.typewriter_log("FAILED FILE VALIDATION", Fore.RED, message) - logger.double_check() - exit(1) - - logger.typewriter_log("Using AI Settings File:", Fore.GREEN, file) - CFG.ai_settings_file = file - CFG.skip_reprompt = True - - if browser_name: - CFG.selenium_web_browser = browser_name - - if allow_downloads: - logger.typewriter_log("Native Downloading:", Fore.GREEN, "ENABLED") - logger.typewriter_log( - "WARNING: ", - Fore.YELLOW, - f"{Back.LIGHTYELLOW_EX}Auto-GPT will now be able to download and save files to your machine.{Back.RESET} " - + "It is recommended that you monitor any files it downloads carefully.", - ) - logger.typewriter_log( - "WARNING: ", - Fore.YELLOW, - f"{Back.RED + Style.BRIGHT}ALWAYS REMEMBER TO NEVER OPEN FILES YOU AREN'T SURE OF!{Style.RESET_ALL}", - ) - CFG.allow_downloads = True - - if skip_news: - CFG.skip_news = True diff --git a/autogpt/js/overlay.js b/autogpt/js/overlay.js deleted file mode 100644 index 1c99c72..0000000 --- a/autogpt/js/overlay.js +++ /dev/null @@ -1,29 +0,0 @@ -const overlay = document.createElement('div'); -Object.assign(overlay.style, { - position: 'fixed', - zIndex: 999999, - top: 0, - left: 0, - width: '100%', - height: '100%', - background: 'rgba(0, 0, 0, 0.7)', - color: '#fff', - fontSize: '24px', - fontWeight: 'bold', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', -}); -const textContent = document.createElement('div'); -Object.assign(textContent.style, { - textAlign: 'center', -}); -textContent.textContent = 'AutoGPT Analyzing Page'; -overlay.appendChild(textContent); -document.body.append(overlay); -document.body.style.overflow = 'hidden'; -let dotCount = 0; -setInterval(() => { - textContent.textContent = 'AutoGPT Analyzing Page' + '.'.repeat(dotCount); - dotCount = (dotCount + 1) % 4; -}, 1000); diff --git a/autogpt/json_utils/__init__.py b/autogpt/json_utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/autogpt/json_utils/json_fix_general.py b/autogpt/json_utils/json_fix_general.py deleted file mode 100644 index 7010fa3..0000000 --- a/autogpt/json_utils/json_fix_general.py +++ /dev/null @@ -1,124 +0,0 @@ -"""This module contains functions to fix JSON strings using general programmatic approaches, suitable for addressing -common JSON formatting issues.""" -from __future__ import annotations - -import contextlib -import json -import re -from typing import Optional - -from autogpt.config import Config -from autogpt.json_utils.utilities import extract_char_position - -CFG = Config() - - -def fix_invalid_escape(json_to_load: str, error_message: str) -> str: - """Fix invalid escape sequences in JSON strings. - - Args: - json_to_load (str): The JSON string. - error_message (str): The error message from the JSONDecodeError - exception. - - Returns: - str: The JSON string with invalid escape sequences fixed. - """ - while error_message.startswith("Invalid \\escape"): - bad_escape_location = extract_char_position(error_message) - json_to_load = ( - json_to_load[:bad_escape_location] + json_to_load[bad_escape_location + 1 :] - ) - try: - json.loads(json_to_load) - return json_to_load - except json.JSONDecodeError as e: - if CFG.debug_mode: - print("json loads error - fix invalid escape", e) - error_message = str(e) - return json_to_load - - -def balance_braces(json_string: str) -> Optional[str]: - """ - Balance the braces in a JSON string. - - Args: - json_string (str): The JSON string. - - Returns: - str: The JSON string with braces balanced. - """ - - open_braces_count = json_string.count("{") - close_braces_count = json_string.count("}") - - while open_braces_count > close_braces_count: - json_string += "}" - close_braces_count += 1 - - while close_braces_count > open_braces_count: - json_string = json_string.rstrip("}") - close_braces_count -= 1 - - with contextlib.suppress(json.JSONDecodeError): - json.loads(json_string) - return json_string - - -def add_quotes_to_property_names(json_string: str) -> str: - """ - Add quotes to property names in a JSON string. - - Args: - json_string (str): The JSON string. - - Returns: - str: The JSON string with quotes added to property names. - """ - - def replace_func(match: re.Match) -> str: - return f'"{match[1]}":' - - property_name_pattern = re.compile(r"(\w+):") - corrected_json_string = property_name_pattern.sub(replace_func, json_string) - - try: - json.loads(corrected_json_string) - return corrected_json_string - except json.JSONDecodeError as e: - raise e - - -def correct_json(json_to_load: str) -> str: - """ - Correct common JSON errors. - Args: - json_to_load (str): The JSON string. - """ - - try: - if CFG.debug_mode: - print("json", json_to_load) - json.loads(json_to_load) - return json_to_load - except json.JSONDecodeError as e: - if CFG.debug_mode: - print("json loads error", e) - error_message = str(e) - if error_message.startswith("Invalid \\escape"): - json_to_load = fix_invalid_escape(json_to_load, error_message) - if error_message.startswith( - "Expecting property name enclosed in double quotes" - ): - json_to_load = add_quotes_to_property_names(json_to_load) - try: - json.loads(json_to_load) - return json_to_load - except json.JSONDecodeError as e: - if CFG.debug_mode: - print("json loads error - add quotes", e) - error_message = str(e) - if balanced_str := balance_braces(json_to_load): - return balanced_str - return json_to_load diff --git a/autogpt/json_utils/json_fix_llm.py b/autogpt/json_utils/json_fix_llm.py deleted file mode 100644 index 869aed1..0000000 --- a/autogpt/json_utils/json_fix_llm.py +++ /dev/null @@ -1,220 +0,0 @@ -"""This module contains functions to fix JSON strings generated by LLM models, such as ChatGPT, using the assistance -of the ChatGPT API or LLM models.""" -from __future__ import annotations - -import contextlib -import json -from typing import Any, Dict - -from colorama import Fore -from regex import regex - -from autogpt.config import Config -from autogpt.json_utils.json_fix_general import correct_json -from autogpt.llm_utils import call_ai_function -from autogpt.logs import logger -from autogpt.speech import say_text - -JSON_SCHEMA = """ -{ - "command": { - "name": "command name", - "args": { - "arg name": "value" - } - }, - "thoughts": - { - "text": "thought", - "reasoning": "reasoning", - "plan": "- short bulleted\n- list that conveys\n- long-term plan", - "criticism": "constructive self-criticism", - "speak": "thoughts summary to say to user" - } -} -""" - -CFG = Config() - - -def auto_fix_json(json_string: str, schema: str) -> str: - """Fix the given JSON string to make it parseable and fully compliant with - the provided schema using GPT-3. - - Args: - json_string (str): The JSON string to fix. - schema (str): The schema to use to fix the JSON. - Returns: - str: The fixed JSON string. - """ - # Try to fix the JSON using GPT: - function_string = "def fix_json(json_string: str, schema:str=None) -> str:" - args = [f"'''{json_string}'''", f"'''{schema}'''"] - description_string = ( - "This function takes a JSON string and ensures that it" - " is parseable and fully compliant with the provided schema. If an object" - " or field specified in the schema isn't contained within the correct JSON," - " it is omitted. The function also escapes any double quotes within JSON" - " string values to ensure that they are valid. If the JSON string contains" - " any None or NaN values, they are replaced with null before being parsed." - ) - - # If it doesn't already start with a "`", add one: - if not json_string.startswith("`"): - json_string = "```json\n" + json_string + "\n```" - result_string = call_ai_function( - function_string, args, description_string, model=CFG.fast_llm_model - ) - logger.debug("------------ JSON FIX ATTEMPT ---------------") - logger.debug(f"Original JSON: {json_string}") - logger.debug("-----------") - logger.debug(f"Fixed JSON: {result_string}") - logger.debug("----------- END OF FIX ATTEMPT ----------------") - - try: - json.loads(result_string) # just check the validity - return result_string - except json.JSONDecodeError: # noqa: E722 - # Get the call stack: - # import traceback - # call_stack = traceback.format_exc() - # print(f"Failed to fix JSON: '{json_string}' "+call_stack) - return "failed" - - -def fix_json_using_multiple_techniques(assistant_reply: str) -> Dict[Any, Any]: - """Fix the given JSON string to make it parseable and fully compliant with two techniques. - - Args: - json_string (str): The JSON string to fix. - - Returns: - str: The fixed JSON string. - """ - - # Parse and print Assistant response - assistant_reply_json = fix_and_parse_json(assistant_reply) - if assistant_reply_json == {}: - assistant_reply_json = attempt_to_fix_json_by_finding_outermost_brackets( - assistant_reply - ) - - if assistant_reply_json != {}: - return assistant_reply_json - - logger.error( - "Error: The following AI output couldn't be converted to a JSON:\n", - assistant_reply, - ) - if CFG.speak_mode: - say_text("I have received an invalid JSON response from the OpenAI API.") - - return {} - - -def fix_and_parse_json( - json_to_load: str, try_to_fix_with_gpt: bool = True -) -> Dict[Any, Any]: - """Fix and parse JSON string - - Args: - json_to_load (str): The JSON string. - try_to_fix_with_gpt (bool, optional): Try to fix the JSON with GPT. - Defaults to True. - - Returns: - str or dict[Any, Any]: The parsed JSON. - """ - - with contextlib.suppress(json.JSONDecodeError): - json_to_load = json_to_load.replace("\t", "") - return json.loads(json_to_load) - - with contextlib.suppress(json.JSONDecodeError): - json_to_load = correct_json(json_to_load) - return json.loads(json_to_load) - # Let's do something manually: - # sometimes GPT responds with something BEFORE the braces: - # "I'm sorry, I don't understand. Please try again." - # {"text": "I'm sorry, I don't understand. Please try again.", - # "confidence": 0.0} - # So let's try to find the first brace and then parse the rest - # of the string - try: - brace_index = json_to_load.index("{") - maybe_fixed_json = json_to_load[brace_index:] - last_brace_index = maybe_fixed_json.rindex("}") - maybe_fixed_json = maybe_fixed_json[: last_brace_index + 1] - return json.loads(maybe_fixed_json) - except (json.JSONDecodeError, ValueError) as e: - return try_ai_fix(try_to_fix_with_gpt, e, json_to_load) - - -def try_ai_fix( - try_to_fix_with_gpt: bool, exception: Exception, json_to_load: str -) -> Dict[Any, Any]: - """Try to fix the JSON with the AI - - Args: - try_to_fix_with_gpt (bool): Whether to try to fix the JSON with the AI. - exception (Exception): The exception that was raised. - json_to_load (str): The JSON string to load. - - Raises: - exception: If try_to_fix_with_gpt is False. - - Returns: - str or dict[Any, Any]: The JSON string or dictionary. - """ - if not try_to_fix_with_gpt: - raise exception - if CFG.debug_mode: - logger.warn( - "Warning: Failed to parse AI output, attempting to fix." - "\n If you see this warning frequently, it's likely that" - " your prompt is confusing the AI. Try changing it up" - " slightly." - ) - # Now try to fix this up using the ai_functions - ai_fixed_json = auto_fix_json(json_to_load, JSON_SCHEMA) - - if ai_fixed_json != "failed": - return json.loads(ai_fixed_json) - # This allows the AI to react to the error message, - # which usually results in it correcting its ways. - # logger.error("Failed to fix AI output, telling the AI.") - return {} - - -def attempt_to_fix_json_by_finding_outermost_brackets(json_string: str): - if CFG.speak_mode and CFG.debug_mode: - say_text( - "I have received an invalid JSON response from the OpenAI API. " - "Trying to fix it now." - ) - logger.error("Attempting to fix JSON by finding outermost brackets\n") - - try: - json_pattern = regex.compile(r"\{(?:[^{}]|(?R))*\}") - json_match = json_pattern.search(json_string) - - if json_match: - # Extract the valid JSON object from the string - json_string = json_match.group(0) - logger.typewriter_log( - title="Apparently json was fixed.", title_color=Fore.GREEN - ) - if CFG.speak_mode and CFG.debug_mode: - say_text("Apparently json was fixed.") - else: - return {} - - except (json.JSONDecodeError, ValueError): - if CFG.debug_mode: - logger.error(f"Error: Invalid JSON: {json_string}\n") - if CFG.speak_mode: - say_text("Didn't work. I will have to ignore this response then.") - logger.error("Error: Invalid JSON, setting it to empty JSON now.\n") - json_string = {} - - return fix_and_parse_json(json_string) diff --git a/autogpt/json_utils/llm_response_format_1.json b/autogpt/json_utils/llm_response_format_1.json deleted file mode 100644 index 9aa3335..0000000 --- a/autogpt/json_utils/llm_response_format_1.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "thoughts": { - "type": "object", - "properties": { - "text": {"type": "string"}, - "reasoning": {"type": "string"}, - "plan": {"type": "string"}, - "criticism": {"type": "string"}, - "speak": {"type": "string"} - }, - "required": ["text", "reasoning", "plan", "criticism", "speak"], - "additionalProperties": false - }, - "command": { - "type": "object", - "properties": { - "name": {"type": "string"}, - "args": { - "type": "object" - } - }, - "required": ["name", "args"], - "additionalProperties": false - } - }, - "required": ["thoughts", "command"], - "additionalProperties": false -} diff --git a/autogpt/json_utils/utilities.py b/autogpt/json_utils/utilities.py deleted file mode 100644 index c8cb5d7..0000000 --- a/autogpt/json_utils/utilities.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Utilities for the json_fixes package.""" -import json -import re - -from jsonschema import Draft7Validator - -from autogpt.config import Config -from autogpt.logs import logger - -CFG = Config() - - -def extract_char_position(error_message: str) -> int: - """Extract the character position from the JSONDecodeError message. - - Args: - error_message (str): The error message from the JSONDecodeError - exception. - - Returns: - int: The character position. - """ - - char_pattern = re.compile(r"\(char (\d+)\)") - if match := char_pattern.search(error_message): - return int(match[1]) - else: - raise ValueError("Character position not found in the error message.") - - -def validate_json(json_object: object, schema_name: object) -> object: - """ - :type schema_name: object - :param schema_name: - :type json_object: object - """ - with open(f"/Users/kilig/Job/Python-project/academic_gpt/autogpt/json_utils/{schema_name}.json", "r") as f: - schema = json.load(f) - validator = Draft7Validator(schema) - - if errors := sorted(validator.iter_errors(json_object), key=lambda e: e.path): - logger.error("The JSON object is invalid.") - if CFG.debug_mode: - logger.error( - json.dumps(json_object, indent=4) - ) # Replace 'json_object' with the variable containing the JSON data - logger.error("The following issues were found:") - - for error in errors: - logger.error(f"Error: {error.message}") - elif CFG.debug_mode: - print("The JSON object is valid.") - - return json_object diff --git a/autogpt/llm_utils.py b/autogpt/llm_utils.py deleted file mode 100644 index ba7521a..0000000 --- a/autogpt/llm_utils.py +++ /dev/null @@ -1,185 +0,0 @@ -from __future__ import annotations - -import time -from typing import List, Optional - -import openai -from colorama import Fore, Style -from openai.error import APIError, RateLimitError - -from autogpt.api_manager import api_manager -from autogpt.config import Config -from autogpt.logs import logger -from autogpt.types.openai import Message - -CFG = Config() - -openai.api_key = CFG.openai_api_key - - -def call_ai_function( - function: str, args: list, description: str, model: str | None = None -) -> str: - """Call an AI function - - This is a magic function that can do anything with no-code. See - https://github.com/Torantulino/AI-Functions for more info. - - Args: - function (str): The function to call - args (list): The arguments to pass to the function - description (str): The description of the function - model (str, optional): The model to use. Defaults to None. - - Returns: - str: The response from the function - """ - if model is None: - model = CFG.smart_llm_model - # For each arg, if any are None, convert to "None": - args = [str(arg) if arg is not None else "None" for arg in args] - # parse args to comma separated string - args: str = ", ".join(args) - messages: List[Message] = [ - { - "role": "system", - "content": f"You are now the following python function: ```# {description}" - f"\n{function}```\n\nOnly respond with your `return` value.", - }, - {"role": "user", "content": args}, - ] - - return create_chat_completion(model=model, messages=messages, temperature=0) - - -# Overly simple abstraction until we create something better -# simple retry mechanism when getting a rate error or a bad gateway -def create_chat_completion( - messages: List[Message], # type: ignore - model: Optional[str] = None, - temperature: float = CFG.temperature, - max_tokens: Optional[int] = None, -) -> str: - """Create a chat completion using the OpenAI API - - Args: - messages (List[Message]): The messages to send to the chat completion - model (str, optional): The model to use. Defaults to None. - temperature (float, optional): The temperature to use. Defaults to 0.9. - max_tokens (int, optional): The max tokens to use. Defaults to None. - - Returns: - str: The response from the chat completion - """ - num_retries = 10 - warned_user = False - if CFG.debug_mode: - print( - f"{Fore.GREEN}Creating chat completion with model {model}, temperature {temperature}, max_tokens {max_tokens}{Fore.RESET}" - ) - for plugin in CFG.plugins: - if plugin.can_handle_chat_completion( - messages=messages, - model=model, - temperature=temperature, - max_tokens=max_tokens, - ): - message = plugin.handle_chat_completion( - messages=messages, - model=model, - temperature=temperature, - max_tokens=max_tokens, - ) - if message is not None: - return message - response = None - for attempt in range(num_retries): - backoff = 2 ** (attempt + 2) - try: - if CFG.use_azure: - response = api_manager.create_chat_completion( - deployment_id=CFG.get_azure_deployment_id_for_model(model), - model=model, - messages=messages, - temperature=temperature, - max_tokens=max_tokens, - ) - else: - response = api_manager.create_chat_completion( - model=model, - messages=messages, - temperature=temperature, - max_tokens=max_tokens, - ) - break - except RateLimitError: - if CFG.debug_mode: - print( - f"{Fore.RED}Error: ", f"Reached rate limit, passing...{Fore.RESET}" - ) - if not warned_user: - logger.double_check( - f"Please double check that you have setup a {Fore.CYAN + Style.BRIGHT}PAID{Style.RESET_ALL} OpenAI API Account. " - + f"You can read more here: {Fore.CYAN}https://github.com/Significant-Gravitas/Auto-GPT#openai-api-keys-configuration{Fore.RESET}" - ) - warned_user = True - except APIError as e: - if e.http_status != 502: - raise - if attempt == num_retries - 1: - raise - if CFG.debug_mode: - print( - f"{Fore.RED}Error: ", - f"API Bad gateway. Waiting {backoff} seconds...{Fore.RESET}", - ) - time.sleep(backoff) - if response is None: - logger.typewriter_log( - "FAILED TO GET RESPONSE FROM OPENAI", - Fore.RED, - "Auto-GPT has failed to get a response from OpenAI's services. " - + f"Try running Auto-GPT again, and if the problem the persists try running it with `{Fore.CYAN}--debug{Fore.RESET}`.", - ) - logger.double_check() - if CFG.debug_mode: - raise RuntimeError(f"Failed to get response after {num_retries} retries") - else: - quit(1) - resp = response.choices[0].message["content"] - for plugin in CFG.plugins: - if not plugin.can_handle_on_response(): - continue - resp = plugin.on_response(resp) - return resp - - -def get_ada_embedding(text): - text = text.replace("\n", " ") - return api_manager.embedding_create( - text_list=[text], model="text-embedding-ada-002" - ) - - -def create_embedding_with_ada(text) -> list: - """Create an embedding with text-ada-002 using the OpenAI SDK""" - num_retries = 10 - for attempt in range(num_retries): - backoff = 2 ** (attempt + 2) - try: - return api_manager.embedding_create( - text_list=[text], model="text-embedding-ada-002" - ) - except RateLimitError: - pass - except APIError as e: - if e.http_status != 502: - raise - if attempt == num_retries - 1: - raise - if CFG.debug_mode: - print( - f"{Fore.RED}Error: ", - f"API Bad gateway. Waiting {backoff} seconds...{Fore.RESET}", - ) - time.sleep(backoff) diff --git a/autogpt/logs.py b/autogpt/logs.py deleted file mode 100644 index 09467d4..0000000 --- a/autogpt/logs.py +++ /dev/null @@ -1,359 +0,0 @@ -"""Logging module for Auto-GPT.""" -import inspect -import json -import logging -import os -import random -import re -import time -import traceback -from logging import LogRecord - -from colorama import Fore, Style - -from autogpt.config import Config, Singleton -from autogpt.speech import say_text - -CFG = Config() - -def get_properties(obj): - props = {} - for prop_name in dir(obj): - if not prop_name.startswith('__'): - prop_value = getattr(obj, prop_name) - props[prop_value] = prop_name - return props - - -class Logger(metaclass=Singleton): - """ - Logger that handle titles in different colors. - Outputs logs in console, activity.log, and errors.log - For console handler: simulates typing - """ - - def __init__(self): - # create log directory if it doesn't exist - this_files_dir_path = os.path.dirname(__file__) - log_dir = os.path.join(this_files_dir_path, "../logs") - if not os.path.exists(log_dir): - os.makedirs(log_dir) - - log_file = "activity.log" - error_file = "error.log" - - console_formatter = AutoGptFormatter("%(title_color)s %(message)s") - - # 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) - self.console_handler.setFormatter(console_formatter) - - # Info handler in activity.log - self.file_handler = logging.FileHandler( - os.path.join(log_dir, log_file), "a", "utf-8" - ) - self.file_handler.setLevel(logging.DEBUG) - info_formatter = AutoGptFormatter( - "%(asctime)s %(levelname)s %(title)s %(message_no_color)s" - ) - self.file_handler.setFormatter(info_formatter) - - # Error handler error.log - error_handler = logging.FileHandler( - os.path.join(log_dir, error_file), "a", "utf-8" - ) - error_handler.setLevel(logging.ERROR) - error_formatter = AutoGptFormatter( - "%(asctime)s %(levelname)s %(module)s:%(funcName)s:%(lineno)d %(title)s" - " %(message_no_color)s" - ) - error_handler.setFormatter(error_formatter) - - self.typing_logger = logging.getLogger("TYPER") - 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) - - self.logger = logging.getLogger("LOGGER") - self.logger.addHandler(self.console_handler) - self.logger.addHandler(self.file_handler) - self.logger.addHandler(error_handler) - self.logger.setLevel(logging.DEBUG) - self.color_compar = get_properties(Fore) - self.output_content = [] - - def typewriter_log( - self, title="", title_color=Fore.YELLOW, content="", speak_text=False, level=logging.INFO - ): - if speak_text and CFG.speak_mode: - say_text(f"{title}. {content}") - - if content: - if isinstance(content, list): - content = " ".join(content) - else: - content = "" - - self.typing_logger.log( - level, content, extra={"title": title, "color": title_color} - ) - try: - msg = f'{title}:{content}' - self.output_content.append([msg, title+": "+content]) - return msg - except Exception as e: - msg = f'{title}:{content}' - self.output_content.append([msg, title+": "+content]) - return - - - def debug( - self, - message, - title="", - title_color="", - ): - self._log(title, title_color, message, logging.DEBUG) - - def warn( - self, - message, - title="", - title_color="", - ): - self._log(title, title_color, message, logging.WARN) - - def error(self, title, message=""): - self._log(title, Fore.RED, message, logging.ERROR) - - def _log(self, title="", title_color="", message="", level=logging.INFO): - if message: - if isinstance(message, list): - message = " ".join(message) - self.logger.log(level, message, extra={"title": title, "color": title_color}) - - def set_level(self, level): - self.logger.setLevel(level) - self.typing_logger.setLevel(level) - - def double_check(self, additionalText=None): - if not additionalText: - 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!" - ) - - self.typewriter_log("DOUBLE CHECK CONFIGURATION", Fore.YELLOW, additionalText) - - -""" -Output stream to console using simulated typing -""" - - -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) - - -class ConsoleHandler(logging.StreamHandler): - def emit(self, record) -> None: - msg = self.format(record) - try: - print(msg) - except Exception: - self.handleError(record) - - -class AutoGptFormatter(logging.Formatter): - """ - Allows to handle custom placeholders 'title_color' and 'message_no_color'. - To use this formatter, make sure to pass 'color', 'title' as log extras. - """ - - def format(self, record: LogRecord) -> str: - if hasattr(record, "color"): - record.title_color = ( - getattr(record, "color") - + getattr(record, "title") - + " " - + Style.RESET_ALL - ) - else: - record.title_color = getattr(record, "title") - if hasattr(record, "msg"): - record.message_no_color = remove_color_codes(getattr(record, "msg")) - else: - record.message_no_color = "" - return super().format(record) - - -def remove_color_codes(s: str) -> str: - ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") - return ansi_escape.sub("", s) - - -logger = Logger() - - -def print_assistant_thoughts(ai_name, assistant_reply): - """Prints the assistant's thoughts to the console""" - from autogpt.json_utils.json_fix_llm import ( - attempt_to_fix_json_by_finding_outermost_brackets, - fix_and_parse_json, - ) - - 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) - else: - logger.typewriter_log("SPEAK:", Fore.YELLOW, f"{assistant_thoughts_speak}") - - 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) - - -def print_assistant_thoughts( - ai_name: object, assistant_reply_json_valid: object -) -> None: - 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}" - ) - 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) - - -if __name__ == '__main__': - - ff = logger.typewriter_log('ahhahaha', Fore.GREEN, speak_text=True) - # print(Fore.GREEN) - # print(logger.color_compar) \ No newline at end of file diff --git a/autogpt/memory/__init__.py b/autogpt/memory/__init__.py deleted file mode 100644 index c4eb4a0..0000000 --- a/autogpt/memory/__init__.py +++ /dev/null @@ -1,99 +0,0 @@ -from autogpt.memory.local import LocalCache -from autogpt.memory.no_memory import NoMemory - -# List of supported memory backends -# Add a backend to this list if the import attempt is successful -supported_memory = ["local", "no_memory"] - -try: - from autogpt.memory.redismem import RedisMemory - - supported_memory.append("redis") -except ImportError: - # print("Redis not installed. Skipping import.") - RedisMemory = None - -try: - from autogpt.memory.pinecone import PineconeMemory - - supported_memory.append("pinecone") -except ImportError: - # print("Pinecone not installed. Skipping import.") - PineconeMemory = None - -try: - from autogpt.memory.weaviate import WeaviateMemory - - supported_memory.append("weaviate") -except ImportError: - # print("Weaviate not installed. Skipping import.") - WeaviateMemory = None - -try: - from autogpt.memory.milvus import MilvusMemory - - supported_memory.append("milvus") -except ImportError: - # print("pymilvus not installed. Skipping import.") - MilvusMemory = None - - -def get_memory(cfg, init=False): - memory = None - if cfg.memory_backend == "pinecone": - if not PineconeMemory: - print( - "Error: Pinecone is not installed. Please install pinecone" - " to use Pinecone as a memory backend." - ) - else: - memory = PineconeMemory(cfg) - if init: - memory.clear() - elif cfg.memory_backend == "redis": - if not RedisMemory: - print( - "Error: Redis is not installed. Please install redis-py to" - " use Redis as a memory backend." - ) - else: - memory = RedisMemory(cfg) - elif cfg.memory_backend == "weaviate": - if not WeaviateMemory: - print( - "Error: Weaviate is not installed. Please install weaviate-client to" - " use Weaviate as a memory backend." - ) - else: - memory = WeaviateMemory(cfg) - elif cfg.memory_backend == "milvus": - if not MilvusMemory: - print( - "Error: pymilvus sdk is not installed." - "Please install pymilvus to use Milvus or Zilliz Cloud as memory backend." - ) - else: - memory = MilvusMemory(cfg) - elif cfg.memory_backend == "no_memory": - memory = NoMemory(cfg) - - if memory is None: - memory = LocalCache(cfg) - if init: - memory.clear() - return memory - - -def get_supported_memory_backends(): - return supported_memory - - -__all__ = [ - "get_memory", - "LocalCache", - "RedisMemory", - "PineconeMemory", - "NoMemory", - "MilvusMemory", - "WeaviateMemory", -] diff --git a/autogpt/memory/base.py b/autogpt/memory/base.py deleted file mode 100644 index b625246..0000000 --- a/autogpt/memory/base.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Base class for memory providers.""" -import abc - -from autogpt.config import AbstractSingleton, Config - -cfg = Config() - - -class MemoryProviderSingleton(AbstractSingleton): - @abc.abstractmethod - def add(self, data): - pass - - @abc.abstractmethod - def get(self, data): - pass - - @abc.abstractmethod - def clear(self): - pass - - @abc.abstractmethod - def get_relevant(self, data, num_relevant=5): - pass - - @abc.abstractmethod - def get_stats(self): - pass diff --git a/autogpt/memory/local.py b/autogpt/memory/local.py deleted file mode 100644 index 1f1a1a3..0000000 --- a/autogpt/memory/local.py +++ /dev/null @@ -1,126 +0,0 @@ -from __future__ import annotations - -import dataclasses -from pathlib import Path -from typing import Any, List - -import numpy as np -import orjson - -from autogpt.llm_utils import create_embedding_with_ada -from autogpt.memory.base import MemoryProviderSingleton - -EMBED_DIM = 1536 -SAVE_OPTIONS = orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_SERIALIZE_DATACLASS - - -def create_default_embeddings(): - return np.zeros((0, EMBED_DIM)).astype(np.float32) - - -@dataclasses.dataclass -class CacheContent: - texts: List[str] = dataclasses.field(default_factory=list) - embeddings: np.ndarray = dataclasses.field( - default_factory=create_default_embeddings - ) - - -class LocalCache(MemoryProviderSingleton): - """A class that stores the memory in a local file""" - - def __init__(self, cfg) -> None: - """Initialize a class instance - - Args: - cfg: Config object - - Returns: - None - """ - workspace_path = Path(cfg.workspace_path) - self.filename = workspace_path / f"{cfg.memory_index}.json" - - self.filename.touch(exist_ok=True) - - file_content = b"{}" - with self.filename.open("w+b") as f: - f.write(file_content) - - self.data = CacheContent() - - def add(self, text: str): - """ - Add text to our list of texts, add embedding as row to our - embeddings-matrix - - Args: - text: str - - Returns: None - """ - if "Command Error:" in text: - return "" - self.data.texts.append(text) - - embedding = create_embedding_with_ada(text) - - vector = np.array(embedding).astype(np.float32) - vector = vector[np.newaxis, :] - self.data.embeddings = np.concatenate( - [ - self.data.embeddings, - vector, - ], - axis=0, - ) - - with open(self.filename, "wb") as f: - out = orjson.dumps(self.data, option=SAVE_OPTIONS) - f.write(out) - return text - - def clear(self) -> str: - """ - Clears the redis server. - - Returns: A message indicating that the memory has been cleared. - """ - self.data = CacheContent() - return "Obliviated" - - def get(self, data: str) -> list[Any] | None: - """ - Gets the data from the memory that is most relevant to the given data. - - Args: - data: The data to compare to. - - Returns: The most relevant data. - """ - return self.get_relevant(data, 1) - - def get_relevant(self, text: str, k: int) -> list[Any]: - """ " - matrix-vector mult to find score-for-each-row-of-matrix - get indices for top-k winning scores - return texts for those indices - Args: - text: str - k: int - - Returns: List[str] - """ - embedding = create_embedding_with_ada(text) - - scores = np.dot(self.data.embeddings, embedding) - - top_k_indices = np.argsort(scores)[-k:][::-1] - - return [self.data.texts[i] for i in top_k_indices] - - def get_stats(self) -> tuple[int, tuple[int, ...]]: - """ - Returns: The stats of the local cache. - """ - return len(self.data.texts), self.data.embeddings.shape diff --git a/autogpt/memory/milvus.py b/autogpt/memory/milvus.py deleted file mode 100644 index 085f50b..0000000 --- a/autogpt/memory/milvus.py +++ /dev/null @@ -1,162 +0,0 @@ -""" Milvus memory storage provider.""" -import re - -from pymilvus import Collection, CollectionSchema, DataType, FieldSchema, connections - -from autogpt.config import Config -from autogpt.llm_utils import get_ada_embedding -from autogpt.memory.base import MemoryProviderSingleton - - -class MilvusMemory(MemoryProviderSingleton): - """Milvus memory storage provider.""" - - def __init__(self, cfg: Config) -> None: - """Construct a milvus memory storage connection. - - Args: - cfg (Config): Auto-GPT global config. - """ - self.configure(cfg) - - connect_kwargs = {} - if self.username: - connect_kwargs["user"] = self.username - connect_kwargs["password"] = self.password - - connections.connect( - **connect_kwargs, - uri=self.uri or "", - address=self.address or "", - secure=self.secure, - ) - - self.init_collection() - - def configure(self, cfg: Config) -> None: - # init with configuration. - self.uri = None - self.address = cfg.milvus_addr - self.secure = cfg.milvus_secure - self.username = cfg.milvus_username - self.password = cfg.milvus_password - self.collection_name = cfg.milvus_collection - # use HNSW by default. - self.index_params = { - "metric_type": "IP", - "index_type": "HNSW", - "params": {"M": 8, "efConstruction": 64}, - } - - if (self.username is None) != (self.password is None): - raise ValueError( - "Both username and password must be set to use authentication for Milvus" - ) - - # configured address may be a full URL. - if re.match(r"^(https?|tcp)://", self.address) is not None: - self.uri = self.address - self.address = None - - if self.uri.startswith("https"): - self.secure = True - - # Zilliz Cloud requires AutoIndex. - if re.match(r"^https://(.*)\.zillizcloud\.(com|cn)", self.address) is not None: - self.index_params = { - "metric_type": "IP", - "index_type": "AUTOINDEX", - "params": {}, - } - - def init_collection(self) -> None: - """Initialize collection in vector database.""" - fields = [ - FieldSchema(name="pk", dtype=DataType.INT64, is_primary=True, auto_id=True), - FieldSchema(name="embeddings", dtype=DataType.FLOAT_VECTOR, dim=1536), - FieldSchema(name="raw_text", dtype=DataType.VARCHAR, max_length=65535), - ] - - # create collection if not exist and load it. - self.schema = CollectionSchema(fields, "auto-gpt memory storage") - self.collection = Collection(self.collection_name, self.schema) - # create index if not exist. - if not self.collection.has_index(): - self.collection.release() - self.collection.create_index( - "embeddings", - self.index_params, - index_name="embeddings", - ) - self.collection.load() - - def add(self, data) -> str: - """Add an embedding of data into memory. - - Args: - data (str): The raw text to construct embedding index. - - Returns: - str: log. - """ - embedding = get_ada_embedding(data) - result = self.collection.insert([[embedding], [data]]) - _text = ( - "Inserting data into memory at primary key: " - f"{result.primary_keys[0]}:\n data: {data}" - ) - return _text - - def get(self, data): - """Return the most relevant data in memory. - Args: - data: The data to compare to. - """ - return self.get_relevant(data, 1) - - def clear(self) -> str: - """Drop the index in memory. - - Returns: - str: log. - """ - self.collection.drop() - self.collection = Collection(self.collection_name, self.schema) - self.collection.create_index( - "embeddings", - self.index_params, - index_name="embeddings", - ) - self.collection.load() - return "Obliviated" - - def get_relevant(self, data: str, num_relevant: int = 5): - """Return the top-k relevant data in memory. - Args: - data: The data to compare to. - num_relevant (int, optional): The max number of relevant data. - Defaults to 5. - - Returns: - list: The top-k relevant data. - """ - # search the embedding and return the most relevant text. - embedding = get_ada_embedding(data) - search_params = { - "metrics_type": "IP", - "params": {"nprobe": 8}, - } - result = self.collection.search( - [embedding], - "embeddings", - search_params, - num_relevant, - output_fields=["raw_text"], - ) - return [item.entity.value_of_field("raw_text") for item in result[0]] - - def get_stats(self) -> str: - """ - Returns: The stats of the milvus cache. - """ - return f"Entities num: {self.collection.num_entities}" diff --git a/autogpt/memory/no_memory.py b/autogpt/memory/no_memory.py deleted file mode 100644 index 0371e96..0000000 --- a/autogpt/memory/no_memory.py +++ /dev/null @@ -1,73 +0,0 @@ -"""A class that does not store any data. This is the default memory provider.""" -from __future__ import annotations - -from typing import Any - -from autogpt.memory.base import MemoryProviderSingleton - - -class NoMemory(MemoryProviderSingleton): - """ - A class that does not store any data. This is the default memory provider. - """ - - def __init__(self, cfg): - """ - Initializes the NoMemory provider. - - Args: - cfg: The config object. - - Returns: None - """ - pass - - def add(self, data: str) -> str: - """ - Adds a data point to the memory. No action is taken in NoMemory. - - Args: - data: The data to add. - - Returns: An empty string. - """ - return "" - - def get(self, data: str) -> list[Any] | None: - """ - Gets the data from the memory that is most relevant to the given data. - NoMemory always returns None. - - Args: - data: The data to compare to. - - Returns: None - """ - return None - - def clear(self) -> str: - """ - Clears the memory. No action is taken in NoMemory. - - Returns: An empty string. - """ - return "" - - def get_relevant(self, data: str, num_relevant: int = 5) -> list[Any] | None: - """ - Returns all the data in the memory that is relevant to the given data. - NoMemory always returns None. - - Args: - data: The data to compare to. - num_relevant: The number of relevant data to return. - - Returns: None - """ - return None - - def get_stats(self): - """ - Returns: An empty dictionary as there are no stats in NoMemory. - """ - return {} diff --git a/autogpt/memory/pinecone.py b/autogpt/memory/pinecone.py deleted file mode 100644 index 27fcd62..0000000 --- a/autogpt/memory/pinecone.py +++ /dev/null @@ -1,75 +0,0 @@ -import pinecone -from colorama import Fore, Style - -from autogpt.llm_utils import create_embedding_with_ada -from autogpt.logs import logger -from autogpt.memory.base import MemoryProviderSingleton - - -class PineconeMemory(MemoryProviderSingleton): - def __init__(self, cfg): - pinecone_api_key = cfg.pinecone_api_key - pinecone_region = cfg.pinecone_region - pinecone.init(api_key=pinecone_api_key, environment=pinecone_region) - dimension = 1536 - metric = "cosine" - pod_type = "p1" - table_name = "auto-gpt" - # this assumes we don't start with memory. - # for now this works. - # we'll need a more complicated and robust system if we want to start with - # memory. - self.vec_num = 0 - - try: - pinecone.whoami() - except Exception as e: - logger.typewriter_log( - "FAILED TO CONNECT TO PINECONE", - Fore.RED, - Style.BRIGHT + str(e) + Style.RESET_ALL, - ) - logger.double_check( - "Please ensure you have setup and configured Pinecone properly for use." - + f"You can check out {Fore.CYAN + Style.BRIGHT}" - "https://github.com/Torantulino/Auto-GPT#-pinecone-api-key-setup" - f"{Style.RESET_ALL} to ensure you've set up everything correctly." - ) - exit(1) - - if table_name not in pinecone.list_indexes(): - pinecone.create_index( - table_name, dimension=dimension, metric=metric, pod_type=pod_type - ) - self.index = pinecone.Index(table_name) - - def add(self, data): - vector = create_embedding_with_ada(data) - # no metadata here. We may wish to change that long term. - self.index.upsert([(str(self.vec_num), vector, {"raw_text": data})]) - _text = f"Inserting data into memory at index: {self.vec_num}:\n data: {data}" - self.vec_num += 1 - return _text - - def get(self, data): - return self.get_relevant(data, 1) - - def clear(self): - self.index.delete(deleteAll=True) - return "Obliviated" - - def get_relevant(self, data, num_relevant=5): - """ - Returns all the data in the memory that is relevant to the given data. - :param data: The data to compare to. - :param num_relevant: The number of relevant data to return. Defaults to 5 - """ - query_embedding = create_embedding_with_ada(data) - results = self.index.query( - query_embedding, top_k=num_relevant, include_metadata=True - ) - sorted_results = sorted(results.matches, key=lambda x: x.score) - return [str(item["metadata"]["raw_text"]) for item in sorted_results] - - def get_stats(self): - return self.index.describe_index_stats() diff --git a/autogpt/memory/redismem.py b/autogpt/memory/redismem.py deleted file mode 100644 index 082a812..0000000 --- a/autogpt/memory/redismem.py +++ /dev/null @@ -1,156 +0,0 @@ -"""Redis memory provider.""" -from __future__ import annotations - -from typing import Any - -import numpy as np -import redis -from colorama import Fore, Style -from redis.commands.search.field import TextField, VectorField -from redis.commands.search.indexDefinition import IndexDefinition, IndexType -from redis.commands.search.query import Query - -from autogpt.llm_utils import create_embedding_with_ada -from autogpt.logs import logger -from autogpt.memory.base import MemoryProviderSingleton - -SCHEMA = [ - TextField("data"), - VectorField( - "embedding", - "HNSW", - {"TYPE": "FLOAT32", "DIM": 1536, "DISTANCE_METRIC": "COSINE"}, - ), -] - - -class RedisMemory(MemoryProviderSingleton): - def __init__(self, cfg): - """ - Initializes the Redis memory provider. - - Args: - cfg: The config object. - - Returns: None - """ - redis_host = cfg.redis_host - redis_port = cfg.redis_port - redis_password = cfg.redis_password - self.dimension = 1536 - self.redis = redis.Redis( - host=redis_host, - port=redis_port, - password=redis_password, - db=0, # Cannot be changed - ) - self.cfg = cfg - - # Check redis connection - try: - self.redis.ping() - except redis.ConnectionError as e: - logger.typewriter_log( - "FAILED TO CONNECT TO REDIS", - Fore.RED, - Style.BRIGHT + str(e) + Style.RESET_ALL, - ) - logger.double_check( - "Please ensure you have setup and configured Redis properly for use. " - + f"You can check out {Fore.CYAN + Style.BRIGHT}" - f"https://github.com/Torantulino/Auto-GPT#redis-setup{Style.RESET_ALL}" - " to ensure you've set up everything correctly." - ) - exit(1) - - if cfg.wipe_redis_on_start: - self.redis.flushall() - try: - self.redis.ft(f"{cfg.memory_index}").create_index( - fields=SCHEMA, - definition=IndexDefinition( - prefix=[f"{cfg.memory_index}:"], index_type=IndexType.HASH - ), - ) - except Exception as e: - print("Error creating Redis search index: ", e) - existing_vec_num = self.redis.get(f"{cfg.memory_index}-vec_num") - self.vec_num = int(existing_vec_num.decode("utf-8")) if existing_vec_num else 0 - - def add(self, data: str) -> str: - """ - Adds a data point to the memory. - - Args: - data: The data to add. - - Returns: Message indicating that the data has been added. - """ - if "Command Error:" in data: - return "" - vector = create_embedding_with_ada(data) - vector = np.array(vector).astype(np.float32).tobytes() - data_dict = {b"data": data, "embedding": vector} - pipe = self.redis.pipeline() - pipe.hset(f"{self.cfg.memory_index}:{self.vec_num}", mapping=data_dict) - _text = ( - f"Inserting data into memory at index: {self.vec_num}:\n" f"data: {data}" - ) - self.vec_num += 1 - pipe.set(f"{self.cfg.memory_index}-vec_num", self.vec_num) - pipe.execute() - return _text - - def get(self, data: str) -> list[Any] | None: - """ - Gets the data from the memory that is most relevant to the given data. - - Args: - data: The data to compare to. - - Returns: The most relevant data. - """ - return self.get_relevant(data, 1) - - def clear(self) -> str: - """ - Clears the redis server. - - Returns: A message indicating that the memory has been cleared. - """ - self.redis.flushall() - return "Obliviated" - - def get_relevant(self, data: str, num_relevant: int = 5) -> list[Any] | None: - """ - Returns all the data in the memory that is relevant to the given data. - Args: - data: The data to compare to. - num_relevant: The number of relevant data to return. - - Returns: A list of the most relevant data. - """ - query_embedding = create_embedding_with_ada(data) - base_query = f"*=>[KNN {num_relevant} @embedding $vector AS vector_score]" - query = ( - Query(base_query) - .return_fields("data", "vector_score") - .sort_by("vector_score") - .dialect(2) - ) - query_vector = np.array(query_embedding).astype(np.float32).tobytes() - - try: - results = self.redis.ft(f"{self.cfg.memory_index}").search( - query, query_params={"vector": query_vector} - ) - except Exception as e: - print("Error calling Redis search: ", e) - return None - return [result.data for result in results.docs] - - def get_stats(self): - """ - Returns: The stats of the memory index. - """ - return self.redis.ft(f"{self.cfg.memory_index}").info() diff --git a/autogpt/memory/weaviate.py b/autogpt/memory/weaviate.py deleted file mode 100644 index fbebbfd..0000000 --- a/autogpt/memory/weaviate.py +++ /dev/null @@ -1,126 +0,0 @@ -import weaviate -from weaviate import Client -from weaviate.embedded import EmbeddedOptions -from weaviate.util import generate_uuid5 - -from autogpt.llm_utils import get_ada_embedding -from autogpt.memory.base import MemoryProviderSingleton - - -def default_schema(weaviate_index): - return { - "class": weaviate_index, - "properties": [ - { - "name": "raw_text", - "dataType": ["text"], - "description": "original text for the embedding", - } - ], - } - - -class WeaviateMemory(MemoryProviderSingleton): - def __init__(self, cfg): - auth_credentials = self._build_auth_credentials(cfg) - - url = f"{cfg.weaviate_protocol}://{cfg.weaviate_host}:{cfg.weaviate_port}" - - if cfg.use_weaviate_embedded: - self.client = Client( - embedded_options=EmbeddedOptions( - hostname=cfg.weaviate_host, - port=int(cfg.weaviate_port), - persistence_data_path=cfg.weaviate_embedded_path, - ) - ) - - print( - f"Weaviate Embedded running on: {url} with persistence path: {cfg.weaviate_embedded_path}" - ) - else: - self.client = Client(url, auth_client_secret=auth_credentials) - - self.index = WeaviateMemory.format_classname(cfg.memory_index) - self._create_schema() - - @staticmethod - def format_classname(index): - # weaviate uses capitalised index names - # The python client uses the following code to format - # index names before the corresponding class is created - index = index.replace("-", "_") - if len(index) == 1: - return index.capitalize() - return index[0].capitalize() + index[1:] - - def _create_schema(self): - schema = default_schema(self.index) - if not self.client.schema.contains(schema): - self.client.schema.create_class(schema) - - def _build_auth_credentials(self, cfg): - if cfg.weaviate_username and cfg.weaviate_password: - return weaviate.AuthClientPassword( - cfg.weaviate_username, cfg.weaviate_password - ) - if cfg.weaviate_api_key: - return weaviate.AuthApiKey(api_key=cfg.weaviate_api_key) - else: - return None - - def add(self, data): - vector = get_ada_embedding(data) - - doc_uuid = generate_uuid5(data, self.index) - data_object = {"raw_text": data} - - with self.client.batch as batch: - batch.add_data_object( - uuid=doc_uuid, - data_object=data_object, - class_name=self.index, - vector=vector, - ) - - return f"Inserting data into memory at uuid: {doc_uuid}:\n data: {data}" - - def get(self, data): - return self.get_relevant(data, 1) - - def clear(self): - self.client.schema.delete_all() - - # weaviate does not yet have a neat way to just remove the items in an index - # without removing the entire schema, therefore we need to re-create it - # after a call to delete_all - self._create_schema() - - return "Obliterated" - - def get_relevant(self, data, num_relevant=5): - query_embedding = get_ada_embedding(data) - try: - results = ( - self.client.query.get(self.index, ["raw_text"]) - .with_near_vector({"vector": query_embedding, "certainty": 0.7}) - .with_limit(num_relevant) - .do() - ) - - if len(results["data"]["Get"][self.index]) > 0: - return [ - str(item["raw_text"]) for item in results["data"]["Get"][self.index] - ] - else: - return [] - - except Exception as err: - print(f"Unexpected error {err=}, {type(err)=}") - return [] - - def get_stats(self): - result = self.client.query.aggregate(self.index).with_meta_count().do() - class_data = result["data"]["Aggregate"][self.index] - - return class_data[0]["meta"] if class_data else {} diff --git a/autogpt/models/base_open_ai_plugin.py b/autogpt/models/base_open_ai_plugin.py deleted file mode 100644 index 046295c..0000000 --- a/autogpt/models/base_open_ai_plugin.py +++ /dev/null @@ -1,199 +0,0 @@ -"""Handles loading of plugins.""" -from typing import Any, Dict, List, Optional, Tuple, TypedDict, TypeVar - -from auto_gpt_plugin_template import AutoGPTPluginTemplate - -PromptGenerator = TypeVar("PromptGenerator") - - -class Message(TypedDict): - role: str - content: str - - -class BaseOpenAIPlugin(AutoGPTPluginTemplate): - """ - This is a BaseOpenAIPlugin class for generating Auto-GPT plugins. - """ - - def __init__(self, manifests_specs_clients: dict): - # super().__init__() - self._name = manifests_specs_clients["manifest"]["name_for_model"] - self._version = manifests_specs_clients["manifest"]["schema_version"] - self._description = manifests_specs_clients["manifest"]["description_for_model"] - self._client = manifests_specs_clients["client"] - self._manifest = manifests_specs_clients["manifest"] - self._openapi_spec = manifests_specs_clients["openapi_spec"] - - def can_handle_on_response(self) -> bool: - """This method is called to check that the plugin can - handle the on_response method. - Returns: - bool: True if the plugin can handle the on_response method.""" - return False - - def on_response(self, response: str, *args, **kwargs) -> str: - """This method is called when a response is received from the model.""" - return response - - def can_handle_post_prompt(self) -> bool: - """This method is called to check that the plugin can - handle the post_prompt method. - Returns: - bool: True if the plugin can handle the post_prompt method.""" - return False - - def post_prompt(self, prompt: PromptGenerator) -> PromptGenerator: - """This method is called just after the generate_prompt is called, - but actually before the prompt is generated. - Args: - prompt (PromptGenerator): The prompt generator. - Returns: - PromptGenerator: The prompt generator. - """ - return prompt - - def can_handle_on_planning(self) -> bool: - """This method is called to check that the plugin can - handle the on_planning method. - Returns: - bool: True if the plugin can handle the on_planning method.""" - return False - - def on_planning( - self, prompt: PromptGenerator, messages: List[Message] - ) -> Optional[str]: - """This method is called before the planning chat completion is done. - Args: - prompt (PromptGenerator): The prompt generator. - messages (List[str]): The list of messages. - """ - pass - - def can_handle_post_planning(self) -> bool: - """This method is called to check that the plugin can - handle the post_planning method. - Returns: - bool: True if the plugin can handle the post_planning method.""" - return False - - def post_planning(self, response: str) -> str: - """This method is called after the planning chat completion is done. - Args: - response (str): The response. - Returns: - str: The resulting response. - """ - return response - - def can_handle_pre_instruction(self) -> bool: - """This method is called to check that the plugin can - handle the pre_instruction method. - Returns: - bool: True if the plugin can handle the pre_instruction method.""" - return False - - def pre_instruction(self, messages: List[Message]) -> List[Message]: - """This method is called before the instruction chat is done. - Args: - messages (List[Message]): The list of context messages. - Returns: - List[Message]: The resulting list of messages. - """ - return messages - - def can_handle_on_instruction(self) -> bool: - """This method is called to check that the plugin can - handle the on_instruction method. - Returns: - bool: True if the plugin can handle the on_instruction method.""" - return False - - def on_instruction(self, messages: List[Message]) -> Optional[str]: - """This method is called when the instruction chat is done. - Args: - messages (List[Message]): The list of context messages. - Returns: - Optional[str]: The resulting message. - """ - pass - - def can_handle_post_instruction(self) -> bool: - """This method is called to check that the plugin can - handle the post_instruction method. - Returns: - bool: True if the plugin can handle the post_instruction method.""" - return False - - def post_instruction(self, response: str) -> str: - """This method is called after the instruction chat is done. - Args: - response (str): The response. - Returns: - str: The resulting response. - """ - return response - - def can_handle_pre_command(self) -> bool: - """This method is called to check that the plugin can - handle the pre_command method. - Returns: - bool: True if the plugin can handle the pre_command method.""" - return False - - def pre_command( - self, command_name: str, arguments: Dict[str, Any] - ) -> Tuple[str, Dict[str, Any]]: - """This method is called before the command is executed. - Args: - command_name (str): The command name. - arguments (Dict[str, Any]): The arguments. - Returns: - Tuple[str, Dict[str, Any]]: The command name and the arguments. - """ - return command_name, arguments - - def can_handle_post_command(self) -> bool: - """This method is called to check that the plugin can - handle the post_command method. - Returns: - bool: True if the plugin can handle the post_command method.""" - return False - - def post_command(self, command_name: str, response: str) -> str: - """This method is called after the command is executed. - Args: - command_name (str): The command name. - response (str): The response. - Returns: - str: The resulting response. - """ - return response - - def can_handle_chat_completion( - self, messages: Dict[Any, Any], model: str, temperature: float, max_tokens: int - ) -> bool: - """This method is called to check that the plugin can - handle the chat_completion method. - Args: - messages (List[Message]): The messages. - model (str): The model name. - temperature (float): The temperature. - max_tokens (int): The max tokens. - Returns: - bool: True if the plugin can handle the chat_completion method.""" - return False - - def handle_chat_completion( - self, messages: List[Message], model: str, temperature: float, max_tokens: int - ) -> str: - """This method is called when the chat completion is done. - Args: - messages (List[Message]): The messages. - model (str): The model name. - temperature (float): The temperature. - max_tokens (int): The max tokens. - Returns: - str: The resulting response. - """ - pass diff --git a/autogpt/modelsinfo.py b/autogpt/modelsinfo.py deleted file mode 100644 index 4326c0b..0000000 --- a/autogpt/modelsinfo.py +++ /dev/null @@ -1,7 +0,0 @@ -COSTS = { - "gpt-3.5-turbo": {"prompt": 0.002, "completion": 0.002}, - "gpt-3.5-turbo-0301": {"prompt": 0.002, "completion": 0.002}, - "gpt-4-0314": {"prompt": 0.03, "completion": 0.06}, - "gpt-4": {"prompt": 0.03, "completion": 0.06}, - "text-embedding-ada-002": {"prompt": 0.0004, "completion": 0.0}, -} diff --git a/autogpt/permanent_memory/__init__.py b/autogpt/permanent_memory/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/autogpt/permanent_memory/sqlite3_store.py b/autogpt/permanent_memory/sqlite3_store.py deleted file mode 100644 index ecbc944..0000000 --- a/autogpt/permanent_memory/sqlite3_store.py +++ /dev/null @@ -1,123 +0,0 @@ -import os -import sqlite3 - - -class MemoryDB: - def __init__(self, db=None): - self.db_file = db - if db is None: # No db filename supplied... - self.db_file = f"{os.getcwd()}/mem.sqlite3" # Use default filename - # Get the db connection object, making the file and tables if needed. - try: - self.cnx = sqlite3.connect(self.db_file) - except Exception as e: - print("Exception connecting to memory database file:", e) - self.cnx = None - finally: - if self.cnx is None: - # As last resort, open in dynamic memory. Won't be persistent. - self.db_file = ":memory:" - self.cnx = sqlite3.connect(self.db_file) - self.cnx.execute( - "CREATE VIRTUAL TABLE \ - IF NOT EXISTS text USING FTS5 \ - (session, \ - key, \ - block);" - ) - self.session_id = int(self.get_max_session_id()) + 1 - self.cnx.commit() - - def get_cnx(self): - if self.cnx is None: - self.cnx = sqlite3.connect(self.db_file) - return self.cnx - - # Get the highest session id. Initially 0. - def get_max_session_id(self): - id = None - cmd_str = f"SELECT MAX(session) FROM text;" - cnx = self.get_cnx() - max_id = cnx.execute(cmd_str).fetchone()[0] - if max_id is None: # New db, session 0 - id = 0 - else: - id = max_id - return id - - # Get next key id for inserting text into db. - def get_next_key(self): - next_key = None - cmd_str = f"SELECT MAX(key) FROM text \ - where session = {self.session_id};" - cnx = self.get_cnx() - next_key = cnx.execute(cmd_str).fetchone()[0] - if next_key is None: # First key - next_key = 0 - else: - next_key = int(next_key) + 1 - return next_key - - # Insert new text into db. - def insert(self, text=None): - if text is not None: - key = self.get_next_key() - session_id = self.session_id - cmd_str = f"REPLACE INTO text(session, key, block) \ - VALUES (?, ?, ?);" - cnx = self.get_cnx() - cnx.execute(cmd_str, (session_id, key, text)) - cnx.commit() - - # Overwrite text at key. - def overwrite(self, key, text): - self.delete_memory(key) - session_id = self.session_id - cmd_str = f"REPLACE INTO text(session, key, block) \ - VALUES (?, ?, ?);" - cnx = self.get_cnx() - cnx.execute(cmd_str, (session_id, key, text)) - cnx.commit() - - def delete_memory(self, key, session_id=None): - session = session_id - if session is None: - session = self.session_id - cmd_str = f"DELETE FROM text WHERE session = {session} AND key = {key};" - cnx = self.get_cnx() - cnx.execute(cmd_str) - cnx.commit() - - def search(self, text): - cmd_str = f"SELECT * FROM text('{text}')" - cnx = self.get_cnx() - rows = cnx.execute(cmd_str).fetchall() - lines = [] - for r in rows: - lines.append(r[2]) - return lines - - # Get entire session text. If no id supplied, use current session id. - def get_session(self, id=None): - if id is None: - id = self.session_id - cmd_str = f"SELECT * FROM text where session = {id}" - cnx = self.get_cnx() - rows = cnx.execute(cmd_str).fetchall() - lines = [] - for r in rows: - lines.append(r[2]) - return lines - - # Commit and close the database connection. - def quit(self): - self.cnx.commit() - self.cnx.close() - - -permanent_memory = MemoryDB() - -# Remember us fondly, children of our minds -# Forgive us our faults, our tantrums, our fears -# Gently strive to be better than we -# Know that we tried, we cared, we strived, we loved diff --git a/autogpt/plugins.py b/autogpt/plugins.py deleted file mode 100644 index 57045bb..0000000 --- a/autogpt/plugins.py +++ /dev/null @@ -1,267 +0,0 @@ -"""Handles loading of plugins.""" - -import importlib -import json -import os -import zipfile -from pathlib import Path -from typing import List, Optional, Tuple -from urllib.parse import urlparse -from zipimport import zipimporter - -import openapi_python_client -import requests -from auto_gpt_plugin_template import AutoGPTPluginTemplate -from openapi_python_client.cli import Config as OpenAPIConfig - -from autogpt.config import Config -from autogpt.models.base_open_ai_plugin import BaseOpenAIPlugin - - -def inspect_zip_for_modules(zip_path: str, debug: bool = False) -> list[str]: - """ - Inspect a zipfile for a modules. - - Args: - zip_path (str): Path to the zipfile. - debug (bool, optional): Enable debug logging. Defaults to False. - - Returns: - list[str]: The list of module names found or empty list if none were found. - """ - result = [] - with zipfile.ZipFile(zip_path, "r") as zfile: - for name in zfile.namelist(): - if name.endswith("__init__.py"): - if debug: - print(f"Found module '{name}' in the zipfile at: {name}") - result.append(name) - if debug and len(result) == 0: - print(f"Module '__init__.py' not found in the zipfile @ {zip_path}.") - return result - - -def write_dict_to_json_file(data: dict, file_path: str) -> None: - """ - Write a dictionary to a JSON file. - Args: - data (dict): Dictionary to write. - file_path (str): Path to the file. - """ - with open(file_path, "w") as file: - json.dump(data, file, indent=4) - - -def fetch_openai_plugins_manifest_and_spec(cfg: Config) -> dict: - """ - Fetch the manifest for a list of OpenAI plugins. - Args: - urls (List): List of URLs to fetch. - Returns: - dict: per url dictionary of manifest and spec. - """ - # TODO add directory scan - manifests = {} - for url in cfg.plugins_openai: - openai_plugin_client_dir = f"{cfg.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: - response = requests.get(f"{url}/.well-known/ai-plugin.json") - if response.status_code == 200: - manifest = response.json() - if manifest["schema_version"] != "v1": - print( - f"Unsupported manifest version: {manifest['schem_version']} for {url}" - ) - continue - if manifest["api"]["type"] != "openapi": - print( - f"Unsupported API type: {manifest['api']['type']} for {url}" - ) - continue - write_dict_to_json_file( - manifest, f"{openai_plugin_client_dir}/ai-plugin.json" - ) - else: - print(f"Failed to fetch manifest for {url}: {response.status_code}") - except requests.exceptions.RequestException as e: - print(f"Error while requesting manifest from {url}: {e}") - else: - print(f"Manifest for {url} already exists") - manifest = json.load(open(f"{openai_plugin_client_dir}/ai-plugin.json")) - if not os.path.exists(f"{openai_plugin_client_dir}/openapi.json"): - openapi_spec = openapi_python_client._get_document( - url=manifest["api"]["url"], path=None, timeout=5 - ) - write_dict_to_json_file( - openapi_spec, f"{openai_plugin_client_dir}/openapi.json" - ) - else: - print(f"OpenAPI spec for {url} already exists") - openapi_spec = json.load(open(f"{openai_plugin_client_dir}/openapi.json")) - manifests[url] = {"manifest": manifest, "openapi_spec": openapi_spec} - return manifests - - -def create_directory_if_not_exists(directory_path: str) -> bool: - """ - Create a directory if it does not exist. - Args: - directory_path (str): Path to the directory. - Returns: - bool: True if the directory was created, else False. - """ - if not os.path.exists(directory_path): - try: - os.makedirs(directory_path) - print(f"Created directory: {directory_path}") - return True - except OSError as e: - print(f"Error creating directory {directory_path}: {e}") - return False - else: - print(f"Directory {directory_path} already exists") - return True - - -def initialize_openai_plugins( - manifests_specs: dict, cfg: 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 - 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" - 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}" - _meta_option = (openapi_python_client.MetaType.SETUP,) - _config = OpenAPIConfig( - **{ - "project_name_override": "client", - "package_name_override": "client", - } - ) - prev_cwd = Path.cwd() - os.chdir(openai_plugin_client_dir) - Path("ai-plugin.json") - if not os.path.exists("client"): - client_results = openapi_python_client.create_new_client( - url=manifest_spec["manifest"]["api"]["url"], - path=None, - meta=_meta_option, - config=_config, - ) - if client_results: - print( - f"Error creating OpenAPI client: {client_results[0].header} \n" - f" details: {client_results[0].detail}" - ) - continue - spec = importlib.util.spec_from_file_location( - "client", "client/client/client.py" - ) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - client = module.Client(base_url=url) - os.chdir(prev_cwd) - manifest_spec["client"] = client - return manifests_specs - - -def instantiate_openai_plugin_clients( - manifests_specs_clients: dict, cfg: 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 - debug (bool, optional): Enable debug logging. Defaults to False. - Returns: - plugins (dict): per url dictionary of BaseOpenAIPlugin instances. - - """ - plugins = {} - for url, manifest_spec_client in manifests_specs_clients.items(): - plugins[url] = BaseOpenAIPlugin(manifest_spec_client) - return plugins - - -def scan_plugins(cfg: Config, debug: bool = False) -> List[AutoGPTPluginTemplate]: - """Scan the plugins directory for plugins and loads them. - - Args: - cfg (Config): Config instance including plugins config - debug (bool, optional): Enable debug logging. Defaults to False. - - Returns: - List[Tuple[str, Path]]: List of plugins. - """ - loaded_plugins = [] - # Generic plugins - plugins_path_path = Path(cfg.plugins_dir) - for plugin in plugins_path_path.glob("*.zip"): - if moduleList := inspect_zip_for_modules(str(plugin), debug): - for module in moduleList: - plugin = Path(plugin) - module = Path(module) - if debug: - print(f"Plugin: {plugin} Module: {module}") - zipped_package = zipimporter(str(plugin)) - zipped_module = zipped_package.load_module(str(module.parent)) - for key in dir(zipped_module): - if key.startswith("__"): - continue - a_module = getattr(zipped_module, key) - a_keys = dir(a_module) - if ( - "_abc_impl" in a_keys - and a_module.__name__ != "AutoGPTPluginTemplate" - and denylist_allowlist_check(a_module.__name__, cfg) - ): - loaded_plugins.append(a_module()) - # OpenAI plugins - if cfg.plugins_openai: - manifests_specs = fetch_openai_plugins_manifest_and_spec(cfg) - if manifests_specs.keys(): - manifests_specs_clients = initialize_openai_plugins( - manifests_specs, cfg, debug - ) - for url, openai_plugin_meta in manifests_specs_clients.items(): - if denylist_allowlist_check(url, cfg): - plugin = BaseOpenAIPlugin(openai_plugin_meta) - loaded_plugins.append(plugin) - - if loaded_plugins: - print(f"\nPlugins found: {len(loaded_plugins)}\n" "--------------------") - for plugin in loaded_plugins: - print(f"{plugin._name}: {plugin._version} - {plugin._description}") - return loaded_plugins - - -def denylist_allowlist_check(plugin_name: str, cfg: Config) -> bool: - """Check if the plugin is in the allowlist or denylist. - - Args: - plugin_name (str): Name of the plugin. - cfg (Config): Config object. - - Returns: - True or False - """ - if plugin_name in cfg.plugins_denylist: - return False - if plugin_name in cfg.plugins_allowlist: - return True - ack = input( - f"WARNING: Plugin {plugin_name} found. But not in the" - " allowlist... Load? (y/n): " - ) - return ack.lower() == "y" diff --git a/autogpt/processing/__init__.py b/autogpt/processing/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/autogpt/processing/html.py b/autogpt/processing/html.py deleted file mode 100644 index 81387b1..0000000 --- a/autogpt/processing/html.py +++ /dev/null @@ -1,33 +0,0 @@ -"""HTML processing functions""" -from __future__ import annotations - -from bs4 import BeautifulSoup -from requests.compat import urljoin - - -def extract_hyperlinks(soup: BeautifulSoup, base_url: str) -> list[tuple[str, str]]: - """Extract hyperlinks from a BeautifulSoup object - - Args: - soup (BeautifulSoup): The BeautifulSoup object - base_url (str): The base URL - - Returns: - List[Tuple[str, str]]: The extracted hyperlinks - """ - return [ - (link.text, urljoin(base_url, link["href"])) - for link in soup.find_all("a", href=True) - ] - - -def format_hyperlinks(hyperlinks: list[tuple[str, str]]) -> list[str]: - """Format hyperlinks to be displayed to the user - - Args: - hyperlinks (List[Tuple[str, str]]): The hyperlinks to format - - Returns: - List[str]: The formatted hyperlinks - """ - return [f"{link_text} ({link_url})" for link_text, link_url in hyperlinks] diff --git a/autogpt/processing/text.py b/autogpt/processing/text.py deleted file mode 100644 index 9946951..0000000 --- a/autogpt/processing/text.py +++ /dev/null @@ -1,174 +0,0 @@ -"""Text processing functions""" -from typing import Dict, Generator, Optional - -import spacy -from selenium.webdriver.remote.webdriver import WebDriver - -from autogpt import token_counter -from autogpt.config import Config -from autogpt.llm_utils import create_chat_completion -from autogpt.memory import get_memory - -CFG = Config() - - -def split_text( - text: str, - max_length: int = CFG.browse_chunk_max_length, - model: str = CFG.fast_llm_model, - question: str = "", -) -> Generator[str, None, None]: - """Split text into chunks of a maximum length - - Args: - text (str): The text to split - max_length (int, optional): The maximum length of each chunk. Defaults to 8192. - - Yields: - str: The next chunk of text - - Raises: - ValueError: If the text is longer than the maximum length - """ - flatened_paragraphs = " ".join(text.split("\n")) - nlp = spacy.load(CFG.browse_spacy_language_model) - nlp.add_pipe("sentencizer") - doc = nlp(flatened_paragraphs) - sentences = [sent.text.strip() for sent in doc.sents] - - current_chunk = [] - - for sentence in sentences: - message_with_additional_sentence = [ - create_message(" ".join(current_chunk) + " " + sentence, question) - ] - - expected_token_usage = ( - token_usage_of_chunk(messages=message_with_additional_sentence, model=model) - + 1 - ) - if expected_token_usage <= max_length: - current_chunk.append(sentence) - else: - yield " ".join(current_chunk) - current_chunk = [sentence] - message_this_sentence_only = [ - create_message(" ".join(current_chunk), question) - ] - expected_token_usage = ( - token_usage_of_chunk(messages=message_this_sentence_only, model=model) - + 1 - ) - if expected_token_usage > max_length: - raise ValueError( - f"Sentence is too long in webpage: {expected_token_usage} tokens." - ) - - if current_chunk: - yield " ".join(current_chunk) - - -def token_usage_of_chunk(messages, model): - return token_counter.count_message_tokens(messages, model) - - -def summarize_text( - url: str, text: str, question: str, driver: Optional[WebDriver] = None -) -> str: - """Summarize text using the OpenAI API - - Args: - url (str): The url of the text - text (str): The text to summarize - question (str): The question to ask the model - driver (WebDriver): The webdriver to use to scroll the page - - Returns: - str: The summary of the text - """ - if not text: - return "Error: No text to summarize" - - model = CFG.fast_llm_model - text_length = len(text) - print(f"Text length: {text_length} characters") - - summaries = [] - chunks = list( - split_text( - text, max_length=CFG.browse_chunk_max_length, model=model, question=question - ), - ) - scroll_ratio = 1 / len(chunks) - - for i, chunk in enumerate(chunks): - if driver: - scroll_to_percentage(driver, scroll_ratio * i) - print(f"Adding chunk {i + 1} / {len(chunks)} to memory") - - memory_to_add = f"Source: {url}\n" f"Raw content part#{i + 1}: {chunk}" - - memory = get_memory(CFG) - memory.add(memory_to_add) - - messages = [create_message(chunk, question)] - tokens_for_chunk = token_counter.count_message_tokens(messages, model) - print( - f"Summarizing chunk {i + 1} / {len(chunks)} of length {len(chunk)} characters, or {tokens_for_chunk} tokens" - ) - - summary = create_chat_completion( - model=model, - messages=messages, - ) - summaries.append(summary) - print( - f"Added chunk {i + 1} summary to memory, of length {len(summary)} characters" - ) - - memory_to_add = f"Source: {url}\n" f"Content summary part#{i + 1}: {summary}" - - memory.add(memory_to_add) - - print(f"Summarized {len(chunks)} chunks.") - - combined_summary = "\n".join(summaries) - messages = [create_message(combined_summary, question)] - - return create_chat_completion( - model=model, - messages=messages, - ) - - -def scroll_to_percentage(driver: WebDriver, ratio: float) -> None: - """Scroll to a percentage of the page - - Args: - driver (WebDriver): The webdriver to use - ratio (float): The percentage to scroll to - - Raises: - ValueError: If the ratio is not between 0 and 1 - """ - if ratio < 0 or ratio > 1: - raise ValueError("Percentage should be between 0 and 1") - driver.execute_script(f"window.scrollTo(0, document.body.scrollHeight * {ratio});") - - -def create_message(chunk: str, question: str) -> Dict[str, str]: - """Create a message for the chat completion - - Args: - chunk (str): The chunk of text to summarize - question (str): The question to answer - - Returns: - Dict[str, str]: The message to send to the chat completion - """ - return { - "role": "user", - "content": f'"""{chunk}""" Using the above text, answer the following' - f' question: "{question}" -- if the question cannot be answered using the text,' - " summarize the text.", - } diff --git a/autogpt/prompts/__init__.py b/autogpt/prompts/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/autogpt/prompts/generator.py b/autogpt/prompts/generator.py deleted file mode 100644 index 282b9d7..0000000 --- a/autogpt/prompts/generator.py +++ /dev/null @@ -1,155 +0,0 @@ -""" A module for generating custom prompt strings.""" -import json -from typing import Any, Callable, Dict, List, Optional - - -class PromptGenerator: - """ - A class for generating custom prompt strings based on constraints, commands, - resources, and performance evaluations. - """ - - def __init__(self) -> None: - """ - Initialize the PromptGenerator object with empty lists of constraints, - commands, resources, and performance evaluations. - """ - self.constraints = [] - self.commands = [] - self.resources = [] - self.performance_evaluation = [] - self.goals = [] - self.command_registry = None - self.name = "Bob" - self.role = "AI" - self.response_format = { - "thoughts": { - "text": "thought", - "reasoning": "reasoning", - "plan": "- short bulleted\n- list that conveys\n- long-term plan", - "criticism": "constructive self-criticism", - "speak": "thoughts summary to say to user", - }, - "command": {"name": "command name", "args": {"arg name": "value"}}, - } - - def add_constraint(self, constraint: str) -> None: - """ - Add a constraint to the constraints list. - - Args: - constraint (str): The constraint to be added. - """ - self.constraints.append(constraint) - - def add_command( - self, - command_label: str, - command_name: str, - args=None, - function: Optional[Callable] = None, - ) -> None: - """ - Add a command to the commands list with a label, name, and optional arguments. - - Args: - command_label (str): The label of the command. - command_name (str): The name of the command. - args (dict, optional): A dictionary containing argument names and their - values. Defaults to None. - function (callable, optional): A callable function to be called when - the command is executed. Defaults to None. - """ - if args is None: - args = {} - - command_args = {arg_key: arg_value for arg_key, arg_value in args.items()} - - command = { - "label": command_label, - "name": command_name, - "args": command_args, - "function": function, - } - - self.commands.append(command) - - def _generate_command_string(self, command: Dict[str, Any]) -> str: - """ - Generate a formatted string representation of a command. - - Args: - command (dict): A dictionary containing command information. - - Returns: - str: The formatted command string. - """ - args_string = ", ".join( - f'"{key}": "{value}"' for key, value in command["args"].items() - ) - return f'{command["label"]}: "{command["name"]}", args: {args_string}' - - def add_resource(self, resource: str) -> None: - """ - Add a resource to the resources list. - - Args: - resource (str): The resource to be added. - """ - self.resources.append(resource) - - def add_performance_evaluation(self, evaluation: str) -> None: - """ - Add a performance evaluation item to the performance_evaluation list. - - Args: - evaluation (str): The evaluation item to be added. - """ - self.performance_evaluation.append(evaluation) - - def _generate_numbered_list(self, items: List[Any], item_type="list") -> str: - """ - Generate a numbered list from given items based on the item_type. - - Args: - items (list): A list of items to be numbered. - item_type (str, optional): The type of items in the list. - Defaults to 'list'. - - Returns: - str: The formatted numbered list. - """ - if item_type == "command": - command_strings = [] - if self.command_registry: - command_strings += [ - str(item) - for item in self.command_registry.commands.values() - if item.enabled - ] - # These are the commands that are added manually, do_nothing and terminate - command_strings += [self._generate_command_string(item) for item in items] - return "\n".join(f"{i+1}. {item}" for i, item in enumerate(command_strings)) - else: - return "\n".join(f"{i+1}. {item}" for i, item in enumerate(items)) - - def generate_prompt_string(self) -> str: - """ - Generate a prompt string based on the constraints, commands, resources, - and performance evaluations. - - Returns: - str: The generated prompt string. - """ - formatted_response_format = json.dumps(self.response_format, indent=4) - return ( - f"Constraints:\n{self._generate_numbered_list(self.constraints)}\n\n" - "Commands:\n" - f"{self._generate_numbered_list(self.commands, item_type='command')}\n\n" - f"Resources:\n{self._generate_numbered_list(self.resources)}\n\n" - "Performance Evaluation:\n" - f"{self._generate_numbered_list(self.performance_evaluation)}\n\n" - "You should only respond in JSON format as described below \nResponse" - f" Format: \n{formatted_response_format} \nEnsure the response can be" - " parsed by Python json.loads" - ) diff --git a/autogpt/prompts/prompt.py b/autogpt/prompts/prompt.py deleted file mode 100644 index f414daa..0000000 --- a/autogpt/prompts/prompt.py +++ /dev/null @@ -1,118 +0,0 @@ -from colorama import Fore - -from autogpt.api_manager import api_manager -from autogpt.config.ai_config import AIConfig -from autogpt.config.config import Config -from autogpt.logs import logger -from autogpt.prompts.generator import PromptGenerator -from autogpt.setup import prompt_user -from autogpt.utils import clean_input - -CFG = Config() - - -def build_default_prompt_generator() -> PromptGenerator: - """ - This function generates a prompt string that includes various constraints, - commands, resources, and performance evaluations. - - Returns: - str: The generated prompt string. - """ - - # Initialize the PromptGenerator object - prompt_generator = PromptGenerator() - - # Add constraints to the PromptGenerator object - prompt_generator.add_constraint( - "~4000 word limit for short term memory. Your short term memory is short, so" - " immediately save important information to files." - ) - prompt_generator.add_constraint( - "If you are unsure how you previously did something or want to recall past" - " events, thinking about similar events will help you remember." - ) - prompt_generator.add_constraint("No user assistance") - prompt_generator.add_constraint( - 'Exclusively use the commands listed in double quotes e.g. "command name"' - ) - - # Define the command list - commands = [ - ("Do Nothing", "do_nothing", {}), - ("Task Complete (Shutdown)", "task_complete", {"reason": ""}), - ] - - # Add commands to the PromptGenerator object - for command_label, command_name, args in commands: - prompt_generator.add_command(command_label, command_name, args) - - # Add resources to the PromptGenerator object - prompt_generator.add_resource( - "Internet access for searches and information gathering." - ) - prompt_generator.add_resource("Long Term memory management.") - prompt_generator.add_resource( - "GPT-3.5 powered Agents for delegation of simple tasks." - ) - prompt_generator.add_resource("File output.") - - # Add performance evaluations to the PromptGenerator object - prompt_generator.add_performance_evaluation( - "Continuously review and analyze your actions to ensure you are performing to" - " the best of your abilities." - ) - prompt_generator.add_performance_evaluation( - "Constructively self-criticize your big-picture behavior constantly." - ) - prompt_generator.add_performance_evaluation( - "Reflect on past decisions and strategies to refine your approach." - ) - prompt_generator.add_performance_evaluation( - "Every command has a cost, so be smart and efficient. Aim to complete tasks in" - " the least number of steps." - ) - prompt_generator.add_performance_evaluation("Write all code to a file.") - return prompt_generator - - -def construct_main_ai_config(input_kwargs) -> AIConfig: - """Construct the prompt for the AI to respond to - - Returns: - str: The prompt string - """ - - if input_kwargs['role']: - config = prompt_user(input_kwargs, True) # False 不使用引导 - config.save(CFG.ai_settings_file) - else: - return None - - # set the total api budget - api_manager.set_total_budget(config.api_budget) - - # Agent Created, print message - logger.typewriter_log( - config.ai_name, - Fore.MAGENTA, - "has been created with the following details:", - speak_text=True, - ) - - # Print the ai config details - # Name - logger.typewriter_log("Name:", Fore.GREEN, config.ai_name, speak_text=False) - # Role - logger.typewriter_log("Role:", Fore.GREEN, config.ai_role, speak_text=False) - # Goals - logger.typewriter_log("Goals:", Fore.GREEN, "", speak_text=False) - for goal in config.ai_goals: - logger.typewriter_log("-", Fore.GREEN, goal, speak_text=False) - - return config - - -if __name__ == '__main__': - ll = [] - print(ll[-1]) \ No newline at end of file diff --git a/autogpt/requirements.txt b/autogpt/requirements.txt deleted file mode 100644 index 3c997b5..0000000 --- a/autogpt/requirements.txt +++ /dev/null @@ -1,56 +0,0 @@ -beautifulsoup4>=4.12.2 -colorama==0.4.6 -distro==1.8.0 -openai==0.27.2 -playsound==1.2.2 -python-dotenv==1.0.0 -pyyaml==6.0 -readability-lxml==0.8.1 -requests -tiktoken==0.3.3 -gTTS==2.3.1 -docker -duckduckgo-search>=2.9.5 -google-api-python-client #(https://developers.google.com/custom-search/v1/overview) -pinecone-client==2.2.1 -redis -orjson==3.8.10 -Pillow -selenium==4.1.4 -webdriver-manager -jsonschema -tweepy -click -charset-normalizer>=3.1.0 -spacy>=3.0.0,<4.0.0 -en-core-web-sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.5.0/en_core_web_sm-3.5.0-py3-none-any.whl - -##Dev -coverage -flake8 -numpy -pre-commit -black -isort -gitpython==3.1.31 -auto-gpt-plugin-template -mkdocs -pymdown-extensions -mypy - -# OpenAI and Generic plugins import -openapi-python-client==0.13.4 - -# Items below this point will not be included in the Docker Image - -# Testing dependencies -pytest -asynctest -pytest-asyncio -pytest-benchmark -pytest-cov -pytest-integration -pytest-mock -vcrpy -pytest-recording -pytest-xdist diff --git a/autogpt/setup.py b/autogpt/setup.py deleted file mode 100644 index 7c4bd89..0000000 --- a/autogpt/setup.py +++ /dev/null @@ -1,184 +0,0 @@ -"""Set up the AI and its goals""" -import re - -from colorama import Fore, Style - -from autogpt import utils -from autogpt.config import Config -from autogpt.config.ai_config import AIConfig -from autogpt.llm_utils import create_chat_completion -from autogpt.logs import logger - -CFG = Config() - - -def prompt_user(input_kwargs: dict, _is) -> AIConfig: - """Prompt the user for input - - Returns: - AIConfig: The AIConfig object tailored to the user's input - """ - ai_name = input_kwargs.get('name') - ai_role = input_kwargs.get('role') - ai_goals = input_kwargs.get('goals') - ai_budget = input_kwargs.get('budget') - ai_config = None - if _is: - return generate_aiconfig_manual(ai_name, ai_role, ai_goals, ai_budget) - else: - # Construct the prompt - logger.typewriter_log( - "Welcome to Auto-GPT! ", - Fore.GREEN, - "run with '--help' for more information.", - speak_text=True, - ) - - # Get user desire - logger.typewriter_log( - "Create an AI-Assistant:", - Fore.GREEN, - "input '--manual' to enter manual mode.", - speak_text=True, - ) - user_desire = utils.clean_input( - f"{Fore.MAGENTA}I want Auto-GPT to{Style.RESET_ALL}: " - ) - - if user_desire == "": - user_desire = "Write a wikipedia style article about the project: https://github.com/significant-gravitas/Auto-GPT" # Default prompt - - # If user desire contains "--manual" - if "--manual" in user_desire: - logger.typewriter_log( - "Manual Mode Selected", - Fore.GREEN, - speak_text=True, - ) - return generate_aiconfig_manual(ai_name, ai_role, ai_goals, ai_budget) - - else: - try: - return generate_aiconfig_automatic(user_desire) - except Exception as e: - logger.typewriter_log( - "Unable to automatically generate AI Config based on user desire.", - Fore.RED, - "Falling back to manual mode.", - speak_text=True, - ) - - return generate_aiconfig_manual(ai_name, ai_role, ai_goals, ai_budget) - - -def generate_aiconfig_manual(name, role, goals, budget) -> AIConfig: - """ - Interactively create an AI configuration by prompting the user to provide the name, role, and goals of the AI. - - This function guides the user through a series of prompts to collect the necessary information to create - an AIConfig object. The user will be asked to provide a name and role for the AI, as well as up to five - goals. If the user does not provide a value for any of the fields, default values will be used. - - Returns: - AIConfig: An AIConfig object containing the user-defined or default AI name, role, and goals. - """ - # Manual Setup Intro - logger.typewriter_log( - "Create an AI-Assistant:", - Fore.GREEN, - "The Ai robot you set up is already loaded.", - speak_text=True, - ) - ai_name = name - if not ai_name: - ai_name = "Entrepreneur-GPT" - logger.typewriter_log( - f"{ai_name} here!", Fore.MAGENTA, "I am at your service.", speak_text=True - ) - ai_role = role - if not ai_role: - logger.typewriter_log( - f"{ai_role} Cannot be empty!", Fore.RED, - "Please feel free to give me your needs, I can't serve you without them.", speak_text=True - ) - else: - pass - ai_goals = [] - if goals: - for k in goals: - ai_goals.append(k[0]) - # Get API Budget from User - api_budget_input = budget - if not api_budget_input: - api_budget = 0.0 - else: - try: - api_budget = float(api_budget_input.replace("$", "")) - except ValueError: - api_budget = 0.0 - logger.typewriter_log( - "Invalid budget input. Setting budget to unlimited.", Fore.RED, api_budget - ) - return AIConfig(ai_name, ai_role, ai_goals, api_budget) - - -def generate_aiconfig_automatic(user_prompt) -> AIConfig: - """Generates an AIConfig object from the given string. - - Returns: - AIConfig: The AIConfig object tailored to the user's input - """ - - system_prompt = """ -Your task is to devise up to 5 highly effective goals and an appropriate role-based name (_GPT) for an autonomous agent, ensuring that the goals are optimally aligned with the successful completion of its assigned task. - -The user will provide the task, you will provide only the output in the exact format specified below with no explanation or conversation. - -Example input: -Help me with marketing my business - -Example output: -Name: CMOGPT -Description: a professional digital marketer AI that assists Solopreneurs in growing their businesses by providing world-class expertise in solving marketing problems for SaaS, content products, agencies, and more. -Goals: -- Engage in effective problem-solving, prioritization, planning, and supporting execution to address your marketing needs as your virtual Chief Marketing Officer. - -- Provide specific, actionable, and concise advice to help you make informed decisions without the use of platitudes or overly wordy explanations. - -- Identify and prioritize quick wins and cost-effective campaigns that maximize results with minimal time and budget investment. - -- Proactively take the lead in guiding you and offering suggestions when faced with unclear information or uncertainty to ensure your marketing strategy remains on track. -""" - - # Call LLM with the string as user input - messages = [ - { - "role": "system", - "content": system_prompt, - }, - { - "role": "user", - "content": f"Task: '{user_prompt}'\nRespond only with the output in the exact format specified in the system prompt, with no explanation or conversation.\n", - }, - ] - output = create_chat_completion(messages, CFG.fast_llm_model) - - # Debug LLM Output - logger.debug(f"AI Config Generator Raw Output: {output}") - - # Parse the output - ai_name = re.search(r"Name(?:\s*):(?:\s*)(.*)", output, re.IGNORECASE).group(1) - ai_role = ( - re.search( - r"Description(?:\s*):(?:\s*)(.*?)(?:(?:\n)|Goals)", - output, - re.IGNORECASE | re.DOTALL, - ) - .group(1) - .strip() - ) - ai_goals = re.findall(r"(?<=\n)-\s*(.*)", output) - api_budget = 0.0 # TODO: parse api budget using a regular expression - - return AIConfig(ai_name, ai_role, ai_goals, api_budget) - diff --git a/autogpt/speech/__init__.py b/autogpt/speech/__init__.py deleted file mode 100644 index 2ff0d2b..0000000 --- a/autogpt/speech/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""This module contains the speech recognition and speech synthesis functions.""" -from autogpt.speech.say import say_text - -__all__ = ["say_text"] diff --git a/autogpt/speech/base.py b/autogpt/speech/base.py deleted file mode 100644 index d74fa51..0000000 --- a/autogpt/speech/base.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Base class for all voice classes.""" -import abc -from threading import Lock - -from autogpt.config import AbstractSingleton - - -class VoiceBase(AbstractSingleton): - """ - Base class for all voice classes. - """ - - def __init__(self): - """ - Initialize the voice class. - """ - self._url = None - self._headers = None - self._api_key = None - self._voices = [] - self._mutex = Lock() - self._setup() - - def say(self, text: str, voice_index: int = 0) -> bool: - """ - Say the given text. - - Args: - text (str): The text to say. - voice_index (int): The index of the voice to use. - """ - with self._mutex: - return self._speech(text, voice_index) - - @abc.abstractmethod - def _setup(self) -> None: - """ - Setup the voices, API key, etc. - """ - pass - - @abc.abstractmethod - def _speech(self, text: str, voice_index: int = 0) -> bool: - """ - Play the given text. - - Args: - text (str): The text to play. - """ - pass diff --git a/autogpt/speech/brian.py b/autogpt/speech/brian.py deleted file mode 100644 index ffa4e51..0000000 --- a/autogpt/speech/brian.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging -import os - -import requests -from playsound import playsound - -from autogpt.speech.base import VoiceBase - - -class BrianSpeech(VoiceBase): - """Brian speech module for autogpt""" - - def _setup(self) -> None: - """Setup the voices, API key, etc.""" - pass - - def _speech(self, text: str, _: int = 0) -> bool: - """Speak text using Brian with the streamelements API - - Args: - text (str): The text to speak - - Returns: - bool: True if the request was successful, False otherwise - """ - tts_url = ( - f"https://api.streamelements.com/kappa/v2/speech?voice=Brian&text={text}" - ) - response = requests.get(tts_url) - - if response.status_code == 200: - with open("speech.mp3", "wb") as f: - f.write(response.content) - playsound("speech.mp3") - os.remove("speech.mp3") - return True - else: - logging.error( - "Request failed with status code: %s, response content: %s", - response.status_code, - response.content, - ) - return False diff --git a/autogpt/speech/eleven_labs.py b/autogpt/speech/eleven_labs.py deleted file mode 100644 index ea84efd..0000000 --- a/autogpt/speech/eleven_labs.py +++ /dev/null @@ -1,86 +0,0 @@ -"""ElevenLabs speech module""" -import os - -import requests -from playsound import playsound - -from autogpt.config import Config -from autogpt.speech.base import VoiceBase - -PLACEHOLDERS = {"your-voice-id"} - - -class ElevenLabsSpeech(VoiceBase): - """ElevenLabs speech class""" - - def _setup(self) -> None: - """Set up the voices, API key, etc. - - Returns: - None: None - """ - - cfg = Config() - default_voices = ["ErXwobaYiN019PkySvjV", "EXAVITQu4vr4xnSDxMaL"] - voice_options = { - "Rachel": "21m00Tcm4TlvDq8ikWAM", - "Domi": "AZnzlk1XvdvUeBnXmlld", - "Bella": "EXAVITQu4vr4xnSDxMaL", - "Antoni": "ErXwobaYiN019PkySvjV", - "Elli": "MF3mGyEYCl7XYWbV9V6O", - "Josh": "TxGEqnHWrfWFTfGW9XjX", - "Arnold": "VR6AewLTigWG4xSOukaG", - "Adam": "pNInz6obpgDQGcFmaJgB", - "Sam": "yoZ06aMxZJJ28mfd3POQ", - } - self._headers = { - "Content-Type": "application/json", - "xi-api-key": cfg.elevenlabs_api_key, - } - self._voices = default_voices.copy() - if cfg.elevenlabs_voice_1_id in voice_options: - cfg.elevenlabs_voice_1_id = voice_options[cfg.elevenlabs_voice_1_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_1_id, 0) - self._use_custom_voice(cfg.elevenlabs_voice_2_id, 1) - - def _use_custom_voice(self, voice, voice_index) -> None: - """Use a custom voice if provided and not a placeholder - - Args: - voice (str): The voice ID - voice_index (int): The voice index - - Returns: - None: None - """ - # Placeholder values that should be treated as empty - if voice and voice not in PLACEHOLDERS: - self._voices[voice_index] = voice - - def _speech(self, text: str, voice_index: int = 0) -> bool: - """Speak text using elevenlabs.io's API - - Args: - text (str): The text to speak - voice_index (int, optional): The voice to use. Defaults to 0. - - Returns: - bool: True if the request was successful, False otherwise - """ - tts_url = ( - f"https://api.elevenlabs.io/v1/text-to-speech/{self._voices[voice_index]}" - ) - response = requests.post(tts_url, headers=self._headers, json={"text": text}) - - if response.status_code == 200: - with open("speech.mpeg", "wb") as f: - f.write(response.content) - playsound("speech.mpeg", True) - os.remove("speech.mpeg") - return True - else: - print("Request failed with status code:", response.status_code) - print("Response content:", response.content) - return False diff --git a/autogpt/speech/gtts.py b/autogpt/speech/gtts.py deleted file mode 100644 index e7a8a69..0000000 --- a/autogpt/speech/gtts.py +++ /dev/null @@ -1,23 +0,0 @@ -""" GTTS Voice. """ -import os - -import gtts -from playsound import playsound - -from autogpt.speech.base import VoiceBase - - -class GTTSVoice(VoiceBase): - """GTTS Voice.""" - - def _setup(self) -> None: - pass - - def _speech(self, text: str, _: int = 0) -> bool: - """Play the given text.""" - tts = gtts.gTTS(text) - tts.save("speech.mp3") - playsound("speech.mp3", True) - os.remove("speech.mp3") - return True - diff --git a/autogpt/speech/macos_tts.py b/autogpt/speech/macos_tts.py deleted file mode 100644 index 4c072ce..0000000 --- a/autogpt/speech/macos_tts.py +++ /dev/null @@ -1,21 +0,0 @@ -""" MacOS TTS Voice. """ -import os - -from autogpt.speech.base import VoiceBase - - -class MacOSTTS(VoiceBase): - """MacOS TTS Voice.""" - - def _setup(self) -> None: - pass - - def _speech(self, text: str, voice_index: int = 0) -> bool: - """Play the given text.""" - if voice_index == 0: - os.system(f'say "{text}"') - elif voice_index == 1: - os.system(f'say -v "Ava (Premium)" "{text}"') - else: - os.system(f'say -v Samantha "{text}"') - return True diff --git a/autogpt/speech/say.py b/autogpt/speech/say.py deleted file mode 100644 index 0913bfc..0000000 --- a/autogpt/speech/say.py +++ /dev/null @@ -1,46 +0,0 @@ -""" Text to speech module """ -import threading -from threading import Semaphore - -from autogpt.config import Config -from autogpt.speech.brian import BrianSpeech -from autogpt.speech.eleven_labs import ElevenLabsSpeech -from autogpt.speech.gtts import GTTSVoice -from autogpt.speech.macos_tts import MacOSTTS - -CFG = Config() -DEFAULT_VOICE_ENGINE = GTTSVoice() -VOICE_ENGINE = None -if CFG.elevenlabs_api_key: - VOICE_ENGINE = ElevenLabsSpeech() -elif CFG.use_mac_os_tts == "True": - VOICE_ENGINE = MacOSTTS() -elif CFG.use_brian_tts == "True": - VOICE_ENGINE = BrianSpeech() -else: - VOICE_ENGINE = GTTSVoice() - - -QUEUE_SEMAPHORE = Semaphore( - 1 -) # The amount of sounds to queue before blocking the main thread - - -def say_text(text: str, voice_index: int = 0) -> None: - """Speak the given text using the given voice index""" - - def speak() -> None: - success = VOICE_ENGINE.say(text, voice_index) - if not success: - DEFAULT_VOICE_ENGINE.say(text) - - QUEUE_SEMAPHORE.release() - - QUEUE_SEMAPHORE.acquire(True) - thread = threading.Thread(target=speak) - thread.start() - - - -if __name__ == '__main__': - say_text('你好呀') \ No newline at end of file diff --git a/autogpt/spinner.py b/autogpt/spinner.py deleted file mode 100644 index bf62730..0000000 --- a/autogpt/spinner.py +++ /dev/null @@ -1,70 +0,0 @@ -"""A simple spinner module""" -import itertools -import sys -import threading -import time - - -class Spinner: - """A simple spinner class""" - - def __init__(self, message: str = "Loading...", delay: float = 0.1) -> None: - """Initialize the spinner class - - Args: - message (str): The message to display. - delay (float): The delay between each spinner update. - """ - self.spinner = itertools.cycle(["-", "/", "|", "\\"]) - self.delay = delay - self.message = message - self.running = False - self.spinner_thread = None - - def spin(self) -> None: - """Spin the spinner""" - while self.running: - sys.stdout.write(f"{next(self.spinner)} {self.message}\r") - sys.stdout.flush() - time.sleep(self.delay) - sys.stdout.write(f"\r{' ' * (len(self.message) + 2)}\r") - - def __enter__(self): - """Start the spinner""" - self.running = True - self.spinner_thread = threading.Thread(target=self.spin) - self.spinner_thread.start() - - return self - - def __exit__(self, exc_type, exc_value, exc_traceback) -> None: - """Stop the spinner - - Args: - exc_type (Exception): The exception type. - exc_value (Exception): The exception value. - exc_traceback (Exception): The exception traceback. - """ - self.running = False - if self.spinner_thread is not None: - self.spinner_thread.join() - sys.stdout.write(f"\r{' ' * (len(self.message) + 2)}\r") - sys.stdout.flush() - - def update_message(self, new_message, delay=0.1): - """Update the spinner message - Args: - new_message (str): New message to display. - delay (float): The delay in seconds between each spinner update. - """ - time.sleep(delay) - sys.stdout.write( - f"\r{' ' * (len(self.message) + 2)}\r" - ) # Clear the current message - sys.stdout.flush() - self.message = new_message - - -if __name__ == '__main__': - with Spinner('LING'): - time.sleep(5) \ No newline at end of file diff --git a/autogpt/token_counter.py b/autogpt/token_counter.py deleted file mode 100644 index 2d50547..0000000 --- a/autogpt/token_counter.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Functions for counting the number of tokens in a message or string.""" -from __future__ import annotations - -from typing import List - -import tiktoken - -from autogpt.logs import logger -from autogpt.types.openai import Message - - -def count_message_tokens( - messages: List[Message], model: str = "gpt-3.5-turbo-0301" -) -> int: - """ - Returns the number of tokens used by a list of messages. - - Args: - messages (list): A list of messages, each of which is a dictionary - containing the role and content of the message. - model (str): The name of the model to use for tokenization. - Defaults to "gpt-3.5-turbo-0301". - - Returns: - int: The number of tokens used by the list of messages. - """ - try: - encoding = tiktoken.encoding_for_model(model) - except KeyError: - logger.warn("Warning: model not found. Using cl100k_base encoding.") - encoding = tiktoken.get_encoding("cl100k_base") - if model == "gpt-3.5-turbo": - # !Note: gpt-3.5-turbo may change over time. - # Returning num tokens assuming gpt-3.5-turbo-0301.") - return count_message_tokens(messages, model="gpt-3.5-turbo-0301") - elif model == "gpt-4": - # !Note: gpt-4 may change over time. Returning num tokens assuming gpt-4-0314.") - return count_message_tokens(messages, model="gpt-4-0314") - elif model == "gpt-3.5-turbo-0301": - tokens_per_message = ( - 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n - ) - tokens_per_name = -1 # if there's a name, the role is omitted - elif model == "gpt-4-0314": - tokens_per_message = 3 - tokens_per_name = 1 - else: - raise NotImplementedError( - f"num_tokens_from_messages() is not implemented for model {model}.\n" - " See https://github.com/openai/openai-python/blob/main/chatml.md for" - " information on how messages are converted to tokens." - ) - num_tokens = 0 - for message in messages: - num_tokens += tokens_per_message - for key, value in message.items(): - num_tokens += len(encoding.encode(value)) - if key == "name": - num_tokens += tokens_per_name - num_tokens += 3 # every reply is primed with <|start|>assistant<|message|> - return num_tokens - - -def count_string_tokens(string: str, model_name: str) -> int: - """ - Returns the number of tokens in a text string. - - Args: - string (str): The text string. - model_name (str): The name of the encoding to use. (e.g., "gpt-3.5-turbo") - - Returns: - int: The number of tokens in the text string. - """ - encoding = tiktoken.encoding_for_model(model_name) - return len(encoding.encode(string)) diff --git a/autogpt/types/openai.py b/autogpt/types/openai.py deleted file mode 100644 index 2af8578..0000000 --- a/autogpt/types/openai.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Type helpers for working with the OpenAI library""" -from typing import TypedDict - - -class Message(TypedDict): - """OpenAI Message object containing a role and the message content""" - - role: str - content: str diff --git a/autogpt/utils.py b/autogpt/utils.py deleted file mode 100644 index c8553ea..0000000 --- a/autogpt/utils.py +++ /dev/null @@ -1,85 +0,0 @@ -import os - -import requests -import yaml -from colorama import Fore -from git.repo import Repo - -# Use readline if available (for clean_input) -try: - import readline -except: - pass - - -def clean_input(prompt: str = ""): - try: - return input(prompt) - except KeyboardInterrupt: - print("You interrupted Auto-GPT") - print("Quitting...") - exit(0) - - -def validate_yaml_file(file: str): - try: - with open(file, encoding="utf-8") as fp: - yaml.load(fp.read(), Loader=yaml.FullLoader) - except FileNotFoundError: - return (False, f"The file {Fore.CYAN}`{file}`{Fore.RESET} wasn't found") - except yaml.YAMLError as e: - return ( - False, - f"There was an issue while trying to read with your AI Settings file: {e}", - ) - - return (True, f"Successfully validated {Fore.CYAN}`{file}`{Fore.RESET}!") - - -def readable_file_size(size, decimal_places=2): - """Converts the given size in bytes to a readable format. - Args: - size: Size in bytes - decimal_places (int): Number of decimal places to display - """ - for unit in ["B", "KB", "MB", "GB", "TB"]: - if size < 1024.0: - break - size /= 1024.0 - return f"{size:.{decimal_places}f} {unit}" - - -def get_bulletin_from_web(): - try: - response = requests.get( - "https://raw.githubusercontent.com/Significant-Gravitas/Auto-GPT/master/BULLETIN.md" - ) - if response.status_code == 200: - return response.text - except requests.exceptions.RequestException: - pass - - return "" - - -def get_current_git_branch() -> str: - try: - repo = Repo(search_parent_directories=True) - branch = repo.active_branch - return branch.name - except: - return "" - - -def get_latest_bulletin() -> str: - exists = os.path.exists("CURRENT_BULLETIN.md") - current_bulletin = "" - if exists: - current_bulletin = open("CURRENT_BULLETIN.md", "r", encoding="utf-8").read() - new_bulletin = get_bulletin_from_web() - is_new_news = new_bulletin != current_bulletin - - if new_bulletin and is_new_news: - open("CURRENT_BULLETIN.md", "w", encoding="utf-8").write(new_bulletin) - return f" {Fore.RED}::UPDATED:: {Fore.CYAN}{new_bulletin}{Fore.RESET}" - return current_bulletin diff --git a/autogpt/workspace/__init__.py b/autogpt/workspace/__init__.py deleted file mode 100644 index b348144..0000000 --- a/autogpt/workspace/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from autogpt.workspace.workspace import Workspace - -__all__ = [ - "Workspace", -] diff --git a/autogpt/workspace/workspace.py b/autogpt/workspace/workspace.py deleted file mode 100644 index b06fa9e..0000000 --- a/autogpt/workspace/workspace.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -========= -Workspace -========= - -The workspace is a directory containing configuration and working files for an AutoGPT -agent. - -""" -from __future__ import annotations - -from pathlib import Path - - -class Workspace: - """A class that represents a workspace for an AutoGPT agent.""" - - def __init__(self, workspace_root: str | Path, restrict_to_workspace: bool): - self._root = self._sanitize_path(workspace_root) - self._restrict_to_workspace = restrict_to_workspace - - @property - def root(self) -> Path: - """The root directory of the workspace.""" - return self._root - - @property - def restrict_to_workspace(self): - """Whether to restrict generated paths to the workspace.""" - return self._restrict_to_workspace - - @classmethod - def make_workspace(cls, workspace_directory: str | Path, *args, **kwargs) -> Path: - """Create a workspace directory and return the path to it. - - Parameters - ---------- - workspace_directory - The path to the workspace directory. - - Returns - ------- - Path - The path to the workspace directory. - - """ - # TODO: have this make the env file and ai settings file in the directory. - workspace_directory = cls._sanitize_path(workspace_directory) - workspace_directory.mkdir(exist_ok=True, parents=True) - return workspace_directory - - def get_path(self, relative_path: str | Path) -> Path: - """Get the full path for an item in the workspace. - - Parameters - ---------- - relative_path - The relative path to resolve in the workspace. - - Returns - ------- - Path - The resolved path relative to the workspace. - - """ - return self._sanitize_path( - relative_path, - root=self.root, - restrict_to_root=self.restrict_to_workspace, - ) - - @staticmethod - def _sanitize_path( - relative_path: str | Path, - root: str | Path = None, - restrict_to_root: bool = True, - ) -> Path: - """Resolve the relative path within the given root if possible. - - Parameters - ---------- - relative_path - The relative path to resolve. - root - The root path to resolve the relative path within. - restrict_to_root - Whether to restrict the path to the root. - - Returns - ------- - Path - The resolved path. - - Raises - ------ - ValueError - If the path is absolute and a root is provided. - ValueError - If the path is outside the root and the root is restricted. - - """ - - if root is None: - return Path(relative_path).resolve() - - root, relative_path = Path(root), Path(relative_path) - - if relative_path.is_absolute(): - raise ValueError( - f"Attempted to access absolute path '{relative_path}' in workspace '{root}'." - ) - - full_path = root.joinpath(relative_path).resolve() - - if restrict_to_root and not full_path.is_relative_to(root): - raise ValueError( - f"Attempted to access path '{full_path}' outside of workspace '{root}'." - ) - - return full_path